Merge remote-tracking branch 'upstream/master' into puppet-server
This commit is contained in:
commit
ee50657097
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,10 +1,25 @@
|
|||
## 0.5.2 (unreleased)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* **New post-processor:** `docker-import` - Import a Docker image
|
||||
and give it a specific repository/tag.
|
||||
* **New post-processor:** `docker-push` - Push an imported image to
|
||||
a registry.
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: Most downloads made by Packer now use a custom user agent. [GH-803]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Fix crash case if blank parameters are given to Packer. [GH-832]
|
||||
* builders/docker: user variables work properly. [GH-777]
|
||||
* builder/virtualbox,vmware: iso\_checksum is not required if the
|
||||
checksum type is "none"
|
||||
* builder/virtualbox,vmware/qemu: Support for additional scancodes for
|
||||
`boot_command` such as `<up>`, `<left>`, `<insert>`, etc. [GH-808]
|
||||
* provisioners/ansible-local: Properly upload custom playbooks. [GH-829]
|
||||
|
||||
## 0.5.1 (01/02/2014)
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
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())
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
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,12 +8,21 @@ import (
|
|||
// Docker. The Driver interface also allows the steps to be tested since
|
||||
// a mock driver can be shimmed in.
|
||||
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(id string, dst io.Writer) error
|
||||
|
||||
// Import imports a container from a tar file
|
||||
Import(path, repo string) (string, error)
|
||||
|
||||
// Pull should pull down the given image.
|
||||
Pull(image string) error
|
||||
|
||||
// Push pushes an image to a Docker index/registry.
|
||||
Push(name string) error
|
||||
|
||||
// StartContainer starts a container and returns the ID for that container,
|
||||
// along with a potential error.
|
||||
StartContainer(*ContainerConfig) (string, error)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
@ -15,6 +16,25 @@ type DockerDriver struct {
|
|||
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 {
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command("docker", "export", id)
|
||||
|
@ -35,11 +55,49 @@ func (d *DockerDriver) Export(id string, dst io.Writer) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerDriver) Import(path string, repo string) (string, error) {
|
||||
var stdout bytes.Buffer
|
||||
cmd := exec.Command("docker", "import", "-", repo)
|
||||
cmd.Stdout = &stdout
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// There should be only one artifact of the Docker builder
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
io.Copy(stdin, file)
|
||||
}()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
err = fmt.Errorf("Error importing container: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
func (d *DockerDriver) Pull(image string) error {
|
||||
cmd := exec.Command("docker", "pull", image)
|
||||
return runAndStream(cmd, d.Ui)
|
||||
}
|
||||
|
||||
func (d *DockerDriver) Push(name string) error {
|
||||
cmd := exec.Command("docker", "push", name)
|
||||
return runAndStream(cmd, d.Ui)
|
||||
}
|
||||
|
||||
func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) {
|
||||
// Build up the template data
|
||||
var tplData startContainerTemplate
|
||||
|
|
|
@ -6,6 +6,20 @@ import (
|
|||
|
||||
// MockDriver is a driver implementation that can be used for tests.
|
||||
type MockDriver struct {
|
||||
DeleteImageCalled bool
|
||||
DeleteImageId string
|
||||
DeleteImageErr error
|
||||
|
||||
ImportCalled bool
|
||||
ImportPath string
|
||||
ImportRepo string
|
||||
ImportId string
|
||||
ImportErr error
|
||||
|
||||
PushCalled bool
|
||||
PushName string
|
||||
PushErr error
|
||||
|
||||
ExportReader io.Reader
|
||||
ExportError error
|
||||
PullError error
|
||||
|
@ -25,6 +39,12 @@ type MockDriver struct {
|
|||
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 {
|
||||
d.ExportCalled = true
|
||||
d.ExportID = id
|
||||
|
@ -39,12 +59,25 @@ func (d *MockDriver) Export(id string, dst io.Writer) error {
|
|||
return d.ExportError
|
||||
}
|
||||
|
||||
func (d *MockDriver) Import(path, repo string) (string, error) {
|
||||
d.ImportCalled = true
|
||||
d.ImportPath = path
|
||||
d.ImportRepo = repo
|
||||
return d.ImportId, d.ImportErr
|
||||
}
|
||||
|
||||
func (d *MockDriver) Pull(image string) error {
|
||||
d.PullCalled = true
|
||||
d.PullImage = image
|
||||
return d.PullError
|
||||
}
|
||||
|
||||
func (d *MockDriver) Push(name string) error {
|
||||
d.PushCalled = true
|
||||
d.PushName = name
|
||||
return d.PushErr
|
||||
}
|
||||
|
||||
func (d *MockDriver) StartContainer(config *ContainerConfig) (string, error) {
|
||||
d.StartCalled = true
|
||||
d.StartConfig = config
|
||||
|
|
|
@ -92,6 +92,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
|
|||
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func vncSendString(c *vnc.ClientConn, original string) {
|
||||
// Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h
|
||||
special := make(map[string]uint32)
|
||||
special["<bs>"] = 0xFF08
|
||||
special["<del>"] = 0xFFFF
|
||||
|
@ -111,6 +112,16 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
special["<f12>"] = 0xFFC9
|
||||
special["<return>"] = 0xFF0D
|
||||
special["<tab>"] = 0xFF09
|
||||
special["<up>"] = 0xFF52
|
||||
special["<down>"] = 0xFF54
|
||||
special["<left>"] = 0xFF51
|
||||
special["<right>"] = 0xFF53
|
||||
special["<spacebar>"] = 0x020
|
||||
special["<insert>"] = 0xFF63
|
||||
special["<home>"] = 0xFF50
|
||||
special["<end>"] = 0xFF57
|
||||
special["<pageUp>"] = 0xFF55
|
||||
special["<pageDown>"] = 0xFF56
|
||||
|
||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
|
|
|
@ -118,6 +118,16 @@ func scancodes(message string) []string {
|
|||
special["<f10>"] = []string{"44", "c4"}
|
||||
special["<return>"] = []string{"1c", "9c"}
|
||||
special["<tab>"] = []string{"0f", "8f"}
|
||||
special["<up>"] = []string{"48", "c8"}
|
||||
special["<down>"] = []string{"50", "d0"}
|
||||
special["<left>"] = []string{"4b", "cb"}
|
||||
special["<right>"] = []string{"4d", "cd"}
|
||||
special["<spacebar>"] = []string{"39", "b9"}
|
||||
special["<insert>"] = []string{"52", "d2"}
|
||||
special["<home>"] = []string{"47", "c7"}
|
||||
special["<end>"] = []string{"4f", "cf"}
|
||||
special["<pageUp>"] = []string{"49", "c9"}
|
||||
special["<pageDown>"] = []string{"51", "d1"}
|
||||
|
||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
|
|||
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func vncSendString(c *vnc.ClientConn, original string) {
|
||||
// Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h
|
||||
special := make(map[string]uint32)
|
||||
special["<bs>"] = 0xFF08
|
||||
special["<del>"] = 0xFFFF
|
||||
|
@ -135,6 +136,16 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
special["<f12>"] = 0xFFC9
|
||||
special["<return>"] = 0xFF0D
|
||||
special["<tab>"] = 0xFF09
|
||||
special["<up>"] = 0xFF52
|
||||
special["<down>"] = 0xFF54
|
||||
special["<left>"] = 0xFF51
|
||||
special["<right>"] = 0xFF53
|
||||
special["<spacebar>"] = 0x020
|
||||
special["<insert>"] = 0xFF63
|
||||
special["<home>"] = 0xFF50
|
||||
special["<end>"] = 0xFF57
|
||||
special["<pageUp>"] = 0xFF55
|
||||
special["<pageDown>"] = 0xFF56
|
||||
|
||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
|
|
|
@ -43,6 +43,10 @@ type DownloadConfig struct {
|
|||
// for the downloader will be used to verify with this checksum after
|
||||
// it is downloaded.
|
||||
Checksum []byte
|
||||
|
||||
// What to use for the user agent for HTTP requests. If set to "", use the
|
||||
// default user agent provided by Go.
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
// A DownloadClient helps download, verify checksums, etc.
|
||||
|
@ -73,8 +77,8 @@ func HashForType(t string) hash.Hash {
|
|||
func NewDownloadClient(c *DownloadConfig) *DownloadClient {
|
||||
if c.DownloaderMap == nil {
|
||||
c.DownloaderMap = map[string]Downloader{
|
||||
"http": new(HTTPDownloader),
|
||||
"https": new(HTTPDownloader),
|
||||
"http": &HTTPDownloader{userAgent: c.UserAgent},
|
||||
"https": &HTTPDownloader{userAgent: c.UserAgent},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,8 +186,9 @@ func (d *DownloadClient) VerifyChecksum(path string) (bool, error) {
|
|||
// HTTPDownloader is an implementation of Downloader that downloads
|
||||
// files over HTTP.
|
||||
type HTTPDownloader struct {
|
||||
progress uint
|
||||
total uint
|
||||
progress uint
|
||||
total uint
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (*HTTPDownloader) Cancel() {
|
||||
|
@ -197,6 +202,10 @@ func (d *HTTPDownloader) Download(dst io.Writer, src *url.URL) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if d.userAgent != "" {
|
||||
req.Header.Set("User-Agent", d.userAgent)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
@ -41,6 +43,91 @@ func TestDownloadClient_VerifyChecksum(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDownloadClientUsesDefaultUserAgent(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile error: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
defaultUserAgent := ""
|
||||
asserted := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if defaultUserAgent == "" {
|
||||
defaultUserAgent = r.UserAgent()
|
||||
} else {
|
||||
incomingUserAgent := r.UserAgent()
|
||||
if incomingUserAgent != defaultUserAgent {
|
||||
t.Fatalf("Expected user agent %s, got: %s", defaultUserAgent, incomingUserAgent)
|
||||
}
|
||||
|
||||
asserted = true
|
||||
}
|
||||
}))
|
||||
|
||||
req, err := http.NewRequest("GET", server.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := &DownloadConfig{
|
||||
Url: server.URL,
|
||||
TargetPath: tf.Name(),
|
||||
}
|
||||
|
||||
client := NewDownloadClient(config)
|
||||
_, err = client.Get()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !asserted {
|
||||
t.Fatal("User-Agent never observed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClientSetsUserAgent(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile error: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
asserted := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
asserted = true
|
||||
if r.UserAgent() != "fancy user agent" {
|
||||
t.Fatalf("Expected useragent fancy user agent, got: %s", r.UserAgent())
|
||||
}
|
||||
}))
|
||||
config := &DownloadConfig{
|
||||
Url: server.URL,
|
||||
TargetPath: tf.Name(),
|
||||
UserAgent: "fancy user agent",
|
||||
}
|
||||
|
||||
client := NewDownloadClient(config)
|
||||
_, err = client.Get()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !asserted {
|
||||
t.Fatal("HTTP request never made")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashForType(t *testing.T) {
|
||||
if h := HashForType("md5"); h == nil {
|
||||
t.Fatalf("md5 hash is nil")
|
||||
|
|
|
@ -70,6 +70,7 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction {
|
|||
CopyFile: false,
|
||||
Hash: HashForType(s.ChecksumType),
|
||||
Checksum: checksum,
|
||||
UserAgent: packer.VersionString(),
|
||||
}
|
||||
|
||||
path, err, retry := s.download(config, state)
|
||||
|
|
|
@ -42,7 +42,9 @@ const defaultConfig = `
|
|||
|
||||
"post-processors": {
|
||||
"vagrant": "packer-post-processor-vagrant",
|
||||
"vsphere": "packer-post-processor-vsphere"
|
||||
"vsphere": "packer-post-processor-vsphere",
|
||||
"docker-push": "packer-post-processor-docker-push",
|
||||
"docker-import": "packer-post-processor-docker-import"
|
||||
},
|
||||
|
||||
"provisioners": {
|
||||
|
|
|
@ -221,7 +221,7 @@ func (e *coreEnvironment) Cli(args []string) (result int, err error) {
|
|||
|
||||
// Trim up to the command name
|
||||
for i, v := range args {
|
||||
if v[0] != '-' {
|
||||
if len(v) > 0 && v[0] != '-' {
|
||||
args = args[i:]
|
||||
break
|
||||
}
|
||||
|
|
|
@ -198,10 +198,17 @@ func TestEnvironment_Cli_CallsRun(t *testing.T) {
|
|||
func TestEnvironment_DefaultCli_Empty(t *testing.T) {
|
||||
defaultEnv := testEnvironment()
|
||||
|
||||
// Test with no args
|
||||
exitCode, _ := defaultEnv.Cli([]string{})
|
||||
if exitCode != 1 {
|
||||
t.Fatalf("bad: %d", exitCode)
|
||||
}
|
||||
|
||||
// Test with only blank args
|
||||
exitCode, _ = defaultEnv.Cli([]string{""})
|
||||
if exitCode != 1 {
|
||||
t.Fatalf("bad: %d", exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_DefaultCli_Help(t *testing.T) {
|
||||
|
|
|
@ -31,6 +31,18 @@ func (versionCommand) Run(env Environment, args []string) int {
|
|||
env.Ui().Machine("version-prelease", VersionPrerelease)
|
||||
env.Ui().Machine("version-commit", GitCommit)
|
||||
|
||||
env.Ui().Say(VersionString())
|
||||
return 0
|
||||
}
|
||||
|
||||
func (versionCommand) Synopsis() string {
|
||||
return "print Packer version"
|
||||
}
|
||||
|
||||
// VersionString returns the Packer version in human-readable
|
||||
// form complete with pre-release and git commit info if it is
|
||||
// available.
|
||||
func VersionString() string {
|
||||
var versionString bytes.Buffer
|
||||
fmt.Fprintf(&versionString, "Packer v%s", Version)
|
||||
if VersionPrerelease != "" {
|
||||
|
@ -41,10 +53,5 @@ func (versionCommand) Run(env Environment, args []string) int {
|
|||
}
|
||||
}
|
||||
|
||||
env.Ui().Say(versionString.String())
|
||||
return 0
|
||||
}
|
||||
|
||||
func (versionCommand) Synopsis() string {
|
||||
return "print Packer version"
|
||||
return versionString.String()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/post-processor/docker-import"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server, err := plugin.Server()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.RegisterPostProcessor(new(dockerimport.PostProcessor))
|
||||
server.Serve()
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/post-processor/docker-push"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server, err := plugin.Server()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.RegisterPostProcessor(new(dockerpush.PostProcessor))
|
||||
server.Serve()
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -0,0 +1,98 @@
|
|||
package dockerimport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/builder/docker"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
const BuilderId = "packer.post-processor.docker-import"
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
Repository string `mapstructure:"repository"`
|
||||
Tag string `mapstructure:"tag"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
_, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := new(packer.MultiError)
|
||||
|
||||
templates := map[string]*string{
|
||||
"repository": &p.config.Repository,
|
||||
"tag": &p.config.Tag,
|
||||
}
|
||||
|
||||
for key, ptr := range templates {
|
||||
if *ptr == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("%s must be set", key))
|
||||
}
|
||||
|
||||
*ptr, err = p.config.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", key, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
if artifact.BuilderId() != docker.BuilderId {
|
||||
err := fmt.Errorf(
|
||||
"Unknown artifact type: %s\nCan only import from Docker builder artifacts.",
|
||||
artifact.BuilderId())
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
importRepo := p.config.Repository
|
||||
if p.config.Tag != "" {
|
||||
importRepo += ":" + p.config.Tag
|
||||
}
|
||||
|
||||
driver := &docker.DockerDriver{Tpl: p.config.tpl, Ui: ui}
|
||||
|
||||
ui.Message("Importing image: " + artifact.Id())
|
||||
ui.Message("Repository: " + importRepo)
|
||||
id, err := driver.Import(artifact.Files()[0], importRepo)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
ui.Message("Imported ID: " + id)
|
||||
|
||||
// Build the artifact
|
||||
artifact = &docker.ImportArtifact{
|
||||
BuilderIdValue: BuilderId,
|
||||
Driver: driver,
|
||||
IdValue: importRepo,
|
||||
}
|
||||
|
||||
return artifact, false, nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package dockerimport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func testPP(t *testing.T) *PostProcessor {
|
||||
var p PostProcessor
|
||||
if err := p.Configure(testConfig()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func testUi() *packer.BasicUi {
|
||||
return &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var _ packer.PostProcessor = new(PostProcessor)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package dockerpush
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/builder/docker"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/post-processor/docker-import"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
_, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := new(packer.MultiError)
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
if artifact.BuilderId() != dockerimport.BuilderId {
|
||||
err := fmt.Errorf(
|
||||
"Unknown artifact type: %s\nCan only import from docker-import artifacts.",
|
||||
artifact.BuilderId())
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
driver := &docker.DockerDriver{Tpl: p.config.tpl, Ui: ui}
|
||||
|
||||
// Get the name. We strip off any tags from the name because the
|
||||
// push doesn't use those.
|
||||
name := artifact.Id()
|
||||
if i := strings.Index(name, ":"); i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
|
||||
ui.Message("Pushing: " + name)
|
||||
if err := driver.Push(name); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package dockerpush
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func testPP(t *testing.T) *PostProcessor {
|
||||
var p PostProcessor
|
||||
if err := p.Configure(testConfig()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func testUi() *packer.BasicUi {
|
||||
return &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var _ packer.PostProcessor = new(PostProcessor)
|
||||
}
|
|
@ -137,7 +137,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|||
}
|
||||
for _, src := range p.config.PlaybookPaths {
|
||||
dst := filepath.Join(p.config.StagingDir, "playbooks", filepath.Base(src))
|
||||
if err := p.uploadFile(ui, comm, dst, src); err != nil {
|
||||
if err := p.uploadDir(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Error uploading playbooks: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ verify_go () {
|
|||
return 0
|
||||
fi
|
||||
local IFS=.
|
||||
local i ver1=($1) ver2=($2)
|
||||
local i ver1="$1" ver2="$2"
|
||||
|
||||
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
|
||||
do
|
||||
|
@ -56,4 +56,10 @@ export XC_OS=$(go env GOOS)
|
|||
./scripts/compile.sh
|
||||
|
||||
# Move all the compiled things to the PATH
|
||||
cp pkg/${XC_OS}_${XC_ARCH}/* ${GOPATH}/bin
|
||||
case $(uname) in
|
||||
CYGWIN*)
|
||||
GOPATH="$(cygpath $GOPATH)"
|
||||
;;
|
||||
esac
|
||||
IFS=: MAIN_GOPATH=( $GOPATH )
|
||||
cp pkg/${XC_OS}_${XC_ARCH}/* ${MAIN_GOPATH}/bin
|
||||
|
|
|
@ -36,14 +36,14 @@ Required:
|
|||
Optional:
|
||||
|
||||
* `image_id` (int) - The ID of the base image to use. This is the image that
|
||||
will be used to launch a new droplet and provision it. Defaults to "284203",
|
||||
which happens to be "Ubuntu 12.04 x64 Server."
|
||||
will be used to launch a new droplet and provision it. Defaults to "1505447",
|
||||
which happens to be "Ubuntu 12.04.3 x64 Server."
|
||||
|
||||
* `region_id` (int) - The ID of the region to launch the droplet in. Consequently,
|
||||
this is the region where the snapshot will be available. This defaults to
|
||||
"1", which is "New York."
|
||||
"1", which is "New York 1."
|
||||
|
||||
* `size_id` (int) - The ID of the droplet size to use. This defaults to "66,"
|
||||
* `size_id` (int) - The ID of the droplet size to use. This defaults to "66",
|
||||
which is the 512MB droplet.
|
||||
|
||||
* `private_networking` (bool) - Set to `true` to enable private networking
|
||||
|
|
|
@ -64,15 +64,35 @@ Optional:
|
|||
`["run", "-d", "-i", "-t", "-v", "{{.Volumes}}", "{{.Image}}", "/bin/bash"]`.
|
||||
As you can see, you have a couple template variables to customize, as well.
|
||||
|
||||
## Using the generated artifact
|
||||
## Using the Artifact
|
||||
|
||||
Once the tar artifact has been generated, you will likely want to import, tag,
|
||||
and push it to a container repository. Until packer supports management of the
|
||||
docker image metadata, this process is manual. For example, the following will
|
||||
import `mycontainer-123456789.tar` to the repository
|
||||
`registry.mydomain.com/mycontainer`, tagged with `latest`:
|
||||
and push it to a container repository. Packer can do this for you automatically
|
||||
with the [docker-import](/docs/post-processors/docker-import.html) and
|
||||
[docker-push](/docs/post-processors/docker-push.html) post-processors.
|
||||
|
||||
sudo docker import - registry.mydomain.com/mycontainer:latest < mycontainer-123456789.tar
|
||||
The example below shows a full configuration that would import and push
|
||||
the created image:
|
||||
|
||||
<pre class="prettyprint">
|
||||
{
|
||||
"post-processors": [
|
||||
[
|
||||
{
|
||||
"type": "docker-import",
|
||||
"repository": "mitchellh/packer",
|
||||
"tag": "0.7"
|
||||
},
|
||||
"docker-push"
|
||||
]
|
||||
]
|
||||
}
|
||||
</pre>
|
||||
|
||||
If you want to do this manually, however, perhaps from a script, you can
|
||||
import the image using the process below:
|
||||
|
||||
docker import - registry.mydomain.com/mycontainer:latest < artifact.tar
|
||||
|
||||
You can then add additional tags and push the image as usual with `docker tag`
|
||||
and `docker push`, respectively.
|
||||
|
@ -103,8 +123,3 @@ by Packer in the future:
|
|||
volumes, and other metadata. Packer builds a raw Docker container image
|
||||
that has none of this metadata. You can pass in much of this metadata
|
||||
at runtime with `docker run`.
|
||||
|
||||
* Images made without dockerfiles are missing critical metadata that
|
||||
make them easily pushable to the Docker registry. You can work around
|
||||
this by using a metadata-only Dockerfile with the exported image and
|
||||
building that. A future Packer version will automatically do this for you.
|
||||
|
|
|
@ -28,16 +28,10 @@ Follow the steps below:
|
|||
2. Click on the project you want to use Packer with (or create one if you
|
||||
don't have one yet).
|
||||
3. Click "APIs & auth" in the left sidebar
|
||||
4. Click "Registered apps" in the left sidebar
|
||||
5. Click "Register App" and register a "Web Application". Choose any
|
||||
name you'd like.
|
||||
7. After creating the app, click "Certificate" (below the OAuth 2.0 Client
|
||||
ID section), and click "Download JSON". This is your _client secrets JSON_
|
||||
file. Make sure you didn't download the JSON from the "OAuth 2.0" section!
|
||||
This is a common mistake and will cause the builder to not work.
|
||||
8. Next, click "Generate Certificate". You should be prompted to download
|
||||
a private key. Note the password for the private key! This private key
|
||||
is your _client private key_.
|
||||
4. Click "Credentials" in the left sidebar
|
||||
5. Click "Create New Client ID" and choose "Service Account"
|
||||
6. A private key will be downloaded for you. Note the password for the private key! This private key is your _client private key_.
|
||||
7. After creating the account, click "Download JSON". This is your _client secrets JSON_ file. Make sure you didn't download the JSON from the "OAuth 2.0" section! This is a common mistake and will cause the builder to not work.
|
||||
|
||||
Finally, one last step, you'll have to convert the `p12` file you
|
||||
got from Google into the PEM format. You can do this with OpenSSL, which
|
||||
|
|
|
@ -8,7 +8,7 @@ Type: `qemu`
|
|||
|
||||
The Qemu builder is able to create [KVM](http://www.linux-kvm.org)
|
||||
and [Xen](http://www.xenproject.org) virtual machine images. Support
|
||||
for Xen is experimanetal at this time.
|
||||
for Xen is experimental at this time.
|
||||
|
||||
The builder builds a virtual machine by creating a new virtual machine
|
||||
from scratch, booting it, installing an OS, rebooting the machine with the
|
||||
|
|
|
@ -5,7 +5,7 @@ page_title: "VirtualBox Builder (from an ISO)"
|
|||
|
||||
# VirtualBox Builder (from an ISO)
|
||||
|
||||
Type: `virtualbox`
|
||||
Type: `virtualbox-iso`
|
||||
|
||||
The VirtualBox builder is able to create [VirtualBox](https://www.virtualbox.org/)
|
||||
virtual machines and export them in the OVF format, starting from an
|
||||
|
|
|
@ -46,11 +46,11 @@ type PostProcessor interface {
|
|||
|
||||
The `Configure` method for each post-processor is called early in the
|
||||
build process to configure the post-processor. The configuration is passed
|
||||
in as a raw `interface{]`. The configure method is responsible for translating
|
||||
in as a raw `interface{}`. The configure method is responsible for translating
|
||||
this configuration into an internal structure, validating it, and returning
|
||||
any errors.
|
||||
|
||||
For decoding the `interface{]` into a meaningful structure, the
|
||||
For decoding the `interface{}` into a meaningful structure, the
|
||||
[mapstructure](https://github.com/mitchellh/mapstructure) library is
|
||||
recommended. Mapstructure will take an `interface{}` and decode it into an
|
||||
arbitrarily complex struct. If there are any errors, it generates very
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "docker-import Post-Processor"
|
||||
---
|
||||
|
||||
# Docker Import Post-Processor
|
||||
|
||||
Type: `docker-import`
|
||||
|
||||
The Docker import post-processor takes an artifact from the
|
||||
[docker builder](/docs/builders/docker.html) and imports it with Docker
|
||||
locally. This allows you to apply a repository and tag to the image
|
||||
and lets you use the other Docker post-processors such as
|
||||
[docker-push](/docs/post-processors/docker-push.html) to push the image
|
||||
to a registry.
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration for this post-processor is extremely simple. At least
|
||||
a repository is required. The tag is optional.
|
||||
|
||||
* `repository` (string) - The repository of the imported image.
|
||||
|
||||
* `tag` (string) - The tag for the imported image. By default this is not
|
||||
set.
|
||||
|
||||
## Example
|
||||
|
||||
An example is shown below, showing only the post-processor configuration:
|
||||
|
||||
<pre class="prettyprint">
|
||||
{
|
||||
"type": "docker-import",
|
||||
"repository": "mitchellh/packer",
|
||||
"tag": "0.7"
|
||||
}
|
||||
</pre>
|
||||
|
||||
This example would take the image created by the Docker builder
|
||||
and import it into the local Docker process with a name of `mitchellh/packer:0.7`.
|
||||
|
||||
Following this, you can use the
|
||||
[docker-push](/docs/post-processors/docker-push.html)
|
||||
post-processor to push it to a registry, if you want.
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Docker Push Post-Processor"
|
||||
---
|
||||
|
||||
# Docker Push Post-Processor
|
||||
|
||||
Type: `docker-push`
|
||||
|
||||
The Docker push post-processor takes an artifact from the
|
||||
[docker-import](/docs/post-processors/docker-import.html) post-processor
|
||||
and pushes it to a Docker registry.
|
||||
|
||||
<div class="alert alert-info alert-block">
|
||||
<strong>Before you use this,</strong> you must manually <code>docker login</code>
|
||||
to the proper repository. A future version of Packer will automate this
|
||||
for you, but for now you must manually do this.
|
||||
</div>
|
||||
|
||||
## Configuration
|
||||
|
||||
This post-processor has no configuration! Simply add it to your chain
|
||||
of post-processors and the image will be uploaded.
|
||||
|
||||
## Example
|
||||
|
||||
For an example of using docker-push, see the section on using
|
||||
generated artifacts from the [docker builder](/docs/builders/docker.html).
|
|
@ -63,7 +63,7 @@ briefly. Create a file `example.json` and fill it with the following contents:
|
|||
}
|
||||
</pre>
|
||||
|
||||
When building, you'll pass in the `aws_access_key` and `aws_access_key` as
|
||||
When building, you'll pass in the `aws_access_key` and `aws_secret_key` as
|
||||
a [user variable](/docs/templates/user-variables.html), keeping your secret
|
||||
keys out of the template. You can create security credentials
|
||||
on [this page](https://console.aws.amazon.com/iam/home?#security_credential).
|
||||
|
|
|
@ -55,6 +55,8 @@
|
|||
|
||||
<ul>
|
||||
<li><h4>Post-Processors</h4></li>
|
||||
<li><a href="/docs/post-processors/docker-import.html">docker-import</a></li>
|
||||
<li><a href="/docs/post-processors/docker-push.html">docker-push</a></li>
|
||||
<li><a href="/docs/post-processors/vagrant.html">Vagrant</a></li>
|
||||
<li><a href="/docs/post-processors/vsphere.html">vSphere</a></li>
|
||||
</ul>
|
||||
|
|
Loading…
Reference in New Issue