diff --git a/config.go b/config.go index a2fe49297..72ed19618 100644 --- a/config.go +++ b/config.go @@ -42,7 +42,9 @@ const defaultConfig = ` "post-processors": { "vagrant": "packer-post-processor-vagrant", - "vsphere": "packer-post-processor-vsphere" + "vsphere": "packer-post-processor-vsphere", + "docker-push": "packer-post-processor-docker-push", + "docker-import": "packer-post-processor-docker-import" }, "provisioners": { diff --git a/plugin/post-processor-docker-import/main.go b/plugin/post-processor-docker-import/main.go new file mode 100644 index 000000000..e9446b113 --- /dev/null +++ b/plugin/post-processor-docker-import/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/docker-import" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterPostProcessor(new(dockerimport.PostProcessor)) + server.Serve() +} diff --git a/plugin/post-processor-docker-import/main_test.go b/plugin/post-processor-docker-import/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/post-processor-docker-import/main_test.go @@ -0,0 +1 @@ +package main diff --git a/plugin/post-processor-docker-push/main.go b/plugin/post-processor-docker-push/main.go new file mode 100644 index 000000000..eb45b13bd --- /dev/null +++ b/plugin/post-processor-docker-push/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/docker-push" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterPostProcessor(new(dockerpush.PostProcessor)) + server.Serve() +} diff --git a/plugin/post-processor-docker-push/main_test.go b/plugin/post-processor-docker-push/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/post-processor-docker-push/main_test.go @@ -0,0 +1 @@ +package main diff --git a/post-processor/docker-import/post-processor.go b/post-processor/docker-import/post-processor.go new file mode 100644 index 000000000..e0162a9c4 --- /dev/null +++ b/post-processor/docker-import/post-processor.go @@ -0,0 +1,218 @@ +package dockerimport + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "io" + "os" + "os/exec" +) + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Repository string `mapstructure:"repository"` + Tag string `mapstructure:"tag"` + Dockerfile string `mapstructure:"dockerfile"` + + tpl *packer.ConfigTemplate +} + +type PostProcessor struct { + 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, + } + + 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) { + id := artifact.Id() + ui.Say("Importing image: " + id) + + if p.config.Tag == "" { + + cmd := exec.Command("docker", + "import", + "-", + p.config.Repository) + + stdin, err := cmd.StdinPipe() + + if err != nil { + return nil, false, err + } + + // There should be only one artifact of the Docker builder + file, err := os.Open(artifact.Files()[0]) + + if err != nil { + return nil, false, err + } + + defer file.Close() + + if err := cmd.Start(); err != nil { + ui.Say("Image import failed") + return nil, false, err + } + + go func() { + io.Copy(stdin, file) + // close stdin so that program will exit + stdin.Close() + }() + + cmd.Wait() + + } else { + + cmd := exec.Command("docker", + "import", + "-", + p.config.Repository+":"+p.config.Tag) + + stdin, err := cmd.StdinPipe() + + if err != nil { + return nil, false, err + } + + // There should be only one artifact of the Docker builder + file, err := os.Open(artifact.Files()[0]) + + if err != nil { + return nil, false, err + } + + defer file.Close() + + if err := cmd.Start(); err != nil { + ui.Say("Image import failed") + return nil, false, err + } + + go func() { + io.Copy(stdin, file) + // close stdin so that program will exit + stdin.Close() + }() + + cmd.Wait() + + } + + // Process Dockerfile if provided + if p.config.Dockerfile != "" { + + if p.config.Tag != "" { + + cmd := exec.Command("docker", "build", "-t="+p.config.Repository+":"+p.config.Tag, "-") + + stdin, err := cmd.StdinPipe() + + if err != nil { + return nil, false, err + } + + // open Dockerfile + file, err := os.Open(p.config.Dockerfile) + + if err != nil { + ui.Say("Could not open Dockerfile: " + p.config.Dockerfile) + return nil, false, err + } + + ui.Say(id) + + defer file.Close() + + if err := cmd.Start(); err != nil { + ui.Say("Failed to build image: " + id) + return nil, false, err + } + + go func() { + io.Copy(stdin, file) + // close stdin so that program will exit + stdin.Close() + }() + + cmd.Wait() + + } else { + + cmd := exec.Command("docker", "build", "-t="+p.config.Repository, "-") + + stdin, err := cmd.StdinPipe() + + if err != nil { + return nil, false, err + } + + // open Dockerfile + file, err := os.Open(p.config.Dockerfile) + + if err != nil { + ui.Say("Could not open Dockerfile: " + p.config.Dockerfile) + return nil, false, err + } + + ui.Say(id) + + defer file.Close() + + if err := cmd.Start(); err != nil { + ui.Say("Failed to build image: " + id) + return nil, false, err + } + + go func() { + io.Copy(stdin, file) + // close stdin so that program will exit + stdin.Close() + }() + + cmd.Wait() + } + + } + return nil, false, nil +} diff --git a/post-processor/docker-import/post-processor_test.go b/post-processor/docker-import/post-processor_test.go new file mode 100644 index 000000000..43ac0b4ef --- /dev/null +++ b/post-processor/docker-import/post-processor_test.go @@ -0,0 +1,31 @@ +package dockerimport + +import ( + "bytes" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{} +} + +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) +} diff --git a/post-processor/docker-push/post-processor.go b/post-processor/docker-push/post-processor.go new file mode 100644 index 000000000..53b8a17a4 --- /dev/null +++ b/post-processor/docker-push/post-processor.go @@ -0,0 +1,148 @@ +package dockerpush + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "os/exec" +) + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Repository string `mapstructure:"repository"` + Tag string `mapstructure:"tag"` + Registry string `mapstructure:"registry"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + Email string `mapstructure:"email"` + + tpl *packer.ConfigTemplate +} + +type PostProcessor struct { + 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{ + "username": &p.config.Username, + "password": &p.config.Password, + "repository": &p.config.Repository, + } + + 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) { + id := artifact.Id() + ui.Say("Pushing image: " + id) + + if p.config.Registry == "" { + + if p.config.Email == "" { + cmd := exec.Command("docker", + "login", + "-u="+p.config.Username, + "-p="+p.config.Password) + + if err := cmd.Run(); err != nil { + ui.Say("Login to the registry " + p.config.Registry + " failed") + return nil, false, err + } + + } else { + cmd := exec.Command("docker", + "login", + "-u="+p.config.Username, + "-p="+p.config.Password, + "-e="+p.config.Email) + + if err := cmd.Run(); err != nil { + ui.Say("Login to the registry " + p.config.Registry + " failed") + return nil, false, err + } + + } + + } else { + if p.config.Email == "" { + cmd := exec.Command("docker", + "login", + "-u="+p.config.Username, + "-p="+p.config.Password, + p.config.Registry) + + if err := cmd.Run(); err != nil { + ui.Say("Login to the registry " + p.config.Registry + " failed") + return nil, false, err + } + + } else { + cmd := exec.Command("docker", + "login", + "-u="+p.config.Username, + "-p="+p.config.Password, + "-e="+p.config.Email, + p.config.Registry) + + if err := cmd.Run(); err != nil { + ui.Say("Login to the registry " + p.config.Registry + " failed") + return nil, false, err + } + + } + } + + if p.config.Tag != "" { + + cmd := exec.Command("docker", "push", p.config.Repository+":"+p.config.Tag) + if err := cmd.Run(); err != nil { + ui.Say("Failed to push image: " + id) + return nil, false, err + } + + } else { + + cmd := exec.Command("docker", "push", p.config.Repository) + if err := cmd.Run(); err != nil { + ui.Say("Failed to push image: " + id) + return nil, false, err + } + + } + + return nil, false, nil +} diff --git a/post-processor/docker-push/post-processor_test.go b/post-processor/docker-push/post-processor_test.go new file mode 100644 index 000000000..7631da79d --- /dev/null +++ b/post-processor/docker-push/post-processor_test.go @@ -0,0 +1,31 @@ +package dockerpush + +import ( + "bytes" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{} +} + +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) +}