From a26a7f20e644a56e8bfb4c00238fb65bdfad4ebf Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sun, 27 Nov 2016 16:37:14 +0100 Subject: [PATCH] Implemented docker_* options --- builder/docker/builder.go | 6 +- builder/docker/config.go | 61 +++++++++++++++----- builder/docker/config_test.go | 32 +++++++++- builder/docker/driver_api.go | 29 +++++++--- builder/docker/step_pull.go | 2 +- builder/docker/step_pull_test.go | 3 +- post-processor/docker-push/post-processor.go | 41 ++++++++----- website/source/docs/builders/docker.html.md | 11 ++++ 8 files changed, 146 insertions(+), 39 deletions(-) diff --git a/builder/docker/builder.go b/builder/docker/builder.go index a0c3bb72887..f5077fe6c62 100644 --- a/builder/docker/builder.go +++ b/builder/docker/builder.go @@ -35,7 +35,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe var driver Driver if os.Getenv("PACKER_DOCKER_API") != "" { - driver = DockerApiDriverInit(&b.config.ctx, ui) + var err error + driver, err = DockerApiDriverInit(&b.config.ctx, &b.config.DockerHostConfig, ui) + if err != nil { + return nil, err + } } else { driver = &DockerDriver{Ctx: &b.config.ctx, Ui: ui} } diff --git a/builder/docker/config.go b/builder/docker/config.go index e242804328f..d70d32d025e 100644 --- a/builder/docker/config.go +++ b/builder/docker/config.go @@ -2,7 +2,9 @@ package docker import ( "fmt" + "log" "os" + "path/filepath" "github.com/mitchellh/mapstructure" "github.com/mitchellh/packer/common" @@ -13,22 +15,24 @@ import ( ) var ( - errArtifactNotUsed = fmt.Errorf("No instructions given for handling the artifact; expected commit, discard, or export_path") - errArtifactUseConflict = fmt.Errorf("Cannot specify more than one of commit, discard, and export_path") - errExportPathNotFile = fmt.Errorf("export_path must be a file, not a directory") - errImageNotSpecified = fmt.Errorf("Image must be specified") + errArtifactNotUsed = fmt.Errorf("No instructions given for handling the artifact; expected commit, discard, or export_path") + errArtifactUseConflict = fmt.Errorf("Cannot specify more than one of commit, discard, and export_path") + errExportPathNotFile = fmt.Errorf("export_path must be a file, not a directory") + errDockerCertPathNotFound = fmt.Errorf("docker_cert_path could not be found") + errImageNotSpecified = fmt.Errorf("Image must be specified") ) type Config struct { common.PackerConfig `mapstructure:",squash"` Comm communicator.Config `mapstructure:",squash"` + DockerHostConfig DockerHostConfig `mapstructure:",squash"` Commit bool Discard bool ExportPath string `mapstructure:"export_path"` Image string Pty bool - Pull bool + Pull *bool RunCommand []string `mapstructure:"run_command"` Volumes map[string]string Privileged bool `mapstructure:"privileged"` @@ -49,6 +53,29 @@ type Config struct { ctx interpolate.Context } +type DockerHostConfig struct { + Host string `mapstructure:"docker_host"` + TlsVerify *bool `mapstructure:"docker_tls_verify"` + CertPath string `mapstructure:"docker_cert_path"` +} + +func (c *DockerHostConfig) Prepare() []error { + var errs []error + + if c.CertPath != "" { + if fi, err := os.Stat(c.CertPath); err != nil && !fi.IsDir() { + errs = append(errs, errDockerCertPathNotFound) + } + files := []string{"ca.pem", "cert.pem", "key.pem"} + for i := range files { + if fi, err := os.Stat(filepath.Join(c.CertPath, files[i])); err != nil && fi.IsDir() { + errs = append(errs, fmt.Errorf("Could not read file: %s", files[i])) + } + } + } + return errs +} + func NewConfig(raws ...interface{}) (*Config, []string, error) { c := new(Config) @@ -66,6 +93,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { if err != nil { return nil, nil, err } + log.Printf("DockerHostConfig: %v", c.DockerHostConfig) // Defaults if len(c.RunCommand) == 0 { @@ -73,16 +101,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } // Default Pull if it wasn't set - hasPull := false - for _, k := range md.Keys { - if k == "Pull" { - hasPull = true - break - } + if c.Pull == nil { + t := true + c.Pull = &t } - if !hasPull { - c.Pull = true + // Default Docker TLS Verify if it wasn't set + if c.DockerHostConfig.TlsVerify == nil { + t := true + c.DockerHostConfig.TlsVerify = &t } // Default to the normal Docker type @@ -91,6 +118,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } var errs *packer.MultiError + + if es := c.DockerHostConfig.Prepare(); len(es) > 0 { + errs = packer.MultiErrorAppend(errs, es...) + } + if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { errs = packer.MultiErrorAppend(errs, es...) } @@ -120,5 +152,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { return nil, nil, errs } + log.Printf("Config: %v", c) + log.Printf("Comm: %v", &c.Comm) + log.Printf("DockerHostConfig: %v", &c.DockerHostConfig) return c, nil, nil } diff --git a/builder/docker/config_test.go b/builder/docker/config_test.go index 442c6ffe244..8b82a66760e 100644 --- a/builder/docker/config_test.go +++ b/builder/docker/config_test.go @@ -129,7 +129,7 @@ func TestConfigPrepare_pull(t *testing.T) { delete(raw, "pull") c, warns, errs := NewConfig(raw) testConfigOk(t, warns, errs) - if !c.Pull { + if !*c.Pull { t.Fatal("should pull by default") } @@ -137,7 +137,35 @@ func TestConfigPrepare_pull(t *testing.T) { raw["pull"] = false c, warns, errs = NewConfig(raw) testConfigOk(t, warns, errs) - if c.Pull { + if *c.Pull { t.Fatal("should not pull") } } + +func TestConfigPrepare_dockerTlsVerify(t *testing.T) { + raw := testConfig() + + // No pull set + delete(raw, "docker_tls_verify") + c, warns, errs := NewConfig(raw) + testConfigOk(t, warns, errs) + if !*c.DockerHostConfig.TlsVerify { + t.Fatal("should verify TLS by default") + } + + // Pull set + raw["docker_tls_verify"] = false + c, warns, errs = NewConfig(raw) + testConfigOk(t, warns, errs) + if *c.DockerHostConfig.TlsVerify { + t.Fatal("should not verify TLS") + } +} + +func TestConfigPrepare_dockerHost(t *testing.T) { + raw := testConfig() + + raw["docker_host"] = "unix:///var/run/docker.sock" + _, warns, errs := NewConfig(raw) + testConfigOk(t, warns, errs) +} diff --git a/builder/docker/driver_api.go b/builder/docker/driver_api.go index 002cf19f87e..da5bc978e37 100644 --- a/builder/docker/driver_api.go +++ b/builder/docker/driver_api.go @@ -5,8 +5,8 @@ import ( "io" "log" "os" + "path/filepath" "strings" - "sync" godocker "github.com/fsouza/go-dockerclient" "github.com/hashicorp/go-version" @@ -18,22 +18,37 @@ type DockerApiDriver struct { Ui packer.Ui Ctx *interpolate.Context - l sync.Mutex client *godocker.Client auth godocker.AuthConfiguration identityToken string } -func DockerApiDriverInit(ctx *interpolate.Context, ui packer.Ui) DockerApiDriver { - - // TODO Allow specefying DOCKER_ - client, _ := godocker.NewClientFromEnv() +func DockerApiDriverInit(ctx *interpolate.Context, config *DockerHostConfig, ui packer.Ui) (DockerApiDriver, error) { + + var client *godocker.Client + var err error + + if config.Host == "" { + log.Println("Using Docker Host settings from environment variables.") + client, err = godocker.NewClientFromEnv() + } else { + if *config.TlsVerify { + log.Printf("Using Docker Host: %s with verified TLS.", config.Host) + client, err = godocker.NewTLSClient(config.Host, + filepath.Join(config.CertPath, "cert.pem"), + filepath.Join(config.CertPath, "key.pem"), + filepath.Join(config.CertPath, "ca.pem")) + } else { + log.Printf("Using Docker Host: %s", config.Host) + client, err = godocker.NewClient(config.Host) + } + } return DockerApiDriver{ Ui: ui, Ctx: ctx, client: client, - } + }, err } func (d DockerApiDriver) DeleteImage(id string) error { diff --git a/builder/docker/step_pull.go b/builder/docker/step_pull.go index 3f5b1b62aa0..5cf1c6799c4 100644 --- a/builder/docker/step_pull.go +++ b/builder/docker/step_pull.go @@ -14,7 +14,7 @@ func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) - if !config.Pull { + if !*config.Pull { log.Println("Pull disabled, won't docker pull") return multistep.ActionContinue } diff --git a/builder/docker/step_pull_test.go b/builder/docker/step_pull_test.go index 7ba5705d514..d2a61f88dcb 100644 --- a/builder/docker/step_pull_test.go +++ b/builder/docker/step_pull_test.go @@ -86,7 +86,8 @@ func TestStepPull_noPull(t *testing.T) { defer step.Cleanup(state) config := state.Get("config").(*Config) - config.Pull = false + p := false + config.Pull = &p driver := state.Get("driver").(*MockDriver) diff --git a/post-processor/docker-push/post-processor.go b/post-processor/docker-push/post-processor.go index 1dac28e8334..097844ca153 100644 --- a/post-processor/docker-push/post-processor.go +++ b/post-processor/docker-push/post-processor.go @@ -16,13 +16,14 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` - Login bool - LoginEmail string `mapstructure:"login_email"` - LoginUsername string `mapstructure:"login_username"` - LoginPassword string `mapstructure:"login_password"` - LoginServer string `mapstructure:"login_server"` - EcrLogin bool `mapstructure:"ecr_login"` - docker.AwsAccessConfig `mapstructure:",squash"` + Login bool + LoginEmail string `mapstructure:"login_email"` + LoginUsername string `mapstructure:"login_username"` + LoginPassword string `mapstructure:"login_password"` + LoginServer string `mapstructure:"login_server"` + EcrLogin bool `mapstructure:"ecr_login"` + docker.DockerHostConfig `mapstructure:",squash"` + docker.AwsAccessConfig `mapstructure:",squash"` ctx interpolate.Context } @@ -45,8 +46,17 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { return err } + var errs *packer.MultiError + + if es := p.config.DockerHostConfig.Prepare(); len(es) > 0 { + errs = packer.MultiErrorAppend(errs, es...) + } + if p.config.EcrLogin && p.config.LoginServer == "" { - return fmt.Errorf("ECR login requires login server to be provided.") + errs = packer.MultiErrorAppend(fmt.Errorf("ECR login requires login server to be provided.")) + } + if errs != nil && len(errs.Errors) > 0 { + return errs } return nil } @@ -61,14 +71,17 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac } driver := p.Driver - if os.Getenv("PACKER_DOCKER_API") != "" { - driver = docker.DockerApiDriverInit(&p.config.ctx, ui) - } else { - driver = &docker.DockerDriver{Ctx: &p.config.ctx, Ui: ui} - } - if driver == nil { // If no driver is set, then we use the real driver + if os.Getenv("PACKER_DOCKER_API") != "" { + var err error + driver, err = docker.DockerApiDriverInit(&p.config.ctx, &p.config.DockerHostConfig, ui) + if err != nil { + return nil, false, err + } + } else { + driver = &docker.DockerDriver{Ctx: &p.config.ctx, Ui: ui} + } } if p.config.EcrLogin { diff --git a/website/source/docs/builders/docker.html.md b/website/source/docs/builders/docker.html.md index a42d83d5833..bb7477fb9e9 100644 --- a/website/source/docs/builders/docker.html.md +++ b/website/source/docs/builders/docker.html.md @@ -103,6 +103,17 @@ You must specify (only) one of `commit`, `discard`, or `export_path`. Example of instructions are `CMD`, `ENTRYPOINT`, `ENV`, and `EXPOSE`. Example: `[ "USER ubuntu", "WORKDIR /app", "EXPOSE 8080" ]` +- `docker_cert_path` (string) - Path a directory containing "ca.pem", "cert.pem", + and "key.pem" needed to connet the Docker host. Will be ignored if `docker_host` + is not specified. Will be read from environment variable `DOCKER_CERT_PATH`. + +- `docker_host` (string) - The Docker host to connect to. If unset environment + variable `DOCKER_HOST` will be used. Defaults to local host. + +- `docker_tls_verify` (boolean) - Define if TLS certificates should be verified. + Will be ignored if `docker_host` is not specified. Will be read from environment + variable `DOCKER_TLS_VERIFY`. Defaults to true. + - `ecr_login` (boolean) - Defaults to false. If true, the builder will login in order to pull the image from [Amazon EC2 Container Registry (ECR)](https://aws.amazon.com/ecr/).