builder/docker: StepExport tests

This commit is contained in:
Mitchell Hashimoto 2013-11-09 13:15:51 -08:00
parent 20f76c6ffc
commit e88a0a57b9
6 changed files with 165 additions and 35 deletions

View File

@ -1,9 +1,16 @@
package docker package docker
import (
"io"
)
// Driver is the interface that has to be implemented to communicate with // Driver is the interface that has to be implemented to communicate with
// Docker. The Driver interface also allows the steps to be tested since // Docker. The Driver interface also allows the steps to be tested since
// a mock driver can be shimmed in. // a mock driver can be shimmed in.
type Driver interface { type Driver interface {
// Export exports the container with the given ID to the given writer.
Export(id string, dst io.Writer) error
// Pull should pull down the given image. // Pull should pull down the given image.
Pull(image string) error Pull(image string) error

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"io"
"log" "log"
"os/exec" "os/exec"
"strings" "strings"
@ -13,6 +14,26 @@ type DockerDriver struct {
Ui packer.Ui Ui packer.Ui
} }
func (d *DockerDriver) Export(id string, dst io.Writer) error {
var stderr bytes.Buffer
cmd := exec.Command("docker", "export", id)
cmd.Stdout = dst
cmd.Stderr = &stderr
log.Printf("Exporting container: %s", id)
if err := cmd.Start(); err != nil {
return err
}
if err := cmd.Wait(); err != nil {
err = fmt.Errorf("Error exporting: %s\nStderr: %s",
err, stderr.String())
return err
}
return nil
}
func (d *DockerDriver) Pull(image string) error { 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)

View File

@ -1,18 +1,40 @@
package docker package docker
import (
"io"
)
// 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 ExportReader io.Reader
StartID string ExportError error
StartError error PullError error
StopError error StartID string
StartError error
StopError error
PullCalled bool ExportCalled bool
PullImage string ExportID string
StartCalled bool PullCalled bool
StartConfig *ContainerConfig PullImage string
StopCalled bool StartCalled bool
StopID string StartConfig *ContainerConfig
StopCalled bool
StopID string
}
func (d *MockDriver) Export(id string, dst io.Writer) error {
d.ExportCalled = true
d.ExportID = id
if d.ExportReader != nil {
_, err := io.Copy(dst, d.ExportReader)
if err != nil {
return err
}
}
return d.ExportError
} }
func (d *MockDriver) Pull(image string) error { func (d *MockDriver) Pull(image string) error {

0
builder/docker/foo Normal file
View File

View File

@ -1,13 +1,10 @@
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" "os"
"os/exec"
) )
// StepExport exports the container to a flat tar file. // StepExport exports the container to a flat tar file.
@ -15,14 +12,10 @@ type StepExport struct{}
func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
containerId := state.Get("container_id").(string) containerId := state.Get("container_id").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Exporting the container")
// Args that we're going to pass to Docker
args := []string{"export", containerId}
// Open the file that we're going to write to // Open the file that we're going to write to
f, err := os.Create(config.ExportPath) f, err := os.Create(config.ExportPath)
if err != nil { if err != nil {
@ -31,30 +24,18 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
defer f.Close()
// Export the thing, take stderr and point it to the file ui.Say("Exporting the container")
var stderr bytes.Buffer if err := driver.Export(containerId, f); err != nil {
cmd := exec.Command("docker", args...) f.Close()
cmd.Stdout = f os.Remove(f.Name())
cmd.Stderr = &stderr
log.Printf("Starting container with args: %v", args)
if err := cmd.Start(); err != nil {
err := fmt.Errorf("Error exporting: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := cmd.Wait(); err != nil {
err := fmt.Errorf("Error exporting: %s\nStderr: %s",
err, stderr.String())
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
f.Close()
return multistep.ActionContinue return multistep.ActionContinue
} }

View File

@ -0,0 +1,99 @@
package docker
import (
"bytes"
"errors"
"github.com/mitchellh/multistep"
"io/ioutil"
"os"
"testing"
)
func testStepExportState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("container_id", "foo")
return state
}
func TestStepExport_impl(t *testing.T) {
var _ multistep.Step = new(StepExport)
}
func TestStepExport(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportReader = bytes.NewReader([]byte("data!"))
// 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.ExportCalled {
t.Fatal("should've exported")
}
if driver.ExportID != "foo" {
t.Fatalf("bad: %#v", driver.ExportID)
}
// verify the data exported to the file
contents, err := ioutil.ReadFile(tf.Name())
if err != nil {
t.Fatalf("err: %s", err)
}
if string(contents) != "data!" {
t.Fatalf("bad: %#v", string(contents))
}
}
func TestStepExport_error(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
if err := os.Remove(tf.Name()); err != nil {
t.Fatalf("err: %s", err)
}
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify we have an error
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
// verify we didn't make that file
if _, err := os.Stat(tf.Name()); err == nil {
t.Fatal("export path shouldn't exist")
}
}