Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be8900cdc7 | |||
| 60a125432b | |||
| 59006d377e | |||
| a1bf3ecf43 | |||
| 992bac69c9 | |||
| 3c63c30eb1 | |||
| 9c79a47f59 | |||
| 98c7930ded | |||
| aad212bd19 | |||
| 74a3fd4b51 | |||
| 27ffb3763c | |||
| c01c60d2d7 | |||
| c0e64b214c | |||
| 778a75da53 | |||
| 44ff78662d | |||
| 074658f86f | |||
| 16d495ede8 | |||
| 2fb296b520 | |||
| 385d9d37f9 | |||
| c37ab71311 | |||
| 36766c3ea0 | |||
| b232b19e1c | |||
| 22dad03f37 | |||
| c98ae34557 | |||
| 6706ffd251 | |||
| 78125e0b5b | |||
| 35673e9494 | |||
| 9d2434f00b | |||
| ac8e9f4219 | |||
| 003c3b5ed3 | |||
| c927dfa9cf | |||
| 6469b66d5d | |||
| 60dca86a8c | |||
| e1b5469f3c | |||
| a9f80c4363 | |||
| 5fc214564f | |||
| be1e88395c | |||
| 8f11257a25 | |||
| 1851ef41d4 | |||
| 7b4e797d80 | |||
| f16ba100cc | |||
| 410c0f37e1 | |||
| c3eceb1af5 | |||
| 3e4dca943a | |||
| dfc8733f43 | |||
| e41f620a6a | |||
| 8a1364dbd8 | |||
| eb468d24ec | |||
| 4f29ed550a | |||
| ec46c709d4 | |||
| f496eb3e87 | |||
| 865c062b19 | |||
| 4a00b520fb | |||
| e9eea49473 | |||
| c1e155808b | |||
| 5fa6c5b6c3 | |||
| decaf04e5b | |||
| 7c091281d8 | |||
| 6839a01e2d | |||
| 5a6c981f6e | |||
| e28db6e48c | |||
| b639618666 | |||
| 40035d8a6c | |||
| 053fcc5ef9 | |||
| 8fb124750e | |||
| 07592bcd6b | |||
| 0e2a552376 | |||
| ea5961f13a | |||
| b43286efd3 | |||
| 741a1134c8 | |||
| 2001a7987e | |||
| 25cb57ef5a | |||
| cf68bf070b | |||
| 2f570c76a4 | |||
| 4815a8e190 | |||
| 1170a4731d | |||
| 022a281560 | |||
| 9067228afa | |||
| 622dc6ec74 | |||
| 6ce1afda77 | |||
| f7f04e57d3 | |||
| 52c9cf6aaf | |||
| 7f6263ec09 | |||
| f64f7ce7da | |||
| eadb510b8f | |||
| dad277e9a0 | |||
| d6bd6fc0ae | |||
| b742823394 | |||
| 47136e8ff2 | |||
| 1bf660666e | |||
| d49ee43ec6 | |||
| 64c0fe8aa1 | |||
| 550df6844c | |||
| 94cfee490d | |||
| 9f80271708 | |||
| 78b859acf6 | |||
| c9fadc06cd | |||
| ad73f02388 | |||
| 9be65b517e | |||
| ea00ec2a10 | |||
| 83c14e0963 | |||
| 7b62784e3e | |||
| 77de557ad1 | |||
| 1581c3c9c0 | |||
| fbebd7fe9c | |||
| 34e7660f2e | |||
| eef21449aa | |||
| eeefde8fc7 | |||
| e1519af7e6 | |||
| 990c393a14 | |||
| 12765ad31d | |||
| f325581bc2 | |||
| 356e872adb | |||
| b0d7933294 | |||
| c8ee2343c2 | |||
| c2cd1af775 | |||
| 73dc53dffb | |||
| f027418794 | |||
| 090718bd8e | |||
| b2327600bc | |||
| 5fc3d67112 | |||
| a03283c3ab | |||
| 6ff1143753 | |||
| f92e5d456d | |||
| e77671ea58 | |||
| 28c93dc1f2 | |||
| c29a068df2 | |||
| f381f04264 | |||
| fdf06a43b4 | |||
| c8d926b372 | |||
| 68062aed69 | |||
| 00cc9fb14c | |||
| 8dfb7842d4 | |||
| ae66764e92 | |||
| 188d4322c7 |
+1
-4
@@ -7,12 +7,9 @@ go:
|
||||
install: make deps
|
||||
script:
|
||||
- go test ./...
|
||||
- go test -race ./...
|
||||
#- go test -race ./...
|
||||
|
||||
notifications:
|
||||
flowdock:
|
||||
secure: fZrcf9rlh2IrQrlch1sHkn3YI7SKvjGnAl/zyV5D6NROe1Bbr6d3QRMuCXWWdhJHzjKmXk5rIzbqJhUc0PNF7YjxGNKSzqWMQ56KcvN1k8DzlqxpqkcA3Jbs6fXCWo2fssRtZ7hj/wOP1f5n6cc7kzHDt9dgaYJ6nO2fqNPJiTc=
|
||||
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#packer-tool"
|
||||
|
||||
@@ -1,3 +1,50 @@
|
||||
## 0.5.2 (02/21/2014)
|
||||
|
||||
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]
|
||||
* builder/googlecompute: SSH private key will be saved to disk if `-debug`
|
||||
is specified. [GH-867]
|
||||
* builder/qemu: Can specify the name of the qemu binary. [GH-854]
|
||||
* builder/virtualbox-ovf: Can specify import options such as "keepallmacs".
|
||||
[GH-883]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Fix crash case if blank parameters are given to Packer. [GH-832]
|
||||
* core: Fix crash if big file uploads are done. [GH-897]
|
||||
* core: Fix crash if machine-readable output is going to a closed
|
||||
pipe. [GH-875]
|
||||
* builder/docker: user variables work properly. [GH-777]
|
||||
* builder/qemu: reboots are now possible in provisioners. [GH-864]
|
||||
* 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]
|
||||
* communicator/ssh: Send TCP keep-alives on connections. [GH-872]
|
||||
* post-processor/vagrant: AWS/DigitalOcean keep input artifacts by
|
||||
default. [GH-55]
|
||||
* provisioners/ansible-local: Properly upload custom playbooks. [GH-829]
|
||||
* provisioners/ansible-local: Better error if ansible isn't installed.
|
||||
[GH-836]
|
||||
|
||||
## 0.5.1 (01/02/2014)
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: If a stream ID loops around, don't let it use stream ID 0 [GH-767]
|
||||
* core: Fix issue where large writes to plugins would result in stream
|
||||
corruption. [GH-727]
|
||||
* builders/virtualbox-ovf: `shutdown_timeout` config works. [GH-772]
|
||||
* builders/vmware-iso: Remote driver works properly again. [GH-773]
|
||||
|
||||
## 0.5.0 (12/30/2013)
|
||||
|
||||
BACKWARDS INCOMPATIBILITIES:
|
||||
|
||||
@@ -74,7 +74,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
"-s {{.SecretKey}} " +
|
||||
"-d {{.BundleDirectory}} " +
|
||||
"--batch " +
|
||||
"--location {{.Region}} " +
|
||||
"--url https://s3-{{.Region}}.amazonaws.com " +
|
||||
"--retry"
|
||||
}
|
||||
|
||||
|
||||
@@ -32,18 +32,12 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// See GH-729 and http://goo.gl/rNZiCs
|
||||
regionName := region.Name
|
||||
if regionName == "us-east-1" {
|
||||
regionName = "US"
|
||||
}
|
||||
|
||||
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
|
||||
AccessKey: config.AccessKey,
|
||||
BucketName: config.S3Bucket,
|
||||
BundleDirectory: config.BundleDestination,
|
||||
ManifestPath: manifestPath,
|
||||
Region: regionName,
|
||||
Region: region.Name,
|
||||
SecretKey: config.SecretKey,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c.tpl.UserVars = c.PackerUserVars
|
||||
|
||||
// Defaults
|
||||
if len(c.RunCommand) == 0 {
|
||||
c.RunCommand = []string{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The unique ID for this builder.
|
||||
@@ -49,9 +49,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
|
||||
// Build the steps.
|
||||
steps := []multistep.Step{
|
||||
new(StepCreateSSHKey),
|
||||
new(StepCreateInstance),
|
||||
new(StepInstanceInfo),
|
||||
&StepCreateSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("gce_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
&StepCreateInstance{
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&StepInstanceInfo{
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
// StepCreateInstance represents a Packer build step that creates GCE instances.
|
||||
type StepCreateInstance struct {
|
||||
Debug bool
|
||||
|
||||
instanceName string
|
||||
}
|
||||
|
||||
@@ -56,6 +58,12 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
|
||||
|
||||
ui.Message("Instance has been created!")
|
||||
|
||||
if s.Debug {
|
||||
if name != "" {
|
||||
ui.Message(fmt.Sprintf("Instance: %s started in %s", name, config.Zone))
|
||||
}
|
||||
}
|
||||
|
||||
// Things succeeded, store the name so we can remove it later
|
||||
state.Put("instance_name", name)
|
||||
s.instanceName = name
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.crypto/ssh"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"code.google.com/p/go.crypto/ssh"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
)
|
||||
|
||||
// StepCreateSSHKey represents a Packer build step that generates SSH key pairs.
|
||||
type StepCreateSSHKey int
|
||||
type StepCreateSSHKey struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
}
|
||||
|
||||
// Run executes the Packer build step that generates SSH key pairs.
|
||||
func (s *StepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
|
||||
@@ -41,9 +44,25 @@ func (s *StepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("ssh_private_key", string(pem.EncodeToMemory(&priv_blk)))
|
||||
state.Put("ssh_public_key", string(ssh.MarshalAuthorizedKey(pub)))
|
||||
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Write out the key
|
||||
err = pem.Encode(f, &priv_blk)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package googlecompute
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -27,3 +30,34 @@ func TestStepCreateSSHKey(t *testing.T) {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSSHKey_debug(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
|
||||
state := testState(t)
|
||||
step := new(StepCreateSSHKey)
|
||||
step.Debug = true
|
||||
step.DebugKeyPath = tf.Name()
|
||||
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify that we have a public/private key
|
||||
if _, ok := state.GetOk("ssh_private_key"); !ok {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
if _, ok := state.GetOk("ssh_public_key"); !ok {
|
||||
t.Fatal("should have key")
|
||||
}
|
||||
if _, err := os.Stat(tf.Name()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@ import (
|
||||
)
|
||||
|
||||
// stepInstanceInfo represents a Packer build step that gathers GCE instance info.
|
||||
type StepInstanceInfo int
|
||||
type StepInstanceInfo struct {
|
||||
Debug bool
|
||||
|
||||
info int
|
||||
}
|
||||
|
||||
// Run executes the Packer build step that gathers GCE instance info.
|
||||
func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
|
||||
@@ -44,6 +48,12 @@ func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.Debug {
|
||||
if ip != "" {
|
||||
ui.Message(fmt.Sprintf("Public IP: %s", ip))
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("IP: %s", ip))
|
||||
state.Put("instance_ip", ip)
|
||||
return multistep.ActionContinue
|
||||
|
||||
+15
-23
@@ -70,6 +70,7 @@ type config struct {
|
||||
NetDevice string `mapstructure:"net_device"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
QemuArgs [][]string `mapstructure:"qemuargs"`
|
||||
QemuBinary string `mapstructure:"qemu_binary"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
@@ -80,7 +81,9 @@ type config struct {
|
||||
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
RunOnce bool `mapstructure:"run_once"`
|
||||
|
||||
// TODO(mitchellh): deprecate
|
||||
RunOnce bool `mapstructure:"run_once"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
RawSingleISOUrl string `mapstructure:"iso_url"`
|
||||
@@ -128,6 +131,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.QemuBinary == "" {
|
||||
b.config.QemuBinary = "qemu-system-x86_64"
|
||||
}
|
||||
|
||||
if b.config.RawBootWait == "" {
|
||||
b.config.RawBootWait = "10s"
|
||||
}
|
||||
@@ -370,7 +377,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with Qemu
|
||||
driver, err := b.newDriver()
|
||||
driver, err := b.newDriver(b.config.QemuBinary)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed creating Qemu driver: %s", err)
|
||||
}
|
||||
@@ -392,26 +399,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
new(stepForwardSSH),
|
||||
new(stepConfigureVNC),
|
||||
&stepRun{
|
||||
BootDrive: "d",
|
||||
BootDrive: "once=d",
|
||||
Message: "Starting VM, booting from CD-ROM",
|
||||
},
|
||||
}
|
||||
|
||||
if !b.config.RunOnce {
|
||||
steps = append(steps,
|
||||
&stepBootWait{},
|
||||
&stepTypeBootCommand{},
|
||||
&stepWaitForShutdown{
|
||||
Message: "Waiting for initial VM boot to shut down",
|
||||
},
|
||||
&stepRun{
|
||||
BootDrive: "c",
|
||||
Message: "Starting VM, booting from hard disk",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
steps = append(steps,
|
||||
&stepBootWait{},
|
||||
&stepTypeBootCommand{},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
@@ -419,7 +411,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepShutdown),
|
||||
)
|
||||
}
|
||||
|
||||
// Setup the state bag
|
||||
state := new(multistep.BasicStateBag)
|
||||
@@ -484,8 +476,8 @@ func (b *Builder) Cancel() {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) newDriver() (Driver, error) {
|
||||
qemuPath, err := exec.LookPath("qemu-system-x86_64")
|
||||
func (b *Builder) newDriver(qemuBinary string) (Driver, error) {
|
||||
qemuPath, err := exec.LookPath(qemuBinary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -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 := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ type Driver interface {
|
||||
Delete(string) error
|
||||
|
||||
// Import a VM
|
||||
Import(string, string) error
|
||||
Import(string, string, string) error
|
||||
|
||||
// Checks if the VM with the given name is running.
|
||||
IsRunning(string) (bool, error)
|
||||
|
||||
@@ -40,11 +40,12 @@ func (d *VBox42Driver) Delete(name string) error {
|
||||
return d.VBoxManage("unregistervm", name, "--delete")
|
||||
}
|
||||
|
||||
func (d *VBox42Driver) Import(name, path string) error {
|
||||
func (d *VBox42Driver) Import(name, path, opts string) error {
|
||||
args := []string{
|
||||
"import", path,
|
||||
"--vsys", "0",
|
||||
"--vmname", name,
|
||||
"--options", opts,
|
||||
}
|
||||
|
||||
return d.VBoxManage(args...)
|
||||
|
||||
@@ -16,6 +16,7 @@ type DriverMock struct {
|
||||
ImportCalled bool
|
||||
ImportName string
|
||||
ImportPath string
|
||||
ImportOpts string
|
||||
ImportErr error
|
||||
|
||||
IsRunningName string
|
||||
@@ -51,10 +52,11 @@ func (d *DriverMock) Delete(name string) error {
|
||||
return d.DeleteErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) Import(name, path string) error {
|
||||
func (d *DriverMock) Import(name, path, opts string) error {
|
||||
d.ImportCalled = true
|
||||
d.ImportName = name
|
||||
d.ImportPath = path
|
||||
d.ImportOpts = opts
|
||||
return d.ImportErr
|
||||
}
|
||||
|
||||
|
||||
@@ -175,19 +175,19 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs, errors.New("http_port_min must be less than http_port_max"))
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||
} else {
|
||||
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksumType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("The iso_checksum_type must be specified."))
|
||||
} else {
|
||||
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
|
||||
if b.config.ISOChecksumType != "none" {
|
||||
if b.config.ISOChecksum == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||
} else {
|
||||
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||
}
|
||||
|
||||
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs,
|
||||
|
||||
@@ -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 := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&StepImport{
|
||||
Name: b.config.VMName,
|
||||
SourcePath: b.config.SourcePath,
|
||||
ImportOpts: b.config.ImportOpts,
|
||||
},
|
||||
/*
|
||||
new(stepAttachGuestAdditions),
|
||||
|
||||
@@ -23,6 +23,7 @@ type Config struct {
|
||||
|
||||
SourcePath string `mapstructure:"source_path"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
ImportOpts string `mapstructure:"import_opts"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
@@ -51,6 +52,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(c.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.VBoxManageConfig.Prepare(c.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.VBoxVersionConfig.Prepare(c.tpl)...)
|
||||
@@ -58,6 +60,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
templates := map[string]*string{
|
||||
"source_path": &c.SourcePath,
|
||||
"vm_name": &c.VMName,
|
||||
"import_opts": &c.ImportOpts,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
|
||||
@@ -13,6 +13,19 @@ func testConfig(t *testing.T) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func getTempFile(t *testing.T) *os.File {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
|
||||
// don't forget to cleanup the file downstream:
|
||||
// defer os.Remove(tf.Name())
|
||||
|
||||
return tf
|
||||
}
|
||||
|
||||
func testConfigErr(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
@@ -45,11 +58,7 @@ func TestNewConfig_sourcePath(t *testing.T) {
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Good
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
tf := getTempFile(t)
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
c = testConfig(t)
|
||||
@@ -57,3 +66,20 @@ func TestNewConfig_sourcePath(t *testing.T) {
|
||||
_, warns, errs = NewConfig(c)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
||||
func TestNewConfig_shutdown_timeout(t *testing.T) {
|
||||
c := testConfig(t)
|
||||
tf := getTempFile(t)
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
// Expect this to fail
|
||||
c["source_path"] = tf.Name()
|
||||
c["shutdown_timeout"] = "NaN"
|
||||
_, warns, errs := NewConfig(c)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Passes when given a valid time duration
|
||||
c["shutdown_timeout"] = "10s"
|
||||
_, warns, errs = NewConfig(c)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
type StepImport struct {
|
||||
Name string
|
||||
SourcePath string
|
||||
ImportOpts string
|
||||
|
||||
vmName string
|
||||
}
|
||||
@@ -20,7 +21,7 @@ func (s *StepImport) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("Importing VM: %s", s.SourcePath))
|
||||
if err := driver.Import(s.Name, s.SourcePath); err != nil {
|
||||
if err := driver.Import(s.Name, s.SourcePath, s.ImportOpts); err != nil {
|
||||
err := fmt.Errorf("Error importing VM: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -206,19 +206,19 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs, errors.New("http_port_min must be less than http_port_max"))
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||
} else {
|
||||
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksumType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("The iso_checksum_type must be specified."))
|
||||
} else {
|
||||
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
|
||||
if b.config.ISOChecksumType != "none" {
|
||||
if b.config.ISOChecksum == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||
} else {
|
||||
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||
}
|
||||
|
||||
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs,
|
||||
|
||||
@@ -126,27 +126,14 @@ func (d *ESX5Driver) Verify() error {
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) HostIP() (string, error) {
|
||||
ip := net.ParseIP(d.Host)
|
||||
interfaces, err := net.Interfaces()
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
||||
defer conn.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, dev := range interfaces {
|
||||
addrs, err := dev.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok {
|
||||
if ipnet.Contains(ip) {
|
||||
return ipnet.IP.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("Unable to determine Host IP")
|
||||
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
return host, err
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint) {
|
||||
@@ -214,6 +201,10 @@ func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// OutputDir implementation
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
func (d *ESX5Driver) DirExists() (bool, error) {
|
||||
err := d.sh("test", "-e", d.outputDir)
|
||||
return err == nil, nil
|
||||
@@ -258,6 +249,10 @@ func (d *ESX5Driver) SetOutputDir(path string) {
|
||||
d.outputDir = d.datastorePath(path)
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) String() string {
|
||||
return d.outputDir
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) datastorePath(path string) string {
|
||||
return filepath.Join("/vmfs/volumes", d.Datastore, path)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package iso
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -9,6 +11,25 @@ func TestESX5Driver_implDriver(t *testing.T) {
|
||||
var _ vmwcommon.Driver = new(ESX5Driver)
|
||||
}
|
||||
|
||||
func TestESX5Driver_implOutputDir(t *testing.T) {
|
||||
var _ vmwcommon.OutputDir = new(ESX5Driver)
|
||||
}
|
||||
|
||||
func TestESX5Driver_implRemoteDriver(t *testing.T) {
|
||||
var _ RemoteDriver = new(ESX5Driver)
|
||||
}
|
||||
|
||||
func TestESX5Driver_HostIP(t *testing.T) {
|
||||
expected_host := "127.0.0.1"
|
||||
|
||||
//create mock SSH server
|
||||
listen, _ := net.Listen("tcp", fmt.Sprintf("%s:0", expected_host))
|
||||
port := listen.Addr().(*net.TCPAddr).Port
|
||||
defer listen.Close()
|
||||
|
||||
driver := ESX5Driver{Host: "localhost", Port: uint(port)}
|
||||
|
||||
if host, _ := driver.HostIP(); host != expected_host {
|
||||
t.Error(fmt.Sprintf("Expected string, %s but got %s", expected_host, host))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ func (*stepUploadTools) Run(state multistep.StateBag) multistep.StepAction {
|
||||
}
|
||||
|
||||
if err := comm.Upload(config.ToolsUploadPath, f); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error uploading VMware Tools: %s", err))
|
||||
err := fmt.Errorf("Error uploading VMware Tools: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
|
||||
+13
-4
@@ -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)
|
||||
|
||||
@@ -10,6 +10,15 @@ import (
|
||||
// is suitable for use with the SSH communicator configuration.
|
||||
func ConnectFunc(network, addr string) func() (net.Conn, error) {
|
||||
return func() (net.Conn, error) {
|
||||
return net.DialTimeout(network, addr, 15*time.Second)
|
||||
c, err := net.DialTimeout(network, addr, 15*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tcpConn, ok := c.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -81,8 +81,9 @@ func DefaultEnvironmentConfig() *EnvironmentConfig {
|
||||
config := &EnvironmentConfig{}
|
||||
config.Commands = make([]string, 0)
|
||||
config.Ui = &BasicUi{
|
||||
Reader: os.Stdin,
|
||||
Writer: os.Stdout,
|
||||
Reader: os.Stdin,
|
||||
Writer: os.Stdout,
|
||||
ErrorWriter: os.Stdout,
|
||||
}
|
||||
|
||||
return config
|
||||
@@ -221,7 +222,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
|
||||
}
|
||||
|
||||
@@ -30,8 +30,9 @@ func testComponentFinder() *ComponentFinder {
|
||||
func testEnvironment() Environment {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Ui = &BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
ErrorWriter: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
env, err := NewEnvironment(config)
|
||||
@@ -198,10 +199,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) {
|
||||
|
||||
+44
-20
@@ -146,6 +146,8 @@ func (m *MuxConn) Accept(id uint32) (io.ReadWriteCloser, error) {
|
||||
// Dial opens a connection to the remote end using the given stream ID.
|
||||
// An Accept on the remote end will only work with if the IDs match.
|
||||
func (m *MuxConn) Dial(id uint32) (io.ReadWriteCloser, error) {
|
||||
//log.Printf("[TRACE] %p: Dial on stream ID: %d", m, id)
|
||||
|
||||
m.muDial.Lock()
|
||||
|
||||
// If we have any streams with this ID, then it is a failure. The
|
||||
@@ -191,13 +193,13 @@ func (m *MuxConn) NextId() uint32 {
|
||||
m.muAccept.Lock()
|
||||
defer m.muAccept.Unlock()
|
||||
|
||||
// We never use stream ID 0 because 0 is the zero value of a uint32
|
||||
// and we want to reserve that for "not in use"
|
||||
if m.curId == 0 {
|
||||
m.curId = 1
|
||||
}
|
||||
|
||||
for {
|
||||
// We never use stream ID 0 because 0 is the zero value of a uint32
|
||||
// and we want to reserve that for "not in use"
|
||||
if m.curId == 0 {
|
||||
m.curId = 1
|
||||
}
|
||||
|
||||
result := m.curId
|
||||
m.curId += 1
|
||||
if _, ok := m.streamsAccept[result]; !ok {
|
||||
@@ -284,10 +286,13 @@ func (m *MuxConn) loop() {
|
||||
|
||||
// TODO(mitchellh): probably would be better to re-use a buffer...
|
||||
data := make([]byte, length)
|
||||
if length > 0 {
|
||||
if _, err := m.rwc.Read(data); err != nil {
|
||||
n := 0
|
||||
for n < int(length) {
|
||||
if n2, err := m.rwc.Read(data[n:]); err != nil {
|
||||
log.Printf("[ERR] Error reading data: %s", err)
|
||||
return
|
||||
} else {
|
||||
n += n2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,13 +407,10 @@ func (m *MuxConn) loop() {
|
||||
case streamStateFinWait2:
|
||||
fallthrough
|
||||
case streamStateEstablished:
|
||||
if len(data) > 0 {
|
||||
select {
|
||||
case stream.writeCh <- data:
|
||||
default:
|
||||
panic(fmt.Sprintf(
|
||||
"Failed to write data, buffer full for stream %d", id))
|
||||
}
|
||||
if len(data) > 0 && stream.writeCh != nil {
|
||||
//log.Printf("[TRACE] %p: Stream %d (%s) WRITE-START", m, id, from)
|
||||
stream.writeCh <- data
|
||||
//log.Printf("[TRACE] %p: Stream %d (%s) WRITE-END", m, id, from)
|
||||
}
|
||||
default:
|
||||
log.Printf("[ERR] Data received for stream in state: %d", stream.state)
|
||||
@@ -434,10 +436,21 @@ func (m *MuxConn) write(from muxPacketFrom, id uint32, dataType muxPacketType, p
|
||||
if err := binary.Write(m.rwc, binary.BigEndian, int32(len(p))); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
|
||||
// Write all the bytes. If we don't write all the bytes, report an error
|
||||
var err error = nil
|
||||
n := 0
|
||||
for n < len(p) {
|
||||
var n2 int
|
||||
n2, err = m.rwc.Write(p[n:])
|
||||
n += n2
|
||||
if err != nil {
|
||||
log.Printf("[ERR] %p: Stream %d (%s) write error: %s", m, id, from, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
return m.rwc.Write(p)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Stream is a single stream of data and implements io.ReadWriteCloser.
|
||||
@@ -490,6 +503,7 @@ func newStream(from muxPacketFrom, id uint32, m *MuxConn) *Stream {
|
||||
go func() {
|
||||
defer dataW.Close()
|
||||
|
||||
drain := false
|
||||
for {
|
||||
data := <-writeCh
|
||||
if data == nil {
|
||||
@@ -498,8 +512,14 @@ func newStream(from muxPacketFrom, id uint32, m *MuxConn) *Stream {
|
||||
return
|
||||
}
|
||||
|
||||
if drain {
|
||||
// We're draining, meaning we're just waiting for the
|
||||
// write channel to close.
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := dataW.Write(data); err != nil {
|
||||
return
|
||||
drain = true
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -542,7 +562,10 @@ func (s *Stream) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (s *Stream) closeWriter() {
|
||||
s.writeCh <- nil
|
||||
if s.writeCh != nil {
|
||||
s.writeCh <- nil
|
||||
s.writeCh = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) setState(state streamState) {
|
||||
@@ -568,6 +591,7 @@ func (s *Stream) waitState(target streamState) error {
|
||||
delete(s.stateChange, stateCh)
|
||||
}()
|
||||
|
||||
//log.Printf("[TRACE] %p: Stream %d (%s) waiting for state: %d", s.mux, s.id, s.from, target)
|
||||
state := <-stateCh
|
||||
if state == target {
|
||||
return nil
|
||||
|
||||
@@ -76,6 +76,7 @@ func TestMuxConn(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer s1.Close()
|
||||
data := readStream(t, s1)
|
||||
if data != "another" {
|
||||
t.Fatalf("bad: %#v", data)
|
||||
@@ -84,6 +85,7 @@ func TestMuxConn(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer s0.Close()
|
||||
data := readStream(t, s0)
|
||||
if data != "hello" {
|
||||
t.Fatalf("bad: %#v", data)
|
||||
@@ -110,6 +112,61 @@ func TestMuxConn(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
s0.Close()
|
||||
s1.Close()
|
||||
|
||||
// Wait for the server to be done
|
||||
<-doneCh
|
||||
}
|
||||
|
||||
func TestMuxConn_lotsOfData(t *testing.T) {
|
||||
client, server := testMux(t)
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
// When the server is done
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
// The server side
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
s0, err := server.Accept(0)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var data [1024]byte
|
||||
for {
|
||||
n, err := s0.Read(data[:])
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
dataString := string(data[0:n])
|
||||
if dataString != "hello" {
|
||||
t.Fatalf("bad: %#v", dataString)
|
||||
}
|
||||
}
|
||||
|
||||
s0.Close()
|
||||
}()
|
||||
|
||||
s0, err := client.Dial(0)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < 4096*4; i++ {
|
||||
if _, err := s0.Write([]byte("hello")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s0.Close(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Wait for the server to be done
|
||||
<-doneCh
|
||||
}
|
||||
|
||||
+14
-2
@@ -11,6 +11,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
@@ -60,6 +61,7 @@ type TargettedUi struct {
|
||||
type BasicUi struct {
|
||||
Reader io.Reader
|
||||
Writer io.Writer
|
||||
ErrorWriter io.Writer
|
||||
l sync.Mutex
|
||||
interrupted bool
|
||||
}
|
||||
@@ -234,8 +236,13 @@ func (rw *BasicUi) Error(message string) {
|
||||
rw.l.Lock()
|
||||
defer rw.l.Unlock()
|
||||
|
||||
writer := rw.ErrorWriter
|
||||
if writer == nil {
|
||||
writer = rw.Writer
|
||||
}
|
||||
|
||||
log.Printf("ui error: %s", message)
|
||||
_, err := fmt.Fprint(rw.Writer, message+"\n")
|
||||
_, err := fmt.Fprint(writer, message+"\n")
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Failed to write to UI: %s", err)
|
||||
}
|
||||
@@ -282,6 +289,11 @@ func (u *MachineReadableUi) Machine(category string, args ...string) {
|
||||
|
||||
_, err := fmt.Fprintf(u.Writer, "%d,%s,%s,%s\n", now.Unix(), target, category, argsString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if err == syscall.EPIPE {
|
||||
// Ignore epipe errors because that just means that the file
|
||||
// is probably closed or going to /dev/null or something.
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-14
@@ -7,10 +7,27 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This reads the output from the bytes.Buffer in our test object
|
||||
// and then resets the buffer.
|
||||
func readWriter(ui *BasicUi) (result string) {
|
||||
buffer := ui.Writer.(*bytes.Buffer)
|
||||
result = buffer.String()
|
||||
buffer.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
func readErrorWriter(ui *BasicUi) (result string) {
|
||||
buffer := ui.ErrorWriter.(*bytes.Buffer)
|
||||
result = buffer.String()
|
||||
buffer.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
func testUi() *BasicUi {
|
||||
return &BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
ErrorWriter: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +49,11 @@ func TestColoredUi(t *testing.T) {
|
||||
|
||||
ui.Error("foo")
|
||||
result = readWriter(bufferUi)
|
||||
if result != "" {
|
||||
t.Fatalf("invalid output: %s", result)
|
||||
}
|
||||
|
||||
result = readErrorWriter(bufferUi)
|
||||
if result != "\033[1;31mfoo\033[0m\n" {
|
||||
t.Fatalf("invalid output: %s", result)
|
||||
}
|
||||
@@ -59,7 +81,7 @@ func TestColoredUi_noColorEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
ui.Error("foo")
|
||||
result = readWriter(bufferUi)
|
||||
result = readErrorWriter(bufferUi)
|
||||
if result != "foo\n" {
|
||||
t.Fatalf("invalid output: %s", result)
|
||||
}
|
||||
@@ -88,7 +110,7 @@ func TestTargettedUi(t *testing.T) {
|
||||
}
|
||||
|
||||
targettedUi.Error("bar")
|
||||
actual = readWriter(bufferUi)
|
||||
actual = readErrorWriter(bufferUi)
|
||||
expected = "==> foo: bar\n"
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
@@ -131,12 +153,13 @@ func TestBasicUi_Error(t *testing.T) {
|
||||
|
||||
var actual, expected string
|
||||
bufferUi.Error("foo")
|
||||
actual = readWriter(bufferUi)
|
||||
actual = readErrorWriter(bufferUi)
|
||||
expected = "foo\n"
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
|
||||
bufferUi.ErrorWriter = nil
|
||||
bufferUi.Error("5")
|
||||
actual = readWriter(bufferUi)
|
||||
expected = "5\n"
|
||||
@@ -214,12 +237,3 @@ func TestMachineReadableUi(t *testing.T) {
|
||||
t.Fatalf("bad: %#v", data)
|
||||
}
|
||||
}
|
||||
|
||||
// This reads the output from the bytes.Buffer in our test object
|
||||
// and then resets the buffer.
|
||||
func readWriter(ui *BasicUi) (result string) {
|
||||
buffer := ui.Writer.(*bytes.Buffer)
|
||||
result = buffer.String()
|
||||
buffer.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
+14
-7
@@ -10,7 +10,7 @@ import (
|
||||
var GitCommit string
|
||||
|
||||
// The version of packer.
|
||||
const Version = "0.5.0"
|
||||
const Version = "0.5.2"
|
||||
|
||||
// Any pre-release marker for the version. If this is "" (empty string),
|
||||
// then it means that it is a final release. Otherwise, this is the
|
||||
@@ -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)
|
||||
}
|
||||
@@ -11,6 +11,10 @@ import (
|
||||
|
||||
type AWSProvider struct{}
|
||||
|
||||
func (p *AWSProvider) KeepInputArtifact() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *AWSProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "aws"}
|
||||
|
||||
@@ -7,3 +7,11 @@ import (
|
||||
func TestAWSProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(AWSProvider)
|
||||
}
|
||||
|
||||
func TestAWSProvider_KeepInputArtifact(t *testing.T) {
|
||||
p := new(AWSProvider)
|
||||
|
||||
if !p.KeepInputArtifact() {
|
||||
t.Fatal("should keep input artifact")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ type digitalOceanVagrantfileTemplate struct {
|
||||
|
||||
type DigitalOceanProvider struct{}
|
||||
|
||||
func (p *DigitalOceanProvider) KeepInputArtifact() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *DigitalOceanProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "digital_ocean"}
|
||||
|
||||
@@ -152,7 +152,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact(name, outputPath), false, nil
|
||||
return NewArtifact(name, outputPath), provider.KeepInputArtifact(), nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) configureSingle(config *Config, raws ...interface{}) error {
|
||||
|
||||
@@ -7,6 +7,10 @@ import (
|
||||
// Provider is the interface that each provider must implement in order
|
||||
// to package the artifacts into a Vagrant-compatible box.
|
||||
type Provider interface {
|
||||
// KeepInputArtifact should return true/false whether this provider
|
||||
// requires the input artifact to be kept by default.
|
||||
KeepInputArtifact() bool
|
||||
|
||||
// Process is called to process an artifact into a Vagrant box. The
|
||||
// artifact is given as well as the temporary directory path to
|
||||
// put things.
|
||||
|
||||
@@ -15,6 +15,10 @@ import (
|
||||
|
||||
type VBoxProvider struct{}
|
||||
|
||||
func (p *VBoxProvider) KeepInputArtifact() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *VBoxProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "virtualbox"}
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
|
||||
type VMwareProvider struct{}
|
||||
|
||||
func (p *VMwareProvider) KeepInputArtifact() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *VMwareProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "vmware_desktop"}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local"
|
||||
@@ -14,6 +15,18 @@ type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
tpl *packer.ConfigTemplate
|
||||
|
||||
// The command to run ansible
|
||||
Command string
|
||||
|
||||
// Extra options to pass to the ansible command
|
||||
ExtraArguments []string `mapstructure:"extra_arguments"`
|
||||
|
||||
// Path to group_vars directory
|
||||
GroupVars string `mapstructure:"group_vars"`
|
||||
|
||||
// Path to host_vars directory
|
||||
HostVars string `mapstructure:"host_vars"`
|
||||
|
||||
// The main playbook file to execute.
|
||||
PlaybookFile string `mapstructure:"playbook_file"`
|
||||
|
||||
@@ -48,12 +61,20 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
// Defaults
|
||||
if p.config.Command == "" {
|
||||
p.config.Command = "ansible-playbook"
|
||||
}
|
||||
|
||||
if p.config.StagingDir == "" {
|
||||
p.config.StagingDir = DefaultStagingDir
|
||||
}
|
||||
|
||||
// Templates
|
||||
templates := map[string]*string{
|
||||
"command": &p.config.Command,
|
||||
"group_vars": &p.config.GroupVars,
|
||||
"host_vars": &p.config.HostVars,
|
||||
"playbook_file": &p.config.PlaybookFile,
|
||||
"staging_dir": &p.config.StagingDir,
|
||||
}
|
||||
@@ -68,8 +89,9 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
sliceTemplates := map[string][]string{
|
||||
"playbook_paths": p.config.PlaybookPaths,
|
||||
"role_paths": p.config.RolePaths,
|
||||
"extra_arguments": p.config.ExtraArguments,
|
||||
"playbook_paths": p.config.PlaybookPaths,
|
||||
"role_paths": p.config.RolePaths,
|
||||
}
|
||||
|
||||
for n, slice := range sliceTemplates {
|
||||
@@ -88,6 +110,21 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
// Check that the group_vars directory exists, if configured
|
||||
if len(p.config.GroupVars) > 0 {
|
||||
if err := validateDirConfig(p.config.GroupVars, "group_vars"); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the host_vars directory exists, if configured
|
||||
if len(p.config.HostVars) > 0 {
|
||||
if err := validateDirConfig(p.config.HostVars, "host_vars"); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range p.config.PlaybookPaths {
|
||||
err := validateDirConfig(path, "playbook_paths")
|
||||
if err != nil {
|
||||
@@ -99,6 +136,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
@@ -120,6 +158,24 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
return fmt.Errorf("Error uploading main playbook: %s", err)
|
||||
}
|
||||
|
||||
if len(p.config.GroupVars) > 0 {
|
||||
ui.Message("Uploading group_vars directory...")
|
||||
src := p.config.GroupVars
|
||||
dst := filepath.Join(p.config.StagingDir, "group_vars")
|
||||
if err := p.uploadDir(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Error uploading group_vars directory: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.config.HostVars) > 0 {
|
||||
ui.Message("Uploading host_vars directory...")
|
||||
src := p.config.HostVars
|
||||
dst := filepath.Join(p.config.StagingDir, "host_vars")
|
||||
if err := p.uploadDir(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Error uploading host_vars directory: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.config.RolePaths) > 0 {
|
||||
ui.Message("Uploading role directories...")
|
||||
for _, src := range p.config.RolePaths {
|
||||
@@ -137,7 +193,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)
|
||||
}
|
||||
}
|
||||
@@ -161,8 +217,13 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) err
|
||||
// The inventory must be set to "127.0.0.1,". The comma is important
|
||||
// as its the only way to override the ansible inventory when dealing
|
||||
// with a single host.
|
||||
command := fmt.Sprintf("ansible-playbook %s -c local -i %s", playbook, `"127.0.0.1,"`)
|
||||
extraArgs := ""
|
||||
if len(p.config.ExtraArguments) > 0 {
|
||||
extraArgs = " " + strings.Join(p.config.ExtraArguments, " ")
|
||||
}
|
||||
|
||||
command := fmt.Sprintf("%s %s%s -c local -i \"127.0.0.1,\"",
|
||||
p.config.Command, playbook, extraArgs)
|
||||
ui.Message(fmt.Sprintf("Executing Ansible: %s", command))
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: command,
|
||||
@@ -171,6 +232,12 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) err
|
||||
return err
|
||||
}
|
||||
if cmd.ExitStatus != 0 {
|
||||
if cmd.ExitStatus == 127 {
|
||||
return fmt.Errorf("%s could not be found. Verify that it is available on the\n" +
|
||||
"PATH after connecting to the machine.",
|
||||
p.config.Command)
|
||||
}
|
||||
|
||||
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -120,4 +120,28 @@ func TestProvisionerPrepare_Dirs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config["group_vars"] = playbook_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatalf("should error if group_vars path is not a dir")
|
||||
}
|
||||
|
||||
config["group_vars"] = os.TempDir()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config["host_vars"] = playbook_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatalf("should error if host_vars path is not a dir")
|
||||
}
|
||||
|
||||
config["host_vars"] = os.TempDir()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Just update this if you want to force a website push and it isn't
|
||||
working:
|
||||
|
||||
1
|
||||
@@ -83,7 +83,7 @@ Optional:
|
||||
|
||||
* `ami_groups` (array of string) - A list of groups that have access
|
||||
to launch the resulting AMI(s). By default no groups have permission
|
||||
to launch the AMI.
|
||||
to launch the AMI. `all` will make the AMI publicly accessible.
|
||||
|
||||
* `ami_product_codes` (array of string) - A list of product codes to
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
|
||||
@@ -69,7 +69,7 @@ Optional:
|
||||
|
||||
* `ami_groups` (array of string) - A list of groups that have access
|
||||
to launch the resulting AMI(s). By default no groups have permission
|
||||
to launch the AMI.
|
||||
to launch the AMI. `all` will make the AMI publicly accessible.
|
||||
|
||||
* `ami_product_codes` (array of string) - A list of product codes to
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
@@ -132,7 +132,7 @@ Optional:
|
||||
* `vpc_id` (string) - If launching into a VPC subnet, Packer needs the
|
||||
VPC ID in order to create a temporary security group within the VPC.
|
||||
|
||||
* `avail_zone` (string) - Destination availability zone to launch instance in.
|
||||
* `availability_zone` (string) - Destination availability zone to launch instance in.
|
||||
Leave this empty to allow Amazon to auto-assign.
|
||||
|
||||
## Basic Example
|
||||
|
||||
@@ -92,7 +92,7 @@ Optional:
|
||||
|
||||
* `ami_groups` (array of string) - A list of groups that have access
|
||||
to launch the resulting AMI(s). By default no groups have permission
|
||||
to launch the AMI.
|
||||
to launch the AMI. `all` will make the AMI publicly accessible.
|
||||
|
||||
* `ami_product_codes` (array of string) - A list of product codes to
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
@@ -277,7 +277,7 @@ sudo -n ec2-upload-bundle \
|
||||
-s {{.SecretKey}} \
|
||||
-d {{.BundleDirectory}} \
|
||||
--batch \
|
||||
--location {{.Region}} \
|
||||
--url https://s3-{{.Region}}.amazonaws.com \
|
||||
--retry
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,7 +17,7 @@ containers with portable scripts or configuration management systems
|
||||
that are not tied to Docker in any way. It also has a simpler mental model:
|
||||
you provision containers much the same way you provision a normal virtualized
|
||||
or dedicated server. For more information, read the section on
|
||||
[Dockerfiles](#toc_3).
|
||||
[Dockerfiles](#toc_4).
|
||||
|
||||
The Docker builder must run on a machine that has Docker installed. Therefore
|
||||
the builder only works on machines that support Docker (modern Linux machines).
|
||||
@@ -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
|
||||
|
||||
@@ -37,7 +37,8 @@ Required:
|
||||
|
||||
* `provider` (string) - The provider used to connect to the OpenStack service.
|
||||
If not specified, Packer will attempt to read this from the
|
||||
`SDK_PROVIDER` environment variable.
|
||||
`SDK_PROVIDER` environment variable. For Rackspace this should be `rackspace-us`
|
||||
or `rackspace-uk`.
|
||||
|
||||
* `region` (string) - The name of the region, such as "DFW", in which
|
||||
to launch the server to create the AMI.
|
||||
@@ -74,7 +75,7 @@ Ubuntu 12.04 LTS (Precise Pangolin) on Rackspace OpenStack cloud offering.
|
||||
"type": "openstack",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"provider": "",
|
||||
"provider": "rackspace-us",
|
||||
"region": "DFW",
|
||||
"ssh_username": "root",
|
||||
"image_name": "Test image",
|
||||
|
||||
@@ -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
|
||||
@@ -199,15 +199,10 @@ Optional:
|
||||
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
|
||||
of the build.
|
||||
|
||||
* `run_once` (boolean) - When set to true, run_once causes the builder to run
|
||||
Qemu only once, rather than twice. Normally (default false) the builder
|
||||
will run Qemu once for an initial OS install, then switch the CDROM device
|
||||
and boot device for a second run. In that case, Packer does not wait for
|
||||
SSH connections until the second power up of the VM. This approach is often
|
||||
necessary in Linux distribution installs. However, in many Windows unattended
|
||||
installs, the setup handles reboots and dealing with the CDROM as the boot
|
||||
device. With care, a power-down/power-up setting (run_once is set to false)
|
||||
could work if the unattended install is set to restart into audit mode.
|
||||
* `qemu_binary` (string) - The name of the Qemu binary to look for. This
|
||||
defaults to "qemu-system-x86_64", but may need to be changed for some
|
||||
platforms. For example "qemu-kvm", or "qemu-system-i386" may be a better
|
||||
choice for some systems.
|
||||
|
||||
* `shutdown_command` (string) - The command to use to gracefully shut down
|
||||
the machine once all the provisioning is done. By default this is an empty
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -147,6 +147,10 @@ Optional:
|
||||
exported. By default this is "packer-BUILDNAME", where "BUILDNAME" is
|
||||
the name of the build.
|
||||
|
||||
* `import_opts` (string) - Additional options to pass to the `VBoxManage import`.
|
||||
This can be useful for passing "keepallmacs" or "keepnatmacs" options for existing
|
||||
ovf images.
|
||||
|
||||
## Guest Additions
|
||||
|
||||
Packer will automatically download the proper guest additions for the
|
||||
|
||||
@@ -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).
|
||||
@@ -97,3 +97,13 @@ VMware, where it will be set to 0.
|
||||
|
||||
The available provider names are: `aws`, `digitalocean`, `virtualbox`,
|
||||
and `vmware`.
|
||||
|
||||
## Input Artifacts
|
||||
|
||||
By default, Packer will delete the original input artifact, assuming
|
||||
you only want the final Vagrant box as the result. If you wish to keep the
|
||||
input artifact (the raw virtual machine, for example), then you must
|
||||
configure Packer to keep it.
|
||||
|
||||
Please see the [documentation on input artifacts](/docs/templates/post-processors.html#toc_2)
|
||||
for more information.
|
||||
|
||||
@@ -34,6 +34,11 @@ Required:
|
||||
|
||||
Optional:
|
||||
|
||||
* `command` (string) - The command to invoke ansible. Defaults to "ansible-playbook".
|
||||
|
||||
* `extra_arguments` (array of strings) - An array of extra arguments to pass to the
|
||||
ansible command. By default, this is empty.
|
||||
|
||||
* `playbook_paths` (array of strings) - An array of paths to playbook files on
|
||||
your local system. These will be uploaded to the remote machine under
|
||||
`staging_directory`/playbooks. By default, this is empty.
|
||||
|
||||
@@ -86,8 +86,9 @@ for readability) to execute Puppet:
|
||||
{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}puppet apply \
|
||||
--verbose \
|
||||
--modulepath='{{.ModulePath}}' \
|
||||
{{if .HieraConfigPath ne ""}}--hiera_config='{{.HieraConfigPath}}' {{end}} \
|
||||
{{if .ManifestDir ne ""}}--manifestdir='{{.ManifestDir}}' {{end}} \
|
||||
{{if ne .HieraConfigPath ""}}--hiera_config='{{.HieraConfigPath}}' {{end}} \
|
||||
{{if ne .ManifestDir ""}}--manifestdir='{{.ManifestDir}}' {{end}} \
|
||||
--detailed-exitcodes \
|
||||
{{.ManifestFile}}
|
||||
```
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -36,9 +36,18 @@ the appropriate [documentation page within the documentation section](/docs).
|
||||
* ***DigitalOcean***. Snapshots for [DigitalOcean](http://www.digitalocean.com)
|
||||
that can be used to start a pre-configured DigitalOcean instance of any size.
|
||||
|
||||
* ***Docker***. Snapshots for [Docker](http://www.docker.io)
|
||||
that can be used to start a pre-configured Docker instance.
|
||||
|
||||
* ***Google Compute Engine***. Snapshots for [Google Compute Engine](https://cloud.google.com/products/compute-engine)
|
||||
that can be used to start a pre-configured Google Compute Engine instance.
|
||||
|
||||
* ***OpenStack***. Images for [OpenStack](http://www.openstack.org)
|
||||
that can be used to start pre-configured OpenStack servers.
|
||||
|
||||
* ***QEMU***. Images for [KVM](http://www.linux-kvm.org/) or [Xen](http://www.xenproject.org/)
|
||||
that can be used to start pre-configured KVM or Xen instances.
|
||||
|
||||
* ***VirtualBox (OVF)***. Exported virtual machines for VirtualBox, including
|
||||
virtual machine metadata such as RAM, CPUs, etc. These virtual machines are
|
||||
portable and can be started on any platform VirtualBox runs on.
|
||||
|
||||
@@ -54,6 +54,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>
|
||||
|
||||
Reference in New Issue
Block a user