diff --git a/builder/docker/artifact_import.go b/builder/docker/artifact_import.go new file mode 100644 index 000000000..4c926eb53 --- /dev/null +++ b/builder/docker/artifact_import.go @@ -0,0 +1,33 @@ +package docker + +import ( + "fmt" +) + +// ImportArtifact is an Artifact implementation for when a container is +// exported from docker into a single flat file. +type ImportArtifact struct { + BuilderIdValue string + Driver Driver + IdValue string +} + +func (a *ImportArtifact) BuilderId() string { + return a.BuilderIdValue +} + +func (*ImportArtifact) Files() []string { + return nil +} + +func (a *ImportArtifact) Id() string { + return a.IdValue +} + +func (a *ImportArtifact) String() string { + return fmt.Sprintf("Imported Docker image: %s", a.Id()) +} + +func (a *ImportArtifact) Destroy() error { + return a.Driver.DeleteImage(a.Id()) +} diff --git a/builder/docker/artifact_import_test.go b/builder/docker/artifact_import_test.go new file mode 100644 index 000000000..971143953 --- /dev/null +++ b/builder/docker/artifact_import_test.go @@ -0,0 +1,57 @@ +package docker + +import ( + "errors" + "github.com/mitchellh/packer/packer" + "testing" +) + +func TestImportArtifact_impl(t *testing.T) { + var _ packer.Artifact = new(ImportArtifact) +} + +func TestImportArtifactBuilderId(t *testing.T) { + a := &ImportArtifact{BuilderIdValue: "foo"} + if a.BuilderId() != "foo" { + t.Fatalf("bad: %#v", a.BuilderId()) + } +} + +func TestImportArtifactFiles(t *testing.T) { + a := &ImportArtifact{} + if a.Files() != nil { + t.Fatalf("bad: %#v", a.Files()) + } +} + +func TestImportArtifactId(t *testing.T) { + a := &ImportArtifact{IdValue: "foo"} + if a.Id() != "foo" { + t.Fatalf("bad: %#v", a.Id()) + } +} + +func TestImportArtifactDestroy(t *testing.T) { + d := new(MockDriver) + a := &ImportArtifact{ + Driver: d, + IdValue: "foo", + } + + // No error + if err := a.Destroy(); err != nil { + t.Fatalf("err: %s", err) + } + if !d.DeleteImageCalled { + t.Fatal("delete image should be called") + } + if d.DeleteImageId != "foo" { + t.Fatalf("bad: %#v", d.DeleteImageId) + } + + // With an error + d.DeleteImageErr = errors.New("foo") + if err := a.Destroy(); err != d.DeleteImageErr { + t.Fatalf("err: %#v", err) + } +} diff --git a/builder/docker/driver.go b/builder/docker/driver.go index d182ba145..174cc8ef2 100644 --- a/builder/docker/driver.go +++ b/builder/docker/driver.go @@ -8,6 +8,9 @@ import ( // Docker. The Driver interface also allows the steps to be tested since // a mock driver can be shimmed in. type Driver interface { + // Delete an image that is imported into Docker + DeleteImage(id string) error + // Export exports the container with the given ID to the given writer. Export(id string, dst io.Writer) error diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index 676045f1a..f55adaba9 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -15,6 +15,25 @@ type DockerDriver struct { Tpl *packer.ConfigTemplate } +func (d *DockerDriver) DeleteImage(id string) error { + var stderr bytes.Buffer + cmd := exec.Command("docker", "rmi", id) + cmd.Stderr = &stderr + + log.Printf("Deleting image: %s", id) + if err := cmd.Start(); err != nil { + return err + } + + if err := cmd.Wait(); err != nil { + err = fmt.Errorf("Error deleting image: %s\nStderr: %s", + err, stderr.String()) + return err + } + + return nil +} + func (d *DockerDriver) Export(id string, dst io.Writer) error { var stderr bytes.Buffer cmd := exec.Command("docker", "export", id) diff --git a/builder/docker/driver_mock.go b/builder/docker/driver_mock.go index be28d680d..7fa118b28 100644 --- a/builder/docker/driver_mock.go +++ b/builder/docker/driver_mock.go @@ -6,6 +6,10 @@ import ( // MockDriver is a driver implementation that can be used for tests. type MockDriver struct { + DeleteImageCalled bool + DeleteImageId string + DeleteImageErr error + ExportReader io.Reader ExportError error PullError error @@ -25,6 +29,12 @@ type MockDriver struct { VerifyCalled bool } +func (d *MockDriver) DeleteImage(id string) error { + d.DeleteImageCalled = true + d.DeleteImageId = id + return d.DeleteImageErr +} + func (d *MockDriver) Export(id string, dst io.Writer) error { d.ExportCalled = true d.ExportID = id diff --git a/post-processor/docker-import/post-processor.go b/post-processor/docker-import/post-processor.go index c752ad5da..dcc9f7bac 100644 --- a/post-processor/docker-import/post-processor.go +++ b/post-processor/docker-import/post-processor.go @@ -1,18 +1,22 @@ package dockerimport import ( + "bytes" "fmt" + "github.com/mitchellh/packer/builder/docker" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "io" "os" "os/exec" + "strings" ) +const BuilderId = "packer.post-processor.docker-import" + type Config struct { common.PackerConfig `mapstructure:",squash"` - Dockerfile string `mapstructure:"dockerfile"` Repository string `mapstructure:"repository"` Tag string `mapstructure:"tag"` @@ -39,7 +43,6 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { errs := new(packer.MultiError) templates := map[string]*string{ - "dockerfile": &p.config.Dockerfile, "repository": &p.config.Repository, "tag": &p.config.Tag, } @@ -71,7 +74,9 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac importRepo += ":" + p.config.Tag } + var stdout bytes.Buffer cmd := exec.Command("docker", "import", "-", importRepo) + cmd.Stdout = &stdout stdin, err := cmd.StdinPipe() if err != nil { return nil, false, err @@ -96,38 +101,21 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac io.Copy(stdin, file) }() - cmd.Wait() - - // Process Dockerfile if provided - if p.config.Dockerfile != "" { - cmd := exec.Command("docker", "build", "-t="+importRepo, "-") - - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, false, err - } - - // open Dockerfile - file, err := os.Open(p.config.Dockerfile) - if err != nil { - err = fmt.Errorf("Couldn't open Dockerfile: %s", err) - return nil, false, err - } - defer file.Close() - - ui.Message("Running docker build with Dockerfile: " + p.config.Dockerfile) - if err := cmd.Start(); err != nil { - err = fmt.Errorf("Failed to start docker build: %s", err) - return nil, false, err - } - - go func() { - defer stdin.Close() - io.Copy(stdin, file) - }() - - cmd.Wait() - + if err := cmd.Wait(); err != nil { + err = fmt.Errorf("Error importing container: %s", err) + return nil, false, err } - return nil, false, nil + + id := strings.TrimSpace(stdout.String()) + ui.Message("Imported ID: " + id) + + // Build the artifact + driver := &docker.DockerDriver{Tpl: p.config.tpl, Ui: ui} + artifact = &docker.ImportArtifact{ + BuilderIdValue: BuilderId, + Driver: driver, + IdValue: id, + } + + return artifact, false, nil }