From bf16683140b9972e3a1e523376126da38da7e7ee Mon Sep 17 00:00:00 2001 From: Andy Thompson Date: Sun, 20 Jul 2014 19:58:03 +0100 Subject: [PATCH] Add a docker tag post processor --- builder/docker/driver.go | 3 + builder/docker/driver_docker.go | 15 +++ builder/docker/driver_mock.go | 12 ++ config.go | 1 + plugin/post-processor-docker-tag/main.go | 15 +++ plugin/post-processor-docker-tag/main_test.go | 1 + post-processor/docker-tag/post-processor.go | 103 ++++++++++++++++++ .../docker-tag/post-processor_test.go | 72 ++++++++++++ 8 files changed, 222 insertions(+) create mode 100644 plugin/post-processor-docker-tag/main.go create mode 100644 plugin/post-processor-docker-tag/main_test.go create mode 100644 post-processor/docker-tag/post-processor.go create mode 100644 post-processor/docker-tag/post-processor_test.go diff --git a/builder/docker/driver.go b/builder/docker/driver.go index f0cf55821..d54eac5a1 100644 --- a/builder/docker/driver.go +++ b/builder/docker/driver.go @@ -33,6 +33,9 @@ type Driver interface { // StopContainer forcibly stops a container. StopContainer(id string) error + // TagImage tags the image with the given ID + TagImage(id string, repo string) error + // Verify verifies that the driver can run Verify() error } diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index c9b1557e9..d334eef2a 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -169,6 +169,21 @@ func (d *DockerDriver) StopContainer(id string) error { return exec.Command("docker", "kill", id).Run() } +func (d *DockerDriver) TagImage(id string, repo string) error { + cmd := exec.Command("docker", "tag", id, repo) + + if err := cmd.Start(); err != nil { + return err + } + + if err := cmd.Wait(); err != nil { + err = fmt.Errorf("Error tagging image: %s", err) + return err + } + + return nil +} + func (d *DockerDriver) Verify() error { if _, err := exec.LookPath("docker"); err != nil { return err diff --git a/builder/docker/driver_mock.go b/builder/docker/driver_mock.go index e45c34e88..8afa4fd85 100644 --- a/builder/docker/driver_mock.go +++ b/builder/docker/driver_mock.go @@ -25,6 +25,11 @@ type MockDriver struct { PushName string PushErr error + TagImageCalled bool + TagImageImageId string + TagImageRepo string + TagImageErr error + ExportReader io.Reader ExportError error PullError error @@ -101,6 +106,13 @@ func (d *MockDriver) StopContainer(id string) error { return d.StopError } +func (d *MockDriver) TagImage(id string, repo string) error { + d.TagImageCalled = true + d.TagImageImageId = id + d.TagImageRepo = repo + return d.TagImageErr +} + func (d *MockDriver) Verify() error { d.VerifyCalled = true return d.VerifyError diff --git a/config.go b/config.go index 44d362d26..4a6b52547 100644 --- a/config.go +++ b/config.go @@ -48,6 +48,7 @@ const defaultConfig = ` "vsphere": "packer-post-processor-vsphere", "docker-push": "packer-post-processor-docker-push", "docker-import": "packer-post-processor-docker-import", + "docker-tag": "packer-post-processor-docker-tag", "vagrant-cloud": "packer-post-processor-vagrant-cloud" }, diff --git a/plugin/post-processor-docker-tag/main.go b/plugin/post-processor-docker-tag/main.go new file mode 100644 index 000000000..e226ff697 --- /dev/null +++ b/plugin/post-processor-docker-tag/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/docker-tag" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterPostProcessor(new(dockertag.PostProcessor)) + server.Serve() +} diff --git a/plugin/post-processor-docker-tag/main_test.go b/plugin/post-processor-docker-tag/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/post-processor-docker-tag/main_test.go @@ -0,0 +1 @@ +package main diff --git a/post-processor/docker-tag/post-processor.go b/post-processor/docker-tag/post-processor.go new file mode 100644 index 000000000..d68b48e4c --- /dev/null +++ b/post-processor/docker-tag/post-processor.go @@ -0,0 +1,103 @@ +package dockertag + +import ( + "fmt" + "github.com/mitchellh/packer/builder/docker" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/post-processor/docker-import" +) + +const BuilderId = "packer.post-processor.docker-tag" + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Repository string `mapstructure:"repository"` + Tag string `mapstructure:"tag"` + + tpl *packer.ConfigTemplate +} + +type PostProcessor struct { + Driver docker.Driver + + config Config +} + +func (p *PostProcessor) Configure(raws ...interface{}) error { + _, err := common.DecodeConfig(&p.config, raws...) + if err != nil { + return err + } + + p.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return err + } + p.config.tpl.UserVars = p.config.PackerUserVars + + // Accumulate any errors + errs := new(packer.MultiError) + + templates := map[string]*string{ + "repository": &p.config.Repository, + "tag": &p.config.Tag, + } + + for key, ptr := range templates { + if *ptr == "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("%s must be set", key)) + } + + *ptr, err = p.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", key, err)) + } + } + + if len(errs.Errors) > 0 { + return errs + } + + return nil + +} + +func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { + if artifact.BuilderId() != dockerimport.BuilderId { + err := fmt.Errorf( + "Unknown artifact type: %s\nCan only tag from Docker builder artifacts.", + artifact.BuilderId()) + return nil, false, err + } + + driver := p.Driver + if driver == nil { + // If no driver is set, then we use the real driver + driver = &docker.DockerDriver{Tpl: p.config.tpl, Ui: ui} + } + + importRepo := p.config.Repository + if p.config.Tag != "" { + importRepo += ":" + p.config.Tag + } + + ui.Message("Tagging image: " + artifact.Id()) + ui.Message("Repository: " + importRepo) + err := driver.TagImage(artifact.Id(), importRepo) + if err != nil { + return nil, false, err + } + + // Build the artifact + artifact = &docker.ImportArtifact{ + BuilderIdValue: BuilderId, + Driver: driver, + IdValue: importRepo, + } + + return artifact, true, nil +} diff --git a/post-processor/docker-tag/post-processor_test.go b/post-processor/docker-tag/post-processor_test.go new file mode 100644 index 000000000..925419a10 --- /dev/null +++ b/post-processor/docker-tag/post-processor_test.go @@ -0,0 +1,72 @@ +package dockertag + +import ( + "bytes" + "github.com/mitchellh/packer/builder/docker" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/post-processor/docker-import" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "repository": "foo", + "tag": "bar", + } +} + +func testPP(t *testing.T) *PostProcessor { + var p PostProcessor + if err := p.Configure(testConfig()); err != nil { + t.Fatalf("err: %s", err) + } + + return &p +} + +func testUi() *packer.BasicUi { + return &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + } +} + +func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { + var _ packer.PostProcessor = new(PostProcessor) +} + +func TestPostProcessor_PostProcess(t *testing.T) { + driver := &docker.MockDriver{} + p := &PostProcessor{Driver: driver} + _, err := common.DecodeConfig(&p.config, testConfig()) + if err != nil { + t.Fatalf("err %s", err) + } + + artifact := &packer.MockArtifact{ + BuilderIdValue: dockerimport.BuilderId, + IdValue: "1234567890abcdef", + } + + result, keep, err := p.PostProcess(testUi(), artifact) + if _, ok := result.(packer.Artifact); !ok { + t.Fatal("should be instance of Artifact") + } + if !keep { + t.Fatal("should keep") + } + if err != nil { + t.Fatalf("err: %s", err) + } + + if !driver.TagImageCalled { + t.Fatal("should call TagImage") + } + if driver.TagImageImageId != "1234567890abcdef" { + t.Fatal("bad image id") + } + if driver.TagImageRepo != "foo:bar" { + t.Fatal("bad repo") + } +}