Commit 34dbf721 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

post-processor/docker-import: have an Artifact

/cc @mmckeen
parent 430963f4
package docker
import (
"fmt"
)
// ImportArtifact is an Artifact implementation for when a container is
// exported from docker into a single flat file.
type ImportArtifact struct {
BuilderIdValue string
Driver Driver
IdValue string
}
func (a *ImportArtifact) BuilderId() string {
return a.BuilderIdValue
}
func (*ImportArtifact) Files() []string {
return nil
}
func (a *ImportArtifact) Id() string {
return a.IdValue
}
func (a *ImportArtifact) String() string {
return fmt.Sprintf("Imported Docker image: %s", a.Id())
}
func (a *ImportArtifact) Destroy() error {
return a.Driver.DeleteImage(a.Id())
}
package docker
import (
"errors"
"github.com/mitchellh/packer/packer"
"testing"
)
func TestImportArtifact_impl(t *testing.T) {
var _ packer.Artifact = new(ImportArtifact)
}
func TestImportArtifactBuilderId(t *testing.T) {
a := &ImportArtifact{BuilderIdValue: "foo"}
if a.BuilderId() != "foo" {
t.Fatalf("bad: %#v", a.BuilderId())
}
}
func TestImportArtifactFiles(t *testing.T) {
a := &ImportArtifact{}
if a.Files() != nil {
t.Fatalf("bad: %#v", a.Files())
}
}
func TestImportArtifactId(t *testing.T) {
a := &ImportArtifact{IdValue: "foo"}
if a.Id() != "foo" {
t.Fatalf("bad: %#v", a.Id())
}
}
func TestImportArtifactDestroy(t *testing.T) {
d := new(MockDriver)
a := &ImportArtifact{
Driver: d,
IdValue: "foo",
}
// No error
if err := a.Destroy(); err != nil {
t.Fatalf("err: %s", err)
}
if !d.DeleteImageCalled {
t.Fatal("delete image should be called")
}
if d.DeleteImageId != "foo" {
t.Fatalf("bad: %#v", d.DeleteImageId)
}
// With an error
d.DeleteImageErr = errors.New("foo")
if err := a.Destroy(); err != d.DeleteImageErr {
t.Fatalf("err: %#v", err)
}
}
...@@ -8,6 +8,9 @@ import ( ...@@ -8,6 +8,9 @@ import (
// 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 {
// Delete an image that is imported into Docker
DeleteImage(id string) error
// Export exports the container with the given ID to the given writer. // Export exports the container with the given ID to the given writer.
Export(id string, dst io.Writer) error Export(id string, dst io.Writer) error
......
...@@ -15,6 +15,25 @@ type DockerDriver struct { ...@@ -15,6 +15,25 @@ type DockerDriver struct {
Tpl *packer.ConfigTemplate Tpl *packer.ConfigTemplate
} }
func (d *DockerDriver) DeleteImage(id string) error {
var stderr bytes.Buffer
cmd := exec.Command("docker", "rmi", id)
cmd.Stderr = &stderr
log.Printf("Deleting image: %s", id)
if err := cmd.Start(); err != nil {
return err
}
if err := cmd.Wait(); err != nil {
err = fmt.Errorf("Error deleting image: %s\nStderr: %s",
err, stderr.String())
return err
}
return nil
}
func (d *DockerDriver) Export(id string, dst io.Writer) error { func (d *DockerDriver) Export(id string, dst io.Writer) error {
var stderr bytes.Buffer var stderr bytes.Buffer
cmd := exec.Command("docker", "export", id) cmd := exec.Command("docker", "export", id)
......
...@@ -6,6 +6,10 @@ import ( ...@@ -6,6 +6,10 @@ import (
// 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 {
DeleteImageCalled bool
DeleteImageId string
DeleteImageErr error
ExportReader io.Reader ExportReader io.Reader
ExportError error ExportError error
PullError error PullError error
...@@ -25,6 +29,12 @@ type MockDriver struct { ...@@ -25,6 +29,12 @@ type MockDriver struct {
VerifyCalled bool VerifyCalled bool
} }
func (d *MockDriver) DeleteImage(id string) error {
d.DeleteImageCalled = true
d.DeleteImageId = id
return d.DeleteImageErr
}
func (d *MockDriver) Export(id string, dst io.Writer) error { func (d *MockDriver) Export(id string, dst io.Writer) error {
d.ExportCalled = true d.ExportCalled = true
d.ExportID = id d.ExportID = id
......
package dockerimport package dockerimport
import ( import (
"bytes"
"fmt" "fmt"
"github.com/mitchellh/packer/builder/docker"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"strings"
) )
const BuilderId = "packer.post-processor.docker-import"
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
Dockerfile string `mapstructure:"dockerfile"`
Repository string `mapstructure:"repository"` Repository string `mapstructure:"repository"`
Tag string `mapstructure:"tag"` Tag string `mapstructure:"tag"`
...@@ -39,7 +43,6 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { ...@@ -39,7 +43,6 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
errs := new(packer.MultiError) errs := new(packer.MultiError)
templates := map[string]*string{ templates := map[string]*string{
"dockerfile": &p.config.Dockerfile,
"repository": &p.config.Repository, "repository": &p.config.Repository,
"tag": &p.config.Tag, "tag": &p.config.Tag,
} }
...@@ -71,7 +74,9 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac ...@@ -71,7 +74,9 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
importRepo += ":" + p.config.Tag importRepo += ":" + p.config.Tag
} }
var stdout bytes.Buffer
cmd := exec.Command("docker", "import", "-", importRepo) cmd := exec.Command("docker", "import", "-", importRepo)
cmd.Stdout = &stdout
stdin, err := cmd.StdinPipe() stdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
return nil, false, err return nil, false, err
...@@ -96,38 +101,21 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac ...@@ -96,38 +101,21 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
io.Copy(stdin, file) io.Copy(stdin, file)
}() }()
cmd.Wait() if err := cmd.Wait(); err != nil {
err = fmt.Errorf("Error importing container: %s", err)
// Process Dockerfile if provided return nil, false, err
if p.config.Dockerfile != "" { }
cmd := exec.Command("docker", "build", "-t="+importRepo, "-")
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, false, err
}
// open Dockerfile
file, err := os.Open(p.config.Dockerfile)
if err != nil {
err = fmt.Errorf("Couldn't open Dockerfile: %s", err)
return nil, false, err
}
defer file.Close()
ui.Message("Running docker build with Dockerfile: " + p.config.Dockerfile)
if err := cmd.Start(); err != nil {
err = fmt.Errorf("Failed to start docker build: %s", err)
return nil, false, err
}
go func() {
defer stdin.Close()
io.Copy(stdin, file)
}()
cmd.Wait() id := strings.TrimSpace(stdout.String())
ui.Message("Imported ID: " + id)
// Build the artifact
driver := &docker.DockerDriver{Tpl: p.config.tpl, Ui: ui}
artifact = &docker.ImportArtifact{
BuilderIdValue: BuilderId,
Driver: driver,
IdValue: id,
} }
return nil, false, nil
return artifact, false, nil
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment