Compare commits

..

1 Commits

Author SHA1 Message Date
Mitchell Hashimoto 9907933bf8 v0.3.2 2013-08-18 09:47:14 -06:00
553 changed files with 6362 additions and 29372 deletions
-4
View File
@@ -4,7 +4,3 @@
/src
/website/.sass-cache
/website/build
.DS_Store
.vagrant
Vagrantfile
test/.env
+2 -4
View File
@@ -1,13 +1,11 @@
language: go
go:
- 1.2
- 1.1
- tip
install: make deps
script:
- go test ./...
- go test -race ./...
script: make test
notifications:
flowdock:
-435
View File
@@ -1,438 +1,3 @@
## 0.5.0 (12/30/2013)
BACKWARDS INCOMPATIBILITIES:
* "virtualbox" builder has been renamed to "virtualbox-iso". Running your
template through `packer fix` will resolve this.
* "vmware" builder has been renamed to "vmware-iso". Running your template
through `packer fix` will resolve this.
* post-processor/vagrant: Syntax for overriding by provider has changed.
See the documentation for more information. Running your template
through `packer fix` should resolve this.
* post-processor/vsphere: Some available configuration options were
changed. Running your template through `packer fix` should resolve
this.
* provisioner/puppet-masterless: The `execute_command` no longer has
the `Has*` variables, since the templating language now supports
comparison operations. See the Go documentation for more info:
http://golang.org/pkg/text/template/
FEATURES:
* **New builder:** Google Compute Engine. You can now build images for
use in Google Compute Engine. See the documentation for more information.
[GH-715]
* **New builder:** "virtualbox-ovf" can build VirtualBox images from
an existing OVF or OVA. [GH-201]
* **New builder:** "vmware-vmx" can build VMware images from an existing
VMX. [GH-201]
* Environmental variables can now be accessed as default values for
user variables using the "env" function. See the documentation for more
information.
* "description" field in templates: write a human-readable description
of what a template does. This will be shown in `packer inspect`.
* Vagrant post-processor now accepts a list of files to include in the
box.
* All provisioners can now have a "pause\_before" parameter to wait
some period of time before running that provisioner. This is useful
for reboots. [GH-737]
IMPROVEMENTS:
* core: Plugins communicate over a single TCP connection per plugin now,
instead of sometimes dozens. Performance around plugin communication
dramatically increased.
* core: Build names are now template processed so you can use things
like user variables in them. [GH-744]
* core: New "pwd" function available globally that returns the working
directory. [GH-762]
* builder/amazon/all: Launched EC2 instances now have a name of
"Packer Builder" so that they are easily recognizable. [GH-642]
* builder/amazon/all: Copying AMIs to multiple regions now happens
in parallel. [GH-495]
* builder/amazon/all: Ability to specify "run\_tags" to tag the instance
while running. [GH-722]
* builder/digitalocean: Private networking support. [GH-698]
* builder/docker: A "run\_command" can be specified, configuring how
the container is started. [GH-648]
* builder/openstack: In debug mode, the generated SSH keypair is saved
so you can SSH into the machine. [GH-746]
* builder/qemu: Floppy files are supported. [GH-686]
* builder/qemu: Next `run_once` option tells Qemu to run only once,
which is useful for Windows installs that handle reboots for you.
[GH-687]
* builder/virtualbox: Nice errors if Packer can't write to
the output directory.
* builder/virtualbox: ISO is ejected prior to export.
* builder/virtualbox: Checksum type can be "none" [GH-471]
* builder/vmware: Can now specify path to the Fusion application. [GH-677]
* builder/vmware: Checksum type can be "none" [GH-471]
* provisioner/puppet-masterless: Can now specify a `manifest_dir` to
upload manifests to the remote machine for imports. [GH-655]
BUG FIXES:
* core: No colored output in machine-readable output. [GH-684]
* core: User variables can now be used for non-string fields. [GH-598]
* core: Fix bad download paths if the download URL contained a "."
before a "/" [GH-716]
* core: "{{timestamp}}" values will always be the same for the entire
duration of a build. [GH-744]
* builder/amazon: Handle cases where security group isn't instantly
available. [GH-494]
* builder/virtualbox: don't download guest additions if disabled. [GH-731]
* post-processor/vsphere: Uploads VM properly. [GH-694]
* post-processor/vsphere: Process user variables.
* provisioner/ansible-local: all configurations are processed as templates
[GH-749]
* provisioner/ansible-local: playbook paths are properly validated
as directories, not files. [GH-710]
* provisioner/chef-solo: Environments are recognized. [GH-726]
## 0.4.1 (December 7, 2013)
IMPROVEMENTS:
* builder/amazon/ebs: New option allows associating a public IP with
non-default VPC instances. [GH-660]
* builder/openstack: A "proxy\_url" setting was added to define an HTTP
proxy to use when building with this builder. [GH-637]
BUG FIXES:
* core: Don't change background color on CLI anymore, making things look
a tad nicer in some terminals.
* core: multiple ISO URLs works properly in all builders. [GH-683]
* builder/amazon/chroot: Block when obtaining file lock to allow
parallel builds. [GH-689]
* builder/amazon/instance: Add location flag to upload bundle command
so that building AMIs works out of us-east-1 [GH-679]
* builder/qemu: Qemu arguments are templated. [GH-688]
* builder/vmware: Cleanup of VMX keys works properly so cd-rom won't
get stuck with ISO. [GH-685]
* builder/vmware: File cleanup is more resilient to file delete races
with the operating system. [GH-675]
* provisioner/puppet-masterless: Check for hiera config path existence
properly. [GH-656]
## 0.4.0 (November 19, 2013)
FEATURES:
* Docker builder: build and export Docker containers, easily provisioned
with any of the Packer built-in provisioners.
* QEMU builder: builds a new VM compatible with KVM or Xen using QEMU.
* Remote ESXi builder: builds a VMware VM using ESXi remotely using only
SSH to an ESXi machine directly.
* vSphere post-processor: Can upload VMware artifacts to vSphere
* Vagrant post-processor can now make DigitalOcean provider boxes. [GH-504]
IMPROVEMENTS:
* builder/amazon/all: Can now specify a list of multiple security group
IDs to apply. [GH-499]
* builder/amazon/all: AWS API requests are now retried when a temporary
network error occurs as well as 500 errors. [GH-559]
* builder/virtualbox: Use VBOX\_INSTALL\_PATH env var on Windows to find
VBoxManage. [GH-628]
* post-processor/vagrant: skips gzip compression when compression_level=0
* provisioner/chef-solo: Encrypted data bag support [GH-625]
BUG FIXES:
* builder/amazon/chroot: Copying empty directories works. [GH-588]
* builder/amazon/chroot: Chroot commands work with shell provisioners. [GH-581]
* builder/amazon/chroot: Don't choose a mount point that is a partition of
an already mounted device. [GH-635]
* builder/virtualbox: Ctrl-C interrupts during waiting for boot. [GH-618]
* builder/vmware: VMX modifications are now case-insensitive. [GH-608]
* builder/vmware: VMware Fusion won't ask for VM upgrade.
* builder/vmware: Ctrl-C interrupts during waiting for boot. [GH-618]
* provisioner/chef-solo: Output is slightly prettier and more informative.
## 0.3.11 (November 4, 2013)
FEATURES:
* builder/amazon/ebs: Ability to specify which availability zone to create
instance in. [GH-536]
IMPROVEMENTS:
* core: builders can now give warnings during validation. warnings won't
fail the build but may hint at potential future problems.
* builder/digitalocean: Can now specify a droplet name
* builder/virtualbox: Can now disable guest addition download entirely
by setting "guest_additions_mode" to "disable" [GH-580]
* builder/virtualbox,vmware: ISO urls can now be https [GH-587]
* builder/virtualbox,vmware: Warning if shutdown command is not specified,
since it is a common case of data loss.
BUG FIXES:
* core: Won't panic when writing to a bad pipe. [GH-560]
* builder/amazon/all: Properly scrub access key and secret key from logs.
[GH-554]
* builder/openstack: Properly scrub password from logs [GH-554]
* builder/virtualbox: No panic if SSH host port min/max is the same. [GH-594]
* builder/vmware: checks if `ifconfig` is in `/sbin` [GH-591]
* builder/vmware: Host IP lookup works for non-C locales. [GH-592]
* common/uuid: Use cryptographically secure PRNG when generating
UUIDs. [GH-552]
* communicator/ssh: File uploads that exceed the size of memory no longer
cause crashes. [GH-561]
## 0.3.10 (October 20, 2013)
FEATURES:
* Ansible provisioner
IMPROVEMENTS:
* post-processor/vagrant: support instance-store AMIs built by Packer. [GH-502]
* post-processor/vagrant: can now specify compression level to use
when creating the box. [GH-506]
BUG FIXES:
* builder/all: timeout waiting for SSH connection is a failure. [GH-491]
* builder/amazon: Scrub sensitive data from the logs. [GH-521]
* builder/amazon: Handle the situation where an EC2 instance might not
be immediately available. [GH-522]
* builder/amazon/chroot: Files copied into the chroot remove destination
before copy, fixing issues with dangling symlinks. [GH-500]
* builder/digitalocean: don't panic if erroneous API response doesn't
contain error message. [GH-492]
* builder/digitalocean: scrub API keys from config debug output [GH-516]
* builder/virtualbox: error if VirtualBox version cant be detected. [GH-488]
* builder/virtualbox: detect if vboxdrv isn't properly setup. [GH-488]
* builder/virtualbox: sleep a bit before export to ensure the sesssion
is unlocked. [GH-512]
* builder/virtualbox: create SATA drives properly on VirtualBox 4.3 [GH-547]
* builder/virtualbox: support user templates in SSH key path. [GH-539]
* builder/vmware: support user templates in SSH key path. [GH-539]
* communicator/ssh: Fix issue where a panic could arise from a nil
dereference. [GH-525]
* post-processor/vagrant: Fix issue with VirtualBox OVA. [GH-548]
* provisioner/salt: Move salt states to correct remote directory. [GH-513]
* provisioner/shell: Won't block on certain scripts on Windows anymore.
[GH-507]
## 0.3.9 (October 2, 2013)
FEATURES:
* The Amazon chroot builder is now able to run without any `sudo` privileges
by using the "command_wrapper" configuration. [GH-430]
* Chef provisioner supports environments. [GH-483]
BUG FIXES:
* core: default user variable values don't need to be strings. [GH-456]
* builder/amazon-chroot: Fix errors with waitin for state change. [GH-459]
* builder/digitalocean: Use proper error message JSON key (DO API change).
* communicator/ssh: SCP uploads now work properly when directories
contain symlinks. [GH-449]
* provisioner/chef-solo: Data bags and roles path are now properly
populated when set. [GH-470]
* provisioner/shell: Windows line endings are actually properly changed
to Unix line endings. [GH-477]
## 0.3.8 (September 22, 2013)
FEATURES:
* core: You can now specify `only` and `except` configurations on any
provisioner or post-processor to specify a list of builds that they
are valid for. [GH-438]
* builders/virtualbox: Guest additions can be attached rather than uploaded,
easier to handle for Windows guests. [GH-405]
* provisioner/chef-solo: Ability to specify a custom Chef configuration
template.
* provisioner/chef-solo: Roles and data bags support. [GH-348]
IMPROVEMENTS:
* core: User variables can now be used for integer, boolean, etc.
values. [GH-418]
* core: Plugins made with incompatible versions will no longer load.
* builder/amazon/all: Interrupts work while waiting for AMI to be ready.
* provisioner/shell: Script line-endings are automatically converted to
Unix-style line-endings. Can be disabled by setting "binary" to "true".
[GH-277]
BUG FIXES:
* core: Set TCP KeepAlives on internally created RPC connections so that
they don't die. [GH-416]
* builder/amazon/all: While waiting for AMI, will detect "failed" state.
* builder/amazon/all: Waiting for state will detect if the resource (AMI,
instance, etc.) disappears from under it.
* builder/amazon/instance: Exclude only contents of /tmp, not /tmp
itself. [GH-437]
* builder/amazon/instance: Make AccessKey/SecretKey available to bundle
command even when they come from the environment. [GH-434]
* builder/virtualbox: F1-F12 and delete scancodes now work. [GH-425]
* post-processor/vagrant: Override configurations properly work. [GH-426]
* provisioner/puppet-masterless: Fix failure case when both facter vars
are used and prevent_sudo. [GH-415]
* provisioner/puppet-masterless: User variables now work properly in
manifest file and hiera path. [GH-448]
## 0.3.7 (September 9, 2013)
BACKWARDS INCOMPATIBILITIES:
* The "event_delay" option for the DigitalOcean builder is now gone.
The builder automatically waits for events to go away. Run your templates
through `packer fix` to get rid of these.
FEATURES:
* **NEW PROVISIONER:** `puppet-masterless`. You can now provision with
a masterless Puppet setup. [GH-234]
* New globally available template function: `uuid`. Generates a new random
UUID.
* New globally available template function: `isotime`. Generates the
current time in ISO standard format.
* New Amazon template function: `clean_ami_name`. Substitutes '-' for
characters that are illegal to use in an AMI name.
IMPROVEMENTS:
* builder/amazon/all: Ability to specify the format of the temporary
keypair created. [GH-389]
* builder/amazon/all: Support the NoDevice flag for block mappings. [GH-396]
* builder/digitalocean: Retry on any pending event errors.
* builder/openstack: Can now specify a project. [GH-382]
* builder/virtualbox: Can now attach hard drive over SATA. [GH-391]
* provisioner/file: Can now upload directories. [GH-251]
BUG FIXES:
* core: Detect if SCP is not enabled on the other side. [GH-386]
* builder/amazon/all: When copying AMI to multiple regions, copy
the metadata (tags and attributes) as well. [GH-388]
* builder/amazon/all: Fix panic case where eventually consistent
instance state caused an index out of bounds.
* builder/virtualbox: The `vm_name` setting now properly sets the OVF
name of the output. [GH-401]
* builder/vmware: Autoanswer VMware dialogs. [GH-393]
* command/inspect: Fix weird output for default values for optional vars.
## 0.3.6 (September 2, 2013)
FEATURES:
* User variables can now be specified as "required", meaning the user
MUST specify a value. Just set the default value to "null". [GH-374]
IMPROVEMENTS:
* core: Much improved interrupt handling. For example, interrupts now
cancel much more quickly within provisioners.
* builder/amazon: In `-debug` mode, the keypair used will be saved to
the current directory so you can access the machine. [GH-373]
* builder/amazon: In `-debug` mode, the DNS is outputted.
* builder/openstack: IPv6 addresses supported for SSH. [GH-379]
* communicator/ssh: Support for private keys encrypted using PKCS8. [GH-376]
* provisioner/chef-solo: You can now use user variables in the `json`
configuration for Chef. [GH-362]
BUG FIXES:
* core: Concurrent map access is completely gone, fixing rare issues
with runtime memory corruption. [GH-307]
* core: Fix possible panic when ctrl-C during provisioner run.
* builder/digitalocean: Retry destroy a few times because DO sometimes
gives false errors.
* builder/openstack: Properly handle the case no image is made. [GH-375]
* builder/openstack: Specifying a region is now required in a template.
* provisioners/salt-masterless: Use filepath join to properly join paths.
## 0.3.5 (August 28, 2013)
FEATURES:
* **NEW BUILDER:** `openstack`. You can now build on OpenStack. [GH-155]
* **NEW PROVISIONER:** `chef-solo`. You can now provision with Chef
using `chef-solo` from local cookbooks.
* builder/amazon: Copy AMI to multiple regions with `ami_regions`. [GH-322]
* builder/virtualbox,vmware: Can now use SSH keys as an auth mechanism for
SSH using `ssh_key_path`. [GH-70]
* builder/virtualbox,vmware: Support SHA512 as a checksum type. [GH-356]
* builder/vmware: The root hard drive type can now be specified with
"disk_type_id" for advanced users. [GH-328]
* provisioner/salt-masterless: Ability to specfy a minion config. [GH-264]
* provisioner/salt-masterless: Ability to upload pillars. [GH-353]
IMPROVEMENTS:
* core: Output message when Ctrl-C received that we're cleaning up. [GH-338]
* builder/amazon: Tagging now works with all amazon builder types.
* builder/vmware: Option `ssh_skip_request_pty` for not requesting a PTY
for the SSH connection. [GH-270]
* builder/vmware: Specify a `vmx_template_path` in order to customize
the generated VMX. [GH-270]
* command/build: Machine-readable output now contains build errors, if any.
* command/build: An "end" sentinel is outputted in machine-readable output
for artifact listing so it is easier to know when it is over.
BUG FIXES:
* core: Fixed a couple cases where a double ctrl-C could panic.
* core: Template validation fails if an override is specified for a
non-existent builder. [GH-336]
* core: The SSH connection is heartbeated so that drops can be
detected. [GH-200]
* builder/amazon/instance: Remove check for ec2-ami-tools because it
didn't allow absolute paths to work properly. [GH-330]
* builder/digitalocean: Send a soft shutdown request so that files
are properly synced before shutdown. [GH-332]
* command/build,command/validate: If a non-existent build is specified to
'-only' or '-except', it is now an error. [GH-326]
* post-processor/vagrant: Setting OutputPath with a timestamp now
always works properly. [GH-324]
* post-processor/vagrant: VirtualBox OVA formats now turn into
Vagrant boxes properly. [GH-331]
* provisioner/shell: Retry upload if start command fails, making reboot
handling much more robust.
## 0.3.4 (August 21, 2013)
IMPROVEMENTS:
* post-processor/vagrant: the file being compressed will be shown
in the UI [GH-314]
BUG FIXES:
* core: Avoid panics when double-interrupting Packer.
* provisioner/shell: Retry shell script uploads, making reboots more
robust if they happen to fail in this stage. [GH-282]
## 0.3.3 (August 19, 2013)
FEATURES:
* builder/virtualbox: support exporting in OVA format. [GH-309]
IMPROVEMENTS:
* core: All HTTP downloads across Packer now support the standard
proxy environmental variables (`HTTP_PROXY`, `NO_PROXY`, etc.) [GH-252]
* builder/amazon: API requests will use HTTP proxy if specified by
enviromental variables.
* builder/digitalocean: API requests will use HTTP proxy if specified
by environmental variables.
BUG FIXES:
* core: TCP connection between plugin processes will keep-alive. [GH-312]
* core: No more "unused key keep_input_artifact" for post processors [GH-310]
* post-processor/vagrant: `output_path` templates now work again.
## 0.3.2 (August 18, 2013)
FEATURES:
+1 -8
View File
@@ -18,12 +18,6 @@ it raises the chances we can quickly merge or address your contributions.
* Make sure you test against the latest released version. It is possible
we already fixed the bug you're experiencing.
* Run the command with debug ouput with the environment variable
`PACKER_LOG`. For example: `PACKER_LOG=1 packer build template.json`. Take
the *entire* output and create a [gist](https://gist.github.com) for linking
to in your issue. Packer should strip sensitive keys from the output,
but take a look through just in case.
* Provide a reproducible test case. If a contributor can't reproduce an
issue, then it dramatically lowers the chances it'll get fixed. And in
some cases, the issue will eventually be closed.
@@ -53,8 +47,7 @@ it raises the chances we can quickly merge or address your contributions.
If you have never worked with Go before, you will have to complete the
following steps in order to be able to compile and test Packer.
1. Install Go. Make sure the Go version is at least Go 1.2. Packer will not work with anything less than
Go 1.2. On a Mac, you can `brew install go` to install Go 1.2.
1. Install Go. On a Mac, you can `brew install go`.
2. Set and export the `GOPATH` environment variable. For example, you can
add `export GOPATH=$HOME/Documents/golang` to your `.bash_profile`.
+3 -9
View File
@@ -2,22 +2,16 @@ NO_COLOR=\033[0m
OK_COLOR=\033[32;01m
ERROR_COLOR=\033[31;01m
WARN_COLOR=\033[33;01m
DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
all: deps
@mkdir -p bin/
@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
@bash --norc -i ./scripts/devcompile.sh
@./scripts/build.sh
deps:
@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
@go get -d -v ./...
@echo $(DEPS) | xargs -n1 go get -d
updatedeps:
@echo "$(OK_COLOR)==> Updating all dependencies$(NO_COLOR)"
@go get -d -v -u ./...
@echo $(DEPS) | xargs -n1 go get -d -u
@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d
clean:
@rm -rf bin/ local/ pkg/ src/ website/.sass-cache website/build
@@ -29,4 +23,4 @@ test: deps
@echo "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)"
go test ./...
.PHONY: all clean deps format test updatedeps
.PHONY: all deps format test
+3 -16
View File
@@ -70,19 +70,12 @@ http://www.packer.io/docs
## Developing Packer
If you wish to work on Packer itself, you'll first need [Go](http://golang.org)
installed (version 1.2+ is _required_). Make sure you have Go properly installed,
installed (version 1.1+ is _required_). Make sure you have Go properly installed,
including setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH).
For some additional dependencies, Go needs [Mercurial](http://mercurial.selenic.com/)
and [Bazaar](http://bazaar.canonical.com/en/) to be installed.
Packer itself doesn't require these, but a dependency of a dependency does.
You'll also need [`gox`](https://github.com/mitchellh/gox)
to compile packer. You can install that with:
```
$ go get -u github.com/mitchellh/gox
```
to be installed. Packer itself doesn't require this but a dependency of a
dependency does.
Next, clone this repository into `$GOPATH/src/github.com/mitchellh/packer` and
then just type `make`. In a few moments, you'll have a working `packer` executable:
@@ -97,9 +90,3 @@ $ bin/packer
You can run tests by typing `make test`.
This will run tests for Packer core along with all the core builders and commands and such that come with Packer.
If you make any changes to the code, run `make format` in order to automatically
format the code according to Go standards.
When new dependencies are added to packer you can use `make updatedeps` to
get the latest and subsequently use `make` to compile and generate the `packer` binary.
+27 -39
View File
@@ -27,36 +27,32 @@ type Config struct {
awscommon.AMIConfig `mapstructure:",squash"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CommandWrapper string `mapstructure:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
MountCommand string `mapstructure:"mount_command"`
MountPath string `mapstructure:"mount_path"`
SourceAmi string `mapstructure:"source_ami"`
UnmountCommand string `mapstructure:"unmount_command"`
tpl *packer.ConfigTemplate
}
type wrappedCommandTemplate struct {
Command string
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
func (b *Builder) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return nil, err
return err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
b.config.tpl.Funcs(awscommon.TemplateFuncs)
// Defaults
if b.config.ChrootMounts == nil {
@@ -81,12 +77,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
if b.config.CommandWrapper == "" {
b.config.CommandWrapper = "{{.Command}}"
if b.config.MountCommand == "" {
b.config.MountCommand = "mount"
}
if b.config.MountPath == "" {
b.config.MountPath = "/mnt/packer-amazon-chroot-volumes/{{.Device}}"
b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}"
}
if b.config.UnmountCommand == "" {
b.config.UnmountCommand = "umount"
}
// Accumulate any errors
@@ -126,8 +126,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
templates := map[string]*string{
"device_path": &b.config.DevicePath,
"source_ami": &b.config.SourceAmi,
"device_path": &b.config.DevicePath,
"mount_command": &b.config.MountCommand,
"source_ami": &b.config.SourceAmi,
"unmount_command": &b.config.UnmountCommand,
}
for n, ptr := range templates {
@@ -140,11 +142,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
return errs
}
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
return nil, nil
log.Printf("Config: %+v", b.config)
return nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
@@ -164,20 +166,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
wrappedCommand := func(command string) (string, error) {
return b.config.tpl.Process(
b.config.CommandWrapper, &wrappedCommandTemplate{
Command: command,
})
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
state := make(map[string]interface{})
state["config"] = &b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
@@ -195,17 +189,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepEarlyCleanup{},
&StepSnapshot{},
&StepRegisterAMI{},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -221,18 +209,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state.GetOk("amis"); !ok {
if _, ok := state["amis"]; !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state.Get("amis").(map[string]string),
Amis: state["amis"].(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+7 -42
View File
@@ -26,10 +26,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test good
config["ami_name"] = "foo"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -37,10 +34,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -48,10 +42,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test bad
delete(config, "ami_name")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -62,10 +53,7 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
config := testConfig()
config["chroot_mounts"] = nil
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Errorf("err: %s", err)
}
@@ -73,10 +61,7 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
config["chroot_mounts"] = [][]string{
[]string{"bad"},
}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -86,33 +71,13 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) {
config := testConfig()
config["source_ami"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
config["source_ami"] = "foo"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CommandWrapper(t *testing.T) {
b := &Builder{}
config := testConfig()
config["command_wrapper"] = "echo hi; {{.Command}}"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Errorf("err: %s", err)
}
+1 -5
View File
@@ -1,10 +1,6 @@
package chroot
import (
"github.com/mitchellh/multistep"
)
// Cleanup is an interface that some steps implement for early cleanup.
type Cleanup interface {
CleanupFunc(multistep.StateBag) error
CleanupFunc(map[string]interface{}) error
}
-15
View File
@@ -1,15 +0,0 @@
package chroot
import (
"os/exec"
)
// CommandWrapper is a type that given a command, will possibly modify that
// command in-flight. This might return an error.
type CommandWrapper func(string) (string, error)
// ShellCommand takes a command string and returns an *exec.Cmd to execute
// it within the context of a shell (/bin/sh).
func ShellCommand(command string) *exec.Cmd {
return exec.Command("/bin/sh", "-c", command)
}
+8 -43
View File
@@ -1,34 +1,28 @@
package chroot
import (
"bytes"
"fmt"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
)
// Communicator is a special communicator that works by executing
// commands locally but within a chroot.
type Communicator struct {
Chroot string
CmdWrapper CommandWrapper
Chroot string
}
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
command, err := c.CmdWrapper(
fmt.Sprintf("chroot %s /bin/sh -c \"%s\"", c.Chroot, cmd.Command))
chrootCmdPath, err := exec.LookPath("chroot")
if err != nil {
return err
}
localCmd := ShellCommand(command)
localCmd := exec.Command(chrootCmdPath, c.Chroot, "/bin/sh", "-c", cmd.Command)
localCmd.Stdin = cmd.Stdin
localCmd.Stdout = cmd.Stdout
localCmd.Stderr = cmd.Stderr
@@ -52,7 +46,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
}
log.Printf(
"Chroot execution exited with '%d': '%s'",
"Chroot executation ended with '%d': '%s'",
exitStatus, cmd.Command)
cmd.SetExited(exitStatus)
}()
@@ -63,46 +57,17 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
func (c *Communicator) Upload(dst string, r io.Reader) error {
dst = filepath.Join(c.Chroot, dst)
log.Printf("Uploading to chroot dir: %s", dst)
tf, err := ioutil.TempFile("", "packer-amazon-chroot")
if err != nil {
return fmt.Errorf("Error preparing shell script: %s", err)
}
defer os.Remove(tf.Name())
io.Copy(tf, r)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp %s %s", tf.Name(), dst))
f, err := os.Create(dst)
if err != nil {
return err
}
defer f.Close()
return ShellCommand(cpCmd).Run()
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
// TODO: remove any file copied if it appears in `exclude`
chrootDest := filepath.Join(c.Chroot, dst)
log.Printf("Uploading directory '%s' to '%s'", src, chrootDest)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s* %s", src, chrootDest))
if err != nil {
if _, err := io.Copy(f, r); err != nil {
return err
}
var stderr bytes.Buffer
cmd := ShellCommand(cpCmd)
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, "LANG=C")
cmd.Stderr = &stderr
err = cmd.Run()
if err == nil {
return err
}
if strings.Contains(stderr.String(), "No such file") {
// This just means that the directory was empty. Just ignore it.
return nil
}
return err
return nil
}
func (c *Communicator) Download(src string, w io.Writer) error {
-1
View File
@@ -1 +0,0 @@
package chroot
-44
View File
@@ -1,44 +0,0 @@
package chroot
import (
"fmt"
"io/ioutil"
"os"
"testing"
)
func TestCopyFile(t *testing.T) {
first, err := ioutil.TempFile("", "copy_files_test")
if err != nil {
t.Fatalf("couldn't create temp file.")
}
defer os.Remove(first.Name())
newName := first.Name() + "-new"
payload := "copy_files_test.go payload"
if _, err = first.WriteString(payload); err != nil {
t.Fatalf("Couldn't write payload to first file.")
}
first.Sync()
cmd := ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
if err := cmd.Run(); err != nil {
t.Fatalf("Couldn't copy file")
}
defer os.Remove(newName)
second, err := os.Open(newName)
if err != nil {
t.Fatalf("Couldn't open copied file.")
}
defer second.Close()
var copiedPayload = make([]byte, len(payload))
if _, err := second.Read(copiedPayload); err != nil {
t.Fatalf("Couldn't open copied file for reading.")
}
if string(copiedPayload) != payload {
t.Fatalf("payload not copied.")
}
}
-8
View File
@@ -19,14 +19,6 @@ func AvailableDevice() (string, error) {
letters := "fghijklmnop"
for _, letter := range letters {
device := fmt.Sprintf("/dev/%s%c", prefix, letter)
// If the block device itself, i.e. /dev/sf, exists, then we
// can't use any of the numbers either.
if _, err := os.Stat(device); err == nil {
continue
}
for i := 1; i < 16; i++ {
device := fmt.Sprintf("/dev/%s%c%d", prefix, letter, i)
if _, err := os.Stat(device); err != nil {
+7 -1
View File
@@ -3,6 +3,7 @@
package chroot
import (
"errors"
"os"
"syscall"
)
@@ -13,8 +14,13 @@ const LOCK_NB = 4
const LOCK_UN = 8
func lockFile(f *os.File) error {
err := syscall.Flock(int(f.Fd()), LOCK_EX)
err := syscall.Flock(int(f.Fd()), LOCK_EX|LOCK_NB)
if err != nil {
errno, ok := err.(syscall.Errno)
if ok && errno == syscall.EWOULDBLOCK {
return errors.New("file already locked")
}
return err
}
+22 -21
View File
@@ -21,12 +21,12 @@ type StepAttachVolume struct {
volumeId string
}
func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
device := state.Get("device").(string)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
device := state["device"].(string)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
volumeId := state["volume_id"].(string)
// For the API call, it expects "sd" prefixed devices.
attachVolume := strings.Replace(device, "/xvd", "/sd", 1)
@@ -35,7 +35,7 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
_, err := ec2conn.AttachVolume(volumeId, instance.InstanceId, attachVolume)
if err != nil {
err := fmt.Errorf("Error attaching volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -46,6 +46,7 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
// Wait for the volume to become attached
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"attaching"},
StepState: state,
Target: "attached",
@@ -59,37 +60,36 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No attachments on volume.")
}
a := resp.Volumes[0].Attachments[0]
return a, a.Status, nil
return nil, resp.Volumes[0].Attachments[0].Status, nil
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("attach_cleanup", s)
state["attach_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepAttachVolume) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepAttachVolume) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
func (s *StepAttachVolume) CleanupFunc(state map[string]interface{}) error {
if !s.attached {
return nil
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Detaching EBS volume...")
_, err := ec2conn.DetachVolume(s.volumeId)
@@ -101,6 +101,7 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
// Wait for the volume to detach
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"attaching", "attached", "detaching"},
StepState: state,
Target: "detached",
@@ -110,12 +111,12 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
return nil, "", err
}
v := resp.Volumes[0]
if len(v.Attachments) > 0 {
return v, v.Attachments[0].Status, nil
} else {
return v, "detached", nil
state := "detached"
if len(resp.Volumes[0].Attachments) > 0 {
state = resp.Volumes[0].Attachments[0].Status
}
return nil, state, nil
},
}
@@ -11,26 +11,24 @@ type StepChrootProvision struct {
mounts []string
}
func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction {
hook := state.Get("hook").(packer.Hook)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
func (s *StepChrootProvision) Run(state map[string]interface{}) multistep.StepAction {
hook := state["hook"].(packer.Hook)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
// Create our communicator
comm := &Communicator{
Chroot: mountPath,
CmdWrapper: wrappedCommand,
Chroot: mountPath,
}
// Provision
log.Println("Running the provision hook")
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
state.Put("error", err)
state["error"] = err
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepChrootProvision) Cleanup(state multistep.StateBag) {}
func (s *StepChrootProvision) Cleanup(state map[string]interface{}) {}
+52 -34
View File
@@ -1,11 +1,12 @@
package chroot
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os"
"path/filepath"
)
@@ -18,12 +19,10 @@ type StepCopyFiles struct {
files []string
}
func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
stderr := new(bytes.Buffer)
func (s *StepCopyFiles) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
s.files = make([]string, 0, len(config.CopyFiles))
if len(config.CopyFiles) > 0 {
@@ -33,21 +32,9 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
chrootPath := filepath.Join(mountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath)
cmdText, err := wrappedCommand(fmt.Sprintf("cp --remove-destination %s %s", path, chrootPath))
if err != nil {
err := fmt.Errorf("Error building copy command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
stderr.Reset()
cmd := ShellCommand(cmdText)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error copying file: %s\nnStderr: %s", err, stderr.String())
state.Put("error", err)
if err := s.copySingle(chrootPath, path); err != nil {
err := fmt.Errorf("Error copying file: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -56,29 +43,22 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
}
}
state.Put("copy_files_cleanup", s)
state["copy_files_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepCopyFiles) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
func (s *StepCopyFiles) CleanupFunc(map[string]interface{}) error {
if s.files != nil {
for _, file := range s.files {
log.Printf("Removing: %s", file)
localCmdText, err := wrappedCommand(fmt.Sprintf("rm -f %s", file))
if err != nil {
return err
}
localCmd := ShellCommand(localCmdText)
if err := localCmd.Run(); err != nil {
if err := os.Remove(file); err != nil {
return err
}
}
@@ -87,3 +67,41 @@ func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
s.files = nil
return nil
}
func (s *StepCopyFiles) copySingle(dst, src string) error {
// Stat the src file so we can copy the mode later
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
// Remove any existing destination file
if err := os.Remove(dst); err != nil {
return err
}
// Copy the files
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
dstF, err := os.Create(dst)
if err != nil {
return err
}
defer dstF.Close()
if _, err := io.Copy(dstF, srcF); err != nil {
return err
}
dstF.Close()
// Match the mode
if err := os.Chmod(dst, srcInfo.Mode()); err != nil {
return err
}
return nil
}
+14 -14
View File
@@ -18,11 +18,11 @@ type StepCreateVolume struct {
volumeId string
}
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
func (s *StepCreateVolume) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
image := state["source_image"].(*ec2.Image)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
// Determine the root device snapshot
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
@@ -36,7 +36,7 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
if rootDevice == nil {
err := fmt.Errorf("Couldn't find root device!")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -54,7 +54,7 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
if err != nil {
err := fmt.Errorf("Error creating root volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -65,6 +65,7 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
// Wait for the volume to become ready
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"creating"},
StepState: state,
Target: "available",
@@ -74,30 +75,29 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", err
}
v := resp.Volumes[0]
return v, v.Status, nil
return nil, resp.Volumes[0].Status, nil
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("volume_id", s.volumeId)
state["volume_id"] = s.volumeId
return multistep.ActionContinue
}
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
func (s *StepCreateVolume) Cleanup(state map[string]interface{}) {
if s.volumeId == "" {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting the created EBS volume...")
_, err := ec2conn.DeleteVolume(s.volumeId)
+5 -5
View File
@@ -11,8 +11,8 @@ import (
// prepare for snapshotting and creating an AMI.
type StepEarlyCleanup struct{}
func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
func (s *StepEarlyCleanup) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
cleanupKeys := []string{
"copy_files_cleanup",
"mount_extra_cleanup",
@@ -21,11 +21,11 @@ func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
}
for _, key := range cleanupKeys {
c := state.Get(key).(Cleanup)
c := state[key].(Cleanup)
log.Printf("Running cleanup func: %s", key)
if err := c.CleanupFunc(state); err != nil {
err := fmt.Errorf("Error cleaning up: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -34,4 +34,4 @@ func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepEarlyCleanup) Cleanup(state multistep.StateBag) {}
func (s *StepEarlyCleanup) Cleanup(state map[string]interface{}) {}
+5 -5
View File
@@ -10,14 +10,14 @@ import (
// StepEarlyUnflock unlocks the flock.
type StepEarlyUnflock struct{}
func (s *StepEarlyUnflock) Run(state multistep.StateBag) multistep.StepAction {
cleanup := state.Get("flock_cleanup").(Cleanup)
ui := state.Get("ui").(packer.Ui)
func (s *StepEarlyUnflock) Run(state map[string]interface{}) multistep.StepAction {
cleanup := state["flock_cleanup"].(Cleanup)
ui := state["ui"].(packer.Ui)
log.Println("Unlocking file lock...")
if err := cleanup.CleanupFunc(state); err != nil {
err := fmt.Errorf("Error unlocking file lock: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -25,4 +25,4 @@ func (s *StepEarlyUnflock) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepEarlyUnflock) Cleanup(state multistep.StateBag) {}
func (s *StepEarlyUnflock) Cleanup(state map[string]interface{}) {}
+9 -9
View File
@@ -17,13 +17,13 @@ type StepFlock struct {
fh *os.File
}
func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
func (s *StepFlock) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
lockfile := "/var/lock/packer-chroot/lock"
if err := os.MkdirAll(filepath.Dir(lockfile), 0755); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -32,15 +32,15 @@ func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
f, err := os.Create(lockfile)
if err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// LOCK!
if err := lockFile(f); err != nil {
err := fmt.Errorf("Error obtaining lock: %s", err)
state.Put("error", err)
err := fmt.Errorf("Error creating lock: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -49,15 +49,15 @@ func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
// the lock.
s.fh = f
state.Put("flock_cleanup", s)
state["flock_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepFlock) Cleanup(state multistep.StateBag) {
func (s *StepFlock) Cleanup(state map[string]interface{}) {
s.CleanupFunc(state)
}
func (s *StepFlock) CleanupFunc(state multistep.StateBag) error {
func (s *StepFlock) CleanupFunc(state map[string]interface{}) error {
if s.fh == nil {
return nil
}
+8 -8
View File
@@ -12,9 +12,9 @@ import (
// StepInstanceInfo verifies that this builder is running on an EC2 instance.
type StepInstanceInfo struct{}
func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepInstanceInfo) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
// Get our own instance ID
ui.Say("Gathering information about this EC2 instance...")
@@ -24,7 +24,7 @@ func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
err := fmt.Errorf(
"Error retrieving the ID of the instance Packer is running on.\n" +
"Please verify Packer is running on a proper AWS EC2 instance.")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -36,22 +36,22 @@ func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
instancesResp, err := ec2conn.Instances([]string{instanceId}, ec2.NewFilter())
if err != nil {
err := fmt.Errorf("Error getting instance data: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(instancesResp.Reservations) == 0 {
err := fmt.Errorf("Error getting instance data: no instance found.")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
instance := &instancesResp.Reservations[0].Instances[0]
state.Put("instance", instance)
state["instance"] = instance
return multistep.ActionContinue
}
func (s *StepInstanceInfo) Cleanup(multistep.StateBag) {}
func (s *StepInstanceInfo) Cleanup(map[string]interface{}) {}
+20 -32
View File
@@ -7,6 +7,7 @@ import (
"github.com/mitchellh/packer/packer"
"log"
"os"
"os/exec"
"path/filepath"
)
@@ -23,11 +24,10 @@ type StepMountDevice struct {
mountPath string
}
func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
device := state["device"].(string)
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
Device: filepath.Base(device),
@@ -35,7 +35,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -43,7 +43,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
mountPath, err = filepath.Abs(mountPath)
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -52,62 +52,50 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
if err := os.MkdirAll(mountPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer)
mountCommand, err := wrappedCommand(
fmt.Sprintf("mount %s %s", device, mountPath))
if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := ShellCommand(mountCommand)
mountCommand := fmt.Sprintf("%s %s %s", config.MountCommand, device, mountPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting root volume: %s\nStderr: %s", err, stderr.String())
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the mount path so we remember to unmount it later
s.mountPath = mountPath
state.Put("mount_path", s.mountPath)
state.Put("mount_device_cleanup", s)
state["mount_path"] = s.mountPath
state["mount_device_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepMountDevice) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepMountDevice) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
func (s *StepMountDevice) CleanupFunc(state map[string]interface{}) error {
if s.mountPath == "" {
return nil
}
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
ui.Say("Unmounting the root device...")
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
cmd := ShellCommand(unmountCommand)
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, s.mountPath)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
if err := cmd.Run(); err != nil {
return fmt.Errorf("Error unmounting root device: %s", err)
}
+19 -28
View File
@@ -6,6 +6,7 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"os/exec"
)
// StepMountExtra mounts the attached device.
@@ -16,11 +17,10 @@ type StepMountExtra struct {
mounts []string
}
func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
s.mounts = make([]string, 0, len(config.ChrootMounts))
@@ -30,7 +30,7 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
if err := os.MkdirAll(innerPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -42,24 +42,18 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
stderr := new(bytes.Buffer)
mountCommand, err := wrappedCommand(fmt.Sprintf(
"mount %s %s %s",
mountCommand := fmt.Sprintf(
"%s %s %s %s",
config.MountCommand,
flags,
mountInfo[1],
innerPath))
if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := ShellCommand(mountCommand)
innerPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting: %s\nStderr: %s", err, stderr.String())
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -67,12 +61,12 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
s.mounts = append(s.mounts, innerPath)
}
state.Put("mount_extra_cleanup", s)
state["mount_extra_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepMountExtra) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepMountExtra) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
@@ -80,23 +74,20 @@ func (s *StepMountExtra) Cleanup(state multistep.StateBag) {
}
}
func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
func (s *StepMountExtra) CleanupFunc(state map[string]interface{}) error {
if s.mounts == nil {
return nil
}
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
config := state["config"].(*Config)
for len(s.mounts) > 0 {
var path string
lastIndex := len(s.mounts) - 1
path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex]
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, path)
stderr := new(bytes.Buffer)
cmd := ShellCommand(unmountCommand)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf(
+7 -7
View File
@@ -13,9 +13,9 @@ type StepPrepareDevice struct {
mounts []string
}
func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
func (s *StepPrepareDevice) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
device := config.DevicePath
if device == "" {
@@ -24,7 +24,7 @@ func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
device, err = AvailableDevice()
if err != nil {
err := fmt.Errorf("Error finding available device: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -32,14 +32,14 @@ func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
if _, err := os.Stat(device); err == nil {
err := fmt.Errorf("Device is in use: %s", device)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Device: %s", device)
state.Put("device", device)
state["device"] = device
return multistep.ActionContinue
}
func (s *StepPrepareDevice) Cleanup(state multistep.StateBag) {}
func (s *StepPrepareDevice) Cleanup(state map[string]interface{}) {}
+12 -19
View File
@@ -11,12 +11,12 @@ import (
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct{}
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
image := state["source_image"].(*ec2.Image)
snapshotId := state["snapshot_id"].(string)
ui := state["ui"].(packer.Ui)
ui.Say("Registering the AMI...")
blockDevices := make([]ec2.BlockDeviceMapping, len(image.BlockDevices))
@@ -40,8 +40,8 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
state.Put("error", fmt.Errorf("Error registering AMI: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
@@ -49,20 +49,13 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = registerResp.ImageId
state.Put("amis", amis)
state["amis"] = amis
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -70,4 +63,4 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
func (s *StepRegisterAMI) Cleanup(state map[string]interface{}) {}
+14 -14
View File
@@ -17,16 +17,16 @@ type StepSnapshot struct {
snapshotId string
}
func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
func (s *StepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
volumeId := state["volume_id"].(string)
ui.Say("Creating snapshot...")
createSnapResp, err := ec2conn.CreateSnapshot(volumeId, "")
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -37,6 +37,7 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
// Wait for the snapshot to be ready
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"pending"},
StepState: state,
Target: "completed",
@@ -50,34 +51,33 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No snapshots found.")
}
s := resp.Snapshots[0]
return s, s.Status, nil
return nil, resp.Snapshots[0].Status, nil
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("snapshot_id", s.snapshotId)
state["snapshot_id"] = s.snapshotId
return multistep.ActionContinue
}
func (s *StepSnapshot) Cleanup(state multistep.StateBag) {
func (s *StepSnapshot) Cleanup(state map[string]interface{}) {
if s.snapshotId == "" {
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
_, cancelled := state[multistep.StateCancelled]
_, halted := state[multistep.StateHalted]
if cancelled || halted {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Removing snapshot since we cancelled or halted...")
_, err := ec2conn.DeleteSnapshots([]string{s.snapshotId})
if err != nil {
@@ -14,23 +14,23 @@ import (
// source_image *ec2.Image - the source AMI info
type StepSourceAMIInfo struct{}
func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepSourceAMIInfo) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Inspecting the source AMI...")
imageResp, err := ec2conn.Images([]string{config.SourceAmi}, ec2.NewFilter())
if err != nil {
err := fmt.Errorf("Error querying AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Source AMI '%s' was not found!", config.SourceAmi)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -40,13 +40,13 @@ func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
// It must be EBS-backed otherwise the build won't work
if image.RootDeviceType != "ebs" {
err := fmt.Errorf("The root device of the source AMI must be EBS-backed.")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("source_image", image)
state["source_image"] = image
return multistep.ActionContinue
}
func (s *StepSourceAMIInfo) Cleanup(multistep.StateBag) {}
func (s *StepSourceAMIInfo) Cleanup(map[string]interface{}) {}
+1 -8
View File
@@ -18,14 +18,7 @@ type AccessConfig struct {
// Auth returns a valid aws.Auth object for access to AWS services, or
// an error if the authentication couldn't be resolved.
func (c *AccessConfig) Auth() (aws.Auth, error) {
auth, err := aws.GetAuth(c.AccessKey, c.SecretKey)
if err == nil {
// Store the accesskey and secret that we got...
c.AccessKey = auth.AccessKey
c.SecretKey = auth.SecretKey
}
return auth, err
return aws.GetAuth(c.AccessKey, c.SecretKey)
}
// Region returns the aws.Region object for access to AWS services, requesting
+30
View File
@@ -0,0 +1,30 @@
package common
import (
"github.com/mitchellh/goamz/ec2"
"log"
"time"
)
// WaitForAMI waits for the given AMI ID to become ready.
func WaitForAMI(c *ec2.EC2, imageId string) error {
for {
imageResp, err := c.Images([]string{imageId}, ec2.NewFilter())
if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
log.Println("AMI not found, probably state issues on AWS side. Trying again.")
continue
}
return err
}
if imageResp.Images[0].State == "available" {
return nil
}
log.Printf("Image in state %s, sleeping 2s before checking again",
imageResp.Images[0].State)
time.Sleep(2 * time.Second)
}
}
+5 -55
View File
@@ -2,19 +2,16 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/packer/packer"
)
// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions"`
AMITags map[string]string `mapstructure:"tags"`
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
}
func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
@@ -45,7 +42,6 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
"ami_users": c.AMIUsers,
"ami_groups": c.AMIGroups,
"ami_product_codes": c.AMIProductCodes,
"ami_regions": c.AMIRegions,
}
for n, slice := range sliceTemplates {
@@ -63,52 +59,6 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
errs = append(errs, fmt.Errorf("ami_name must be specified"))
}
if len(c.AMIRegions) > 0 {
regionSet := make(map[string]struct{})
regions := make([]string, 0, len(c.AMIRegions))
for _, region := range c.AMIRegions {
// If we already saw the region, then don't look again
if _, ok := regionSet[region]; ok {
continue
}
// Mark that we saw the region
regionSet[region] = struct{}{}
// Verify the region is real
if _, ok := aws.Regions[region]; !ok {
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
continue
}
regions = append(regions, region)
}
c.AMIRegions = regions
}
newTags := make(map[string]string)
for k, v := range c.AMITags {
k, err := t.Process(k, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err := t.Process(v, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
c.AMITags = newTags
if len(errs) > 0 {
return errs
}
+1 -25
View File
@@ -1,7 +1,6 @@
package common
import (
"reflect"
"testing"
)
@@ -11,7 +10,7 @@ func testAMIConfig() *AMIConfig {
}
}
func TestAMIConfigPrepare_name(t *testing.T) {
func TestAMIConfigPrepare_Region(t *testing.T) {
c := testAMIConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
@@ -22,26 +21,3 @@ func TestAMIConfigPrepare_name(t *testing.T) {
t.Fatal("should have error")
}
}
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAMIConfig()
c.AMIRegions = nil
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIRegions = []string{"foo"}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
if err := c.Prepare(nil); err != nil {
t.Fatalf("bad: %s", err)
}
expected := []string{"us-east-1", "us-west-1"}
if !reflect.DeepEqual(c.AMIRegions, expected) {
t.Fatalf("bad: %#v", c.AMIRegions)
}
}
+3 -5
View File
@@ -2,7 +2,6 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/packer/packer"
"log"
@@ -52,10 +51,9 @@ func (a *Artifact) String() string {
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
for region, imageId := range a.Amis {
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
regionconn := ec2.New(a.Conn.Auth, aws.Regions[region])
if _, err := regionconn.DeregisterImage(imageId); err != nil {
for _, imageId := range a.Amis {
log.Printf("Deregistering image ID: %s", imageId)
if _, err := a.Conn.DeregisterImage(imageId); err != nil {
errors = append(errors, err)
}
+11 -7
View File
@@ -1,15 +1,21 @@
package common
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"testing"
)
func TestArtifact_Impl(t *testing.T) {
var _ packer.Artifact = new(Artifact)
assert := asserts.NewTestingAsserts(t, true)
var actual packer.Artifact
assert.Implementor(&Artifact{}, &actual, "should be an Artifact")
}
func TestArtifactId(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `east:foo,west:bar`
amis := make(map[string]string)
@@ -21,12 +27,12 @@ func TestArtifactId(t *testing.T) {
}
result := a.Id()
if result != expected {
t.Fatalf("bad: %s", result)
}
assert.Equal(result, expected, "should match output")
}
func TestArtifactString(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `AMIs were created:
east: foo
@@ -38,7 +44,5 @@ west: bar`
a := &Artifact{Amis: amis}
result := a.String()
if result != expected {
t.Fatalf("bad: %s", result)
}
assert.Equal(result, expected, "should match output")
}
+2 -4
View File
@@ -13,12 +13,11 @@ type BlockDevice struct {
VolumeSize int64 `mapstructure:"volume_size"`
DeleteOnTermination bool `mapstructure:"delete_on_termination"`
IOPS int64 `mapstructure:"iops"`
NoDevice bool `mapstructure:"no_device"`
}
type BlockDevices struct {
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings,squash"`
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings,squash"`
}
func buildBlockDevices(b []BlockDevice) []ec2.BlockDeviceMapping {
@@ -33,7 +32,6 @@ func buildBlockDevices(b []BlockDevice) []ec2.BlockDeviceMapping {
VolumeSize: blockDevice.VolumeSize,
DeleteOnTermination: blockDevice.DeleteOnTermination,
IOPS: blockDevice.IOPS,
NoDevice: blockDevice.NoDevice,
})
}
return blockDevices
+5 -8
View File
@@ -1,12 +1,14 @@
package common
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/goamz/ec2"
"reflect"
"testing"
)
func TestBlockDevice(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
ec2Mapping := []ec2.BlockDeviceMapping{
ec2.BlockDeviceMapping{
DeviceName: "/dev/sdb",
@@ -34,11 +36,6 @@ func TestBlockDevice(t *testing.T) {
LaunchMappings: []BlockDevice{blockDevice},
}
if !reflect.DeepEqual(ec2Mapping, blockDevices.BuildAMIDevices()) {
t.Fatalf("bad: %#v", ec2Mapping)
}
if !reflect.DeepEqual(ec2Mapping, blockDevices.BuildLaunchDevices()) {
t.Fatalf("bad: %#v", ec2Mapping)
}
assert.Equal(ec2Mapping, blockDevices.BuildAMIDevices(), "should match output")
assert.Equal(ec2Mapping, blockDevices.BuildLaunchDevices(), "should match output")
}
@@ -1,10 +1,10 @@
package openstack
package common
import (
"errors"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/rackspace/gophercloud"
"log"
"time"
)
@@ -14,32 +14,34 @@ import (
//
// It returns three results. `result` is any object that will be returned
// as the final object after waiting for state change. This allows you to
// return the final updated object, for example an openstack instance after
// refreshing it.
// return the final updated object, for example an EC2 instance after refreshing
// it.
//
// `state` is the latest state of that object. And `err` is any error that
// may have happened while refreshing the state.
type StateRefreshFunc func() (result interface{}, state string, progress int, err error)
type StateRefreshFunc func() (result interface{}, state string, err error)
// StateChangeConf is the configuration struct used for `WaitForState`.
type StateChangeConf struct {
Conn *ec2.EC2
Pending []string
Refresh StateRefreshFunc
StepState multistep.StateBag
StepState map[string]interface{}
Target string
}
// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an openstacn server.
func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc {
return func() (interface{}, string, int, error) {
resp, err := csp.ServerById(s.Id)
// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an EC2 instance.
func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
if err != nil {
log.Printf("Error on ServerStateRefresh: %s", err)
return nil, "", 0, err
log.Printf("Error on InstanceStateRefresh: %s", err)
return nil, "", err
}
return resp, resp.Status, resp.Progress, nil
i = &resp.Reservations[0].Instances[0]
return i, i.State.Name, nil
}
}
@@ -49,9 +51,8 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
for {
var currentProgress int
var currentState string
i, currentState, currentProgress, err = conf.Refresh()
i, currentState, err = conf.Refresh()
if err != nil {
return
}
@@ -61,7 +62,7 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
}
if conf.StepState != nil {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
if _, ok := conf.StepState[multistep.StateCancelled]; ok {
return nil, errors.New("interrupted")
}
}
@@ -79,7 +80,6 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
return
}
log.Printf("Waiting for state to become: %s currently %s (%d%%)", conf.Target, currentState, currentProgress)
time.Sleep(2 * time.Second)
}
+19 -74
View File
@@ -11,22 +11,17 @@ import (
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
AvailabilityZone string `mapstructure:"availability_zone"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
RunTags map[string]string `mapstructure:"run_tags"`
SourceAmi string `mapstructure:"source_ami"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
VpcId string `mapstructure:"vpc_id"`
SourceAmi string `mapstructure:"source_ami"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SubnetId string `mapstructure:"subnet_id"`
VpcId string `mapstructure:"vpc_id"`
// Unexported fields that are calculated from others
sshTimeout time.Duration
@@ -50,10 +45,6 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
c.RawSSHTimeout = "1m"
}
if c.TemporaryKeyPairName == "" {
c.TemporaryKeyPairName = "packer {{uuid}}"
}
// Validation
var err error
errs := make([]error, 0)
@@ -77,25 +68,15 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
}
}
if c.SecurityGroupId != "" {
if len(c.SecurityGroupIds) > 0 {
errs = append(errs, fmt.Errorf("Only one of security_group_id or security_group_ids can be specified."))
} else {
c.SecurityGroupIds = []string{c.SecurityGroupId}
c.SecurityGroupId = ""
}
}
templates := map[string]*string{
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"ssh_timeout": &c.RawSSHTimeout,
"ssh_username": &c.SSHUsername,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
"temporary_key_pair_name": &c.TemporaryKeyPairName,
"vpc_id": &c.VpcId,
"availability_zone": &c.AvailabilityZone,
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"ssh_timeout": &c.RawSSHTimeout,
"security_group_id": &c.SecurityGroupId,
"ssh_username": &c.SSHUsername,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
"vpc_id": &c.VpcId,
}
for n, ptr := range templates {
@@ -107,42 +88,6 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
}
}
sliceTemplates := map[string][]string{
"security_group_ids": c.SecurityGroupIds,
}
for n, slice := range sliceTemplates {
for i, elem := range slice {
var err error
slice[i], err = t.Process(elem, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
}
}
}
newTags := make(map[string]string)
for k, v := range c.RunTags {
k, err := t.Process(k, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err := t.Process(v, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
c.RunTags = newTags
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
-12
View File
@@ -126,15 +126,3 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.TemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.TemporaryKeyPairName == "" {
t.Fatal("keypair empty")
}
}
+8 -13
View File
@@ -5,26 +5,21 @@ import (
"errors"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"time"
)
// SSHAddress returns a function that can be given to the SSH communicator
// for determining the SSH address based on the instance DNS name.
func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
func SSHAddress(e *ec2.EC2, port int) func(map[string]interface{}) (string, error) {
return func(state map[string]interface{}) (string, error) {
for j := 0; j < 2; j++ {
var host string
i := state.Get("instance").(*ec2.Instance)
i := state["instance"].(*ec2.Instance)
if i.DNSName != "" {
host = i.DNSName
} else if i.VpcId != "" {
if i.PublicIpAddress != "" {
host = i.PublicIpAddress
} else {
host = i.PrivateIpAddress
}
host = i.PrivateIpAddress
}
if host != "" {
@@ -40,7 +35,7 @@ func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
return "", fmt.Errorf("instance not found: %s", i.InstanceId)
}
state.Put("instance", &r.Reservations[0].Instances[0])
state["instance"] = &r.Reservations[0].Instances[0]
time.Sleep(1 * time.Second)
}
@@ -51,9 +46,9 @@ func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
func SSHConfig(username string) func(map[string]interface{}) (*gossh.ClientConfig, error) {
return func(state map[string]interface{}) (*gossh.ClientConfig, error) {
privateKey := state["privateKey"].(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
-137
View File
@@ -1,137 +0,0 @@
package common
import (
"errors"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"log"
"time"
)
// StateRefreshFunc is a function type used for StateChangeConf that is
// responsible for refreshing the item being watched for a state change.
//
// It returns three results. `result` is any object that will be returned
// as the final object after waiting for state change. This allows you to
// return the final updated object, for example an EC2 instance after refreshing
// it.
//
// `state` is the latest state of that object. And `err` is any error that
// may have happened while refreshing the state.
type StateRefreshFunc func() (result interface{}, state string, err error)
// StateChangeConf is the configuration struct used for `WaitForState`.
type StateChangeConf struct {
Pending []string
Refresh StateRefreshFunc
StepState multistep.StateBag
Target string
}
// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an AMI for state changes.
func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.Images([]string{imageId}, ec2.NewFilter())
if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else {
log.Printf("Error on AMIStateRefresh: %s", err)
return nil, "", err
}
}
if resp == nil || len(resp.Images) == 0 {
// Sometimes AWS has consistency issues and doesn't see the
// AMI. Return an empty state.
return nil, "", nil
}
i := resp.Images[0]
return i, i.State, nil
}
}
// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an EC2 instance.
func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else {
log.Printf("Error on InstanceStateRefresh: %s", err)
return nil, "", err
}
}
if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
// Sometimes AWS just has consistency issues and doesn't see
// our instance yet. Return an empty state.
return nil, "", nil
}
i = &resp.Reservations[0].Instances[0]
return i, i.State.Name, nil
}
}
// WaitForState watches an object and waits for it to achieve a certain
// state.
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
notfoundTick := 0
for {
var currentState string
i, currentState, err = conf.Refresh()
if err != nil {
return
}
if i == nil {
// If we didn't find the resource, check if we have been
// not finding it for awhile, and if so, report an error.
notfoundTick += 1
if notfoundTick > 20 {
return nil, errors.New("couldn't find resource")
}
} else {
// Reset the counter for when a resource isn't found
notfoundTick = 0
if currentState == conf.Target {
return
}
if conf.StepState != nil {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted")
}
}
found := false
for _, allowed := range conf.Pending {
if currentState == allowed {
found = true
break
}
}
if !found {
fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
return
}
}
time.Sleep(2 * time.Second)
}
return
}
@@ -1,98 +0,0 @@
package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"sync"
)
type StepAMIRegionCopy struct {
Regions []string
}
func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
ami := amis[ec2conn.Region.Name]
if len(s.Regions) == 0 {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Copying AMI (%s) to other regions...", ami))
var lock sync.Mutex
var wg sync.WaitGroup
errs := new(packer.MultiError)
for _, region := range s.Regions {
wg.Add(1)
ui.Message(fmt.Sprintf("Copying to: %s", region))
go func(region string) {
defer wg.Done()
id, err := amiRegionCopy(state, ec2conn.Auth, ami,
aws.Regions[region], ec2conn.Region)
lock.Lock()
defer lock.Unlock()
amis[region] = id
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}(region)
}
// TODO(mitchellh): Wait but also allow for cancels to go through...
ui.Message("Waiting for all copies to complete...")
wg.Wait()
// If there were errors, show them
if len(errs.Errors) > 0 {
state.Put("error", errs)
ui.Error(errs.Error())
return multistep.ActionHalt
}
state.Put("amis", amis)
return multistep.ActionContinue
}
func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
// No cleanup...
}
// amiRegionCopy does a copy for the given AMI to the target region and
// returns the resulting ID or error.
func amiRegionCopy(state multistep.StateBag, auth aws.Auth, imageId string,
target aws.Region, source aws.Region) (string, error) {
// Connect to the region where the AMI will be copied to
regionconn := ec2.New(auth, target)
resp, err := regionconn.CopyImage(&ec2.CopyImage{
SourceRegion: source.Name,
SourceImageId: imageId,
})
if err != nil {
return "", fmt.Errorf("Error Copying AMI (%s) to region (%s): %s",
imageId, target, err)
}
stateChange := StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: AMIStateRefreshFunc(regionconn, resp.ImageId),
StepState: state,
}
if _, err := WaitForState(&stateChange); err != nil {
return "", fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
resp.ImageId, target, err)
}
return resp.ImageId, nil
}
+18 -21
View File
@@ -2,7 +2,6 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
@@ -12,35 +11,33 @@ type StepCreateTags struct {
Tags map[string]string
}
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
func (s *StepCreateTags) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
amis := state["amis"].(map[string]string)
ami := amis[ec2conn.Region.Name]
if len(s.Tags) > 0 {
for region, ami := range amis {
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
var ec2Tags []ec2.Tag
for key, value := range s.Tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
}
var ec2Tags []ec2.Tag
for key, value := range s.Tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
}
regionconn := ec2.New(ec2conn.Auth, aws.Regions[region])
_, err := regionconn.CreateTags([]string{ami}, ec2Tags)
if err != nil {
err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
_, err := ec2conn.CreateTags([]string{ami}, ec2Tags)
if err != nil {
err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
func (s *StepCreateTags) Cleanup(state map[string]interface{}) {
// No cleanup...
}
+17 -44
View File
@@ -1,77 +1,50 @@
package common
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"runtime"
"log"
)
type StepKeyPair struct {
Debug bool
DebugKeyPath string
KeyPairName string
keyName string
}
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.KeyPairName))
keyResp, err := ec2conn.CreateKeyPair(s.KeyPairName)
ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
log.Printf("temporary keypair name: %s", keyName)
keyResp, err := ec2conn.CreateKeyPair(keyName)
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
state["error"] = fmt.Errorf("Error creating temporary keypair: %s", err)
return multistep.ActionHalt
}
// Set the keyname so we know to delete it later
s.keyName = s.KeyPairName
s.keyName = keyName
// Set some state data for use in future steps
state.Put("keyPair", s.keyName)
state.Put("privateKey", keyResp.KeyMaterial)
// If we're in debug mode, output the private key to the working
// directory.
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
}
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(keyResp.KeyMaterial)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
state["keyPair"] = keyName
state["privateKey"] = keyResp.KeyMaterial
return multistep.ActionContinue
}
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
func (s *StepKeyPair) Cleanup(state map[string]interface{}) {
// If no key name is set, then we never created it, so just return
if s.keyName == "" {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting temporary keypair...")
_, err := ec2conn.DeleteKeyPair(s.keyName)
@@ -2,7 +2,6 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
@@ -15,10 +14,11 @@ type StepModifyAMIAttributes struct {
Description string
}
func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
func (s *StepModifyAMIAttributes) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
amis := state["amis"].(map[string]string)
ami := amis[ec2conn.Region.Name]
// Determine if there is any work to do.
valid := false
@@ -59,24 +59,21 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
}
}
for region, ami := range amis {
ui.Say(fmt.Sprintf("Modifying attributes on AMI (%s)...", ami))
regionconn := ec2.New(ec2conn.Auth, aws.Regions[region])
for name, opts := range options {
ui.Message(fmt.Sprintf("Modifying: %s", name))
_, err := regionconn.ModifyImageAttribute(ami, opts)
if err != nil {
err := fmt.Errorf("Error modify AMI attributes: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Modifying AMI attributes...")
for name, opts := range options {
ui.Message(fmt.Sprintf("Modifying: %s", name))
_, err := ec2conn.ModifyImageAttribute(ami, opts)
if err != nil {
err := fmt.Errorf("Error modify AMI attributes: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepModifyAMIAttributes) Cleanup(state multistep.StateBag) {
func (s *StepModifyAMIAttributes) Cleanup(state map[string]interface{}) {
// No cleanup...
}
@@ -6,107 +6,86 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
)
type StepRunSourceInstance struct {
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
Debug bool
ExpectedRootDevice string
InstanceType string
IamInstanceProfile string
SourceAMI string
SubnetId string
Tags map[string]string
UserData string
UserDataFile string
ExpectedRootDevice string
InstanceType string
UserData string
UserDataFile string
SourceAMI string
IamInstanceProfile string
SubnetId string
BlockDevices BlockDevices
instance *ec2.Instance
}
func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
keyName := state.Get("keyPair").(string)
securityGroupIds := state.Get("securityGroupIds").([]string)
ui := state.Get("ui").(packer.Ui)
func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
keyName := state["keyPair"].(string)
securityGroupId := state["securityGroupId"].(string)
ui := state["ui"].(packer.Ui)
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
state["error"] = fmt.Errorf("Problem reading user data file: %s", err)
return multistep.ActionHalt
}
userData = string(contents)
}
securityGroups := make([]ec2.SecurityGroup, len(securityGroupIds))
for n, securityGroupId := range securityGroupIds {
securityGroups[n] = ec2.SecurityGroup{Id: securityGroupId}
}
runOpts := &ec2.RunInstances{
KeyName: keyName,
ImageId: s.SourceAMI,
InstanceType: s.InstanceType,
UserData: []byte(userData),
MinCount: 0,
MaxCount: 0,
SecurityGroups: securityGroups,
IamInstanceProfile: s.IamInstanceProfile,
SubnetId: s.SubnetId,
AssociatePublicIpAddress: s.AssociatePublicIpAddress,
BlockDevices: s.BlockDevices.BuildLaunchDevices(),
AvailZone: s.AvailabilityZone,
KeyName: keyName,
ImageId: s.SourceAMI,
InstanceType: s.InstanceType,
UserData: []byte(userData),
MinCount: 0,
MaxCount: 0,
SecurityGroups: []ec2.SecurityGroup{ec2.SecurityGroup{Id: securityGroupId}},
IamInstanceProfile: s.IamInstanceProfile,
SubnetId: s.SubnetId,
BlockDevices: s.BlockDevices.BuildLaunchDevices(),
}
ui.Say("Launching a source AWS instance...")
imageResp, err := ec2conn.Images([]string{s.SourceAMI}, ec2.NewFilter())
if err != nil {
state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err))
state["error"] = fmt.Errorf("There was a problem with the source AMI: %s", err)
return multistep.ActionHalt
}
if len(imageResp.Images) != 1 {
state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI))
state["error"] = fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI)
return multistep.ActionHalt
}
if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice {
state.Put("error", fmt.Errorf(
state["error"] = fmt.Errorf(
"The provided source AMI has an invalid root device type.\n"+
"Expected '%s', got '%s'.",
s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType))
s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType)
return multistep.ActionHalt
}
runResp, err := ec2conn.RunInstances(runOpts)
if err != nil {
err := fmt.Errorf("Error launching source instance: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
s.instance = &runResp.Instances[0]
ui.Message(fmt.Sprintf("Instance ID: %s", s.instance.InstanceId))
ec2Tags := make([]ec2.Tag, 1, len(s.Tags)+1)
ec2Tags[0] = ec2.Tag{"Name", "Packer Builder"}
for k, v := range s.Tags {
ec2Tags = append(ec2Tags, ec2.Tag{k, v})
}
_, err = ec2conn.CreateTags([]string{s.instance.InstanceId}, ec2Tags)
if err != nil {
ui.Message(
fmt.Sprintf("Failed to tag a Name on the builder instance: %s", err))
}
log.Printf("instance id: %s", s.instance.InstanceId)
ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", s.instance.InstanceId))
stateChange := StateChangeConf{
Conn: ec2conn,
Pending: []string{"pending"},
Target: "running",
Refresh: InstanceStateRefreshFunc(ec2conn, s.instance),
@@ -115,39 +94,24 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
latestInstance, err := WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
s.instance = latestInstance.(*ec2.Instance)
if s.Debug {
if s.instance.DNSName != "" {
ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName))
}
if s.instance.PublicIpAddress != "" {
ui.Message(fmt.Sprintf("Public IP: %s", s.instance.PublicIpAddress))
}
if s.instance.PrivateIpAddress != "" {
ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress))
}
}
state.Put("instance", s.instance)
state["instance"] = s.instance
return multistep.ActionContinue
}
func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
func (s *StepRunSourceInstance) Cleanup(state map[string]interface{}) {
if s.instance == nil {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Terminating the source AWS instance...")
if _, err := ec2conn.TerminateInstances([]string{s.instance.InstanceId}); err != nil {
@@ -156,6 +120,7 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
}
stateChange := StateChangeConf{
Conn: ec2conn,
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
Refresh: InstanceStateRefreshFunc(ec2conn, s.instance),
Target: "terminated",
+18 -30
View File
@@ -1,30 +1,31 @@
package common
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type StepSecurityGroup struct {
SecurityGroupIds []string
SSHPort int
VpcId string
SecurityGroupId string
SSHPort int
VpcId string
createdGroupId string
}
func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepSecurityGroup) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
if len(s.SecurityGroupIds) > 0 {
log.Printf("Using specified security groups: %v", s.SecurityGroupIds)
state.Put("securityGroupIds", s.SecurityGroupIds)
if s.SecurityGroupId != "" {
log.Printf("Using specified security group: %s", s.SecurityGroupId)
state["securityGroupId"] = s.SecurityGroupId
return multistep.ActionContinue
}
@@ -34,7 +35,7 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
// Create the group
ui.Say("Creating temporary security group for this instance...")
groupName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID())
groupName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
log.Printf("Temporary group name: %s", groupName)
group := ec2.SecurityGroup{
Name: groupName,
@@ -60,40 +61,27 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
},
}
// We loop and retry this a few times because sometimes the security
// group isn't available immediately because AWS resources are eventaully
// consistent.
ui.Say("Authorizing SSH access on the temporary security group...")
for i := 0; i < 5; i++ {
_, err = ec2conn.AuthorizeSecurityGroup(groupResp.SecurityGroup, perms)
if err == nil {
break
}
log.Printf("Error authorizing. Will sleep and retry. %s", err)
time.Sleep((time.Duration(i) * time.Second) + 1)
}
if err != nil {
if _, err := ec2conn.AuthorizeSecurityGroup(groupResp.SecurityGroup, perms); err != nil {
err := fmt.Errorf("Error creating temporary security group: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set some state data for use in future steps
state.Put("securityGroupIds", []string{s.createdGroupId})
state["securityGroupId"] = s.createdGroupId
return multistep.ActionContinue
}
func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
func (s *StepSecurityGroup) Cleanup(state map[string]interface{}) {
if s.createdGroupId == "" {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting temporary security group...")
-38
View File
@@ -1,38 +0,0 @@
package common
import (
"bytes"
"text/template"
)
func isalphanumeric(b byte) bool {
if '0' <= b && b <= '9' {
return true
}
if 'a' <= b && b <= 'z' {
return true
}
if 'A' <= b && b <= 'Z' {
return true
}
return false
}
// Clean up AMI name by replacing invalid characters with "-"
func templateCleanAMIName(s string) string {
allowed := []byte{'(', ')', ',', '/', '-', '_'}
b := []byte(s)
newb := make([]byte, len(b))
for i, c := range b {
if isalphanumeric(c) || bytes.IndexByte(allowed, c) != -1 {
newb[i] = c
} else {
newb[i] = '-'
}
}
return string(newb[:])
}
var TemplateFuncs = template.FuncMap{
"clean_ami_name": templateCleanAMIName,
}
@@ -1,16 +0,0 @@
package common
import (
"testing"
)
func TestAMITemplatePrepare_clean(t *testing.T) {
origName := "AMZamz09(),/-_:&^$%"
expected := "AMZamz09(),/-_-----"
name := templateCleanAMIName(origName)
if name != expected {
t.Fatalf("template names do not match: expected %s got %s\n", expected, name)
}
}
+53 -42
View File
@@ -25,6 +25,9 @@ type config struct {
awscommon.BlockDevices `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
// Tags for the AMI
Tags map[string]string
tpl *packer.ConfigTemplate
}
@@ -33,18 +36,17 @@ type Builder struct {
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
func (b *Builder) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return nil, err
return err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
b.config.tpl.Funcs(awscommon.TemplateFuncs)
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
@@ -52,12 +54,34 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
// Accumulate any errors
newTags := make(map[string]string)
for k, v := range b.config.Tags {
k, err = b.config.tpl.Process(k, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err = b.config.tpl.Process(v, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
return nil, nil
b.config.Tags = newTags
if errs != nil && len(errs.Errors) > 0 {
return errs
}
log.Printf("Config: %+v", b.config)
return nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
@@ -74,37 +98,29 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["config"] = b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
KeyPairName: b.config.TemporaryKeyPairName,
},
&awscommon.StepKeyPair{},
&awscommon.StepSecurityGroup{
SecurityGroupIds: b.config.SecurityGroupIds,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
BlockDevices: b.config.BlockDevices,
},
&common.StepConnectSSH{
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),
@@ -114,17 +130,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&common.StepProvision{},
&stepStopInstance{},
&stepCreateAMI{},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -140,18 +151,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state.GetOk("amis"); !ok {
if _, ok := state["amis"]; !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state.Get("amis").(map[string]string),
Amis: state["amis"].(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+5 -20
View File
@@ -31,10 +31,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
"access_key": []string{},
}
warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(c)
if err == nil {
t.Fatalf("prepare should fail")
}
@@ -46,10 +43,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test good
config["ami_name"] = "foo"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -57,10 +51,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -68,10 +59,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test bad
delete(config, "ami_name")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -83,10 +71,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
// Add a random key
config["i_should_not_be_valid"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
+11 -18
View File
@@ -10,11 +10,11 @@ import (
type stepCreateAMI struct{}
func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(config)
ec2conn := state["ec2"].(*ec2.EC2)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
// Create the image
ui.Say(fmt.Sprintf("Creating the AMI: %s", config.AMIName))
@@ -27,29 +27,22 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
createResp, err := ec2conn.CreateImage(createOpts)
if err != nil {
err := fmt.Errorf("Error creating AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the AMI ID in the state
ui.Message(fmt.Sprintf("AMI: %s", createResp.ImageId))
ui.Say(fmt.Sprintf("AMI: %s", createResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = createResp.ImageId
state.Put("amis", amis)
state["amis"] = amis
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, createResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
if err := awscommon.WaitForAMI(ec2conn, createResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -57,6 +50,6 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepCreateAMI) Cleanup(multistep.StateBag) {
func (s *stepCreateAMI) Cleanup(map[string]interface{}) {
// No cleanup...
}
+8 -7
View File
@@ -10,17 +10,17 @@ import (
type stepStopInstance struct{}
func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
func (s *stepStopInstance) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
// Stop the instance so we can create an AMI from it
ui.Say("Stopping the source instance...")
_, err := ec2conn.StopInstances(instance.InstanceId)
if err != nil {
err := fmt.Errorf("Error stopping instance: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -28,6 +28,7 @@ func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
// Wait for the instance to actual stop
ui.Say("Waiting for the instance to stop...")
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"running", "stopping"},
Target: "stopped",
Refresh: awscommon.InstanceStateRefreshFunc(ec2conn, instance),
@@ -36,7 +37,7 @@ func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -44,6 +45,6 @@ func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepStopInstance) Cleanup(multistep.StateBag) {
func (s *stepStopInstance) Cleanup(map[string]interface{}) {
// No cleanup...
}
+29 -43
View File
@@ -33,6 +33,7 @@ type Config struct {
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
BundleVolCommand string `mapstructure:"bundle_vol_command"`
S3Bucket string `mapstructure:"s3_bucket"`
Tags map[string]string
X509CertPath string `mapstructure:"x509_cert_path"`
X509KeyPath string `mapstructure:"x509_key_path"`
X509UploadPath string `mapstructure:"x509_upload_path"`
@@ -45,18 +46,17 @@ type Builder struct {
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
func (b *Builder) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return nil, err
return err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
b.config.tpl.Funcs(awscommon.TemplateFuncs)
if b.config.BundleDestination == "" {
b.config.BundleDestination = "/tmp"
@@ -74,7 +74,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"-s {{.SecretKey}} " +
"-d {{.BundleDirectory}} " +
"--batch " +
"--location {{.Region}} " +
"--retry"
}
@@ -84,7 +83,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"-u {{.AccountId}} " +
"-c {{.CertPath}} " +
"-r {{.Architecture}} " +
"-e {{.PrivatePath}}/* " +
"-e {{.PrivatePath}} " +
"-d {{.Destination}} " +
"-p {{.Prefix}} " +
"--batch"
@@ -157,11 +156,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
return errs
}
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
return nil, nil
log.Printf("Config: %+v", b.config)
return nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
@@ -178,37 +177,29 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["config"] = &b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
KeyPairName: b.config.TemporaryKeyPairName,
},
&awscommon.StepKeyPair{},
&awscommon.StepSecurityGroup{
SecurityGroupIds: b.config.SecurityGroupIds,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
BlockDevices: b.config.BlockDevices,
},
&common.StepConnectSSH{
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),
@@ -220,18 +211,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepBundleVolume{},
&StepUploadBundle{},
&StepRegisterAMI{},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -247,18 +233,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state.GetOk("amis"); !ok {
if _, ok := state["amis"]; !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state.Get("amis").(map[string]string),
Amis: state["amis"].(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+18 -72
View File
@@ -40,28 +40,19 @@ func TestBuilderPrepare_AccountId(t *testing.T) {
config := testConfig()
config["account_id"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
config["account_id"] = "foo"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Errorf("err: %s", err)
}
config["account_id"] = "0123-0456-7890"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
@@ -77,10 +68,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test good
config["ami_name"] = "foo"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -88,10 +76,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test bad
config["ami_name"] = "foo {{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -99,10 +84,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) {
// Test bad
delete(config, "ami_name")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -113,10 +95,7 @@ func TestBuilderPrepare_BundleDestination(t *testing.T) {
config := testConfig()
config["bundle_destination"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
@@ -131,10 +110,7 @@ func TestBuilderPrepare_BundlePrefix(t *testing.T) {
config := testConfig()
config["bundle_prefix"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
@@ -150,10 +126,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
// Add a random key
config["i_should_not_be_valid"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -164,19 +137,13 @@ func TestBuilderPrepare_S3Bucket(t *testing.T) {
config := testConfig()
config["s3_bucket"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
config["s3_bucket"] = "foo"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Errorf("err: %s", err)
}
@@ -187,19 +154,13 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) {
config := testConfig()
config["x509_cert_path"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
config["x509_cert_path"] = "i/am/a/file/that/doesnt/exist"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Error("should have error")
}
@@ -211,10 +172,7 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) {
defer os.Remove(tf.Name())
config["x509_cert_path"] = tf.Name()
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -225,19 +183,13 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) {
config := testConfig()
config["x509_key_path"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
config["x509_key_path"] = "i/am/a/file/that/doesnt/exist"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Error("should have error")
}
@@ -249,10 +201,7 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) {
defer os.Remove(tf.Name())
config["x509_key_path"] = tf.Name()
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -263,10 +212,7 @@ func TestBuilderPrepare_X509UploadPath(t *testing.T) {
config := testConfig()
config["x509_upload_path"] = ""
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
+38 -19
View File
@@ -19,13 +19,32 @@ type bundleCmdData struct {
type StepBundleVolume struct{}
func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
x509RemoteCertPath := state.Get("x509RemoteCertPath").(string)
x509RemoteKeyPath := state.Get("x509RemoteKeyPath").(string)
func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
x509RemoteCertPath := state["x509RemoteCertPath"].(string)
x509RemoteKeyPath := state["x509RemoteKeyPath"].(string)
// Verify the AMI tools are available
ui.Say("Checking for EC2 AMI tools...")
cmd := &packer.RemoteCmd{Command: "ec2-ami-tools-version"}
if err := comm.Start(cmd); err != nil {
state["error"] = fmt.Errorf("Error checking for AMI tools: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
cmd.Wait()
if cmd.ExitStatus != 0 {
state["error"] = fmt.Errorf(
"The EC2 AMI tools could not be detected. These must be manually\n" +
"via a provisioner or some other means and are required for Packer\n" +
"to create an instance-store AMI.")
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
// Bundle the volume
var err error
@@ -40,35 +59,35 @@ func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction {
})
if err != nil {
err := fmt.Errorf("Error processing bundle volume command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Bundling the volume...")
cmd := new(packer.RemoteCmd)
cmd = new(packer.RemoteCmd)
cmd.Command = config.BundleVolCommand
if err := cmd.StartWithUi(comm, ui); err != nil {
state.Put("error", fmt.Errorf("Error bundling volume: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error bundling volume: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
state.Put("error", fmt.Errorf(
"Volume bundling failed. Please see the output above for more\n"+
"details on what went wrong."))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf(
"Volume bundling failed. Please see the output above for more\n" +
"details on what went wrong.")
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
// Store the manifest path
manifestName := config.BundlePrefix + ".manifest.xml"
state.Put("manifest_name", manifestName)
state.Put("manifest_path", fmt.Sprintf(
"%s/%s", config.BundleDestination, manifestName))
state["manifest_name"] = manifestName
state["manifest_path"] = fmt.Sprintf(
"%s/%s", config.BundleDestination, manifestName)
return multistep.ActionContinue
}
func (s *StepBundleVolume) Cleanup(multistep.StateBag) {}
func (s *StepBundleVolume) Cleanup(map[string]interface{}) {}
+11 -18
View File
@@ -10,11 +10,11 @@ import (
type StepRegisterAMI struct{}
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
manifestPath := state.Get("remote_manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
manifestPath := state["remote_manifest_path"].(string)
ui := state["ui"].(packer.Ui)
ui.Say("Registering the AMI...")
registerOpts := &ec2.RegisterImage{
@@ -25,8 +25,8 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
state.Put("error", fmt.Errorf("Error registering AMI: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
@@ -34,20 +34,13 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = registerResp.ImageId
state.Put("amis", amis)
state["amis"] = amis
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -55,4 +48,4 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepRegisterAMI) Cleanup(multistep.StateBag) {}
func (s *StepRegisterAMI) Cleanup(map[string]interface{}) {}
+17 -32
View File
@@ -11,44 +11,29 @@ type uploadCmdData struct {
BucketName string
BundleDirectory string
ManifestPath string
Region string
SecretKey string
}
type StepUploadBundle struct{}
func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
manifestName := state.Get("manifest_name").(string)
manifestPath := state.Get("manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
region, err := config.Region()
if err != nil {
err := fmt.Errorf("Error retrieving region: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// See GH-729 and http://goo.gl/rNZiCs
regionName := region.Name
if regionName == "us-east-1" {
regionName = "US"
}
func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
manifestName := state["manifest_name"].(string)
manifestPath := state["manifest_path"].(string)
ui := state["ui"].(packer.Ui)
var err error
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
AccessKey: config.AccessKey,
BucketName: config.S3Bucket,
BundleDirectory: config.BundleDestination,
ManifestPath: manifestPath,
Region: regionName,
SecretKey: config.SecretKey,
})
if err != nil {
err := fmt.Errorf("Error processing bundle upload command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -56,23 +41,23 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Uploading the bundle...")
cmd := &packer.RemoteCmd{Command: config.BundleUploadCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
state.Put("error", fmt.Errorf("Error uploading volume: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error uploading volume: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
state.Put("error", fmt.Errorf(
"Bundle upload failed. Please see the output above for more\n"+
"details on what went wrong."))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf(
"Bundle upload failed. Please see the output above for more\n" +
"details on what went wrong.")
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
state.Put("remote_manifest_path", fmt.Sprintf(
"%s/%s", config.S3Bucket, manifestName))
state["remote_manifest_path"] = fmt.Sprintf(
"%s/%s", config.S3Bucket, manifestName)
return multistep.ActionContinue
}
func (s *StepUploadBundle) Cleanup(state multistep.StateBag) {}
func (s *StepUploadBundle) Cleanup(state map[string]interface{}) {}
@@ -9,34 +9,34 @@ import (
type StepUploadX509Cert struct{}
func (s *StepUploadX509Cert) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
func (s *StepUploadX509Cert) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
x509RemoteCertPath := config.X509UploadPath + "/cert.pem"
x509RemoteKeyPath := config.X509UploadPath + "/key.pem"
ui.Say("Uploading X509 Certificate...")
if err := s.uploadSingle(comm, x509RemoteCertPath, config.X509CertPath); err != nil {
state.Put("error", fmt.Errorf("Error uploading X509 cert: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error uploading X509 cert: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
if err := s.uploadSingle(comm, x509RemoteKeyPath, config.X509KeyPath); err != nil {
state.Put("error", fmt.Errorf("Error uploading X509 cert: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error uploading X509 cert: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
state.Put("x509RemoteCertPath", x509RemoteCertPath)
state.Put("x509RemoteKeyPath", x509RemoteKeyPath)
state["x509RemoteCertPath"] = x509RemoteCertPath
state["x509RemoteKeyPath"] = x509RemoteKeyPath
return multistep.ActionContinue
}
func (s *StepUploadX509Cert) Cleanup(multistep.StateBag) {}
func (s *StepUploadX509Cert) Cleanup(map[string]interface{}) {}
func (s *StepUploadX509Cert) uploadSingle(comm packer.Communicator, dst, src string) error {
f, err := os.Open(src)
+30 -103
View File
@@ -14,7 +14,6 @@ import (
"net/http"
"net/url"
"strings"
"time"
)
const DIGITALOCEAN_API_URL = "https://api.digitalocean.com"
@@ -29,15 +28,6 @@ type ImagesResp struct {
Images []Image
}
type Region struct {
Id uint
Name string
}
type RegionsResp struct {
Regions []Region
}
type DigitalOceanClient struct {
// The http client for communicating
client *http.Client
@@ -53,11 +43,7 @@ type DigitalOceanClient struct {
// Creates a new client for communicating with DO
func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient {
c := &DigitalOceanClient{
client: &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
},
client: http.DefaultClient,
BaseURL: DIGITALOCEAN_API_URL,
ClientID: client,
APIKey: key,
@@ -90,14 +76,13 @@ func (d DigitalOceanClient) DestroyKey(id uint) error {
}
// Creates a droplet and returns it's id
func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, region uint, keyId uint, privateNetworking bool) (uint, error) {
func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, region uint, keyId uint) (uint, error) {
params := url.Values{}
params.Set("name", name)
params.Set("size_id", fmt.Sprintf("%v", size))
params.Set("image_id", fmt.Sprintf("%v", image))
params.Set("region_id", fmt.Sprintf("%v", region))
params.Set("ssh_key_ids", fmt.Sprintf("%v", keyId))
params.Set("private_networking", fmt.Sprintf("%v", privateNetworking))
body, err := NewRequest(d, "droplets/new", params)
if err != nil {
@@ -126,15 +111,6 @@ func (d DigitalOceanClient) PowerOffDroplet(id uint) error {
return err
}
// Shutsdown a droplet. This is a "soft" shutdown.
func (d DigitalOceanClient) ShutdownDroplet(id uint) error {
path := fmt.Sprintf("droplets/%v/shutdown", id)
_, err := NewRequest(d, path, url.Values{})
return err
}
// Creates a snaphot of a droplet by it's ID
func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error {
path := fmt.Sprintf("droplets/%v/snapshot", id)
@@ -202,95 +178,46 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin
url := fmt.Sprintf("%s/%s?%s", DIGITALOCEAN_API_URL, path, params.Encode())
var decodedResponse map[string]interface{}
// Do some basic scrubbing so sensitive information doesn't appear in logs
scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1)
scrubbedUrl = strings.Replace(scrubbedUrl, d.APIKey, "API_KEY", -1)
log.Printf("sending new request to digitalocean: %s", scrubbedUrl)
var lastErr error
for attempts := 1; attempts < 10; attempts++ {
resp, err := client.Get(url)
if err != nil {
return nil, err
}
resp, err := client.Get(url)
if err != nil {
return decodedResponse, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
log.Printf("response from digitalocean: %s", body)
resp.Body.Close()
if err != nil {
return decodedResponse, err
}
var decodedResponse map[string]interface{}
err = json.Unmarshal(body, &decodedResponse)
if err != nil {
err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
resp.StatusCode, body))
return decodedResponse, err
}
log.Printf("response from digitalocean: %s", body)
// Check for errors sent by digitalocean
status := decodedResponse["status"].(string)
if status == "OK" {
return decodedResponse, nil
}
err = json.Unmarshal(body, &decodedResponse)
// Check for bad JSON
if err != nil {
err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
resp.StatusCode, body))
return decodedResponse, err
}
// Check for errors sent by digitalocean
status := decodedResponse["status"]
if status != "OK" {
// Get the actual error message if there is one
if status == "ERROR" {
statusRaw, ok := decodedResponse["error_message"]
if ok {
status = statusRaw.(string)
} else {
status = fmt.Sprintf(
"Unknown error. Full response body: %s", body)
}
status = decodedResponse["error_message"]
}
lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
resp.StatusCode, status))
log.Println(lastErr)
if strings.Contains(status, "a pending event") {
// Retry, DigitalOcean sends these dumb "pending event"
// errors all the time.
time.Sleep(5 * time.Second)
continue
}
// Some other kind of error. Just return.
return decodedResponse, lastErr
err = errors.New(fmt.Sprintf("Received bad response (HTTP %v) from DigitalOcean: %s", resp.StatusCode, status))
return decodedResponse, err
}
return nil, lastErr
}
// Returns all available regions.
func (d DigitalOceanClient) Regions() ([]Region, error) {
resp, err := NewRequest(d, "regions", url.Values{})
if err != nil {
return nil, err
}
var result RegionsResp
if err := mapstructure.Decode(resp, &result); err != nil {
return nil, err
}
return result.Regions, nil
}
func (d DigitalOceanClient) RegionName(region_id uint) (string, error) {
regions, err := d.Regions()
if err != nil {
return "", err
}
for _, region := range regions {
if region.Id == region_id {
return region.Name, nil
}
}
err = errors.New(fmt.Sprintf("Unknown region id %v", region_id))
return "", err
return decodedResponse, nil
}
+3 -10
View File
@@ -12,12 +12,6 @@ type Artifact struct {
// The ID of the image
snapshotId uint
// The name of the region
regionName string
// The ID of the region
regionId uint
// The client for making API calls
client *DigitalOceanClient
}
@@ -32,15 +26,14 @@ func (*Artifact) Files() []string {
}
func (a *Artifact) Id() string {
// mimicing the aws builder
return fmt.Sprintf("%s:%s", a.regionName, a.snapshotName)
return a.snapshotName
}
func (a *Artifact) String() string {
return fmt.Sprintf("A snapshot was created: '%v' in region '%v'", a.snapshotName, a.regionName)
return fmt.Sprintf("A snapshot was created: %v", a.snapshotName)
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
log.Printf("Destroying image: %d", a.snapshotId)
return a.client.DestroyImage(a.snapshotId)
}
+2 -2
View File
@@ -14,8 +14,8 @@ func TestArtifact_Impl(t *testing.T) {
}
func TestArtifactString(t *testing.T) {
a := &Artifact{"packer-foobar", 42, "San Francisco", 3, nil}
expected := "A snapshot was created: 'packer-foobar' in region 'San Francisco'"
a := &Artifact{"packer-foobar", 42, nil}
expected := "A snapshot was created: packer-foobar"
if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
+36 -39
View File
@@ -8,7 +8,6 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
"log"
"os"
@@ -30,18 +29,18 @@ type config struct {
SizeID uint `mapstructure:"size_id"`
ImageID uint `mapstructure:"image_id"`
PrivateNetworking bool `mapstructure:"private_networking"`
SnapshotName string `mapstructure:"snapshot_name"`
DropletName string `mapstructure:"droplet_name"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort uint `mapstructure:"ssh_port"`
SnapshotName string `mapstructure:"snapshot_name"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort uint `mapstructure:"ssh_port"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
RawEventDelay string `mapstructure:"event_delay"`
RawStateTimeout string `mapstructure:"state_timeout"`
// These are unexported since they're set by other fields
// being set.
sshTimeout time.Duration
eventDelay time.Duration
stateTimeout time.Duration
tpl *packer.ConfigTemplate
@@ -52,15 +51,15 @@ type Builder struct {
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
func (b *Builder) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return nil, err
return err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
@@ -89,8 +88,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
if b.config.ImageID == 0 {
// Default to base image "Ubuntu 12.04.3 x64 Server (id: 1505447)"
b.config.ImageID = 1505447
// Default to base image "Ubuntu 12.04 x64 Server (id: 284203)"
b.config.ImageID = 284203
}
if b.config.SnapshotName == "" {
@@ -98,11 +97,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.SnapshotName = "packer-{{timestamp}}"
}
if b.config.DropletName == "" {
// Default to packer-[time-ordered-uuid]
b.config.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
}
if b.config.SSHUsername == "" {
// Default to "root". You can override this if your
// SourceImage has a different user account then the DO default
@@ -119,6 +113,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.RawSSHTimeout = "1m"
}
if b.config.RawEventDelay == "" {
// Default to 5 second delays after creating events
// to allow DO to process
b.config.RawEventDelay = "5s"
}
if b.config.RawStateTimeout == "" {
// Default to 6 minute timeouts waiting for
// desired state. i.e waiting for droplet to become active
@@ -129,9 +129,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"client_id": &b.config.ClientID,
"api_key": &b.config.APIKey,
"snapshot_name": &b.config.SnapshotName,
"droplet_name": &b.config.DropletName,
"ssh_username": &b.config.SSHUsername,
"ssh_timeout": &b.config.RawSSHTimeout,
"event_delay": &b.config.RawEventDelay,
"state_timeout": &b.config.RawStateTimeout,
}
@@ -162,6 +162,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
b.config.sshTimeout = sshTimeout
eventDelay, err := time.ParseDuration(b.config.RawEventDelay)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing event_delay: %s", err))
}
b.config.eventDelay = eventDelay
stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
@@ -170,11 +177,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.stateTimeout = stateTimeout
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
return errs
}
common.ScrubConfig(b.config, b.config.ClientID, b.config.APIKey)
return nil, nil
log.Printf("Config: %+v", b.config)
return nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
@@ -182,11 +189,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey)
// Set up the state
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("client", client)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["config"] = b.config
state["client"] = client
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
@@ -199,7 +206,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SSHWaitTimeout: 5 * time.Minute,
},
new(common.StepProvision),
new(stepShutdown),
new(stepPowerOff),
new(stepSnapshot),
}
@@ -217,27 +223,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
if _, ok := state.GetOk("snapshot_name"); !ok {
if _, ok := state["snapshot_name"]; !ok {
log.Println("Failed to find snapshot_name in state. Bug?")
return nil, nil
}
region_id := state.Get("region_id").(uint)
regionName, err := client.RegionName(region_id)
if err != nil {
return nil, err
}
artifact := &Artifact{
snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_image_id").(uint),
regionId: region_id,
regionName: regionName,
snapshotName: state["snapshot_name"].(string),
snapshotId: state["snapshot_image_id"].(uint),
client: client,
}
+56 -184
View File
@@ -34,10 +34,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
"api_key": []string{},
}
warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(c)
if err == nil {
t.Fatalf("prepare should fail")
}
@@ -49,10 +46,7 @@ func TestBuilderPrepare_APIKey(t *testing.T) {
// Test good
config["api_key"] = "foo"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -64,10 +58,7 @@ func TestBuilderPrepare_APIKey(t *testing.T) {
// Test bad
delete(config, "api_key")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -76,10 +67,7 @@ func TestBuilderPrepare_APIKey(t *testing.T) {
delete(config, "api_key")
os.Setenv("DIGITALOCEAN_API_KEY", "foo")
defer os.Setenv("DIGITALOCEAN_API_KEY", "")
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -91,10 +79,7 @@ func TestBuilderPrepare_ClientID(t *testing.T) {
// Test good
config["client_id"] = "foo"
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -106,10 +91,7 @@ func TestBuilderPrepare_ClientID(t *testing.T) {
// Test bad
delete(config, "client_id")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -118,10 +100,7 @@ func TestBuilderPrepare_ClientID(t *testing.T) {
delete(config, "client_id")
os.Setenv("DIGITALOCEAN_CLIENT_ID", "foo")
defer os.Setenv("DIGITALOCEAN_CLIENT_ID", "")
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -133,10 +112,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
// Add a random key
config["i_should_not_be_valid"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -147,10 +123,7 @@ func TestBuilderPrepare_RegionID(t *testing.T) {
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -162,10 +135,7 @@ func TestBuilderPrepare_RegionID(t *testing.T) {
// Test set
config["region_id"] = 2
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -180,10 +150,7 @@ func TestBuilderPrepare_SizeID(t *testing.T) {
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -195,10 +162,7 @@ func TestBuilderPrepare_SizeID(t *testing.T) {
// Test set
config["size_id"] = 67
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -213,10 +177,7 @@ func TestBuilderPrepare_ImageID(t *testing.T) {
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -228,10 +189,7 @@ func TestBuilderPrepare_ImageID(t *testing.T) {
// Test set
config["size_id"] = 2
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -246,10 +204,7 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) {
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -261,10 +216,7 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) {
// Test set
config["ssh_username"] = "foo"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -279,10 +231,7 @@ func TestBuilderPrepare_SSHTimeout(t *testing.T) {
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -294,10 +243,7 @@ func TestBuilderPrepare_SSHTimeout(t *testing.T) {
// Test set
config["ssh_timeout"] = "30s"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -305,10 +251,39 @@ func TestBuilderPrepare_SSHTimeout(t *testing.T) {
// Test bad
config["ssh_timeout"] = "tubes"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_EventDelay(t *testing.T) {
var b Builder
config := testConfig()
// Test default
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.RawEventDelay != "5s" {
t.Errorf("invalid: %d", b.config.RawEventDelay)
}
// Test set
config["event_delay"] = "10s"
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["event_delay"] = "tubes"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
@@ -320,10 +295,7 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) {
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -335,10 +307,7 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) {
// Test set
config["state_timeout"] = "5m"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -346,58 +315,19 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) {
// Test bad
config["state_timeout"] = "tubes"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_PrivateNetworking(t *testing.T) {
var b Builder
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.PrivateNetworking != false {
t.Errorf("invalid: %s", b.config.PrivateNetworking)
}
// Test set
config["private_networking"] = true
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.PrivateNetworking != true {
t.Errorf("invalid: %s", b.config.PrivateNetworking)
}
}
func TestBuilderPrepare_SnapshotName(t *testing.T) {
var b Builder
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -409,10 +339,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) {
// Test set
config["snapshot_name"] = "foobarbaz"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -420,10 +347,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) {
// Test set with template
config["snapshot_name"] = "{{timestamp}}"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
@@ -434,55 +358,3 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) {
}
}
func TestBuilderPrepare_DropletName(t *testing.T) {
var b Builder
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.DropletName == "" {
t.Errorf("invalid: %s", b.config.DropletName)
}
// Test normal set
config["droplet_name"] = "foobar"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test with template
config["droplet_name"] = "foobar-{{timestamp}}"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test with bad template
config["droplet_name"] = "foobar-{{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
+6 -7
View File
@@ -3,19 +3,18 @@ package digitalocean
import (
gossh "code.google.com/p/go.crypto/ssh"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
)
func sshAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(config)
ipAddress := state.Get("droplet_ip").(string)
func sshAddress(state map[string]interface{}) (string, error) {
config := state["config"].(config)
ipAddress := state["droplet_ip"].(string)
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
}
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(config)
privateKey := state.Get("privateKey").(string)
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
config := state["config"].(config)
privateKey := state["privateKey"].(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
+30 -17
View File
@@ -1,29 +1,35 @@
package digitalocean
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type stepCreateDroplet struct {
dropletId uint
}
func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
sshKeyId := state.Get("ssh_key_id").(uint)
func (s *stepCreateDroplet) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
sshKeyId := state["ssh_key_id"].(uint)
ui.Say("Creating droplet...")
// Create the droplet based on configuration
dropletId, err := client.CreateDroplet(c.DropletName, c.SizeID, c.ImageID, c.RegionID, sshKeyId, c.PrivateNetworking)
// Some random droplet name as it's temporary
name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw()))
// Create the droplet based on configuration
dropletId, err := client.CreateDroplet(name, c.SizeID, c.ImageID, c.RegionID, sshKeyId)
if err != nil {
err := fmt.Errorf("Error creating droplet: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -32,29 +38,36 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
s.dropletId = dropletId
// Store the droplet id for later
state.Put("droplet_id", dropletId)
state["droplet_id"] = dropletId
return multistep.ActionContinue
}
func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
// If the dropletid isn't there, we probably never created it
if s.dropletId == 0 {
return
}
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
// Destroy the droplet we just created
ui.Say("Destroying droplet...")
err := client.DestroyDroplet(s.dropletId)
if err != nil {
curlstr := fmt.Sprintf("curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'",
DIGITALOCEAN_API_URL, s.dropletId, c.ClientID, c.APIKey)
// Sleep arbitrarily before sending destroy request
// Otherwise we get "pending event" errors, even though there isn't
// one.
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
time.Sleep(c.eventDelay)
err := client.DestroyDroplet(s.dropletId)
curlstr := fmt.Sprintf("curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'",
DIGITALOCEAN_API_URL, s.dropletId, c.ClientID, c.APIKey)
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying droplet. Please destroy it manually: %v", curlstr))
}
+15 -15
View File
@@ -1,14 +1,15 @@
package digitalocean
import (
"cgl.tideland.biz/identifier"
"code.google.com/p/go.crypto/ssh"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
"log"
)
@@ -17,9 +18,9 @@ type stepCreateSSHKey struct {
keyId uint
}
func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
ui.Say("Creating temporary ssh key for droplet...")
@@ -34,21 +35,20 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
}
// Set the private key in the statebag for later
state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
state["privateKey"] = string(pem.EncodeToMemory(&priv_blk))
// Marshal the public key into SSH compatible format
// TODO properly handle the public key error
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
pub := priv.PublicKey
pub_sshformat := string(ssh.MarshalAuthorizedKey(&pub))
// The name of the public key on DO
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw()))
// Create the key!
keyId, err := client.CreateKey(name, pub_sshformat)
if err != nil {
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -59,20 +59,20 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
log.Printf("temporary ssh key name: %s", name)
// Remember some state for the future
state.Put("ssh_key_id", keyId)
state["ssh_key_id"] = keyId
return multistep.ActionContinue
}
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
func (s *stepCreateSSHKey) Cleanup(state map[string]interface{}) {
// If no key name is set, then we never created it, so just return
if s.keyId == 0 {
return
}
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
ui.Say("Deleting temporary ssh key...")
err := client.DestroyKey(s.keyId)
+10 -10
View File
@@ -8,18 +8,18 @@ import (
type stepDropletInfo struct{}
func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
dropletId := state["droplet_id"].(uint)
ui.Say("Waiting for droplet to become active...")
err := waitForDropletState("active", dropletId, client, c.stateTimeout)
err := waitForDropletState("active", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -28,16 +28,16 @@ func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
ip, _, err := client.DropletStatus(dropletId)
if err != nil {
err := fmt.Errorf("Error retrieving droplet ID: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("droplet_ip", ip)
state["droplet_ip"] = ip
return multistep.ActionContinue
}
func (s *stepDropletInfo) Cleanup(state multistep.StateBag) {
func (s *stepDropletInfo) Cleanup(state map[string]interface{}) {
// no cleanup
}
+20 -24
View File
@@ -5,43 +5,39 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type stepPowerOff struct{}
func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
c := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint)
func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
c := state["config"].(config)
ui := state["ui"].(packer.Ui)
dropletId := state["droplet_id"].(uint)
_, status, err := client.DropletStatus(dropletId)
if err != nil {
err := fmt.Errorf("Error checking droplet state: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Sleep arbitrarily before sending power off request
// Otherwise we get "pending event" errors, even though there isn't
// one.
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
time.Sleep(c.eventDelay)
if status == "off" {
// Droplet is already off, don't do anything
return multistep.ActionContinue
}
// Poweroff the droplet so it can be snapshot
err := client.PowerOffDroplet(dropletId)
// Pull the plug on the Droplet
ui.Say("Forcefully shutting down Droplet...")
err = client.PowerOffDroplet(dropletId)
if err != nil {
err := fmt.Errorf("Error powering off droplet: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Println("Waiting for poweroff event to complete...")
err = waitForDropletState("off", dropletId, client, c.stateTimeout)
ui.Say("Waiting for droplet to power off...")
err = waitForDropletState("off", dropletId, client, c)
if err != nil {
state.Put("error", err)
err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -49,6 +45,6 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepPowerOff) Cleanup(state multistep.StateBag) {
func (s *stepPowerOff) Cleanup(state map[string]interface{}) {
// no cleanup
}
-74
View File
@@ -1,74 +0,0 @@
package digitalocean
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type stepShutdown struct{}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint)
// Gracefully power off the droplet. We have to retry this a number
// of times because sometimes it says it completed when it actually
// did absolutely nothing (*ALAKAZAM!* magic!). We give up after
// a pretty arbitrary amount of time.
ui.Say("Gracefully shutting down droplet...")
err := client.ShutdownDroplet(dropletId)
if err != nil {
// If we get an error the first time, actually report it
err := fmt.Errorf("Error shutting down droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// A channel we use as a flag to end our goroutines
done := make(chan struct{})
shutdownRetryDone := make(chan struct{})
// Make sure we wait for the shutdown retry goroutine to end
// before moving on.
defer func() {
close(done)
<-shutdownRetryDone
}()
// Start a goroutine that just keeps trying to shut down the
// droplet.
go func() {
defer close(shutdownRetryDone)
for attempts := 2; attempts > 0; attempts++ {
log.Printf("ShutdownDroplet attempt #%d...", attempts)
err := client.ShutdownDroplet(dropletId)
if err != nil {
log.Printf("Shutdown retry error: %s", err)
}
select {
case <-done:
return
case <-time.After(20 * time.Second):
// Retry!
}
}
}()
err = waitForDropletState("off", dropletId, client, 2*time.Minute)
if err != nil {
log.Printf("Error waiting for graceful off: %s", err)
}
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
// no cleanup
}
+13 -14
View File
@@ -10,26 +10,26 @@ import (
type stepSnapshot struct{}
func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
dropletId := state["droplet_id"].(uint)
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
err := client.CreateSnapshot(dropletId, c.SnapshotName)
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Waiting for snapshot to complete...")
err = waitForDropletState("active", dropletId, client, c.stateTimeout)
err = waitForDropletState("active", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot to complete: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -38,7 +38,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
images, err := client.Images()
if err != nil {
err := fmt.Errorf("Error looking up snapshot ID: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -53,20 +53,19 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
if imageId == 0 {
err := errors.New("Couldn't find snapshot to get the image ID. Bug?")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Snapshot image ID: %d", imageId)
state.Put("snapshot_image_id", imageId)
state.Put("snapshot_name", c.SnapshotName)
state.Put("region_id", c.RegionID)
state["snapshot_image_id"] = imageId
state["snapshot_name"] = c.SnapshotName
return multistep.ActionContinue
}
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
func (s *stepSnapshot) Cleanup(state map[string]interface{}) {
// no cleanup
}
+24 -25
View File
@@ -1,18 +1,16 @@
package digitalocean
import (
"fmt"
"errors"
"log"
"time"
)
// waitForState simply blocks until the droplet is in
// a state we expect, while eventually timing out.
func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, timeout time.Duration) error {
done := make(chan struct{})
defer close(done)
func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, c config) error {
active := make(chan bool, 1)
result := make(chan error, 1)
go func() {
attempts := 0
for {
@@ -21,35 +19,36 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce
log.Printf("Checking droplet status... (attempt: %d)", attempts)
_, status, err := client.DropletStatus(dropletId)
if err != nil {
result <- err
return
log.Println(err)
break
}
if status == desiredState {
result <- nil
return
break
}
// Wait 3 seconds in between
time.Sleep(3 * time.Second)
// Verify we shouldn't exit
select {
case <-done:
// We finished, so just exit the goroutine
return
default:
// Keep going
}
}
active <- true
}()
log.Printf("Waiting for up to %d seconds for droplet to become %s", timeout/time.Second, desiredState)
select {
case err := <-result:
return err
case <-time.After(timeout):
err := fmt.Errorf("Timeout while waiting to for droplet to become '%s'", desiredState)
return err
log.Printf("Waiting for up to %s for droplet to become %s", c.RawStateTimeout, desiredState)
timeout := time.After(c.stateTimeout)
ActiveWaitLoop:
for {
select {
case <-active:
// We connected. Just break the loop.
break ActiveWaitLoop
case <-timeout:
err := errors.New("Timeout while waiting to for droplet to become active")
return err
}
}
// If we got this far, there were no errors
return nil
}
-32
View File
@@ -1,32 +0,0 @@
package docker
import (
"fmt"
"os"
)
// ExportArtifact is an Artifact implementation for when a container is
// exported from docker into a single flat file.
type ExportArtifact struct {
path string
}
func (*ExportArtifact) BuilderId() string {
return BuilderId
}
func (a *ExportArtifact) Files() []string {
return []string{a.path}
}
func (*ExportArtifact) Id() string {
return "Container"
}
func (a *ExportArtifact) String() string {
return fmt.Sprintf("Exported Docker file: %s", a.path)
}
func (a *ExportArtifact) Destroy() error {
return os.Remove(a.path)
}
-10
View File
@@ -1,10 +0,0 @@
package docker
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestExportArtifact_impl(t *testing.T) {
var _ packer.Artifact = new(ExportArtifact)
}
-77
View File
@@ -1,77 +0,0 @@
package docker
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"log"
)
const BuilderId = "packer.docker"
type Builder struct {
config *Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
c, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = c
return warnings, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
driver := &DockerDriver{Tpl: b.config.tpl, Ui: ui}
if err := driver.Verify(); err != nil {
return nil, err
}
steps := []multistep.Step{
&StepTempDir{},
&StepPull{},
&StepRun{},
&StepProvision{},
&StepExport{},
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("hook", hook)
state.Put("ui", ui)
// Setup the driver that will talk to Docker
state.Put("driver", driver)
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// No errors, must've worked
artifact := &ExportArtifact{path: b.config.ExportPath}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
-10
View File
@@ -1,10 +0,0 @@
package docker
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestBuilder_implBuilder(t *testing.T) {
var _ packer.Builder = new(Builder)
}
-290
View File
@@ -1,290 +0,0 @@
package docker
import (
"bytes"
"fmt"
"github.com/ActiveState/tail"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
)
type Communicator struct {
ContainerId string
HostDir string
ContainerDir string
lock sync.Mutex
}
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
// Create a temporary file to store the output. Because of a bug in
// Docker, sometimes all the output doesn't properly show up. This
// file will capture ALL of the output, and we'll read that.
//
// https://github.com/dotcloud/docker/issues/2625
outputFile, err := ioutil.TempFile(c.HostDir, "cmd")
if err != nil {
return err
}
outputFile.Close()
// This file will store the exit code of the command once it is complete.
exitCodePath := outputFile.Name() + "-exit"
cmd := exec.Command("docker", "attach", c.ContainerId)
stdin_w, err := cmd.StdinPipe()
if err != nil {
// We have to do some cleanup since run was never called
os.Remove(outputFile.Name())
os.Remove(exitCodePath)
return err
}
// Run the actual command in a goroutine so that Start doesn't block
go c.run(cmd, remote, stdin_w, outputFile, exitCodePath)
return nil
}
func (c *Communicator) Upload(dst string, src io.Reader) error {
// Create a temporary file to store the upload
tempfile, err := ioutil.TempFile(c.HostDir, "upload")
if err != nil {
return err
}
defer os.Remove(tempfile.Name())
// Copy the contents to the temporary file
_, err = io.Copy(tempfile, src)
tempfile.Close()
if err != nil {
return err
}
// Copy the file into place by copying the temporary file we put
// into the shared folder into the proper location in the container
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("cp %s/%s %s", c.ContainerDir,
filepath.Base(tempfile.Name()), dst),
}
if err := c.Start(cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
}
return nil
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
// Create the temporary directory that will store the contents of "src"
// for copying into the container.
td, err := ioutil.TempDir(c.HostDir, "dirupload")
if err != nil {
return err
}
defer os.RemoveAll(td)
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relpath, err := filepath.Rel(src, path)
if err != nil {
return err
}
hostpath := filepath.Join(td, relpath)
// If it is a directory, just create it
if info.IsDir() {
return os.MkdirAll(hostpath, info.Mode())
}
// It is a file, copy it over, including mode.
src, err := os.Open(path)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(hostpath)
if err != nil {
return err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return err
}
si, err := src.Stat()
if err != nil {
return err
}
return dst.Chmod(si.Mode())
}
// Copy the entire directory tree to the temporary directory
if err := filepath.Walk(src, walkFn); err != nil {
return err
}
// Determine the destination directory
containerSrc := filepath.Join(c.ContainerDir, filepath.Base(td))
containerDst := dst
if src[len(src)-1] != '/' {
containerDst = filepath.Join(dst, filepath.Base(src))
}
// Make the directory, then copy into it
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("set -e; mkdir -p %s; cp -R %s/* %s",
containerDst, containerSrc, containerDst),
}
if err := c.Start(cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
}
return nil
}
func (c *Communicator) Download(src string, dst io.Writer) error {
panic("not implemented")
}
// Runs the given command and blocks until completion
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) {
// For Docker, remote communication must be serialized since it
// only supports single execution.
c.lock.Lock()
defer c.lock.Unlock()
// Clean up after ourselves by removing our temporary files
defer os.Remove(outputFile.Name())
defer os.Remove(exitCodePath)
// Tail the output file and send the data to the stdout listener
tail, err := tail.TailFile(outputFile.Name(), tail.Config{
Poll: true,
ReOpen: true,
Follow: true,
})
if err != nil {
log.Printf("Error tailing output file: %s", err)
remote.SetExited(254)
return
}
defer tail.Stop()
// Modify the remote command so that all the output of the commands
// go to a single file and so that the exit code is redirected to
// a single file. This lets us determine both when the command
// is truly complete (because the file will have data), what the
// exit status is (because Docker loses it because of the pty, not
// Docker's fault), and get the output (Docker bug).
remoteCmd := fmt.Sprintf("(%s) >%s 2>&1; echo $? >%s",
remote.Command,
filepath.Join(c.ContainerDir, filepath.Base(outputFile.Name())),
filepath.Join(c.ContainerDir, filepath.Base(exitCodePath)))
// Start the command
log.Printf("Executing in container %s: %#v", c.ContainerId, remoteCmd)
if err := cmd.Start(); err != nil {
log.Printf("Error executing: %s", err)
remote.SetExited(254)
return
}
go func() {
defer stdin_w.Close()
// This sleep needs to be here because of the issue linked to below.
// Basically, without it, Docker will hang on reading stdin forever,
// and won't see what we write, for some reason.
//
// https://github.com/dotcloud/docker/issues/2628
time.Sleep(2 * time.Second)
stdin_w.Write([]byte(remoteCmd + "\n"))
}()
// Start a goroutine to read all the lines out of the logs
go func() {
for line := range tail.Lines {
if remote.Stdout != nil {
remote.Stdout.Write([]byte(line.Text + "\n"))
} else {
log.Printf("Command stdout: %#v", line.Text)
}
}
}()
err = cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus := 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
// Say that we ended, since if Docker itself failed, then
// the command must've not run, or so we assume
remote.SetExited(exitStatus)
return
}
// Wait for the exit code to appear in our file...
log.Println("Waiting for exit code to appear for remote command...")
for {
fi, err := os.Stat(exitCodePath)
if err == nil && fi.Size() > 0 {
break
}
time.Sleep(1 * time.Second)
}
// Read the exit code
exitRaw, err := ioutil.ReadFile(exitCodePath)
if err != nil {
log.Printf("Error executing: %s", err)
remote.SetExited(254)
return
}
exitStatus, err := strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0)
if err != nil {
log.Printf("Error executing: %s", err)
remote.SetExited(254)
return
}
log.Printf("Executed command exit status: %d", exitStatus)
// Finally, we're done
remote.SetExited(int(exitStatus))
}
-10
View File
@@ -1,10 +0,0 @@
package docker
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestCommunicator_impl(t *testing.T) {
var _ packer.Communicator = new(Communicator)
}
-87
View File
@@ -1,87 +0,0 @@
package docker
import (
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ExportPath string `mapstructure:"export_path"`
Image string
Pull bool
RunCommand []string `mapstructure:"run_command"`
tpl *packer.ConfigTemplate
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
md, err := common.DecodeConfig(c, raws...)
if err != nil {
return nil, nil, err
}
c.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, nil, err
}
// Defaults
if len(c.RunCommand) == 0 {
c.RunCommand = []string{
"run",
"-d", "-i", "-t",
"-v", "{{.Volumes}}",
"{{.Image}}",
"/bin/bash",
}
}
// Default Pull if it wasn't set
hasPull := false
for _, k := range md.Keys {
if k == "Pull" {
hasPull = true
break
}
}
if !hasPull {
c.Pull = true
}
errs := common.CheckUnusedConfig(md)
templates := map[string]*string{
"export_path": &c.ExportPath,
"image": &c.Image,
}
for n, ptr := range templates {
var err error
*ptr, err = c.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if c.ExportPath == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("export_path must be specified"))
}
if c.Image == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("image must be specified"))
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
return c, nil, nil
}
-90
View File
@@ -1,90 +0,0 @@
package docker
import (
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"export_path": "foo",
"image": "bar",
}
}
func testConfigStruct(t *testing.T) *Config {
c, warns, errs := NewConfig(testConfig())
if len(warns) > 0 {
t.Fatalf("bad: %#v", len(warns))
}
if errs != nil {
t.Fatalf("bad: %#v", errs)
}
return c
}
func testConfigErr(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should error")
}
}
func testConfigOk(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad: %s", err)
}
}
func TestConfigPrepare_exportPath(t *testing.T) {
raw := testConfig()
// No export path
delete(raw, "export_path")
_, warns, errs := NewConfig(raw)
testConfigErr(t, warns, errs)
// Good export path
raw["export_path"] = "good"
_, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs)
}
func TestConfigPrepare_image(t *testing.T) {
raw := testConfig()
// No image
delete(raw, "image")
_, warns, errs := NewConfig(raw)
testConfigErr(t, warns, errs)
// Good image
raw["image"] = "path"
_, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs)
}
func TestConfigPrepare_pull(t *testing.T) {
raw := testConfig()
// No pull set
delete(raw, "pull")
c, warns, errs := NewConfig(raw)
testConfigOk(t, warns, errs)
if !c.Pull {
t.Fatal("should pull by default")
}
// Pull set
raw["pull"] = false
c, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs)
if c.Pull {
t.Fatal("should not pull")
}
}
-39
View File
@@ -1,39 +0,0 @@
package docker
import (
"io"
)
// Driver is the interface that has to be implemented to communicate with
// Docker. The Driver interface also allows the steps to be tested since
// a mock driver can be shimmed in.
type Driver interface {
// Export exports the container with the given ID to the given writer.
Export(id string, dst io.Writer) error
// Pull should pull down the given image.
Pull(image string) error
// StartContainer starts a container and returns the ID for that container,
// along with a potential error.
StartContainer(*ContainerConfig) (string, error)
// StopContainer forcibly stops a container.
StopContainer(id string) error
// Verify verifies that the driver can run
Verify() error
}
// ContainerConfig is the configuration used to start a container.
type ContainerConfig struct {
Image string
RunCommand []string
Volumes map[string]string
}
// This is the template that is used for the RunCommand in the ContainerConfig.
type startContainerTemplate struct {
Image string
Volumes string
}
-103
View File
@@ -1,103 +0,0 @@
package docker
import (
"bytes"
"fmt"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os/exec"
"strings"
)
type DockerDriver struct {
Ui packer.Ui
Tpl *packer.ConfigTemplate
}
func (d *DockerDriver) Export(id string, dst io.Writer) error {
var stderr bytes.Buffer
cmd := exec.Command("docker", "export", id)
cmd.Stdout = dst
cmd.Stderr = &stderr
log.Printf("Exporting container: %s", id)
if err := cmd.Start(); err != nil {
return err
}
if err := cmd.Wait(); err != nil {
err = fmt.Errorf("Error exporting: %s\nStderr: %s",
err, stderr.String())
return err
}
return nil
}
func (d *DockerDriver) Pull(image string) error {
cmd := exec.Command("docker", "pull", image)
return runAndStream(cmd, d.Ui)
}
func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) {
// Build up the template data
var tplData startContainerTemplate
tplData.Image = config.Image
if len(config.Volumes) > 0 {
volumes := make([]string, 0, len(config.Volumes))
for host, guest := range config.Volumes {
volumes = append(volumes, fmt.Sprintf("%s:%s", host, guest))
}
tplData.Volumes = strings.Join(volumes, ",")
}
// Args that we're going to pass to Docker
args := config.RunCommand
for i, v := range args {
var err error
args[i], err = d.Tpl.Process(v, &tplData)
if err != nil {
return "", err
}
}
d.Ui.Message(fmt.Sprintf(
"Run command: docker %s", strings.Join(args, " ")))
// Start the container
var stdout, stderr bytes.Buffer
cmd := exec.Command("docker", args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
log.Printf("Starting container with args: %v", args)
if err := cmd.Start(); err != nil {
return "", err
}
log.Println("Waiting for container to finish starting")
if err := cmd.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("Docker exited with a non-zero exit status.\nStderr: %s",
stderr.String())
}
return "", err
}
// Capture the container ID, which is alone on stdout
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) StopContainer(id string) error {
return exec.Command("docker", "kill", id).Run()
}
func (d *DockerDriver) Verify() error {
if _, err := exec.LookPath("docker"); err != nil {
return err
}
return nil
}
-7
View File
@@ -1,7 +0,0 @@
package docker
import "testing"
func TestDockerDriver_impl(t *testing.T) {
var _ Driver = new(DockerDriver)
}
-63
View File
@@ -1,63 +0,0 @@
package docker
import (
"io"
)
// MockDriver is a driver implementation that can be used for tests.
type MockDriver struct {
ExportReader io.Reader
ExportError error
PullError error
StartID string
StartError error
StopError error
VerifyError error
ExportCalled bool
ExportID string
PullCalled bool
PullImage string
StartCalled bool
StartConfig *ContainerConfig
StopCalled bool
StopID string
VerifyCalled bool
}
func (d *MockDriver) Export(id string, dst io.Writer) error {
d.ExportCalled = true
d.ExportID = id
if d.ExportReader != nil {
_, err := io.Copy(dst, d.ExportReader)
if err != nil {
return err
}
}
return d.ExportError
}
func (d *MockDriver) Pull(image string) error {
d.PullCalled = true
d.PullImage = image
return d.PullError
}
func (d *MockDriver) StartContainer(config *ContainerConfig) (string, error) {
d.StartCalled = true
d.StartConfig = config
return d.StartID, d.StartError
}
func (d *MockDriver) StopContainer(id string) error {
d.StopCalled = true
d.StopID = id
return d.StopError
}
func (d *MockDriver) Verify() error {
d.VerifyCalled = true
return d.VerifyError
}
-7
View File
@@ -1,7 +0,0 @@
package docker
import "testing"
func TestMockDriver_impl(t *testing.T) {
var _ Driver = new(MockDriver)
}
-102
View File
@@ -1,102 +0,0 @@
package docker
import (
"fmt"
"github.com/mitchellh/iochan"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
)
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
defer stdout_w.Close()
defer stderr_w.Close()
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
if err := cmd.Start(); err != nil {
return err
}
// Create the channels we'll use for data
exitCh := make(chan int, 1)
stdoutCh := iochan.DelimReader(stdout_r, '\n')
stderrCh := iochan.DelimReader(stderr_r, '\n')
// Start the goroutine to watch for the exit
go func() {
defer stdout_w.Close()
defer stderr_w.Close()
exitStatus := 0
err := cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus = 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
}
exitCh <- exitStatus
}()
// This waitgroup waits for the streaming to end
var streamWg sync.WaitGroup
streamWg.Add(2)
streamFunc := func(ch <-chan string) {
defer streamWg.Done()
for data := range ch {
data = cleanOutputLine(data)
if data != "" {
ui.Message(data)
}
}
}
// Stream stderr/stdout
go streamFunc(stderrCh)
go streamFunc(stdoutCh)
// Wait for the process to end and then wait for the streaming to end
exitStatus := <-exitCh
streamWg.Wait()
if exitStatus != 0 {
return fmt.Errorf("Bad exit status: %d", exitStatus)
}
return nil
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func cleanOutputLine(line string) string {
// Build a regular expression that will get rid of shell codes
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
line = re.ReplaceAllString(line, "")
// Trim surrounding whitespace
line = strings.TrimSpace(line)
// Trim up to the first carriage return, since that text would be
// lost anyways.
idx := strings.LastIndex(line, "\r")
if idx > -1 {
line = line[idx+1:]
}
return line
}
-24
View File
@@ -1,24 +0,0 @@
package docker
import (
"testing"
)
func TestCleanLine(t *testing.T) {
cases := []struct {
input string
output string
}{
{
"\x1b[0A\x1b[2K\r8dbd9e392a96: Pulling image (precise) from ubuntu\r\x1b[0B\x1b[1A\x1b[2K\r8dbd9e392a96: Pulling image (precise) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/\r\x1b[1B",
"8dbd9e392a96: Pulling image (precise) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/",
},
}
for _, tc := range cases {
actual := cleanOutputLine(tc.input)
if actual != tc.output {
t.Fatalf("bad: %#v %#v", tc.input, actual)
}
}
}
-42
View File
@@ -1,42 +0,0 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
)
// StepExport exports the container to a flat tar file.
type StepExport struct{}
func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
containerId := state.Get("container_id").(string)
ui := state.Get("ui").(packer.Ui)
// Open the file that we're going to write to
f, err := os.Create(config.ExportPath)
if err != nil {
err := fmt.Errorf("Error creating output file: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Exporting the container")
if err := driver.Export(containerId, f); err != nil {
f.Close()
os.Remove(f.Name())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
f.Close()
return multistep.ActionContinue
}
func (s *StepExport) Cleanup(state multistep.StateBag) {}
-99
View File
@@ -1,99 +0,0 @@
package docker
import (
"bytes"
"errors"
"github.com/mitchellh/multistep"
"io/ioutil"
"os"
"testing"
)
func testStepExportState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("container_id", "foo")
return state
}
func TestStepExport_impl(t *testing.T) {
var _ multistep.Step = new(StepExport)
}
func TestStepExport(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportReader = bytes.NewReader([]byte("data!"))
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.ExportCalled {
t.Fatal("should've exported")
}
if driver.ExportID != "foo" {
t.Fatalf("bad: %#v", driver.ExportID)
}
// verify the data exported to the file
contents, err := ioutil.ReadFile(tf.Name())
if err != nil {
t.Fatalf("err: %s", err)
}
if string(contents) != "data!" {
t.Fatalf("bad: %#v", string(contents))
}
}
func TestStepExport_error(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
if err := os.Remove(tf.Name()); err != nil {
t.Fatalf("err: %s", err)
}
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify we have an error
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
// verify we didn't make that file
if _, err := os.Stat(tf.Name()); err == nil {
t.Fatal("export path shouldn't exist")
}
}
-26
View File
@@ -1,26 +0,0 @@
package docker
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
)
type StepProvision struct{}
func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
containerId := state.Get("container_id").(string)
tempDir := state.Get("temp_dir").(string)
// Create the communicator that talks to Docker via various
// os/exec tricks.
comm := &Communicator{
ContainerId: containerId,
HostDir: tempDir,
ContainerDir: "/packer-files",
}
prov := common.StepProvision{Comm: comm}
return prov.Run(state)
}
func (s *StepProvision) Cleanup(state multistep.StateBag) {}
-34
View File
@@ -1,34 +0,0 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type StepPull struct{}
func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if !config.Pull {
log.Println("Pull disabled, won't docker pull")
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image))
if err := driver.Pull(config.Image); err != nil {
err := fmt.Errorf("Error pulling Docker image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPull) Cleanup(state multistep.StateBag) {
}
-73
View File
@@ -1,73 +0,0 @@
package docker
import (
"errors"
"github.com/mitchellh/multistep"
"testing"
)
func TestStepPull_impl(t *testing.T) {
var _ multistep.Step = new(StepPull)
}
func TestStepPull(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.PullCalled {
t.Fatal("should've pulled")
}
if driver.PullImage != config.Image {
t.Fatalf("bad: %#v", driver.PullImage)
}
}
func TestStepPull_error(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.PullError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify we have an error
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
}
func TestStepPull_noPull(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
config.Pull = false
driver := state.Get("driver").(*MockDriver)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if driver.PullCalled {
t.Fatal("shouldn't have pulled")
}
}
-59
View File
@@ -1,59 +0,0 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepRun struct {
containerId string
}
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
tempDir := state.Get("temp_dir").(string)
ui := state.Get("ui").(packer.Ui)
runConfig := ContainerConfig{
Image: config.Image,
RunCommand: config.RunCommand,
Volumes: map[string]string{
tempDir: "/packer-files",
},
}
ui.Say("Starting docker container...")
containerId, err := driver.StartContainer(&runConfig)
if err != nil {
err := fmt.Errorf("Error running container: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Save the container ID
s.containerId = containerId
state.Put("container_id", s.containerId)
ui.Message(fmt.Sprintf("Container ID: %s", s.containerId))
return multistep.ActionContinue
}
func (s *StepRun) Cleanup(state multistep.StateBag) {
if s.containerId == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
// Kill the container. We don't handle errors because errors usually
// just mean that the container doesn't exist anymore, which isn't a
// big deal.
ui.Say(fmt.Sprintf("Killing the container: %s", s.containerId))
driver.StopContainer(s.containerId)
// Reset the container ID so that we're idempotent
s.containerId = ""
}
-95
View File
@@ -1,95 +0,0 @@
package docker
import (
"errors"
"github.com/mitchellh/multistep"
"testing"
)
func testStepRunState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("temp_dir", "/foo")
return state
}
func TestStepRun_impl(t *testing.T) {
var _ multistep.Step = new(StepRun)
}
func TestStepRun(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
driver.StartID = "foo"
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.StartCalled {
t.Fatal("should've called")
}
if driver.StartConfig.Image != config.Image {
t.Fatalf("bad: %#v", driver.StartConfig.Image)
}
// verify the ID is saved
idRaw, ok := state.GetOk("container_id")
if !ok {
t.Fatal("should've saved ID")
}
id := idRaw.(string)
if id != "foo" {
t.Fatalf("bad: %#v", id)
}
// Verify we haven't called stop yet
if driver.StopCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if !driver.StopCalled {
t.Fatal("should've stopped")
}
if driver.StopID != id {
t.Fatalf("bad: %#v", driver.StopID)
}
}
func TestStepRun_error(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.StartError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify the ID is not saved
if _, ok := state.GetOk("container_id"); ok {
t.Fatal("shouldn't save container ID")
}
// Verify we haven't called stop yet
if driver.StopCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if driver.StopCalled {
t.Fatal("should not have stopped")
}
}
-38
View File
@@ -1,38 +0,0 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
)
// StepTempDir creates a temporary directory that we use in order to
// share data with the docker container over the communicator.
type StepTempDir struct {
tempDir string
}
func (s *StepTempDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating a temporary directory for sharing data...")
td, err := ioutil.TempDir("", "packer-docker")
if err != nil {
err := fmt.Errorf("Error making temp dir: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.tempDir = td
state.Put("temp_dir", s.tempDir)
return multistep.ActionContinue
}
func (s *StepTempDir) Cleanup(state multistep.StateBag) {
if s.tempDir != "" {
os.RemoveAll(s.tempDir)
}
}
-44
View File
@@ -1,44 +0,0 @@
package docker
import (
"github.com/mitchellh/multistep"
"os"
"testing"
)
func TestStepTempDir_impl(t *testing.T) {
var _ multistep.Step = new(StepTempDir)
}
func TestStepTempDir(t *testing.T) {
state := testState(t)
step := new(StepTempDir)
defer step.Cleanup(state)
// sanity test
if _, ok := state.GetOk("temp_dir"); ok {
t.Fatalf("temp_dir should not be in state yet")
}
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// Verify that we got the temp dir
dirRaw, ok := state.GetOk("temp_dir")
if !ok {
t.Fatalf("should've made temp_dir")
}
dir := dirRaw.(string)
if _, err := os.Stat(dir); err != nil {
t.Fatalf("err: %s", err)
}
// Cleanup
step.Cleanup(state)
if _, err := os.Stat(dir); err == nil {
t.Fatalf("dir should be gone")
}
}
-20
View File
@@ -1,20 +0,0 @@
package docker
import (
"bytes"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"testing"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("config", testConfigStruct(t))
state.Put("driver", &MockDriver{})
state.Put("hook", &packer.MockHook{})
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
-39
View File
@@ -1,39 +0,0 @@
package googlecompute
import (
"fmt"
"log"
)
// Artifact represents a GCE image as the result of a Packer build.
type Artifact struct {
imageName string
driver Driver
}
// BuilderId returns the builder Id.
func (*Artifact) BuilderId() string {
return BuilderId
}
// Destroy destroys the GCE image represented by the artifact.
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %s", a.imageName)
errCh := a.driver.DeleteImage(a.imageName)
return <-errCh
}
// Files returns the files represented by the artifact.
func (*Artifact) Files() []string {
return nil
}
// Id returns the GCE image name.
func (a *Artifact) Id() string {
return a.imageName
}
// String returns the string representation of the artifact.
func (a *Artifact) String() string {
return fmt.Sprintf("A disk image was created: %v", a.imageName)
}
-10
View File
@@ -1,10 +0,0 @@
package googlecompute
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestArtifact_impl(t *testing.T) {
var _ packer.Artifact = new(Artifact)
}

Some files were not shown because too many files have changed in this diff Show More