builder/docker: StepRun tests
This commit is contained in:
parent
b10abe30e0
commit
20f76c6ffc
|
@ -6,4 +6,17 @@ package docker
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
// Pull should pull down the given image.
|
// Pull should pull down the given image.
|
||||||
Pull(image string) error
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DockerDriver struct {
|
type DockerDriver struct {
|
||||||
|
@ -13,3 +17,42 @@ func (d *DockerDriver) Pull(image string) error {
|
||||||
cmd := exec.Command("docker", "pull", image)
|
cmd := exec.Command("docker", "pull", image)
|
||||||
return runAndStream(cmd, d.Ui)
|
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()
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,17 @@ package docker
|
||||||
|
|
||||||
// MockDriver is a driver implementation that can be used for tests.
|
// MockDriver is a driver implementation that can be used for tests.
|
||||||
type MockDriver struct {
|
type MockDriver struct {
|
||||||
PullError error
|
PullError error
|
||||||
|
StartID string
|
||||||
|
StartError error
|
||||||
|
StopError error
|
||||||
|
|
||||||
PullCalled bool
|
PullCalled bool
|
||||||
PullImage string
|
PullImage string
|
||||||
|
StartCalled bool
|
||||||
|
StartConfig *ContainerConfig
|
||||||
|
StopCalled bool
|
||||||
|
StopID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *MockDriver) Pull(image string) error {
|
func (d *MockDriver) Pull(image string) error {
|
||||||
|
@ -13,3 +20,15 @@ func (d *MockDriver) Pull(image string) error {
|
||||||
d.PullImage = image
|
d.PullImage = image
|
||||||
return d.PullError
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestMockDriver_impl(t *testing.T) {
|
||||||
|
var _ Driver = new(MockDriver)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDockerDriver_impl(t *testing.T) {
|
||||||
|
var _ Driver = new(DockerDriver)
|
||||||
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepRun struct {
|
type StepRun struct {
|
||||||
|
@ -16,47 +12,30 @@ type StepRun struct {
|
||||||
|
|
||||||
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
tempDir := state.Get("temp_dir").(string)
|
tempDir := state.Get("temp_dir").(string)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Starting docker container with /bin/bash")
|
runConfig := ContainerConfig{
|
||||||
|
Image: config.Image,
|
||||||
// Args that we're going to pass to Docker
|
Volumes: map[string]string{
|
||||||
args := []string{
|
tempDir: "/packer-files",
|
||||||
"run",
|
},
|
||||||
"-d", "-i", "-t",
|
|
||||||
"-v", fmt.Sprintf("%s:/packer-files", tempDir),
|
|
||||||
config.Image,
|
|
||||||
"/bin/bash",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the container
|
ui.Say("Starting docker container with /bin/bash")
|
||||||
var stdout, stderr bytes.Buffer
|
containerId, err := driver.StartContainer(&runConfig)
|
||||||
cmd := exec.Command("docker", args...)
|
if err != nil {
|
||||||
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)
|
err := fmt.Errorf("Error running container: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
// Save the container ID
|
||||||
err := fmt.Errorf("Error running container: %s\nStderr: %s",
|
s.containerId = containerId
|
||||||
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)
|
state.Put("container_id", s.containerId)
|
||||||
|
ui.Message(fmt.Sprintf("Container ID: %s", s.containerId))
|
||||||
return multistep.ActionContinue
|
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
|
// Kill the container. We don't handle errors because errors usually
|
||||||
// just mean that the container doesn't exist anymore, which isn't a
|
// just mean that the container doesn't exist anymore, which isn't a
|
||||||
// big deal.
|
// 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 = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue