Compare commits

...

112 Commits

Author SHA1 Message Date
Mitchell Hashimoto be8900cdc7 v0.5.2 2014-02-21 20:31:10 -08:00
Mitchell Hashimoto 60a125432b provisioner/ansible-local: better error if missing [GH-836] 2014-02-21 20:30:04 -08:00
Mitchell Hashimoto 59006d377e packer: send error output to stdout for now 2014-02-21 20:14:51 -08:00
Mitchell Hashimoto a1bf3ecf43 Remove race tests from travis, too many false 2014-02-21 20:10:28 -08:00
Mitchell Hashimoto 992bac69c9 post-processor/vagrant: tests on input artifact 2014-02-21 20:04:03 -08:00
Mitchell Hashimoto 3c63c30eb1 fmt 2014-02-21 20:02:55 -08:00
Mitchell Hashimoto 9c79a47f59 post-processor/vagrant: AWS/DO keep input artifacts [GH-55] 2014-02-21 20:02:49 -08:00
Mitchell Hashimoto 98c7930ded website: correct availability zone docs 2014-02-21 18:35:48 -08:00
Mitchell Hashimoto aad212bd19 packer: errors go to stderr [GH-868] 2014-02-21 18:29:15 -08:00
Mitchell Hashimoto 74a3fd4b51 packer: closed pipe is caught [GH-875] 2014-02-21 17:43:45 -08:00
Mitchell Hashimoto 27ffb3763c fmt 2014-02-21 16:48:30 -08:00
Mitchell Hashimoto c01c60d2d7 packer/rpc: fix a blocking issue 2014-02-21 16:48:17 -08:00
Mitchell Hashimoto c0e64b214c Remove flowdock notifications 2014-02-21 15:29:12 -08:00
Mitchell Hashimoto 778a75da53 provisioner/ansible-local: style 2014-02-21 15:27:39 -08:00
Mitchell Hashimoto 44ff78662d Merge pull request #842 from strcrzy/ansible_config_options
provisioner/ansible: more configuration options
2014-02-21 15:24:50 -08:00
Mitchell Hashimoto 074658f86f Update cHANGELOG 2014-02-21 15:23:20 -08:00
Mitchell Hashimoto 16d495ede8 builder/qemu: style 2014-02-21 15:22:58 -08:00
Mitchell Hashimoto 2fb296b520 Merge pull request #854 from qur/qemu-config-binary
builder/qemu: binary name to be configured
2014-02-21 15:22:27 -08:00
Mitchell Hashimoto 385d9d37f9 builder/qemu: document run_once is gone (but keep it around for compat) 2014-02-21 15:18:03 -08:00
Mitchell Hashimoto c37ab71311 update CHANGELOG 2014-02-21 15:16:20 -08:00
Mitchell Hashimoto 36766c3ea0 Merge pull request #864 from CoreMedia/master
builder/qemu: handle reboots in the shell provisioner
2014-02-21 15:15:47 -08:00
Mitchell Hashimoto b232b19e1c fmt 2014-02-21 15:11:56 -08:00
Mitchell Hashimoto 22dad03f37 Merge pull request #884 from pmyjavec/vmware-iso-driver-fix
builder/vmware: get host IP by creating & inspecting connection, allows hypervisor...
2014-02-21 15:11:35 -08:00
Mitchell Hashimoto c98ae34557 builder/googlecompute: tests for #867 2014-02-21 15:10:09 -08:00
Mitchell Hashimoto 6706ffd251 Merge pull request #870 from vogxn/master
builder/googlecompute: save private key when debugging gce builder
2014-02-21 15:01:00 -08:00
Mitchell Hashimoto 78125e0b5b Update CHANGELOG 2014-02-21 14:59:21 -08:00
Mitchell Hashimoto 35673e9494 Merge pull request #883 from b2jrock/bjj-add-importopts
builder/virtualbox/ovf: Add import_opts to the virtualbox-ovf builder.
2014-02-21 14:58:36 -08:00
Mitchell Hashimoto 9d2434f00b Merge pull request #885 from rosstimson/ec2-upload-bundle
builder/amazon/instance: use --url flag rather than --location for ec2-upload-bundle
2014-02-21 14:57:12 -08:00
Mitchell Hashimoto ac8e9f4219 communicator/ssh: fix build 2014-02-21 14:51:33 -08:00
Mitchell Hashimoto 003c3b5ed3 communicator/ssh: set TCP keep-alive [GH-872] 2014-02-21 14:46:05 -08:00
Mitchell Hashimoto c927dfa9cf packer/rpc: fix crashes with big file uploads [GH-897] 2014-02-21 14:24:22 -08:00
Mitchell Hashimoto 6469b66d5d website: update Vagrant PP docs for input artifacts [GH-898] 2014-02-21 14:11:33 -08:00
Mitchell Hashimoto 60dca86a8c builder/vmware-iso: output upload error [GH-899] 2014-02-21 14:07:25 -08:00
Paul Myjavec e1b5469f3c esx5, Get host IP by creating & inspecting connection, allows hypervisor to reside in other networks 2014-02-20 19:37:12 +11:00
Brian Johnson a9f80c4363 Merge branch 'bjj-add-importopts' of github.com:b2jrock/packer into bjj-add-importopts 2014-02-18 16:44:47 -08:00
Brian Johnson 5fc214564f Add import_opts to the virtualbox-ovf builder. My use case is that I
have existing virtualbox virtual machines where I need to keep the mac
addresses so that the network interfaces come up when packer boots them.
Otherwise I just get SSH timeout and I'm unable to configure the machines.
2014-02-18 16:44:27 -08:00
Jack Pearkes be1e88395c Merge pull request #894 from vogxn/docfix-ami-groups
Adding docs to #645 on the website
2014-02-18 09:35:01 -05:00
Prasanna Santhanam 8f11257a25 Adding docs to #645 on the website
Setting `"ami_groups": ["all"]` will make the AMI publicly accessible.
Correcting website docs for this.
2014-02-18 17:05:24 +05:30
Ross Timson 1851ef41d4 Use --url flag rather than --location for ec2-upload-bundle
AWS docs (http://goo.gl/rNZiCs) show that --location is deprecated and
to use the --url flag and reference the region endpoint instead.
2014-02-13 09:36:45 +00:00
Brian Johnson 7b4e797d80 Add import_opts to the virtualbox-ovf builder. My use case is that I
have existing virtualbox virtual machines where I need to keep the mac
addresses so that the network interfaces come up when packer boots them.
Otherwise I just get SSH timeout and I'm unable to configure the machines.
2014-02-12 15:29:13 -08:00
Bernard McKeever f16ba100cc provisioner/ansible: support {group,host}_vars
ansible looks for group and host vars directories as well as playbooks
this revision to the provisioner uploads those as well.
squashed to a single commit by strcrzy.
2014-02-02 09:34:41 -08:00
Prasanna Santhanam 410c0f37e1 Formatted googlecompute builder as per make format 2014-01-31 22:26:09 +05:30
Prasanna Santhanam c3eceb1af5 Fixes #867: save private key when debugging gce builder
Signed-off-by: Prasanna Santhanam <tsp@qubole.com>
2014-01-31 19:13:31 +05:30
mheidenr 3e4dca943a enable qemu builder to handle reboots in the shell provisioner 2014-01-30 08:46:54 +01:00
Julian Phillips dfc8733f43 Allow Qemu binary name to be configured
On some systems the default Qemu binary name of qemu-system-x86_64
isn't appropriate.  So expose the binary name as a configurable item,
allowing the user to set it to something more appropriate if they need
to.
2014-01-24 18:01:42 +00:00
jamie brim e41f620a6a provisioner/ansible: configurable command / args
allow users to provide a replacement command / extra args to the
ansible-local provisioner.

this is useful, for example, to unbuffer output when using the
docker builder, or to modify ansible behavior, like enabling
verbose mode, injecting variables, or changing paths.
2014-01-22 14:34:34 -08:00
Jack Pearkes 8a1364dbd8 Merge pull request #833 from yosssi/fix-html
Change the word `aws_access_key` to `aws_secret_key`.
2014-01-20 08:55:40 -08:00
yosssi eb468d24ec Change the word aws_access_key to aws_secret_key. 2014-01-21 01:43:51 +09:00
Mitchell Hashimoto 4f29ed550a Update CHANGELOG 2014-01-19 20:49:17 -08:00
Mitchell Hashimoto ec46c709d4 website: update formatting for docker docs 2014-01-19 20:48:18 -08:00
Mitchell Hashimoto f496eb3e87 Merge pull request #794 from mitchellh/do-doc-defaults
website: update defaults for DO ID's
2014-01-19 20:43:43 -08:00
Mitchell Hashimoto 865c062b19 website: add docs for docker-import and docker-push 2014-01-19 20:42:42 -08:00
Mitchell Hashimoto 4a00b520fb post-processor/docker-push 2014-01-19 20:34:20 -08:00
Mitchell Hashimoto e9eea49473 post-processor/docker-import: use the repo tag as the ID 2014-01-19 20:20:00 -08:00
Mitchell Hashimoto c1e155808b post-processor/docker-import: verify proper builder ID 2014-01-19 19:56:50 -08:00
Mitchell Hashimoto 5fa6c5b6c3 builder/docker: make Import part of this Driver for reuse 2014-01-19 19:55:01 -08:00
Mitchell Hashimoto decaf04e5b post-processor/docker-import: have an Artifact
/cc @mmckeen
2014-01-19 19:48:06 -08:00
Mitchell Hashimoto 7c091281d8 fmt 2014-01-19 19:30:11 -08:00
Mitchell Hashimoto 6839a01e2d post-processor/docker-import: refactor
/cc @mmckeen - Did a refactor here, CC'd in case you're curious
or want to learn :)
2014-01-19 19:15:25 -08:00
Mitchell Hashimoto 5a6c981f6e Merge branch 'docker-metadata' of https://github.com/mmckeen/packer into mmckeen-docker-metadata 2014-01-19 19:04:04 -08:00
Mitchell Hashimoto e28db6e48c Update CHANGELOG 2014-01-19 15:48:56 -08:00
Mitchell Hashimoto b639618666 Merge pull request #803 from mark-rushakoff/useragent
common: set user agent in downloader
2014-01-19 15:46:45 -08:00
Mitchell Hashimoto 40035d8a6c Update CHANGELOG 2014-01-19 15:41:33 -08:00
Mitchell Hashimoto 053fcc5ef9 Merge pull request #808 from masteinhauser/add-scan-codes
builder/virtualbox,vmware,qemu: add more special scancodes
2014-01-19 15:35:53 -08:00
Mitchell Hashimoto 8fb124750e Update cHANGELOG 2014-01-19 15:21:40 -08:00
Mitchell Hashimoto 07592bcd6b Merge pull request #829 from strcrzy/upload_ansible_playbooks_dir
provisioner/ansible: upload playbooks correctly
2014-01-19 15:21:14 -08:00
Mitchell Hashimoto 0e2a552376 Merge pull request #831 from DracoBlue/patch-1
website: fix typo in qemu documentation
2014-01-19 15:20:31 -08:00
Mitchell Hashimoto ea5961f13a packer: don't crash if arg is empty [GH-832] 2014-01-19 15:19:10 -08:00
DracoBlue b43286efd3 Fixed typo in qemu documentation 2014-01-18 22:03:36 +01:00
jamie brim 741a1134c8 provisioner/ansible: upload playbooks correctly
`Provision` calls uploadFile on each path in playbook_paths, but
playbook_paths can only contain directories, per bf7530ca28.

This changes `Provision` to call uploadDir instead.
2014-01-17 16:54:20 -08:00
Mitchell Hashimoto 2001a7987e scripts: devcompile works properly in Cygwin 2014-01-16 15:52:42 -08:00
Jack Pearkes 25cb57ef5a Merge pull request #823 from dmerrick/improvements_to_gce_documentation
Updating the googlecompute builder documentation.
2014-01-16 13:13:43 -08:00
Mitchell Hashimoto cf68bf070b scripts: Fix slight bash error 2014-01-16 12:16:12 -08:00
Dana Merrick 2f570c76a4 Updating the googlecompute builder documentation. 2014-01-16 12:03:56 -08:00
Jack Pearkes 4815a8e190 Merge pull request #819 from klynton/patch-1
Update post-processor.html.markdown
2014-01-15 08:41:25 -08:00
Klynton Jessup 1170a4731d Update post-processor.html.markdown
Fixed some typos in the interface{} mentions.
2014-01-14 20:13:43 -07:00
Matthew McKeen 022a281560 Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-13 15:56:04 -08:00
Matthew McKeen 9067228afa docker-push: add code to handle seperate registry, push a specific repository/tag #774 2014-01-13 13:37:09 -08:00
Jack Pearkes 622dc6ec74 Merge pull request #815 from rasa/patch-1
Update virtualbox-iso.html.markdown
2014-01-13 13:35:02 -08:00
Matthew McKeen 6ce1afda77 docker-import + docker-push: Add some beginning tests. #774 2014-01-13 13:22:43 -08:00
Ross Smith II f7f04e57d3 Update virtualbox-iso.html.markdown
Changed type to virtualbox-iso
2014-01-13 11:11:55 -08:00
Myles Steinhauser 52c9cf6aaf Fix tabs vs spaces 2014-01-13 00:03:10 -05:00
Matthew McKeen 7f6263ec09 Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-12 17:14:19 -08:00
Myles Steinhauser f64f7ce7da Add more special scancodes 2014-01-10 12:40:43 -05:00
Mitchell Hashimoto eadb510b8f Merge pull request #807 from rickard-von-essen/806_multiple_gopaths
Fixes #806 Build fails if GOPATH has multiple paths
2014-01-10 07:58:51 -08:00
Rickard von Essen dad277e9a0 Fixes #806 Build fails if GOPATH has multiple paths 2014-01-10 16:35:07 +01:00
Mark Rushakoff d6bd6fc0ae StepDownload uses packer version as user agent 2014-01-09 13:20:30 -08:00
Mark Rushakoff b742823394 HTTPDownloader uses UserAgent from DownloadConfig 2014-01-09 08:41:34 -08:00
Matthew McKeen 47136e8ff2 docker-import: finish up Dockerfile provisioning, Add TODO for next section #774 2014-01-06 15:12:08 -08:00
Matthew McKeen 1bf660666e docker-import post-processor, add TODO #774 2014-01-06 13:43:30 -08:00
Matthew McKeen d49ee43ec6 Start adding dockerfile provisioning to docker-import post-processor #774 2014-01-06 13:42:32 -08:00
Jack Pearkes 64c0fe8aa1 website: update defaults for DO ID's 2014-01-06 16:13:22 -05:00
Matthew McKeen 550df6844c Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-05 16:36:51 -08:00
Mitchell Hashimoto 94cfee490d Merge pull request #791 from stefanfoulis/patch-1
website: includes information about "provider"
2014-01-04 13:54:48 -08:00
Stefan Foulis 9f80271708 includes information about "provider" 2014-01-04 22:24:14 +01:00
Mitchell Hashimoto 78b859acf6 builder/virtualbox,vmware: checksum not required if checksum type is
none
2014-01-04 11:10:13 -08:00
Mitchell Hashimoto c9fadc06cd Merge pull request #789 from bpot/puppet_default_really_correct
website: really fix puppet provisioner cmd docs
2014-01-04 10:58:37 -08:00
Bob Potter ad73f02388 website: really fix puppet provisioner cmd docs 2014-01-04 12:55:56 -06:00
Matthew McKeen 9be65b517e Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-03 18:03:39 -08:00
Mitchell Hashimoto ea00ec2a10 Update CHANGELOG.md 2014-01-02 15:14:51 -08:00
Mitchell Hashimoto 83c14e0963 Merge pull request #777 from mitchellh/docker-user-vars
builder/docker: include user vars for docker configuration
2014-01-02 15:14:15 -08:00
Matthew McKeen 7b62784e3e Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-02 14:51:15 -08:00
Jack Pearkes 77de557ad1 builder/docker: include user vars for docker configuration 2014-01-02 14:49:30 -08:00
Matthew McKeen 1581c3c9c0 Add docker-import post-processor.
Implemented initial working version
of Docker image importing code. #774
2014-01-02 14:49:14 -08:00
Mitchell Hashimoto fbebd7fe9c up version for dev 2014-01-02 13:33:50 -08:00
Matthew McKeen 34e7660f2e Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-02 13:10:24 -08:00
Mitchell Hashimoto eef21449aa website: stupid file to increase counter 2014-01-02 09:26:52 -08:00
Matthew McKeen e1519af7e6 Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-01 23:33:55 -08:00
Matthew McKeen 990c393a14 Finish up parameter parsing and validation.
Login to a docker index now works, ready for
implementation of the actual push logic.
2014-01-01 23:29:27 -08:00
Matthew McKeen 12765ad31d Rename docker post processor to docker-push.
Implement login to a docker registry, error handling
2014-01-01 22:30:28 -08:00
Matthew McKeen b2327600bc Merge branch 'master' of https://github.com/mitchellh/packer into docker-metadata 2014-01-01 21:49:57 -08:00
Matthew McKeen 6ff1143753 Do some forward porting of the old work of
mitchellh/packer's docker branch. #774
2014-01-01 21:18:09 -08:00
79 changed files with 1215 additions and 178 deletions
+1 -4
View File
@@ -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"
+37
View File
@@ -1,3 +1,40 @@
## 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:
+1 -1
View File
@@ -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 {
+33
View File
@@ -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())
}
+57
View File
@@ -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)
}
}
+2
View File
@@ -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{
+9
View File
@@ -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)
+58
View File
@@ -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
+33
View File
@@ -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
+13 -6
View File
@@ -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
+23 -4
View File
@@ -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)
}
}
+11 -1
View File
@@ -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
View File
@@ -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
}
+11
View File
@@ -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 := "~!@#$%^&*()_+{}|:\"<>?"
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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...)
+3 -1
View File
@@ -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
}
+7 -7
View File
@@ -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 := "~!@#$%^&*()_+{}|:\"<>?"
+1
View File
@@ -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),
+2
View File
@@ -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
}
@@ -59,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 {
+2 -1
View File
@@ -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())
+7 -7
View File
@@ -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,
+4 -17
View File
@@ -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) {
+17
View File
@@ -1,7 +1,9 @@
package iso
import (
"fmt"
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
"net"
"testing"
)
@@ -16,3 +18,18 @@ func TestESX5Driver_implOutputDir(t *testing.T) {
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 := "~!@#$%^&*()_+{}|:\"<>?"
+3 -1
View File
@@ -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
View File
@@ -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,
+87
View File
@@ -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")
+1
View File
@@ -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 -1
View File
@@ -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
}
}
+3 -1
View File
@@ -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": {
+4 -3
View File
@@ -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
}
+10 -2
View File
@@ -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) {
+17 -9
View File
@@ -407,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)
@@ -506,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 {
@@ -514,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
}
}
}()
@@ -558,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) {
@@ -584,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
+57
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -10,7 +10,7 @@ import (
var GitCommit string
// The version of packer.
const Version = "0.5.1"
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
+15
View File
@@ -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)
}
+4
View File
@@ -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"}
+8
View File
@@ -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")
}
}
+4
View File
@@ -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"}
+1 -1
View File
@@ -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 {
+4
View File
@@ -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.
+4
View File
@@ -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"}
+4
View File
@@ -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"}
+71 -4
View File
@@ -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)
}
}
+8 -2
View File
@@ -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
+4
View File
@@ -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
@@ -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.
@@ -88,6 +88,7 @@ for readability) to execute Puppet:
--modulepath='{{.ModulePath}}' \
{{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).
+2
View File
@@ -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>