From 034e04cc1e58ae26f2d6d4011c7fe756558ec5fd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 8 Nov 2013 22:00:57 -0800 Subject: [PATCH] builder/docker: pull images --- builder/docker/builder.go | 6 ++- builder/docker/config.go | 2 + builder/docker/exec.go | 102 ++++++++++++++++++++++++++++++++++++ builder/docker/step_pull.go | 29 ++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 builder/docker/exec.go create mode 100644 builder/docker/step_pull.go diff --git a/builder/docker/builder.go b/builder/docker/builder.go index b9e69151e..9f14a20d0 100644 --- a/builder/docker/builder.go +++ b/builder/docker/builder.go @@ -36,11 +36,15 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - steps := []multistep.Step{} + steps := []multistep.Step{ + &StepPull{}, + } // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", &b.config) + state.Put("hook", hook) + state.Put("ui", ui) // Run! if b.config.PackerDebug { diff --git a/builder/docker/config.go b/builder/docker/config.go index 038b00dda..4bab81bdd 100644 --- a/builder/docker/config.go +++ b/builder/docker/config.go @@ -8,5 +8,7 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` + Image string + tpl *packer.ConfigTemplate } diff --git a/builder/docker/exec.go b/builder/docker/exec.go new file mode 100644 index 000000000..598702410 --- /dev/null +++ b/builder/docker/exec.go @@ -0,0 +1,102 @@ +package docker + +import ( + "fmt" + "github.com/mitchellh/iochan" + "github.com/mitchellh/packer/packer" + "io" + "log" + "os/exec" + "regexp" + "strings" + "sync" + "syscall" +) + +func runAndStream(cmd *exec.Cmd, ui packer.Ui) error { + stdout_r, stdout_w := io.Pipe() + stderr_r, stderr_w := io.Pipe() + defer stdout_w.Close() + defer stderr_w.Close() + + log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:]) + cmd.Stdout = stdout_w + cmd.Stderr = stderr_w + if err := cmd.Start(); err != nil { + return err + } + + // Create the channels we'll use for data + exitCh := make(chan int, 1) + stdoutCh := iochan.DelimReader(stdout_r, '\n') + stderrCh := iochan.DelimReader(stderr_r, '\n') + + // Start the goroutine to watch for the exit + go func() { + defer stdout_w.Close() + defer stderr_w.Close() + exitStatus := 0 + + err := cmd.Wait() + if exitErr, ok := err.(*exec.ExitError); ok { + exitStatus = 1 + + // There is no process-independent way to get the REAL + // exit status so we just try to go deeper. + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + exitStatus = status.ExitStatus() + } + } + + exitCh <- exitStatus + }() + + // This waitgroup waits for the streaming to end + var streamWg sync.WaitGroup + streamWg.Add(2) + + streamFunc := func(ch <-chan string) { + defer streamWg.Done() + + for data := range ch { + data = cleanOutputLine(data) + if data != "" { + ui.Message(data) + } + } + } + + // Stream stderr/stdout + go streamFunc(stderrCh) + go streamFunc(stdoutCh) + + // Wait for the process to end and then wait for the streaming to end + exitStatus := <-exitCh + streamWg.Wait() + + if exitStatus != 0 { + return fmt.Errorf("Bad exit status: %d", exitStatus) + } + + return nil +} + +// cleanOutputLine cleans up a line so that '\r' don't muck up the +// UI output when we're reading from a remote command. +func cleanOutputLine(line string) string { + // Build a regular expression that will get rid of shell codes + re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]") + line = re.ReplaceAllString(line, "") + + // Trim surrounding whitespace + line = strings.TrimSpace(line) + + // Trim up to the first carriage return, since that text would be + // lost anyways. + idx := strings.LastIndex(line, "\r") + if idx > -1 { + line = line[idx+1:] + } + + return line +} diff --git a/builder/docker/step_pull.go b/builder/docker/step_pull.go new file mode 100644 index 000000000..28662eccb --- /dev/null +++ b/builder/docker/step_pull.go @@ -0,0 +1,29 @@ +package docker + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "os/exec" +) + +type StepPull struct{} + +func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + ui := state.Get("ui").(packer.Ui) + + ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image)) + cmd := exec.Command("docker", "pull", config.Image) + if err := runAndStream(cmd, ui); err != nil { + err := fmt.Errorf("Error pulling Docker image: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepPull) Cleanup(state multistep.StateBag) { +}