builder/docker: StepRun tests

This commit is contained in:
Mitchell Hashimoto 2013-11-09 13:03:01 -08:00
parent b10abe30e0
commit 20f76c6ffc
7 changed files with 204 additions and 37 deletions

View File

@ -6,4 +6,17 @@ package docker
type Driver interface {
// Pull should pull down the given image.
Pull(image string) error
// StartContainer starts a container and returns the ID for that container,
// along with a potential error.
StartContainer(*ContainerConfig) (string, error)
// StopContainer forcibly stops a container.
StopContainer(id string) error
}
// ContainerConfig is the configuration used to start a container.
type ContainerConfig struct {
Image string
Volumes map[string]string
}

View File

@ -1,8 +1,12 @@
package docker
import (
"bytes"
"fmt"
"github.com/mitchellh/packer/packer"
"log"
"os/exec"
"strings"
)
type DockerDriver struct {
@ -13,3 +17,42 @@ func (d *DockerDriver) Pull(image string) error {
cmd := exec.Command("docker", "pull", image)
return runAndStream(cmd, d.Ui)
}
func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) {
// Args that we're going to pass to Docker
args := []string{"run", "-d", "-i", "-t"}
if len(config.Volumes) > 0 {
volumes := make([]string, 0, len(config.Volumes))
for host, guest := range config.Volumes {
volumes = append(volumes, fmt.Sprintf("%s:%s", host, guest))
}
args = append(args, "-v", strings.Join(volumes, ","))
}
args = append(args, config.Image, "/bin/bash")
// Start the container
var stdout, stderr bytes.Buffer
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 {
return "", err
}
log.Println("Waiting for container to finish starting")
if err := cmd.Wait(); err != nil {
return "", err
}
// Capture the container ID, which is alone on stdout
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) StopContainer(id string) error {
return exec.Command("docker", "kill", id).Run()
}

View File

@ -3,9 +3,16 @@ package docker
// MockDriver is a driver implementation that can be used for tests.
type MockDriver struct {
PullError error
StartID string
StartError error
StopError error
PullCalled bool
PullImage string
StartCalled bool
StartConfig *ContainerConfig
StopCalled bool
StopID string
}
func (d *MockDriver) Pull(image string) error {
@ -13,3 +20,15 @@ func (d *MockDriver) Pull(image string) error {
d.PullImage = image
return d.PullError
}
func (d *MockDriver) StartContainer(config *ContainerConfig) (string, error) {
d.StartCalled = true
d.StartConfig = config
return d.StartID, d.StartError
}
func (d *MockDriver) StopContainer(id string) error {
d.StopCalled = true
d.StopID = id
return d.StopError
}

View File

@ -0,0 +1,7 @@
package docker
import "testing"
func TestMockDriver_impl(t *testing.T) {
var _ Driver = new(MockDriver)
}

View File

@ -0,0 +1,7 @@
package docker
import "testing"
func TestDockerDriver_impl(t *testing.T) {
var _ Driver = new(DockerDriver)
}

View File

@ -1,13 +1,9 @@
package docker
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os/exec"
"strings"
)
type StepRun struct {
@ -16,47 +12,30 @@ type StepRun struct {
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
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",
runConfig := ContainerConfig{
Image: config.Image,
Volumes: map[string]string{
tempDir: "/packer-files",
},
}
// Start the container
var stdout, stderr bytes.Buffer
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 {
ui.Say("Starting docker container with /bin/bash")
containerId, err := driver.StartContainer(&runConfig)
if err != nil {
err := fmt.Errorf("Error running container: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := cmd.Wait(); err != nil {
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))
// Save the container ID
s.containerId = containerId
state.Put("container_id", s.containerId)
ui.Message(fmt.Sprintf("Container ID: %s", s.containerId))
return multistep.ActionContinue
}
@ -68,5 +47,9 @@ func (s *StepRun) Cleanup(state multistep.StateBag) {
// 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()
driver := state.Get("driver").(Driver)
driver.StopContainer(s.containerId)
// Reset the container ID so that we're idempotent
s.containerId = ""
}

View File

@ -0,0 +1,95 @@
package docker
import (
"errors"
"github.com/mitchellh/multistep"
"testing"
)
func testStepRunState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("temp_dir", "/foo")
return state
}
func TestStepRun_impl(t *testing.T) {
var _ multistep.Step = new(StepRun)
}
func TestStepRun(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
driver.StartID = "foo"
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.StartCalled {
t.Fatal("should've called")
}
if driver.StartConfig.Image != config.Image {
t.Fatalf("bad: %#v", driver.StartConfig.Image)
}
// verify the ID is saved
idRaw, ok := state.GetOk("container_id")
if !ok {
t.Fatal("should've saved ID")
}
id := idRaw.(string)
if id != "foo" {
t.Fatalf("bad: %#v", id)
}
// Verify we haven't called stop yet
if driver.StopCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if !driver.StopCalled {
t.Fatal("should've stopped")
}
if driver.StopID != id {
t.Fatalf("bad: %#v", driver.StopID)
}
}
func TestStepRun_error(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.StartError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify the ID is not saved
if _, ok := state.GetOk("container_id"); ok {
t.Fatal("shouldn't save container ID")
}
// Verify we haven't called stop yet
if driver.StopCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if driver.StopCalled {
t.Fatal("should not have stopped")
}
}