diff --git a/builder/docker/builder.go b/builder/docker/builder.go index 2dddbf94e..96a79b02d 100644 --- a/builder/docker/builder.go +++ b/builder/docker/builder.go @@ -1,10 +1,11 @@ package docker import ( + "log" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" - "log" ) const BuilderId = "packer.docker" @@ -31,6 +32,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, err } + version, err := driver.Version() + if err != nil { + return nil, err + } + log.Printf("[DEBUG] Docker version: %s", version.String()) + steps := []multistep.Step{ &StepTempDir{}, &StepPull{}, @@ -70,8 +77,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, rawErr.(error) } - var artifact packer.Artifact + // If it was cancelled, then just return + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, nil + } + // No errors, must've worked + var artifact packer.Artifact if b.config.Commit { artifact = &ImportArtifact{ IdValue: state.Get("image_id").(string), @@ -81,6 +93,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } else { artifact = &ExportArtifact{path: b.config.ExportPath} } + return artifact, nil } diff --git a/builder/docker/communicator.go b/builder/docker/communicator.go index bad2c1ff6..6fedf2769 100644 --- a/builder/docker/communicator.go +++ b/builder/docker/communicator.go @@ -15,6 +15,7 @@ import ( "time" "github.com/ActiveState/tail" + "github.com/hashicorp/go-version" "github.com/mitchellh/packer/packer" ) @@ -22,6 +23,7 @@ type Communicator struct { ContainerId string HostDir string ContainerDir string + Version *version.Version lock sync.Mutex } @@ -41,7 +43,13 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { // This file will store the exit code of the command once it is complete. exitCodePath := outputFile.Name() + "-exit" - cmd := exec.Command("docker", "attach", c.ContainerId) + var cmd *exec.Cmd + if c.canExec() { + cmd = exec.Command("docker", "exec", "-i", c.ContainerId, "/bin/sh") + } else { + cmd = exec.Command("docker", "attach", c.ContainerId) + } + stdin_w, err := cmd.StdinPipe() if err != nil { // We have to do some cleanup since run was never called @@ -117,7 +125,7 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error return os.MkdirAll(hostpath, info.Mode()) } - if info.Mode() & os.ModeSymlink == os.ModeSymlink { + if info.Mode()&os.ModeSymlink == os.ModeSymlink { dest, err := os.Readlink(path) if err != nil { @@ -186,6 +194,15 @@ func (c *Communicator) Download(src string, dst io.Writer) error { panic("not implemented") } +// canExec tells us whether `docker exec` is supported +func (c *Communicator) canExec() bool { + execConstraint, err := version.NewConstraint(">= 1.4.0") + if err != nil { + panic(err) + } + return execConstraint.Check(c.Version) +} + // Runs the given command and blocks until completion func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) { // For Docker, remote communication must be serialized since it diff --git a/builder/docker/config.go b/builder/docker/config.go index e261068df..4fc6f762a 100644 --- a/builder/docker/config.go +++ b/builder/docker/config.go @@ -30,7 +30,7 @@ type Config struct { } func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) + var c Config var md mapstructure.Metadata err := config.Decode(&c, &config.DecodeOpts{ @@ -83,5 +83,5 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { return nil, nil, errs } - return c, nil, nil + return &c, nil, nil } diff --git a/builder/docker/driver.go b/builder/docker/driver.go index 85b87b1d0..d9cb94f9b 100644 --- a/builder/docker/driver.go +++ b/builder/docker/driver.go @@ -2,6 +2,8 @@ package docker import ( "io" + + "github.com/hashicorp/go-version" ) // Driver is the interface that has to be implemented to communicate with @@ -48,6 +50,9 @@ type Driver interface { // Verify verifies that the driver can run Verify() error + + // Version reads the Docker version + Version() (*version.Version, error) } // ContainerConfig is the configuration used to start a container. diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index 038aa046e..017c33ee0 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -7,9 +7,11 @@ import ( "log" "os" "os/exec" + "regexp" "strings" "sync" + "github.com/hashicorp/go-version" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/template/interpolate" ) @@ -263,3 +265,17 @@ func (d *DockerDriver) Verify() error { return nil } + +func (d *DockerDriver) Version() (*version.Version, error) { + output, err := exec.Command("docker", "-v").Output() + if err != nil { + return nil, err + } + + match := regexp.MustCompile(version.VersionRegexpRaw).FindSubmatch(output) + if match == nil { + return nil, fmt.Errorf("unknown version: %s", output) + } + + return version.NewVersion(string(match[0])) +} diff --git a/builder/docker/driver_mock.go b/builder/docker/driver_mock.go index 549e79611..390a7e308 100644 --- a/builder/docker/driver_mock.go +++ b/builder/docker/driver_mock.go @@ -2,6 +2,8 @@ package docker import ( "io" + + "github.com/hashicorp/go-version" ) // MockDriver is a driver implementation that can be used for tests. @@ -63,6 +65,9 @@ type MockDriver struct { StopCalled bool StopID string VerifyCalled bool + + VersionCalled bool + VersionVersion string } func (d *MockDriver) Commit(id string) (string, error) { @@ -162,3 +167,8 @@ func (d *MockDriver) Verify() error { d.VerifyCalled = true return d.VerifyError } + +func (d *MockDriver) Version() (*version.Version, error) { + d.VersionCalled = true + return version.NewVersion(d.VersionVersion) +} diff --git a/builder/docker/step_provision.go b/builder/docker/step_provision.go index 726653763..d9852ae2b 100644 --- a/builder/docker/step_provision.go +++ b/builder/docker/step_provision.go @@ -9,14 +9,23 @@ type StepProvision struct{} func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction { containerId := state.Get("container_id").(string) + driver := state.Get("driver").(Driver) tempDir := state.Get("temp_dir").(string) + // Get the version so we can pass it to the communicator + version, err := driver.Version() + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + // Create the communicator that talks to Docker via various // os/exec tricks. comm := &Communicator{ ContainerId: containerId, HostDir: tempDir, ContainerDir: "/packer-files", + Version: version, } prov := common.StepProvision{Comm: comm}