From cab2665119a0253448824e6b0ae4363bf6026cbf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 14 Jun 2015 22:09:38 -0700 Subject: [PATCH] builder/docker: support custom communicators --- builder/docker/builder.go | 11 +++- builder/docker/comm.go | 52 +++++++++++++++++++ builder/docker/config.go | 10 ++++ builder/docker/driver.go | 4 ++ builder/docker/driver_docker.go | 17 ++++++ builder/docker/driver_mock.go | 11 ++++ ...ep_provision.go => step_connect_docker.go} | 11 ++-- helper/communicator/step_connect.go | 8 +++ 8 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 builder/docker/comm.go rename builder/docker/{step_provision.go => step_connect_docker.go} (68%) diff --git a/builder/docker/builder.go b/builder/docker/builder.go index 96a79b02d..89880aacc 100644 --- a/builder/docker/builder.go +++ b/builder/docker/builder.go @@ -5,6 +5,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/communicator" "github.com/mitchellh/packer/packer" ) @@ -42,7 +43,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepTempDir{}, &StepPull{}, &StepRun{}, - &StepProvision{}, + &communicator.StepConnect{ + Config: &b.config.Comm, + Host: commHost, + SSHConfig: sshConfig(&b.config.Comm), + CustomConnect: map[string]multistep.Step{ + "docker": &StepConnectDocker{}, + }, + }, + &common.StepProvision{}, } if b.config.Commit { diff --git a/builder/docker/comm.go b/builder/docker/comm.go new file mode 100644 index 000000000..a42d12525 --- /dev/null +++ b/builder/docker/comm.go @@ -0,0 +1,52 @@ +package docker + +import ( + "fmt" + "io/ioutil" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/communicator/ssh" + "github.com/mitchellh/packer/helper/communicator" + gossh "golang.org/x/crypto/ssh" +) + +func commHost(state multistep.StateBag) (string, error) { + containerId := state.Get("container_id").(string) + driver := state.Get("driver").(Driver) + return driver.IPAddress(containerId) +} + +func sshConfig(comm *communicator.Config) func(state multistep.StateBag) (*gossh.ClientConfig, error) { + return func(state multistep.StateBag) (*gossh.ClientConfig, error) { + if comm.SSHPrivateKey != "" { + // key based auth + bytes, err := ioutil.ReadFile(comm.SSHPrivateKey) + if err != nil { + return nil, fmt.Errorf("Error setting up SSH config: %s", err) + } + privateKey := string(bytes) + + signer, err := gossh.ParsePrivateKey([]byte(privateKey)) + if err != nil { + return nil, fmt.Errorf("Error setting up SSH config: %s", err) + } + + return &gossh.ClientConfig{ + User: comm.SSHUsername, + Auth: []gossh.AuthMethod{ + gossh.PublicKeys(signer), + }, + }, nil + } else { + // password based auth + return &gossh.ClientConfig{ + User: comm.SSHUsername, + Auth: []gossh.AuthMethod{ + gossh.Password(comm.SSHPassword), + gossh.KeyboardInteractive( + ssh.PasswordKeyboardInteractive(comm.SSHPassword)), + }, + }, nil + } + } +} diff --git a/builder/docker/config.go b/builder/docker/config.go index d5801c8ba..34fda4309 100644 --- a/builder/docker/config.go +++ b/builder/docker/config.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/mapstructure" "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/communicator" "github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/template/interpolate" @@ -13,6 +14,7 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` + Comm communicator.Config `mapstructure:",squash"` Commit bool ExportPath string `mapstructure:"export_path"` @@ -69,7 +71,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.Pull = true } + // Default to the normal Docker type + if c.Comm.Type == "" { + c.Comm.Type = "docker" + } + var errs *packer.MultiError + if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { + errs = packer.MultiErrorAppend(errs, es...) + } if c.Image == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("image must be specified")) diff --git a/builder/docker/driver.go b/builder/docker/driver.go index 7c9e6b868..d88c71022 100644 --- a/builder/docker/driver.go +++ b/builder/docker/driver.go @@ -22,6 +22,10 @@ type Driver interface { // Import imports a container from a tar file Import(path, repo string) (string, error) + // IPAddress returns the address of the container that can be used + // for external access. + IPAddress(id string) (string, error) + // Login. This will lock the driver from performing another Login // until Logout is called. Therefore, any users MUST call Logout. Login(repo, email, username, password string) error diff --git a/builder/docker/driver_docker.go b/builder/docker/driver_docker.go index ea895d362..0d406b1fa 100644 --- a/builder/docker/driver_docker.go +++ b/builder/docker/driver_docker.go @@ -116,6 +116,23 @@ func (d *DockerDriver) Import(path string, repo string) (string, error) { return strings.TrimSpace(stdout.String()), nil } +func (d *DockerDriver) IPAddress(id string) (string, error) { + var stderr, stdout bytes.Buffer + cmd := exec.Command( + "docker", + "inspect", + "--format", + "{{ .NetworkSettings.IPAddress }}", + id) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("Error: %s\n\nStderr: %s", err, stderr.String()) + } + + return strings.TrimSpace(stdout.String()), nil +} + func (d *DockerDriver) Login(repo, email, user, pass string) error { d.l.Lock() diff --git a/builder/docker/driver_mock.go b/builder/docker/driver_mock.go index 420856742..b0170f85f 100644 --- a/builder/docker/driver_mock.go +++ b/builder/docker/driver_mock.go @@ -23,6 +23,11 @@ type MockDriver struct { ImportId string ImportErr error + IPAddressCalled bool + IPAddressID string + IPAddressResult string + IPAddressErr error + LoginCalled bool LoginEmail string LoginUsername string @@ -104,6 +109,12 @@ func (d *MockDriver) Import(path, repo string) (string, error) { return d.ImportId, d.ImportErr } +func (d *MockDriver) IPAddress(id string) (string, error) { + d.IPAddressCalled = true + d.IPAddressID = id + return d.IPAddressResult, d.IPAddressErr +} + func (d *MockDriver) Login(r, e, u, p string) error { d.LoginCalled = true d.LoginRepo = r diff --git a/builder/docker/step_provision.go b/builder/docker/step_connect_docker.go similarity index 68% rename from builder/docker/step_provision.go rename to builder/docker/step_connect_docker.go index d9852ae2b..31f2ea2e4 100644 --- a/builder/docker/step_provision.go +++ b/builder/docker/step_connect_docker.go @@ -2,12 +2,11 @@ package docker import ( "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/common" ) -type StepProvision struct{} +type StepConnectDocker struct{} -func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepConnectDocker) Run(state multistep.StateBag) multistep.StepAction { containerId := state.Get("container_id").(string) driver := state.Get("driver").(Driver) tempDir := state.Get("temp_dir").(string) @@ -28,8 +27,8 @@ func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction { Version: version, } - prov := common.StepProvision{Comm: comm} - return prov.Run(state) + state.Put("communicator", comm) + return multistep.ActionContinue } -func (s *StepProvision) Cleanup(state multistep.StateBag) {} +func (s *StepConnectDocker) Cleanup(state multistep.StateBag) {} diff --git a/helper/communicator/step_connect.go b/helper/communicator/step_connect.go index ce77333e1..0c1522330 100644 --- a/helper/communicator/step_connect.go +++ b/helper/communicator/step_connect.go @@ -32,6 +32,11 @@ type StepConnect struct { // connecting via WinRM. WinRMConfig func(multistep.StateBag) (*WinRMConfig, error) + // CustomConnect can be set to have custom connectors for specific + // types. These take highest precedence so you can also override + // existing types. + CustomConnect map[string]multistep.Step + substep multistep.Step } @@ -50,6 +55,9 @@ func (s *StepConnect) Run(state multistep.StateBag) multistep.StepAction { WinRMConfig: s.WinRMConfig, }, } + for k, v := range s.CustomConnect { + typeMap[k] = v + } step, ok := typeMap[s.Config.Type] if !ok {