A Dockerfile both describes a Docker image as well as layers for the working directory, environment variables, ports, entrypoint commands, and other important interfaces.
Test-Driven Design should drive a developer toward implementation details, not the other way around.
A devops without tests is a sad devops indeed.
Working toward a docker based development environment, my first thoughts were toward Serverspec by Gosuke Miayshita, as it is entirely framework agnostic. Gosuke gave an excellent presentation at ChefConf this year re-inforcing that Serverspec is not a chef centric tool, and works quite well in conjunction with other configuration management tools.
Researching Serverspec and docker a bit more, Taichi Nakashima based his TDD of Dockerfile by RSpec on OS/X using ssh.
With Docker 1.3 and later, there is a “docker exec” interactive docker API for allowing live sessions on processes spawned in the same process namespace as a running container, effectively allowing external access into a running docker container using only the docker API.
PIETER JOOST VAN DE SANDE posted about using the docker-api to accomplish the goal of testing a Dockerfile. His work is based on the docker-api gem (github swipely/docker-api).
Looking into the docker-api source, there is no support yet for docker 1.3’s exec API interface to run Serverspec tests against the contents of a running docker container.
Attempting even the most basic docker API calls with docker-api, issue 202 made it apparent that TLS support for boot2docker would need to be addressed first.
Here is my functional spec_helper.rb
with the fixes necessary to use docker-api without modifications:
require "docker"
docker_host = ENV['DOCKER_HOST'].dup
if(ENV['DOCKER_TLS_VERIFY'])
cert_path = File.expand_path ENV['DOCKER_CERT_PATH']
Docker.options = {
client_cert: File.join(cert_path, 'cert.pem'),
client_key: File.join(cert_path, 'key.pem')
}
Excon.defaults[:ssl_ca_file] = File.join(cert_path, 'ca.pem')
docker_host.gsub!(/^tcp/,'https')
end
Docker.url = docker_host
Following this, I can drive the generation of a Dockerfile with a spec:
require "spec_helper"
describe "dockerfile built my_app image" do
before(:all) do
@image = Docker::Image.all(:all => true).find { |image|
Docker::Util.parse_repo_tag( image.info['RepoTags'].first ).first == 'my_app'
}
p @image.json["Env"]
end
it "should exist" do
expect(@image).not_to be_nil
end
it "should have CMD" do
expect(@image.json["Config"]["Cmd"]).to include("/run.sh")
end
it "should expose the default port" do
expect(@image.json["Config"]["ExposedPorts"].has_key?("3000/tcp")).to be_truthy
end
it "should have environmental variable" do
expect(@image.json["Config"]["Env"]).to include("HOME=/usr/src/app")
end
end
This drives me iteratively to write a Dockerfile that looks like:
FROM rails:onbuild
ENV HOME /usr/src/app
ADD docker/run.sh /run.sh
RUN chmod 755 /run.sh
EXPOSE 3000
CMD /run.sh
Next step: extend docker-api to support exec for serverspec based testing of actual docker image contents.
Sláinte!