builder/docker: a non-working communicator

This commit is contained in:
Mitchell Hashimoto 2013-11-08 23:43:41 -08:00
parent 2e080ece6d
commit 797c44bfc1
6 changed files with 203 additions and 3 deletions

View File

@ -37,8 +37,10 @@ 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{
&StepTempDir{},
&StepPull{},
&StepRun{},
&StepProvision{},
}
// Setup the state bag and initial state for the steps

View File

@ -0,0 +1,98 @@
package docker
import (
"fmt"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"syscall"
)
type Communicator struct {
ContainerId string
HostDir string
ContainerDir string
}
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
cmd := exec.Command("docker", "attach", c.ContainerId)
stdin_w, err := cmd.StdinPipe()
if err != nil {
return err
}
cmd.Stdout = remote.Stdout
cmd.Stderr = remote.Stderr
log.Printf("Executing in container %s: %#v", c.ContainerId, remote.Command)
if err := cmd.Start(); err != nil {
return err
}
go func() {
defer stdin_w.Close()
stdin_w.Write([]byte(remote.Command + "\n"))
}()
var exitStatus int = 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()
}
}
if exitStatus != 0 {
return fmt.Errorf("Exit status: %d", exitStatus)
}
return nil
}
func (c *Communicator) Upload(dst string, src io.Reader) error {
// Create a temporary file to store the upload
tempfile, err := ioutil.TempFile(c.HostDir, "upload")
if err != nil {
return err
}
defer os.Remove(tempfile.Name())
// Copy the contents to the temporary file
_, err = io.Copy(tempfile, src)
tempfile.Close()
if err != nil {
return err
}
// TODO(mitchellh): Copy the file into place
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("cp %s %s", tempfile.Name(), dst),
}
if err := c.Start(cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
}
return nil
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
return nil
}
func (c *Communicator) Download(src string, dst io.Writer) error {
return nil
}

View File

@ -0,0 +1,10 @@
package docker
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestCommunicator_impl(t *testing.T) {
var _ packer.Communicator = new(Communicator)
}

View File

@ -0,0 +1,33 @@
package docker
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepProvision struct{}
func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
containerId := state.Get("container_id").(string)
hook := state.Get("hook").(packer.Hook)
tempDir := state.Get("temp_dir").(string)
ui := state.Get("ui").(packer.Ui)
// Create the communicator that talks to Docker via various
// os/exec tricks.
comm := &Communicator{
ContainerId: containerId,
HostDir: tempDir,
ContainerDir: "/packer-files",
}
// Run the provisioning hook
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepProvision) Cleanup(state multistep.StateBag) {}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os/exec"
"strings"
)
@ -15,14 +16,27 @@ type StepRun struct {
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
tempDir := state.Get("temp_dir").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Starting docker container with /bin/bash")
// Args that we're going to pass to Docker
args := []string{
"run",
"-d", "-i", "-t",
"-v", fmt.Sprintf("%s:/packer-files", tempDir),
config.Image,
"/bin/bash",
}
// Start the container
var stdout, stderr bytes.Buffer
cmd := exec.Command("docker", "run", "-d", "-i", "-t", config.Image, "/bin/bash")
cmd := exec.Command("docker", args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
log.Printf("Starting container with args: %v", args)
if err := cmd.Start(); err != nil {
err := fmt.Errorf("Error running container: %s", err)
state.Put("error", err)
@ -31,15 +45,18 @@ func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
}
if err := cmd.Wait(); err != nil {
err := fmt.Errorf("Error running container: %s", err)
err := fmt.Errorf("Error running container: %s\nStderr: %s",
err, stderr.String())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Capture the container ID, which is alone on stdout
s.containerId = strings.TrimSpace(stdout.String())
ui.Message(fmt.Sprintf("Container ID: %s", s.containerId))
state.Put("container_id", s.containerId)
return multistep.ActionContinue
}
@ -48,6 +65,8 @@ func (s *StepRun) Cleanup(state multistep.StateBag) {
return
}
// TODO(mitchellh): handle errors
// Kill the container. We don't handle errors because errors usually
// just mean that the container doesn't exist anymore, which isn't a
// big deal.
exec.Command("docker", "kill", s.containerId).Run()
}

View File

@ -0,0 +1,38 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
)
// StepTempDir creates a temporary directory that we use in order to
// share data with the docker container over the communicator.
type StepTempDir struct {
tempDir string
}
func (s *StepTempDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating a temporary directory for sharing data...")
td, err := ioutil.TempDir("", "packer-docker")
if err != nil {
err := fmt.Errorf("Error making temp dir: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.tempDir = td
state.Put("temp_dir", s.tempDir)
return multistep.ActionContinue
}
func (s *StepTempDir) Cleanup(state multistep.StateBag) {
if s.tempDir != "" {
os.RemoveAll(s.tempDir)
}
}