Compare commits

...

136 Commits

Author SHA1 Message Date
Mitchell Hashimoto 15c4fb66e3 v0.3.10 2013-10-20 17:55:54 -07:00
Mitchell Hashimoto c4515dbd56 Update CHANGELOG and link in Ansible 2013-10-20 17:48:03 -07:00
Mitchell Hashimoto 39f532f611 Merge pull request #475 from kelseyhightower/ansible-provisioner
provisioner/ansible-local: Add support for provisioning with Ansible
2013-10-20 17:46:36 -07:00
Mitchell Hashimoto afe0b88fca Update CHANGELOG 2013-10-20 17:44:56 -07:00
Mitchell Hashimoto 5cf345f932 Merge pull request #506 from maspwr/compression_level
post-processors/vagrant: add compression_level option
2013-10-20 17:44:26 -07:00
Mitchell Hashimoto 32737c255b Merge pull request #514 from rgarcia/salt-masterless-uploaddir
provisioner/salt-masterless: use communicator.UploadDir
2013-10-20 17:42:38 -07:00
Mitchell Hashimoto dfc553d36d Update CHANGELOG 2013-10-20 17:41:58 -07:00
Mitchell Hashimoto ee39f8e09e Merge pull request #513 from rgarcia/salt-masterless-mv-bug
provisioner/salt-masterless: move states to /srv/salt, not /srv/salt/states
2013-10-20 17:40:27 -07:00
Mitchell Hashimoto e7886ff807 Build script now installs binaries to $GOPATH/bin 2013-10-20 16:16:05 -07:00
Mitchell Hashimoto 65169b4576 builder/virtualbox,vmware: support user templates in SSH key path
[GH-539]
2013-10-20 15:58:39 -07:00
Mitchell Hashimoto a54e5e70da builder/virtualbox: use proper SATA port arg [GH-547] 2013-10-20 15:55:31 -07:00
Mitchell Hashimoto 85bad26ec3 Merge pull request #548 from roktas/master
post-processor/vagrant: fix bug in ova support caused by wrong file path
2013-10-20 15:43:25 -07:00
Recai Oktaş 6d6b32924d post-processor/vagrant: fix bug in ova support caused by wrong file path 2013-10-19 01:39:12 +03:00
Mitchell Hashimoto 9357c25991 Update CHANGELOG 2013-10-17 22:18:47 -10:00
Mitchell Hashimoto 9699c1bb37 Merge pull request #544 from mitchellh/fix#500
builder/amazon/chroot: use --remove-destination for cp
2013-10-18 01:17:47 -07:00
Mitchell Hashimoto a6fecce367 Merge pull request #545 from mitchellh/contributing-log
contributing: clarify how to submit debug output with a new issue
2013-10-18 01:13:26 -07:00
Jack Pearkes f7656aad9c contributing: clarify how to submit debug output with a new issue 2013-10-18 10:04:33 +02:00
Matthew Hooker fa3e70ef75 builder/amazon/chroot:
Delete files at destination before copy. This should help with the dangling
symbolic link issue we've been seeing with ubuntu.

fixes GH-500
2013-10-17 22:50:02 +00:00
Jack Pearkes 5bb28e7ece Merge pull request #543 from tateeskew/master
fix typo in docs for saltmasterless provisioner.
2013-10-17 09:24:01 -07:00
Tate Eskew 45f4a1a914 fix typo in docs for saltmasterless provisioner. bootstrap was spelled incorrectly 2013-10-17 11:12:21 -05:00
Mitchell Hashimoto fae3b96658 Merge pull request #535 from lookfirst/patch-1
website/docs: note shutdown to run as root, why
2013-10-17 00:24:03 -07:00
Mitchell Hashimoto cfcdeadf53 common/uuid: add test, albeit weak 2013-10-16 21:19:53 -10:00
Mitchell Hashimoto 7d3987d840 packer: no more asserts lib 2013-10-16 21:09:27 -10:00
Mitchell Hashimoto cf9dafd373 packer/rpc: don't depend on cgl 2013-10-16 17:04:57 -10:00
Mitchell Hashimoto 3fad717ba4 builder/amazon,builder/openstack: remove more cgl libs 2013-10-16 16:29:53 -10:00
Mitchell Hashimoto ac64805a9e command/build: remove asserts framewor 2013-10-16 16:27:15 -10:00
Mitchell Hashimoto 697c91b0b0 Remove dependency on identifier package, use time ordered UUID [GH-541] 2013-10-16 16:21:14 -10:00
Mitchell Hashimoto 7b09e24ac3 Update CHANGELOG 2013-10-15 09:51:47 -10:00
Mitchell Hashimoto d3ab4d6b40 provisioner/shell: comment on why we return right away if advance == 0
/cc @mwhooker
2013-10-15 09:48:12 -10:00
Mitchell Hashimoto f2c0aeacf5 provisoiner/shell: append only needs to add a byte for UnixReader 2013-10-15 09:40:47 -10:00
Matthew Hooker 4beec63845 Merge pull request #537 from mitchellh/fix507
provisioner/shell: fix EOF detection
2013-10-15 06:02:39 -07:00
Matthew Hooker abe92ed739 provisioner/shell: fix EOF detection
Fixes #507

The scanUnixLine function was erroneously returning empty lines when EOF
was reached.
This commit adds a test for the problem and changes the scanUnixLine
function to elide the trailing \n if no content was read.
2013-10-15 05:56:10 -07:00
Jon Stevens bd68f3c52b I found that I needed the shutdown command to be run as root.
Also added a note about shutdown_command that bit me.
2013-10-14 17:59:53 -07:00
Mitchell Hashimoto 61ea7de66b Update CHANGELOG 2013-10-13 22:57:04 -10:00
Mitchell Hashimoto 111392ad43 Merge pull request #503 from zimbatm/fix/vagrant-aws-instance
post-processor/vagrant: support instance store AMIs [GH-503]
2013-10-14 01:56:24 -07:00
Mitchell Hashimoto bfba541338 Update CHANGELOG 2013-10-13 22:51:36 -10:00
Mitchell Hashimoto e7414f334b Merge pull request #512 from hoshposh/vbox-shutdown-issue
builder/virtualbox: sleep after shutdown before export to avoid session locks
2013-10-14 01:50:55 -07:00
Mitchell Hashimoto 93ab5c8f63 Update CHANGELOG 2013-10-13 22:46:31 -10:00
Mitchell Hashimoto 54e1f70a3c Merge pull request #522 from patricklucas/fix_instance_not_found_race
builder/amzon: fix race condition after launching EC2 instance
2013-10-14 01:45:55 -07:00
Mitchell Hashimoto eb2bcfe456 Update CHANGELOG 2013-10-13 22:36:15 -10:00
Mitchell Hashimoto e05cc3220f Merge pull request #531 from mitchellh/521
common/config: config filter function [GH-521]
2013-10-14 01:35:23 -07:00
Mitchell Hashimoto df3d751b19 communicator/ssh: explicitly set c.conn = nil 2013-10-13 22:21:52 -10:00
Matthew Hooker 7ae1042696 common/config: strip keys appropriate for OpenStack. 2013-10-13 11:03:45 -07:00
Matthew Hooker 739fbc8962 common/config: config filter function [GH-521]
Fixes #521
2013-10-13 11:01:12 -07:00
Patrick Lucas aeb0e8bc06 Fix race condition after launching EC2 instance
It is possible for an instance to not immediately exist after it is
launched. Previously, InstanceStateRefreshFunc would crash if this race
condition were realized.

This change takes the exact same approach of the function above,
AMIStateRefreshFunc, treating 'InvalidInstanceID.NotFound' as if there
were an empty result.
2013-10-11 14:25:18 -07:00
Jack Pearkes 4afe900535 Update CHANGELOG 2013-10-11 01:07:06 +02:00
Jack Pearkes fcb83ced59 Merge pull request #518 from mitchellh/fix516
builder/digitalocean: Scrub config before logging
2013-10-10 16:02:21 -07:00
Matthew Hooker 2c5f742fa3 builder/digitalocean: Scrub config before logging [GH-516] 2013-10-10 15:04:42 -07:00
Rafael Garcia f0ebd2c61b provisioner/salt-masterless: use communicator.UploadDir 2013-10-09 18:51:30 -07:00
Rafael Garcia a91e6a9eed provisioner/salt-masterless: move states to /srv/salt, not /srv/salt/states
Latest version copies local state tree to {tmp}/states and then executes `mv {tmp}/states /srv/salt/`. This creates a `/srv/salt/states` instead of the correct `/srv/salt` directory.
2013-10-09 13:49:30 -07:00
Lyndon Washington 53b15e62cc Formatted contributions 2013-10-09 13:14:09 -04:00
Lyndon Washington 50bc71e4a2 Moved the 1 sec timeout to the export step 2013-10-09 12:59:47 -04:00
Mark Aaron Shirley 4c0ba7ff12 Update docs for Virtualbox and VMware as well 2013-10-08 09:34:37 -07:00
Mark Aaron Shirley 4f10ff2480 Add compression_level option to vagrant post-processors 2013-10-07 21:59:26 -07:00
Jonas Pfenniger c2ff4ded9c Use the same vagrant post-processor for amazon instances than EBS. Fixes #502 2013-10-05 23:32:41 +01:00
Mitchell Hashimoto 9b501b93d6 provisioner/shell: add more tests for UnixReader 2013-10-04 10:47:28 -07:00
Mitchell Hashimoto 58899f3888 builder/digitalocean: don't panic if error contains no message [GH-492] 2013-10-04 10:44:04 -07:00
Mitchell Hashimoto 475b65cb3b builder/virtualbox: detect vboxdrv issues [GH-488] 2013-10-02 17:13:22 -07:00
Mitchell Hashimoto c897998ec9 builder/virtualbox: error if version can't be detected [GH-488] 2013-10-02 17:11:42 -07:00
Mitchell Hashimoto 2057ed1366 Update CHANGELOG 2013-10-02 17:05:33 -07:00
Mitchell Hashimoto 1fd165bb87 Merge pull request #491 from patricklucas/ssh_timeout_should_be_error
common: An SSH timeout should be treated as an error
2013-10-02 17:04:52 -07:00
Patrick Lucas 9ca3c82228 An SSH timeout should be treated as an error 2013-10-02 14:55:28 -07:00
Mitchell Hashimoto 9afa4732dd Up version for dev 2013-10-02 10:56:04 -07:00
Mitchell Hashimoto 8597b5c5a3 v0.3.9 2013-10-02 08:34:29 -07:00
Mitchell Hashimoto 5b8c6582b6 provisioner/chef-solo: unused var 2013-10-02 08:29:12 -07:00
Mitchell Hashimoto fbd5a3c534 provisioner/chef-solo: alphabetize tests and such
/cc @netshade
2013-10-02 08:28:51 -07:00
Mitchell Hashimoto a4309ff33a Update CHANGELOG 2013-10-02 08:27:38 -07:00
Mitchell Hashimoto 96a0ae50b7 Merge pull request #483 from netshade/chef-environment-support
provisioner/chef-solo: chef environment support
2013-10-02 08:26:47 -07:00
Mitchell Hashimoto bb99b79733 Merge pull request #486 from tchaudhri/update_site_avail_commands_list
website: Updated web site doc. for avail commands list
2013-10-01 19:08:51 -07:00
Tehmasp Chaudhri 0d5cd549a4 Updated web site doc. for avail commands list 2013-10-01 19:45:11 -06:00
Chris 8ba2346fe3 formatting 2013-10-01 11:21:16 -04:00
Chris cf5abfc0f1 add environments_path and chef_environment support to chef provisioner 2013-10-01 11:20:36 -04:00
Matthew Hooker 2af19060ce Fix the way we run commands in chroot.
* Single quotes around chroot command cause it to fail.
* The chroot command also gets passed through the CommandWrapper template, so having sudo would clash with however the user wants to run it.
* Fix spelling mistake.
2013-10-01 09:35:55 +00:00
Matthew Hooker 43e76cc7d0 Fix UnixReader panic on empty lines.
Added a test for the failure.

Most of the scanner code looks like it's a modified version of
bufio.ScanLines, so I changed it to use that but always add a line feed.
2013-10-01 09:06:01 +00:00
Mitchell Hashimoto b230453b0a Update CHANGELOG 2013-09-30 22:03:14 -07:00
Jason A. Beranek b2a00e5772 provisioner/shell: fix provisioner to use UnixReader [GH-477]
Packer was using the original file reader when uploading, instead of the UnixReader /cc @mitchellh
2013-09-30 23:54:54 -05:00
Mitchell Hashimoto 7bad5923d3 go fmt 2013-09-30 11:39:52 -07:00
Mitchell Hashimoto ff163e4459 provisioner/shell: add another UnixReader test for sanity 2013-09-30 11:39:24 -07:00
Mitchell Hashimoto a793e03fb6 builder/amazon/chroot: remove unused comment 2013-09-30 09:35:58 -07:00
Mitchell Hashimoto c84dd13539 builder/amazon/chroot: CommandWrapper
/cc @mwhooker - I changed the interface up a bit to return an error,
since things should return errors in Go (the ui.Error bit was kind of
ghetto because it had no way to bubble that error up except through the
UI).

Using this, I made it so that the communicator uses both a
CommandWrapper and ShellCommand with chroot so that the chroot commannd
is also wrapped (it wasn't before).

I think the functionality of all this is the same but I'd love if you
could look it over and make sure.
2013-09-30 09:33:57 -07:00
Mitchell Hashimoto 5b7c745c3c builder/amazon/chroot: alphebatize the configs
/cc @mwhooker
2013-09-30 09:32:20 -07:00
Mitchell Hashimoto d916604c24 Update CHANGELOG 2013-09-30 09:02:18 -07:00
Mitchell Hashimoto 68fb788c97 Merge pull request #432 from mwhooker/chroot_cmd
build/amazon/chroot: command_wrapper to support sudo-less
2013-09-30 09:00:22 -07:00
Matthew Hooker c92b73899c Merge remote-tracking branch 'origin/master' 2013-09-30 04:46:15 +00:00
Kelsey Hightower 32dd300b35 Enable ansible-local provisioner 2013-09-29 16:24:21 -07:00
Kelsey Hightower f817b08bde Add tests for ansible provisioner default settings 2013-09-29 15:44:21 -07:00
Kelsey Hightower d437ecee2a Define the default staging dir using a const var 2013-09-29 15:42:42 -07:00
Kelsey Hightower 0ec41b0d90 Add tests for ansible-local provisioner 2013-09-29 14:54:49 -07:00
Kelsey Hightower f0d34f9897 Add ansible-local provisioner website docs 2013-09-29 13:56:43 -07:00
Kelsey Hightower 7674513aaa Add ansible-local provisioner 2013-09-29 13:38:37 -07:00
Mitchell Hashimoto d10a2596b8 Update CHANGELOG 2013-09-29 15:11:32 -04:00
Mitchell Hashimoto 604fe9ffaa Merge pull request #473 from mitchellh/do-error-message
builder/digitalocean: error message key is "message" not "error_message"
2013-09-29 12:10:00 -07:00
Jack Pearkes 22a3cdb6bc builder/digitalocean: error message key is "message" not "error_message" 2013-09-29 11:01:30 +02:00
Matthew Hooker 6f7aad23f0 comment, formatting. 2013-09-29 08:04:57 +00:00
Mitchell Hashimoto 581a33ab7d Update CHANGELOG 2013-09-28 09:39:34 +02:00
Mitchell Hashimoto 2fa53edb2d Merge pull request #470 from kirikaza/fix_chef_config
provisioner/chef-solo: fix .RolesPath and .DataBagsPath in the config template
2013-09-28 00:38:10 -07:00
Mitchell Hashimoto dc5fd1cc17 Merge pull request #469 from kirikaza/fix_final_sigterm
scripts: build.sh exits without being killed by SIGTERM
2013-09-28 00:37:44 -07:00
Matthew Hooker a84c74318c WIP copying files. 2013-09-28 01:10:33 +00:00
Matthew Hooker 837e641802 move wrapper definitions around. 2013-09-27 22:08:15 +00:00
Matthew Hooker 8857358830 building but there's an exec error. 2013-09-27 20:47:44 +00:00
Kirill Kazakov 7761c9329d provisioner/chef-solo: fix .RolesPath and .DataBagsPath in the config template 2013-09-27 19:13:55 +04:00
Matthew Hooker 40e15c84ef fixing up types. 2013-09-27 11:55:19 +00:00
Matthew Hooker 299ee6efd2 replace command config with wrapper command. 2013-09-27 10:54:53 +00:00
Kirill Kazakov ace8109867 scripts: build.sh exits without being killed by SIGTERM 2013-09-27 14:27:47 +04:00
Mitchell Hashimoto 2da011f46c Merge pull request #466 from podollb/master
website: removing the docs for digital ocean's optional param 'event_delay'
2013-09-26 23:55:25 -07:00
Matthew Hooker 93c2de0af3 fix chroot Upload command. 2013-09-27 02:11:28 +00:00
Matthew Hooker ccd9b56f9c wip 2013-09-26 18:34:01 -07:00
Matthew Hooker 6d158eaa29 bugfixes, wip 2013-09-27 01:28:06 +00:00
Ben Podoll 34cafbe68e removing the docs for digital ocean's optional param 'event_delay' as it is no longer used 2013-09-26 20:24:18 -05:00
Matthew Hooker 308b148bb4 get chroot command from proper place. 2013-09-26 16:39:37 -07:00
Matthew Hooker 6d4b834f61 Merge remote-tracking branch 'origin/master' into 430 2013-09-26 16:24:28 -07:00
Matthew Hooker 75d2d769a9 We're not using this log package. 2013-09-26 02:31:10 -07:00
Matthew Hooker 7e2b9de778 Unit tests. 2013-09-26 02:25:57 -07:00
Matthew Hooker 74adaa534e bugfix. 2013-09-26 01:35:29 -07:00
Matthew Hooker da467b7d67 using blocking cp method. 2013-09-26 01:32:53 -07:00
Matthew Hooker 0659ccdea0 Revert "notes/reorg."
This reverts commit 73c5aec24d.

Conflicts:
	builder/amazon/chroot/step_copy_files.go
2013-09-26 01:16:51 -07:00
Matthew Hooker 95f5cea285 wip 2013-09-26 01:08:06 -07:00
Matthew Hooker 03994f052d Merge branch 'master' into 430 2013-09-26 01:04:42 -07:00
Matthew Hooker a03807f8e8 wip 2013-09-26 00:58:25 -07:00
Matthew Hooker 73c5aec24d notes/reorg. 2013-09-26 00:31:07 -07:00
Matthew Hooker cd0cae207c pass chroot command to /bin/sh
this allows us to specify an arbitrary chroot command, i.e. for sudo
2013-09-25 03:20:22 -07:00
Mitchell Hashimoto 67d8803223 communicator/ssh: dir upload works when dir contains symlinks [Gh-449] 2013-09-25 10:42:49 +02:00
Mitchell Hashimoto cf89e8b783 Update CHANGELOG 2013-09-25 09:29:01 +02:00
Mitchell Hashimoto a3a654732c Merge pull request #459 from bpot/fix_amazon_chroot
builder/amazon-chroot: fix builder-amazon-chroot find states
2013-09-25 00:28:13 -07:00
Mitchell Hashimoto 2e095aa292 Merge pull request #460 from bpot/add_iam_role_to_chroot_documentation
website: document permissions required for amazon-chroot build to function.
2013-09-25 00:27:12 -07:00
Bob Potter 71d3f89b00 Document permissions required for amazon-chroot build to function. 2013-09-25 00:09:10 -05:00
Bob Potter db84c9f547 Update builder-amazon-chroot to work with WaitForState changes in 7c56148f 2013-09-24 23:44:03 -05:00
Mitchell Hashimoto 633d6d8d45 packer: skip the user var if there was an error 2013-09-24 23:01:16 +02:00
Mitchell Hashimoto 1e4d207149 packer: default user var values needn't be strings [GH-456] 2013-09-24 23:00:19 +02:00
Mitchell Hashimoto 6067167713 Merge pull request #458 from mwhooker/fixmake
use interactive shell to run build script
2013-09-24 13:27:18 -07:00
Matthew Hooker e1cb7a5ed4 use interactive shell to run build script. 2013-09-24 01:40:42 -07:00
Mitchell Hashimoto f3ef6fc1da Merge pull request #457 from CpuID/master
website: fix variable name
2013-09-24 00:32:23 -07:00
Nathan Sullivan 59e7c94bbc fix variable name 2013-09-24 16:14:34 +10:00
Mitchell Hashimoto 6509e899d0 Update version for dev 2013-09-22 16:15:55 -05:00
Matthew Hooker 277d54fe69 some documentation. 2013-09-17 06:01:23 -07:00
Matthew Hooker bac3d8ffed Allow user to override chroot command. 2013-09-17 05:58:16 -07:00
89 changed files with 1822 additions and 767 deletions
+57
View File
@@ -1,3 +1,60 @@
## 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:
+6
View File
@@ -18,6 +18,12 @@ 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.
+1 -1
View File
@@ -7,7 +7,7 @@ DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
all: deps
@mkdir -p bin/
@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
@./scripts/build.sh
@bash --norc -i ./scripts/build.sh
deps:
@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
+18 -13
View File
@@ -27,16 +27,19 @@ 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
@@ -78,18 +81,14 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
if b.config.MountCommand == "" {
b.config.MountCommand = "mount"
if b.config.CommandWrapper == "" {
b.config.CommandWrapper = "{{.Command}}"
}
if b.config.MountPath == "" {
b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}"
}
if b.config.UnmountCommand == "" {
b.config.UnmountCommand = "umount"
}
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
@@ -127,10 +126,8 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
templates := map[string]*string{
"device_path": &b.config.DevicePath,
"mount_command": &b.config.MountCommand,
"source_ami": &b.config.SourceAmi,
"unmount_command": &b.config.UnmountCommand,
"device_path": &b.config.DevicePath,
"source_ami": &b.config.SourceAmi,
}
for n, ptr := range templates {
@@ -146,7 +143,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return errs
}
log.Printf("Config: %+v", b.config)
log.Println(common.ScrubConfig(b.config), b.config.AccessKey, b.config.SecretKey)
return nil
}
@@ -167,12 +164,20 @@ 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))
// Build the steps
steps := []multistep.Step{
+11
View File
@@ -82,3 +82,14 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CommandWrapper(t *testing.T) {
b := &Builder{}
config := testConfig()
config["command_wrapper"] = "echo hi; {{.Command}}"
err := b.Prepare(config)
if err != nil {
t.Errorf("err: %s", err)
}
}
+15
View File
@@ -0,0 +1,15 @@
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)
}
+24 -38
View File
@@ -1,8 +1,10 @@
package chroot
import (
"fmt"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
@@ -13,16 +15,18 @@ import (
// Communicator is a special communicator that works by executing
// commands locally but within a chroot.
type Communicator struct {
Chroot string
Chroot string
CmdWrapper CommandWrapper
}
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
chrootCmdPath, err := exec.LookPath("chroot")
command, err := c.CmdWrapper(
fmt.Sprintf("chroot %s %s", c.Chroot, cmd.Command))
if err != nil {
return err
}
localCmd := exec.Command(chrootCmdPath, c.Chroot, "/bin/sh", "-c", cmd.Command)
localCmd := ShellCommand(command)
localCmd.Stdin = cmd.Stdin
localCmd.Stdout = cmd.Stdout
localCmd.Stderr = cmd.Stderr
@@ -46,7 +50,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
}
log.Printf(
"Chroot executation ended with '%d': '%s'",
"Chroot execution exited with '%d': '%s'",
exitStatus, cmd.Command)
cmd.SetExited(exitStatus)
}()
@@ -57,49 +61,31 @@ 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)
f, err := os.Create(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))
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(f, r); err != nil {
return err
}
return nil
return ShellCommand(cpCmd).Run()
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
walkFn := func(fullPath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
path, err := filepath.Rel(src, fullPath)
if err != nil {
return err
}
for _, e := range exclude {
if e == path {
log.Printf("Skipping excluded file: %s", path)
return nil
}
}
dstPath := filepath.Join(dst, path)
f, err := os.Open(fullPath)
if err != nil {
return err
}
defer f.Close()
return c.Upload(dstPath, f)
// 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 {
return err
}
log.Printf("Uploading directory '%s' to '%s'", src, dst)
return filepath.Walk(src, walkFn)
return ShellCommand(cpCmd).Run()
}
func (c *Communicator) Download(src string, w io.Writer) error {
+1
View File
@@ -0,0 +1 @@
package chroot
+44
View File
@@ -0,0 +1,44 @@
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.")
}
}
+7 -6
View File
@@ -60,7 +60,8 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No attachments on volume.")
}
return nil, resp.Volumes[0].Attachments[0].Status, nil
a := resp.Volumes[0].Attachments[0]
return a, a.Status, nil
},
}
@@ -111,12 +112,12 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
return nil, "", err
}
state := "detached"
if len(resp.Volumes[0].Attachments) > 0 {
state = resp.Volumes[0].Attachments[0].Status
v := resp.Volumes[0]
if len(v.Attachments) > 0 {
return v, v.Attachments[0].Status, nil
} else {
return v, "detached", nil
}
return nil, state, nil
},
}
@@ -15,10 +15,12 @@ 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)
// Create our communicator
comm := &Communicator{
Chroot: mountPath,
Chroot: mountPath,
CmdWrapper: wrappedCommand,
}
// Provision
+26 -44
View File
@@ -1,12 +1,11 @@
package chroot
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os"
"path/filepath"
)
@@ -23,6 +22,8 @@ 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)
s.files = make([]string, 0, len(config.CopyFiles))
if len(config.CopyFiles) > 0 {
@@ -32,8 +33,20 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
chrootPath := filepath.Join(mountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath)
if err := s.copySingle(chrootPath, path); err != nil {
err := fmt.Errorf("Error copying file: %s", err)
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)
ui.Error(err.Error())
return multistep.ActionHalt
@@ -54,11 +67,18 @@ func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
}
}
func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error {
func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if s.files != nil {
for _, file := range s.files {
log.Printf("Removing: %s", file)
if err := os.Remove(file); err != nil {
localCmdText, err := wrappedCommand(fmt.Sprintf("rm -f %s", file))
if err != nil {
return err
}
localCmd := ShellCommand(localCmdText)
if err := localCmd.Run(); err != nil {
return err
}
}
@@ -67,41 +87,3 @@ func (s *StepCopyFiles) CleanupFunc(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
}
+2 -1
View File
@@ -75,7 +75,8 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", err
}
return nil, resp.Volumes[0].Status, nil
v := resp.Volumes[0]
return v, v.Status, nil
},
}
+19 -7
View File
@@ -7,7 +7,6 @@ import (
"github.com/mitchellh/packer/packer"
"log"
"os"
"os/exec"
"path/filepath"
)
@@ -28,6 +27,7 @@ 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)
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
Device: filepath.Base(device),
@@ -59,8 +59,16 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer)
mountCommand := fmt.Sprintf("%s %s %s", config.MountCommand, device, mountPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
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)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
@@ -90,12 +98,16 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
return nil
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unmounting the root device...")
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, s.mountPath)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
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)
if err := cmd.Run(); err != nil {
return fmt.Errorf("Error unmounting root device: %s", err)
}
+18 -9
View File
@@ -6,7 +6,6 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"os/exec"
)
// StepMountExtra mounts the attached device.
@@ -21,6 +20,7 @@ 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)
s.mounts = make([]string, 0, len(config.ChrootMounts))
@@ -42,13 +42,19 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
stderr := new(bytes.Buffer)
mountCommand := fmt.Sprintf(
"%s %s %s %s",
config.MountCommand,
mountCommand, err := wrappedCommand(fmt.Sprintf(
"mount %s %s %s",
flags,
mountInfo[1],
innerPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
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)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
@@ -79,15 +85,18 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
return nil
}
config := state.Get("config").(*Config)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
for len(s.mounts) > 0 {
var path string
lastIndex := len(s.mounts) - 1
path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex]
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, path)
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
stderr := new(bytes.Buffer)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
cmd := ShellCommand(unmountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf(
+2 -1
View File
@@ -51,7 +51,8 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No snapshots found.")
}
return nil, resp.Snapshots[0].Status, nil
s := resp.Snapshots[0]
return s, s.Status, nil
},
}
+7 -11
View File
@@ -1,21 +1,15 @@
package common
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"testing"
)
func TestArtifact_Impl(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var actual packer.Artifact
assert.Implementor(&Artifact{}, &actual, "should be an Artifact")
var _ packer.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `east:foo,west:bar`
amis := make(map[string]string)
@@ -27,12 +21,12 @@ func TestArtifactId(t *testing.T) {
}
result := a.Id()
assert.Equal(result, expected, "should match output")
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactString(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `AMIs were created:
east: foo
@@ -44,5 +38,7 @@ west: bar`
a := &Artifact{Amis: amis}
result := a.String()
assert.Equal(result, expected, "should match output")
if result != expected {
t.Fatalf("bad: %s", result)
}
}
+8 -5
View File
@@ -1,14 +1,12 @@
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",
@@ -36,6 +34,11 @@ func TestBlockDevice(t *testing.T) {
LaunchMappings: []BlockDevice{blockDevice},
}
assert.Equal(ec2Mapping, blockDevices.BuildAMIDevices(), "should match output")
assert.Equal(ec2Mapping, blockDevices.BuildLaunchDevices(), "should match output")
if !reflect.DeepEqual(ec2Mapping, blockDevices.BuildAMIDevices()) {
t.Fatalf("bad: %#v", ec2Mapping)
}
if !reflect.DeepEqual(ec2Mapping, blockDevices.BuildLaunchDevices()) {
t.Fatalf("bad: %#v", ec2Mapping)
}
}
+8 -3
View File
@@ -62,11 +62,16 @@ 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 InstanceStateRefresh: %s", err)
return nil, "", err
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 len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
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
+2 -3
View File
@@ -1,11 +1,10 @@
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"
@@ -35,7 +34,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", hex.EncodeToString(identifier.NewUUID().Raw()))
groupName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID())
log.Printf("Temporary group name: %s", groupName)
group := ec2.SecurityGroup{
Name: groupName,
+1 -1
View File
@@ -56,7 +56,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return errs
}
log.Printf("Config: %+v", b.config)
log.Println(common.ScrubConfig(b.config), b.config.AccessKey, b.config.SecretKey)
return nil
}
+1 -1
View File
@@ -159,7 +159,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return errs
}
log.Printf("Config: %+v", b.config)
log.Println(common.ScrubConfig(b.config), b.config.AccessKey, b.config.SecretKey)
return nil
}
+7 -1
View File
@@ -227,7 +227,13 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin
}
if status == "ERROR" {
status = decodedResponse["error_message"].(string)
statusRaw, ok := decodedResponse["message"]
if ok {
status = statusRaw.(string)
} else {
status = fmt.Sprintf(
"Unknown error. Full response body: %s", body)
}
}
lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
+1 -1
View File
@@ -164,7 +164,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return errs
}
log.Printf("Config: %+v", b.config)
common.ScrubConfig(b.config, b.config.ClientID, b.config.APIKey)
return nil
}
+2 -3
View File
@@ -1,10 +1,9 @@
package digitalocean
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
)
@@ -21,7 +20,7 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Creating droplet...")
// Some random droplet name as it's temporary
name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw()))
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
// Create the droplet based on configuration
dropletId, err := client.CreateDroplet(name, c.SizeID, c.ImageID, c.RegionID, sshKeyId)
+2 -3
View File
@@ -1,15 +1,14 @@
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"
)
@@ -43,7 +42,7 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
// The name of the public key on DO
name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw()))
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
// Create the key!
keyId, err := client.CreateKey(name, pub_sshformat)
+7 -11
View File
@@ -1,21 +1,15 @@
package openstack
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"testing"
)
func TestArtifact_Impl(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var actual packer.Artifact
assert.Implementor(&Artifact{}, &actual, "should be an Artifact")
var _ packer.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `b8cdf55b-c916-40bd-b190-389ec144c4ed`
a := &Artifact{
@@ -23,17 +17,19 @@ func TestArtifactId(t *testing.T) {
}
result := a.Id()
assert.Equal(result, expected, "should match output")
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactString(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := "An image was created: b8cdf55b-c916-40bd-b190-389ec144c4ed"
a := &Artifact{
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
}
result := a.String()
assert.Equal(result, expected, "should match output")
if result != expected {
t.Fatalf("bad: %s", result)
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return errs
}
log.Printf("Config: %+v", b.config)
log.Println(common.ScrubConfig(b.config), b.config.Password)
return nil
}
+2 -3
View File
@@ -1,10 +1,9 @@
package openstack
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
@@ -19,7 +18,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
keyName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID())
log.Printf("temporary keypair name: %s", keyName)
keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName})
if err != nil {
+1
View File
@@ -154,6 +154,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"iso_url": &b.config.RawSingleISOUrl,
"output_directory": &b.config.OutputDir,
"shutdown_command": &b.config.ShutdownCommand,
"ssh_key_path": &b.config.SSHKeyPath,
"ssh_password": &b.config.SSHPassword,
"ssh_username": &b.config.SSHUser,
"virtualbox_version_file": &b.config.VBoxVersionFile,
+38 -2
View File
@@ -11,8 +11,15 @@ import (
)
// A driver is able to talk to VirtualBox and perform certain
// operations with it.
// operations with it. Some of the operations on here may seem overly
// specific, but they were built specifically in mind to handle features
// of the VirtualBox builder for Packer, and to abstract differences in
// versions out of the builder steps, so sometimes the methods are
// extremely specific.
type Driver interface {
// Create a SATA controller.
CreateSATAController(vm string, controller string) error
// Checks if the VM with the given name is running.
IsRunning(string) (bool, error)
@@ -40,6 +47,27 @@ type VBox42Driver struct {
VBoxManagePath string
}
func (d *VBox42Driver) CreateSATAController(vmName string, name string) error {
version, err := d.Version()
if err != nil {
return err
}
portCountArg := "sataportcount"
if strings.HasPrefix(version, "4.3") {
portCountArg = "portcount"
}
command := []string{
"storagectl", vmName,
"--name", name,
"--add", "sata",
portCountArg, "1",
}
return d.VBoxManage(command...)
}
func (d *VBox42Driver) IsRunning(name string) (bool, error) {
var stdout bytes.Buffer
@@ -135,9 +163,17 @@ func (d *VBox42Driver) Version() (string, error) {
versionOutput := strings.TrimSpace(stdout.String())
log.Printf("VBoxManage --version output: %s", versionOutput)
// If the "--version" output contains vboxdrv, then this is indicative
// of problems with the VirtualBox setup and we shouldn't really continue,
// whether or not we can read the version.
if strings.Contains(versionOutput, "vboxdrv") {
return "", fmt.Errorf("VirtualBox is not properly setup: %s", versionOutput)
}
versionRe := regexp.MustCompile("[^.0-9]")
matches := versionRe.Split(versionOutput, 2)
if len(matches) == 0 {
if len(matches) == 0 || matches[0] == "" {
return "", fmt.Errorf("No version found: %s", versionOutput)
}
+1 -7
View File
@@ -56,13 +56,7 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
// that.
if config.HardDriveInterface == "sata" {
controllerName = "SATA Controller"
command = []string{
"storagectl", vmName,
"--name", controllerName,
"--add", "sata",
"--sataportcount", "1",
}
if err := driver.VBoxManage(command...); err != nil {
if err := driver.CreateSATAController(vmName, controllerName); err != nil {
err := fmt.Errorf("Error creating disk controller: %s", err)
state.Put("error", err)
ui.Error(err.Error())
+6
View File
@@ -4,7 +4,9 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"path/filepath"
"time"
)
// This step cleans up forwarded ports and exports the VM to an OVF.
@@ -21,6 +23,10 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
// Wait a second to ensure VM is really shutdown.
log.Println("1 second timeout to ensure VM is really shutdown")
time.Sleep(1 * time.Second)
// Clear out the Packer-created forwarding rule
ui.Say("Preparing to export machine...")
ui.Message(fmt.Sprintf("Deleting forwarded port mapping for SSH (host port %d)", state.Get("sshHostPort")))
+1
View File
@@ -148,6 +148,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"iso_url": &b.config.RawSingleISOUrl,
"output_directory": &b.config.OutputDir,
"shutdown_command": &b.config.ShutdownCommand,
"ssh_key_path": &b.config.SSHKeyPath,
"ssh_password": &b.config.SSHPassword,
"ssh_username": &b.config.SSHUser,
"tools_upload_flavor": &b.config.ToolsUploadFlavor,
+10 -11
View File
@@ -2,7 +2,6 @@ package build
import (
"bytes"
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"testing"
)
@@ -23,33 +22,33 @@ func testEnvironment() packer.Environment {
}
func TestCommand_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var actual packer.Command
assert.Implementor(new(Command), &actual, "should be a Command")
var _ packer.Command = new(Command)
}
func TestCommand_Run_NoArgs(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
command := new(Command)
result := command.Run(testEnvironment(), make([]string, 0))
assert.Equal(result, 1, "no args should error")
if result != 1 {
t.Fatalf("bad: %d", result)
}
}
func TestCommand_Run_MoreThanOneArg(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
command := new(Command)
args := []string{"one", "two"}
result := command.Run(testEnvironment(), args)
assert.Equal(result, 1, "More than one arg should fail")
if result != 1 {
t.Fatalf("bad: %d", result)
}
}
func TestCommand_Run_MissingFile(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
command := new(Command)
args := []string{"i-better-not-exist"}
result := command.Run(testEnvironment(), args)
assert.Equal(result, 1, "a non-existent file should error")
if result != 1 {
t.Fatalf("bad: %d", result)
}
}
+10
View File
@@ -12,6 +12,16 @@ import (
"strings"
)
// ScrubConfig is a helper that returns a string representation of
// any struct with the given values stripped out.
func ScrubConfig(target interface{}, values ...string) string {
conf := fmt.Sprintf("Config: %+v", target)
for _, value := range values {
conf = strings.Replace(conf, value, "<Filtered>", -1)
}
return conf
}
// CheckUnusedConfig is a helper that makes sure that the there are no
// unused configuration keys, properly ignoring keys that don't matter.
func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError {
+17
View File
@@ -159,3 +159,20 @@ func TestDownloadableURL_FilePaths(t *testing.T) {
}
}
}
func TestScrubConfig(t *testing.T) {
type Inner struct {
Baz string
}
type Local struct {
Foo string
Bar string
Inner
}
c := Local{"foo", "bar", Inner{"bar"}}
expect := "Config: {Foo:foo Bar:<Filtered> Inner:{Baz:<Filtered>}}"
conf := ScrubConfig(c, c.Bar)
if conf != expect {
t.Fatalf("got %s, expected %s", conf, expect)
}
}
+3 -1
View File
@@ -72,7 +72,9 @@ WaitLoop:
state.Put("communicator", comm)
break WaitLoop
case <-timeout:
ui.Error("Timeout waiting for SSH.")
err := fmt.Errorf("Timeout waiting for SSH.")
state.Put("error", err)
ui.Error(err.Error())
close(cancel)
return multistep.ActionHalt
case <-time.After(1 * time.Second):
+23
View File
@@ -0,0 +1,23 @@
package uuid
import (
"fmt"
"math/rand"
"time"
)
// Generates a time ordered UUID. Top 32 bits are a timestamp,
// bottom 96 are random.
func TimeOrderedUUID() string {
unix := uint32(time.Now().UTC().Unix())
rand1 := rand.Uint32()
rand2 := rand.Uint32()
rand3 := rand.Uint32()
return fmt.Sprintf("%08x-%04x-%04x-%04x-%04x%08x",
unix,
uint16(rand1>>16),
uint16(rand1&0xffff),
uint16(rand2>>16),
uint16(rand2&0xffff),
rand3)
}
+12
View File
@@ -0,0 +1,12 @@
package uuid
import (
"testing"
)
func TestTimeOrderedUuid(t *testing.T) {
uuid := TimeOrderedUUID()
if len(uuid) != 36 {
t.Fatalf("bad: %s", uuid)
}
}
+29 -2
View File
@@ -238,6 +238,14 @@ func (c *comm) reconnect() (err error) {
log.Printf("reconnecting to TCP connection for SSH")
c.conn, err = c.config.Connection()
if err != nil {
// Explicitly set this to the REAL nil. Connection() can return
// a nil implementation of net.Conn which will make the
// "if c.conn == nil" check fail above. Read here for more information
// on this psychotic language feature:
//
// http://golang.org/doc/faq#nil_error
c.conn = nil
log.Printf("reconnection error: %s", err)
return
}
@@ -408,8 +416,27 @@ func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) e
for _, fi := range fs {
realPath := filepath.Join(root, fi.Name())
if !fi.IsDir() {
// It is a regular file, just upload it
// Track if this is actually a symlink to a directory. If it is
// a symlink to a file we don't do any special behavior because uploading
// a file just works. If it is a directory, we need to know so we
// treat it as such.
isSymlinkToDir := false
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
symPath, err := filepath.EvalSymlinks(realPath)
if err != nil {
return err
}
symFi, err := os.Lstat(symPath)
if err != nil {
return err
}
isSymlinkToDir = symFi.IsDir()
}
if !fi.IsDir() && !isSymlinkToDir {
// It is a regular file (or symlink to a file), just upload it
f, err := os.Open(realPath)
if err != nil {
return err
+1
View File
@@ -40,6 +40,7 @@ const defaultConfig = `
},
"provisioners": {
"ansible-local": "packer-provisioner-ansible-local",
"chef-solo": "packer-provisioner-chef-solo",
"file": "packer-provisioner-file",
"puppet-masterless": "packer-provisioner-puppet-masterless",
+64 -33
View File
@@ -1,7 +1,6 @@
package packer
import (
"cgl.tideland.biz/asserts"
"reflect"
"testing"
)
@@ -41,32 +40,43 @@ func testDefaultPackerConfig() map[string]interface{} {
}
}
func TestBuild_Name(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
build := testBuild()
assert.Equal(build.Name(), "test", "should have a name")
if build.Name() != "test" {
t.Fatalf("bad: %s", build.Name())
}
}
func TestBuild_Prepare(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
packerConfig := testDefaultPackerConfig()
build := testBuild()
builder := build.builder.(*TestBuilder)
build.Prepare(nil)
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
if !builder.prepareCalled {
t.Fatal("should be called")
}
if !reflect.DeepEqual(builder.prepareConfig, []interface{}{42, packerConfig}) {
t.Fatalf("bad: %#v", builder.prepareConfig)
}
coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*MockProvisioner)
assert.True(prov.PrepCalled, "prepare should be called")
assert.Equal(prov.PrepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config")
if !prov.PrepCalled {
t.Fatal("prep should be called")
}
if !reflect.DeepEqual(prov.PrepConfigs, []interface{}{42, packerConfig}) {
t.Fatalf("bad: %#v", prov.PrepConfigs)
}
corePP := build.postProcessors[0][0]
pp := corePP.processor.(*TestPostProcessor)
assert.True(pp.configCalled, "config should be called")
assert.Equal(pp.configVal, []interface{}{make(map[string]interface{}), packerConfig}, "config should have right value")
if !pp.configCalled {
t.Fatal("should be called")
}
if !reflect.DeepEqual(pp.configVal, []interface{}{make(map[string]interface{}), packerConfig}) {
t.Fatalf("bad: %#v", pp.configVal)
}
}
func TestBuild_Prepare_Twice(t *testing.T) {
@@ -90,8 +100,6 @@ func TestBuild_Prepare_Twice(t *testing.T) {
}
func TestBuild_Prepare_Debug(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
packerConfig := testDefaultPackerConfig()
packerConfig[DebugConfigKey] = true
@@ -100,13 +108,21 @@ func TestBuild_Prepare_Debug(t *testing.T) {
build.SetDebug(true)
build.Prepare(nil)
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
if !builder.prepareCalled {
t.Fatalf("should be called")
}
if !reflect.DeepEqual(builder.prepareConfig, []interface{}{42, packerConfig}) {
t.Fatalf("bad: %#v", builder.prepareConfig)
}
coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*MockProvisioner)
assert.True(prov.PrepCalled, "prepare should be called")
assert.Equal(prov.PrepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config")
if !prov.PrepCalled {
t.Fatal("prepare should be called")
}
if !reflect.DeepEqual(prov.PrepConfigs, []interface{}{42, packerConfig}) {
t.Fatalf("bad: %#v", prov.PrepConfigs)
}
}
func TestBuildPrepare_variables_default(t *testing.T) {
@@ -186,37 +202,49 @@ func TestBuildPrepare_variablesRequired(t *testing.T) {
}
func TestBuild_Run(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
cache := &TestCache{}
ui := testUi()
build := testBuild()
build.Prepare(nil)
artifacts, err := build.Run(ui, cache)
assert.Nil(err, "should not error")
assert.Equal(len(artifacts), 2, "should have two artifacts")
if err != nil {
t.Fatalf("err: %s", err)
}
if len(artifacts) != 2 {
t.Fatalf("bad: %#v", artifacts)
}
// Verify builder was run
builder := build.builder.(*TestBuilder)
assert.True(builder.runCalled, "run should be called")
if !builder.runCalled {
t.Fatal("should be called")
}
// Verify hooks are disapatchable
dispatchHook := builder.runHook
dispatchHook.Run("foo", nil, nil, 42)
hook := build.hooks["foo"][0].(*MockHook)
assert.True(hook.RunCalled, "run should be called")
assert.Equal(hook.RunData, 42, "should have correct data")
if !hook.RunCalled {
t.Fatal("should be called")
}
if hook.RunData != 42 {
t.Fatalf("bad: %#v", hook.RunData)
}
// Verify provisioners run
dispatchHook.Run(HookProvision, nil, nil, 42)
prov := build.provisioners[0].provisioner.(*MockProvisioner)
assert.True(prov.ProvCalled, "provision should be called")
if !prov.ProvCalled {
t.Fatal("should be called")
}
// Verify post-processor was run
pp := build.postProcessors[0][0].processor.(*TestPostProcessor)
assert.True(pp.ppCalled, "post processor should be called")
if !pp.ppCalled {
t.Fatal("should be called")
}
}
func TestBuild_Run_Artifacts(t *testing.T) {
@@ -356,23 +384,26 @@ func TestBuild_Run_Artifacts(t *testing.T) {
}
func TestBuild_RunBeforePrepare(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
defer func() {
p := recover()
assert.NotNil(p, "should panic")
assert.Equal(p.(string), "Prepare must be called first", "right panic")
if p == nil {
t.Fatal("should panic")
}
if p.(string) != "Prepare must be called first" {
t.Fatalf("bad: %s", p.(string))
}
}()
testBuild().Run(testUi(), &TestCache{})
}
func TestBuild_Cancel(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
build := testBuild()
build.Cancel()
builder := build.builder.(*TestBuilder)
assert.True(builder.cancelCalled, "cancel should be called")
if !builder.cancelCalled {
t.Fatal("cancel should be called")
}
}
+2 -3
View File
@@ -2,9 +2,8 @@ package packer
import (
"bytes"
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/packer/common/uuid"
"strconv"
"text/template"
"time"
@@ -94,5 +93,5 @@ func templateTimestamp() string {
}
func templateUuid() string {
return hex.EncodeToString(identifier.NewUUID().Raw())
return uuid.TimeOrderedUUID()
}
+1 -1
View File
@@ -80,7 +80,7 @@ func TestConfigTemplateProcess_uuid(t *testing.T) {
t.Fatalf("err: %s", err)
}
if len(result) != 32 {
if len(result) != 36 {
t.Fatalf("err: %s", result)
}
}
+154 -97
View File
@@ -2,12 +2,11 @@ package packer
import (
"bytes"
"cgl.tideland.biz/asserts"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"reflect"
"strings"
"testing"
)
@@ -44,40 +43,48 @@ func testEnvironment() Environment {
}
func TestEnvironment_DefaultConfig_Commands(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
assert.Empty(config.Commands, "should have no commands")
if len(config.Commands) != 0 {
t.Fatalf("bad: %#v", config.Commands)
}
}
func TestEnvironment_DefaultConfig_Ui(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
assert.NotNil(config.Ui, "default UI should not be nil")
if config.Ui == nil {
t.Fatal("config.Ui should not be nil")
}
rwUi, ok := config.Ui.(*BasicUi)
assert.True(ok, "default UI should be BasicUi")
assert.Equal(rwUi.Writer, os.Stdout, "default UI should go to stdout")
assert.Equal(rwUi.Reader, os.Stdin, "default UI should read from stdin")
if !ok {
t.Fatal("default UI should be BasicUi")
}
if rwUi.Writer != os.Stdout {
t.Fatal("default UI should go to stdout")
}
if rwUi.Reader != os.Stdin {
t.Fatal("default UI reader should go to stdin")
}
}
func TestNewEnvironment_NoConfig(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
env, err := NewEnvironment(nil)
assert.Nil(env, "env should be nil")
assert.NotNil(err, "should be an error")
if env != nil {
t.Fatal("env should be nil")
}
if err == nil {
t.Fatal("should have error")
}
}
func TestEnvironment_NilComponents(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components = *new(ComponentFinder)
env, err := NewEnvironment(config)
assert.Nil(err, "should not have an error")
if err != nil {
t.Fatalf("err: %s", err)
}
// All of these should not cause panics... so we don't assert
// anything but if there is a panic in the test then yeah, something
@@ -90,8 +97,6 @@ func TestEnvironment_NilComponents(t *testing.T) {
}
func TestEnvironment_Builder(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
builder := &TestBuilder{}
builders := make(map[string]Builder)
builders["foo"] = builder
@@ -101,33 +106,43 @@ func TestEnvironment_Builder(t *testing.T) {
env, _ := NewEnvironment(config)
returnedBuilder, err := env.Builder("foo")
assert.Nil(err, "should be no error")
assert.Equal(returnedBuilder, builder, "should return correct builder")
if err != nil {
t.Fatalf("err: %s", err)
}
if returnedBuilder != builder {
t.Fatalf("bad: %#v", returnedBuilder)
}
}
func TestEnvironment_Builder_NilError(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.Builder = func(n string) (Builder, error) { return nil, nil }
env, _ := NewEnvironment(config)
returnedBuilder, err := env.Builder("foo")
assert.NotNil(err, "should be an error")
assert.Nil(returnedBuilder, "should be no builder")
if err == nil {
t.Fatal("should have error")
}
if returnedBuilder != nil {
t.Fatalf("bad: %#v", returnedBuilder)
}
}
func TestEnvironment_Builder_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.Builder = func(n string) (Builder, error) { return nil, errors.New("foo") }
env, _ := NewEnvironment(config)
returnedBuilder, err := env.Builder("foo")
assert.NotNil(err, "should be an error")
assert.Equal(err.Error(), "foo", "should be correct error")
assert.Nil(returnedBuilder, "should be no builder")
if err == nil {
t.Fatal("should have error")
}
if err.Error() != "foo" {
t.Fatalf("bad err: %s", err)
}
if returnedBuilder != nil {
t.Fatalf("should be nil: %#v", returnedBuilder)
}
}
func TestEnvironment_Cache(t *testing.T) {
@@ -139,20 +154,20 @@ func TestEnvironment_Cache(t *testing.T) {
}
func TestEnvironment_Cli_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.Command = func(n string) (Command, error) { return nil, errors.New("foo") }
env, _ := NewEnvironment(config)
_, err := env.Cli([]string{"foo"})
assert.NotNil(err, "should be an error")
assert.Equal(err.Error(), "foo", "should be correct error")
if err == nil {
t.Fatal("should have error")
}
if err.Error() != "foo" {
t.Fatalf("bad: %s", err)
}
}
func TestEnvironment_Cli_CallsRun(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
command := &TestCommand{}
commands := make(map[string]Command)
commands["foo"] = command
@@ -163,25 +178,33 @@ func TestEnvironment_Cli_CallsRun(t *testing.T) {
env, _ := NewEnvironment(config)
exitCode, err := env.Cli([]string{"foo", "bar", "baz"})
assert.Nil(err, "should be no error")
assert.Equal(exitCode, 0, "runs foo command")
assert.True(command.runCalled, "run should've been called")
assert.Equal(command.runEnv, env, "should've ran with env")
assert.Equal(command.runArgs, []string{"bar", "baz"}, "should have right args")
if err != nil {
t.Fatalf("err: %s", err)
}
if exitCode != 0 {
t.Fatalf("bad: %d", exitCode)
}
if !command.runCalled {
t.Fatal("command should be run")
}
if command.runEnv != env {
t.Fatalf("bad env: %#v", command.runEnv)
}
if !reflect.DeepEqual(command.runArgs, []string{"bar", "baz"}) {
t.Fatalf("bad: %#v", command.runArgs)
}
}
func TestEnvironment_DefaultCli_Empty(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
defaultEnv := testEnvironment()
exitCode, _ := defaultEnv.Cli([]string{})
assert.Equal(exitCode, 1, "CLI with no args")
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
}
func TestEnvironment_DefaultCli_Help(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
defaultEnv := testEnvironment()
// A little lambda to help us test the output actually contains help
@@ -189,44 +212,52 @@ func TestEnvironment_DefaultCli_Help(t *testing.T) {
buffer := defaultEnv.Ui().(*BasicUi).Writer.(*bytes.Buffer)
output := buffer.String()
buffer.Reset()
assert.True(strings.Contains(output, "usage: packer"), "should print help")
if !strings.Contains(output, "usage: packer") {
t.Fatalf("should contain help: %#v", output)
}
}
// Test "--help"
exitCode, _ := defaultEnv.Cli([]string{"--help"})
assert.Equal(exitCode, 1, "--help should print")
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
testOutput()
// Test "-h"
exitCode, _ = defaultEnv.Cli([]string{"--help"})
assert.Equal(exitCode, 1, "--help should print")
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
testOutput()
}
func TestEnvironment_DefaultCli_Version(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
defaultEnv := testEnvironment()
versionCommands := []string{"version", "--version", "-v"}
for _, command := range versionCommands {
exitCode, _ := defaultEnv.Cli([]string{command})
assert.Equal(exitCode, 0, fmt.Sprintf("%s should work", command))
if exitCode != 0 {
t.Fatalf("bad: %d", exitCode)
}
// Test the --version and -v can appear anywhere
exitCode, _ = defaultEnv.Cli([]string{"bad", command})
if command != "version" {
assert.Equal(exitCode, 0, fmt.Sprintf("%s should work anywhere", command))
if exitCode != 0 {
t.Fatalf("bad: %d", exitCode)
}
} else {
assert.Equal(exitCode, 1, fmt.Sprintf("%s should NOT work anywhere", command))
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
}
}
}
func TestEnvironment_Hook(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
hook := &MockHook{}
hooks := make(map[string]Hook)
hooks["foo"] = hook
@@ -236,38 +267,46 @@ func TestEnvironment_Hook(t *testing.T) {
env, _ := NewEnvironment(config)
returned, err := env.Hook("foo")
assert.Nil(err, "should be no error")
assert.Equal(returned, hook, "should return correct hook")
if err != nil {
t.Fatalf("err: %s", err)
}
if returned != hook {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironment_Hook_NilError(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.Hook = func(n string) (Hook, error) { return nil, nil }
env, _ := NewEnvironment(config)
returned, err := env.Hook("foo")
assert.NotNil(err, "should be an error")
assert.Nil(returned, "should be no hook")
if err == nil {
t.Fatal("should have error")
}
if returned != nil {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironment_Hook_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.Hook = func(n string) (Hook, error) { return nil, errors.New("foo") }
env, _ := NewEnvironment(config)
returned, err := env.Hook("foo")
assert.NotNil(err, "should be an error")
assert.Equal(err.Error(), "foo", "should be correct error")
assert.Nil(returned, "should be no hook")
if err == nil {
t.Fatal("should have error")
}
if err.Error() != "foo" {
t.Fatalf("err: %s", err)
}
if returned != nil {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironment_PostProcessor(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
pp := &TestPostProcessor{}
pps := make(map[string]PostProcessor)
pps["foo"] = pp
@@ -277,38 +316,46 @@ func TestEnvironment_PostProcessor(t *testing.T) {
env, _ := NewEnvironment(config)
returned, err := env.PostProcessor("foo")
assert.Nil(err, "should be no error")
assert.Equal(returned, pp, "should return correct pp")
if err != nil {
t.Fatalf("err: %s", err)
}
if returned != pp {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironment_PostProcessor_NilError(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, nil }
env, _ := NewEnvironment(config)
returned, err := env.PostProcessor("foo")
assert.NotNil(err, "should be an error")
assert.Nil(returned, "should be no pp")
if err == nil {
t.Fatal("should have error")
}
if returned != nil {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironment_PostProcessor_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, errors.New("foo") }
env, _ := NewEnvironment(config)
returned, err := env.PostProcessor("foo")
assert.NotNil(err, "should be an error")
assert.Equal(err.Error(), "foo", "should be correct error")
assert.Nil(returned, "should be no pp")
if err == nil {
t.Fatal("should be an error")
}
if err.Error() != "foo" {
t.Fatalf("bad err: %s", err)
}
if returned != nil {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironmentProvisioner(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
p := &MockProvisioner{}
ps := make(map[string]Provisioner)
ps["foo"] = p
@@ -318,25 +365,29 @@ func TestEnvironmentProvisioner(t *testing.T) {
env, _ := NewEnvironment(config)
returned, err := env.Provisioner("foo")
assert.Nil(err, "should be no error")
assert.Equal(returned, p, "should return correct provisioner")
if err != nil {
t.Fatalf("err: %s", err)
}
if returned != p {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironmentProvisioner_NilError(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.Provisioner = func(n string) (Provisioner, error) { return nil, nil }
env, _ := NewEnvironment(config)
returned, err := env.Provisioner("foo")
assert.NotNil(err, "should be an error")
assert.Nil(returned, "should be no provisioner")
if err == nil {
t.Fatal("should have error")
}
if returned != nil {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironmentProvisioner_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
config := DefaultEnvironmentConfig()
config.Components.Provisioner = func(n string) (Provisioner, error) {
return nil, errors.New("foo")
@@ -344,14 +395,18 @@ func TestEnvironmentProvisioner_Error(t *testing.T) {
env, _ := NewEnvironment(config)
returned, err := env.Provisioner("foo")
assert.NotNil(err, "should be an error")
assert.Equal(err.Error(), "foo", "should be correct error")
assert.Nil(returned, "should be no provisioner")
if err == nil {
t.Fatal("should have error")
}
if err.Error() != "foo" {
t.Fatalf("err: %s", err)
}
if returned != nil {
t.Fatalf("bad: %#v", returned)
}
}
func TestEnvironment_SettingUi(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
ui := &BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
@@ -362,5 +417,7 @@ func TestEnvironment_SettingUi(t *testing.T) {
env, _ := NewEnvironment(config)
assert.Equal(env.Ui(), ui, "UIs should be equal")
if env.Ui() != ui {
t.Fatalf("UI should be equal: %#v", env.Ui())
}
}
+10 -12
View File
@@ -1,7 +1,6 @@
package packer
import (
"cgl.tideland.biz/asserts"
"sync"
"testing"
"time"
@@ -42,12 +41,7 @@ func (h *CancelHook) Cancel() {
}
func TestDispatchHook_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var r Hook
c := &DispatchHook{}
assert.Implementor(c, &r, "should be a Hook")
var _ Hook = new(DispatchHook)
}
func TestDispatchHook_Run_NoHooks(t *testing.T) {
@@ -57,8 +51,6 @@ func TestDispatchHook_Run_NoHooks(t *testing.T) {
}
func TestDispatchHook_Run(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
hook := &MockHook{}
mapping := make(map[string][]Hook)
@@ -66,9 +58,15 @@ func TestDispatchHook_Run(t *testing.T) {
dh := &DispatchHook{Mapping: mapping}
dh.Run("foo", nil, nil, 42)
assert.True(hook.RunCalled, "run should be called")
assert.Equal(hook.RunName, "foo", "should be proper event")
assert.Equal(hook.RunData, 42, "should be correct data")
if !hook.RunCalled {
t.Fatal("should be called")
}
if hook.RunName != "foo" {
t.Fatalf("bad: %s", hook.RunName)
}
if hook.RunData != 42 {
t.Fatalf("bad: %#v", hook.RunData)
}
}
func TestDispatchHook_cancel(t *testing.T) {
+3 -4
View File
@@ -1,7 +1,6 @@
package packer
import (
"cgl.tideland.biz/asserts"
"errors"
"testing"
)
@@ -15,8 +14,6 @@ func TestMultiError_Impl(t *testing.T) {
}
func TestMultiErrorError(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `2 error(s) occurred:
* foo
@@ -28,7 +25,9 @@ func TestMultiErrorError(t *testing.T) {
}
multi := &MultiError{errors}
assert.Equal(multi.Error(), expected, "should have proper error")
if multi.Error() != expected {
t.Fatalf("bad: %s", multi.Error())
}
}
func TestMultiErrorAppend_MultiError(t *testing.T) {
+20 -14
View File
@@ -1,9 +1,9 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
"testing"
)
@@ -30,8 +30,6 @@ func (testArtifact) Destroy() error {
}
func TestArtifactRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the interface to test
a := new(testArtifact)
@@ -42,21 +40,29 @@ func TestArtifactRPC(t *testing.T) {
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be able to connect")
if err != nil {
t.Fatalf("err: %s", err)
}
aClient := Artifact(client)
// Test
assert.Equal(aClient.BuilderId(), "bid", "should have correct builder ID")
assert.Equal(aClient.Files(), []string{"a", "b"}, "should have correct builder ID")
assert.Equal(aClient.Id(), "id", "should have correct builder ID")
assert.Equal(aClient.String(), "string", "should have correct builder ID")
if aClient.BuilderId() != "bid" {
t.Fatalf("bad: %s", aClient.BuilderId())
}
if !reflect.DeepEqual(aClient.Files(), []string{"a", "b"}) {
t.Fatalf("bad: %#v", aClient.Files())
}
if aClient.Id() != "id" {
t.Fatalf("bad: %s", aClient.Id())
}
if aClient.String() != "string" {
t.Fatalf("bad: %s", aClient.String())
}
}
func TestArtifact_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var r packer.Artifact
a := Artifact(nil)
assert.Implementor(a, &r, "should be an Artifact")
var _ packer.Artifact = Artifact(nil)
}
+48 -23
View File
@@ -1,7 +1,6 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"errors"
"github.com/mitchellh/packer/packer"
"net/rpc"
@@ -60,8 +59,6 @@ func (b *testBuild) Cancel() {
}
func TestBuildRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the interface to test
b := new(testBuild)
@@ -72,16 +69,23 @@ func TestBuildRPC(t *testing.T) {
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be able to connect")
if err != nil {
t.Fatalf("err: %s", err)
}
bClient := Build(client)
// Test Name
bClient.Name()
assert.True(b.nameCalled, "name should be called")
if !b.nameCalled {
t.Fatal("name should be called")
}
// Test Prepare
bClient.Prepare(map[string]string{"foo": "bar"})
assert.True(b.prepareCalled, "prepare should be called")
if !b.prepareCalled {
t.Fatal("prepare should be called")
}
if len(b.prepareVars) != 1 {
t.Fatalf("bad vars: %#v", b.prepareVars)
}
@@ -94,44 +98,65 @@ func TestBuildRPC(t *testing.T) {
cache := new(testCache)
ui := new(testUi)
artifacts, err := bClient.Run(ui, cache)
assert.True(b.runCalled, "run should be called")
assert.Nil(err, "should not error")
assert.Equal(len(artifacts), 1, "should have one artifact")
assert.Equal(artifacts[0].BuilderId(), "bid", "should have proper builder id")
if !b.runCalled {
t.Fatal("run should be called")
}
if err != nil {
t.Fatalf("err: %s", err)
}
if len(artifacts) != 1 {
t.Fatalf("bad: %#v", artifacts)
}
if artifacts[0].BuilderId() != "bid" {
t.Fatalf("bad: %#v", artifacts)
}
// Test the UI given to run, which should be fully functional
if b.runCalled {
b.runCache.Lock("foo")
assert.True(cache.lockCalled, "lock should be called")
if !cache.lockCalled {
t.Fatal("lock shuld be called")
}
b.runUi.Say("format")
assert.True(ui.sayCalled, "say should be called")
assert.Equal(ui.sayMessage, "format", "message should be correct")
if !ui.sayCalled {
t.Fatal("say should be called")
}
if ui.sayMessage != "format" {
t.Fatalf("bad: %#v", ui.sayMessage)
}
}
// Test run with an error
b.errRunResult = true
_, err = bClient.Run(ui, cache)
assert.NotNil(err, "should not nil")
if err == nil {
t.Fatal("should error")
}
// Test SetDebug
bClient.SetDebug(true)
assert.True(b.setDebugCalled, "should be called")
if !b.setDebugCalled {
t.Fatal("should be called")
}
// Test SetForce
bClient.SetForce(true)
assert.True(b.setForceCalled, "should be called")
if !b.setForceCalled {
t.Fatal("should be called")
}
// Test Cancel
bClient.Cancel()
assert.True(b.cancelCalled, "cancel should be called")
if !b.cancelCalled {
t.Fatal("should be called")
}
}
func TestBuild_ImplementsBuild(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var realBuild packer.Build
b := Build(nil)
assert.Implementor(b, &realBuild, "should be a Build")
var _ packer.Build = Build(nil)
}
+50 -24
View File
@@ -1,10 +1,10 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"errors"
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
"testing"
)
@@ -49,8 +49,6 @@ func (b *testBuilder) Cancel() {
}
func TestBuilderRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the interface to test
b := new(testBuilder)
@@ -61,60 +59,88 @@ func TestBuilderRPC(t *testing.T) {
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be able to connect")
if err != nil {
t.Fatalf("err: %s", err)
}
// Test Prepare
config := 42
bClient := Builder(client)
bClient.Prepare(config)
assert.True(b.prepareCalled, "prepare should be called")
assert.Equal(b.prepareConfig, []interface{}{42}, "prepare should be called with right arg")
if !b.prepareCalled {
t.Fatal("should be called")
}
if !reflect.DeepEqual(b.prepareConfig, []interface{}{42}) {
t.Fatalf("bad: %#v", b.prepareConfig)
}
// Test Run
cache := new(testCache)
hook := &packer.MockHook{}
ui := &testUi{}
artifact, err := bClient.Run(ui, hook, cache)
assert.Nil(err, "should have no error")
assert.True(b.runCalled, "runs hould be called")
if err != nil {
t.Fatalf("err: %s", err)
}
if !b.runCalled {
t.Fatal("run should be called")
}
if b.runCalled {
b.runCache.Lock("foo")
assert.True(cache.lockCalled, "lock should be called")
if !cache.lockCalled {
t.Fatal("should be called")
}
b.runHook.Run("foo", nil, nil, nil)
assert.True(hook.RunCalled, "run should be called")
if !hook.RunCalled {
t.Fatal("should be called")
}
b.runUi.Say("format")
assert.True(ui.sayCalled, "say should be called")
assert.Equal(ui.sayMessage, "format", "message should be correct")
if !ui.sayCalled {
t.Fatal("say should be called")
}
assert.Equal(artifact.Id(), testBuilderArtifact.Id(), "should have artifact Id")
if ui.sayMessage != "format" {
t.Fatalf("bad: %s", ui.sayMessage)
}
if artifact.Id() != testBuilderArtifact.Id() {
t.Fatalf("bad: %s", artifact.Id())
}
}
// Test run with nil result
b.nilRunResult = true
artifact, err = bClient.Run(ui, hook, cache)
assert.Nil(artifact, "should be nil")
assert.Nil(err, "should have no error")
if artifact != nil {
t.Fatalf("bad: %#v", artifact)
}
if err != nil {
t.Fatalf("bad: %#v", err)
}
// Test with an error
b.errRunResult = true
b.nilRunResult = false
artifact, err = bClient.Run(ui, hook, cache)
assert.Nil(artifact, "should be nil")
assert.NotNil(err, "should have error")
if artifact != nil {
t.Fatalf("bad: %#v", artifact)
}
if err == nil {
t.Fatal("should have error")
}
// Test Cancel
bClient.Cancel()
assert.True(b.cancelCalled, "cancel should be called")
if !b.cancelCalled {
t.Fatal("cancel should be called")
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var realBuilder packer.Builder
b := Builder(nil)
assert.Implementor(b, &realBuilder, "should be a Builder")
var _ packer.Builder = Builder(nil)
}
+27 -12
View File
@@ -1,7 +1,6 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"testing"
@@ -49,8 +48,6 @@ func TestCache_Implements(t *testing.T) {
}
func TestCacheRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the interface to test
c := new(testCache)
@@ -61,26 +58,44 @@ func TestCacheRPC(t *testing.T) {
// Create the client over RPC and run some methods to verify it works
rpcClient, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be able to connect")
if err != nil {
t.Fatalf("bad: %s", err)
}
client := Cache(rpcClient)
// Test Lock
client.Lock("foo")
assert.True(c.lockCalled, "should be called")
assert.Equal(c.lockKey, "foo", "should have proper key")
if !c.lockCalled {
t.Fatal("should be called")
}
if c.lockKey != "foo" {
t.Fatalf("bad: %s", c.lockKey)
}
// Test Unlock
client.Unlock("foo")
assert.True(c.unlockCalled, "should be called")
assert.Equal(c.unlockKey, "foo", "should have proper key")
if !c.unlockCalled {
t.Fatal("should be called")
}
if c.unlockKey != "foo" {
t.Fatalf("bad: %s", c.unlockKey)
}
// Test RLock
client.RLock("foo")
assert.True(c.rlockCalled, "should be called")
assert.Equal(c.rlockKey, "foo", "should have proper key")
if !c.rlockCalled {
t.Fatal("should be called")
}
if c.rlockKey != "foo" {
t.Fatalf("bad: %s", c.rlockKey)
}
// Test RUnlock
client.RUnlock("foo")
assert.True(c.runlockCalled, "should be called")
assert.Equal(c.runlockKey, "foo", "should have proper key")
if !c.runlockCalled {
t.Fatal("should be called")
}
if c.runlockKey != "foo" {
t.Fatalf("bad: %s", c.runlockKey)
}
}
+24 -18
View File
@@ -1,9 +1,9 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
"testing"
)
@@ -29,8 +29,6 @@ func (tc *TestCommand) Synopsis() string {
}
func TestRPCCommand(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the command
command := new(TestCommand)
@@ -42,37 +40,45 @@ func TestRPCCommand(t *testing.T) {
// Create the command client over RPC and run some methods to verify
// we get the proper behavior.
client, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be no error")
if err != nil {
t.Fatalf("err: %s", err)
}
clientComm := Command(client)
//Test Help
help := clientComm.Help()
assert.Equal(help, "bar", "helps hould be correct")
if help != "bar" {
t.Fatalf("bad: %s", help)
}
// Test run
runArgs := []string{"foo", "bar"}
testEnv := &testEnvironment{}
exitCode := clientComm.Run(testEnv, runArgs)
assert.Equal(command.runArgs, runArgs, "Correct args should be sent")
assert.Equal(exitCode, 0, "Exit code should be correct")
if !reflect.DeepEqual(command.runArgs, runArgs) {
t.Fatalf("bad: %#v", command.runArgs)
}
if exitCode != 0 {
t.Fatalf("bad: %d", exitCode)
}
assert.NotNil(command.runEnv, "should have an env")
if command.runEnv != nil {
command.runEnv.Ui()
assert.True(testEnv.uiCalled, "UI should be called on env")
if command.runEnv == nil {
t.Fatal("runEnv should not be nil")
}
command.runEnv.Ui()
if !testEnv.uiCalled {
t.Fatal("ui should be called")
}
// Test Synopsis
synopsis := clientComm.Synopsis()
assert.Equal(synopsis, "foo", "Synopsis should be correct")
if synopsis != "foo" {
t.Fatalf("bad: %#v", synopsis)
}
}
func TestCommand_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var r packer.Command
c := Command(nil)
assert.Implementor(c, &r, "should be a Builder")
var _ packer.Command = Command(nil)
}
+41 -22
View File
@@ -1,9 +1,9 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
"testing"
)
@@ -65,8 +65,6 @@ func (e *testEnvironment) Ui() packer.Ui {
}
func TestEnvironmentRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the interface to test
e := &testEnvironment{}
@@ -77,49 +75,70 @@ func TestEnvironmentRPC(t *testing.T) {
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be able to connect")
if err != nil {
t.Fatalf("err: %s", err)
}
eClient := &Environment{client}
// Test Builder
builder, _ := eClient.Builder("foo")
assert.True(e.builderCalled, "Builder should be called")
assert.Equal(e.builderName, "foo", "Correct name for Builder")
if !e.builderCalled {
t.Fatal("builder should be called")
}
if e.builderName != "foo" {
t.Fatalf("bad: %#v", e.builderName)
}
builder.Prepare(nil)
assert.True(testEnvBuilder.prepareCalled, "Prepare should be called")
if !testEnvBuilder.prepareCalled {
t.Fatal("should be called")
}
// Test Cache
cache := eClient.Cache()
cache.Lock("foo")
assert.True(testEnvCache.lockCalled, "lock should be called")
if !testEnvCache.lockCalled {
t.Fatal("should be called")
}
// Test Cli
cliArgs := []string{"foo", "bar"}
result, _ := eClient.Cli(cliArgs)
assert.True(e.cliCalled, "CLI should be called")
assert.Equal(e.cliArgs, cliArgs, "args should match")
assert.Equal(result, 42, "result shuld be 42")
if !e.cliCalled {
t.Fatal("should be called")
}
if !reflect.DeepEqual(e.cliArgs, cliArgs) {
t.Fatalf("bad: %#v", e.cliArgs)
}
if result != 42 {
t.Fatalf("bad: %#v", result)
}
// Test Provisioner
_, _ = eClient.Provisioner("foo")
assert.True(e.provCalled, "provisioner should be called")
assert.Equal(e.provName, "foo", "should have proper name")
if !e.provCalled {
t.Fatal("should be called")
}
if e.provName != "foo" {
t.Fatalf("bad: %s", e.provName)
}
// Test Ui
ui := eClient.Ui()
assert.True(e.uiCalled, "Ui should've been called")
if !e.uiCalled {
t.Fatal("should be called")
}
// Test calls on the Ui
ui.Say("format")
assert.True(testEnvUi.sayCalled, "Say should be called")
assert.Equal(testEnvUi.sayMessage, "format", "message should match")
if !testEnvUi.sayCalled {
t.Fatal("should be called")
}
if testEnvUi.sayMessage != "format" {
t.Fatalf("bad: %#v", testEnvUi.sayMessage)
}
}
func TestEnvironment_ImplementsEnvironment(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var realVar packer.Environment
e := &Environment{nil}
assert.Implementor(e, &realVar, "should be an Environment")
var _ packer.Environment = new(Environment)
}
+4 -10
View File
@@ -1,25 +1,19 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"errors"
"testing"
)
func TestBasicError_ImplementsError(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var r error
e := &BasicError{""}
assert.Implementor(e, &r, "should be an error")
var _ error = new(BasicError)
}
func TestBasicError_MatchesMessage(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
err := errors.New("foo")
wrapped := NewBasicError(err)
assert.Equal(wrapped.Error(), err.Error(), "should have the same error")
if wrapped.Error() != err.Error() {
t.Fatalf("bad: %#v", wrapped.Error())
}
}
+10 -12
View File
@@ -1,7 +1,6 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
@@ -11,8 +10,6 @@ import (
)
func TestHookRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the UI to test
h := new(packer.MockHook)
@@ -23,27 +20,28 @@ func TestHookRPC(t *testing.T) {
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be able to connect")
if err != nil {
t.Fatalf("err: %s", err)
}
hClient := Hook(client)
// Test Run
ui := &testUi{}
hClient.Run("foo", ui, nil, 42)
assert.True(h.RunCalled, "run should be called")
if !h.RunCalled {
t.Fatal("should be called")
}
// Test Cancel
hClient.Cancel()
assert.True(h.CancelCalled, "cancel should be called")
if !h.CancelCalled {
t.Fatal("should be called")
}
}
func TestHook_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var r packer.Hook
h := &hook{nil}
assert.Implementor(h, &r, "should be a Hook")
var _ packer.Hook = new(hook)
}
func TestHook_cancelWhileRun(t *testing.T) {
+12 -7
View File
@@ -1,7 +1,6 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"net"
"strings"
"testing"
@@ -13,21 +12,27 @@ func addrPort(address net.Addr) string {
}
func Test_netListenerInRange(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Open up port 10000 so that we take up a port
L1000, err := net.Listen("tcp", "127.0.0.1:11000")
defer L1000.Close()
assert.Nil(err, "should be able to bind to port 10000")
if err != nil {
t.Fatalf("bad: %s", err)
}
if err == nil {
// Verify it selects an open port
L := netListenerInRange(11000, 11005)
assert.NotNil(L, "should have a listener")
assert.Equal(addrPort(L.Addr()), "11001", "should bind to open port")
if L == nil {
t.Fatal("L should not be nil")
}
if addrPort(L.Addr()) != "11001" {
t.Fatalf("bad: %s", L.Addr())
}
// Returns nil if there are no open ports
L = netListenerInRange(11000, 11000)
assert.Nil(L, "should not get a listener")
if L != nil {
t.Fatalf("bad: %#v", L)
}
}
}
+17 -14
View File
@@ -1,15 +1,13 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
"testing"
)
func TestProvisionerRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the interface to test
p := new(packer.MockProvisioner)
@@ -20,23 +18,33 @@ func TestProvisionerRPC(t *testing.T) {
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
assert.Nil(err, "should be able to connect")
if err != nil {
t.Fatalf("err: %s", err)
}
// Test Prepare
config := 42
pClient := Provisioner(client)
pClient.Prepare(config)
assert.True(p.PrepCalled, "prepare should be called")
assert.Equal(p.PrepConfigs, []interface{}{42}, "prepare should be called with right arg")
if !p.PrepCalled {
t.Fatal("should be called")
}
if !reflect.DeepEqual(p.PrepConfigs, []interface{}{42}) {
t.Fatalf("bad: %#v", p.PrepConfigs)
}
// Test Provision
ui := &testUi{}
comm := &packer.MockCommunicator{}
pClient.Provision(ui, comm)
assert.True(p.ProvCalled, "provision should be called")
if !p.ProvCalled {
t.Fatal("should be called")
}
p.ProvUi.Say("foo")
assert.True(ui.sayCalled, "say should be called")
if !ui.sayCalled {
t.Fatal("should be called")
}
// Test Cancel
pClient.Cancel()
@@ -46,10 +54,5 @@ func TestProvisionerRPC(t *testing.T) {
}
func TestProvisioner_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var r packer.Provisioner
p := Provisioner(nil)
assert.Implementor(p, &r, "should be a provisioner")
var _ packer.Provisioner = Provisioner(nil)
}
+21 -10
View File
@@ -1,7 +1,6 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"net/rpc"
"reflect"
"testing"
@@ -49,8 +48,6 @@ func (u *testUi) Say(message string) {
}
func TestUiRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the UI to test
ui := new(testUi)
@@ -69,19 +66,33 @@ func TestUiRPC(t *testing.T) {
// Basic error and say tests
result, err := uiClient.Ask("query")
assert.Nil(err, "should not error")
assert.True(ui.askCalled, "ask should be called")
assert.Equal(ui.askQuery, "query", "should be correct")
assert.Equal(result, "foo", "should have correct result")
if err != nil {
t.Fatalf("err: %s", err)
}
if !ui.askCalled {
t.Fatal("should be called")
}
if ui.askQuery != "query" {
t.Fatalf("bad: %s", ui.askQuery)
}
if result != "foo" {
t.Fatalf("bad: %#v", result)
}
uiClient.Error("message")
assert.Equal(ui.errorMessage, "message", "message should be correct")
if ui.errorMessage != "message" {
t.Fatalf("bad: %#v", ui.errorMessage)
}
uiClient.Message("message")
assert.Equal(ui.messageMessage, "message", "message should be correct")
if ui.messageMessage != "message" {
t.Fatalf("bad: %#v", ui.errorMessage)
}
uiClient.Say("message")
assert.Equal(ui.sayMessage, "message", "message should be correct")
if ui.sayMessage != "message" {
t.Fatalf("bad: %#v", ui.errorMessage)
}
uiClient.Machine("foo", "bar", "baz")
if !ui.machineCalled {
+16 -9
View File
@@ -124,18 +124,25 @@ func ParseTemplate(data []byte) (t *Template, err error) {
// Gather all the variables
for k, v := range rawTpl.Variables {
var variable RawVariable
variable.Default = ""
variable.Required = v == nil
if v != nil {
def, ok := v.(string)
if !ok {
errors = append(errors,
fmt.Errorf("variable '%s': default value must be string or null", k))
continue
}
// Create a new mapstructure decoder in order to decode the default
// value since this is the only value in the regular template that
// can be weakly typed.
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &variable.Default,
WeaklyTypedInput: true,
})
if err != nil {
// This should never happen.
panic(err)
}
variable.Default = def
err = decoder.Decode(v)
if err != nil {
errors = append(errors,
fmt.Errorf("Error decoding default value for user var '%s': %s", k, err))
continue
}
t.Variables[k] = variable
+197 -107
View File
@@ -1,7 +1,6 @@
package packer
import (
"cgl.tideland.biz/asserts"
"io/ioutil"
"os"
"reflect"
@@ -94,8 +93,6 @@ func TestParseTemplateFile_stdin(t *testing.T) {
}
func TestParseTemplate_Basic(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [{"type": "something"}]
@@ -103,14 +100,18 @@ func TestParseTemplate_Basic(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
assert.NotNil(result, "template should not be nil")
assert.Length(result.Builders, 1, "one builder")
if err != nil {
t.Fatalf("err: %s", err)
}
if result == nil {
t.Fatal("should have result")
}
if len(result.Builders) != 1 {
t.Fatalf("bad: %#v", result.Builders)
}
}
func TestParseTemplate_Invalid(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Note there is an extra comma below for a purposeful
// syntax error in the JSON.
data := `
@@ -120,13 +121,15 @@ func TestParseTemplate_Invalid(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.NotNil(err, "should have an error")
assert.Nil(result, "should have no result")
if err == nil {
t.Fatal("shold have error")
}
if result != nil {
t.Fatal("should not have result")
}
}
func TestParseTemplate_InvalidKeys(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Note there is an extra comma below for a purposeful
// syntax error in the JSON.
data := `
@@ -137,13 +140,15 @@ func TestParseTemplate_InvalidKeys(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.NotNil(err, "should have an error")
assert.Nil(result, "should have no result")
if err == nil {
t.Fatal("should have error")
}
if result != nil {
t.Fatal("should not have result")
}
}
func TestParseTemplate_BuilderWithoutType(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [{}]
@@ -151,12 +156,12 @@ func TestParseTemplate_BuilderWithoutType(t *testing.T) {
`
_, err := ParseTemplate([]byte(data))
assert.NotNil(err, "should have error")
if err == nil {
t.Fatal("should have error")
}
}
func TestParseTemplate_BuilderWithNonStringType(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [{
@@ -166,12 +171,12 @@ func TestParseTemplate_BuilderWithNonStringType(t *testing.T) {
`
_, err := ParseTemplate([]byte(data))
assert.NotNil(err, "should have error")
if err == nil {
t.Fatal("should have error")
}
}
func TestParseTemplate_BuilderWithoutName(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -183,18 +188,26 @@ func TestParseTemplate_BuilderWithoutName(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
assert.NotNil(result, "template should not be nil")
assert.Length(result.Builders, 1, "should have one builder")
if err != nil {
t.Fatalf("err: %s", err)
}
if result == nil {
t.Fatal("should have result")
}
if len(result.Builders) != 1 {
t.Fatalf("bad: %#v", result.Builders)
}
builder, ok := result.Builders["amazon-ebs"]
assert.True(ok, "should have amazon-ebs builder")
assert.Equal(builder.Type, "amazon-ebs", "builder should be amazon-ebs")
if !ok {
t.Fatal("should be ok")
}
if builder.Type != "amazon-ebs" {
t.Fatalf("bad: %#v", builder.Type)
}
}
func TestParseTemplate_BuilderWithName(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -207,13 +220,23 @@ func TestParseTemplate_BuilderWithName(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
assert.NotNil(result, "template should not be nil")
assert.Length(result.Builders, 1, "should have one builder")
if err != nil {
t.Fatalf("err: %s", err)
}
if result == nil {
t.Fatal("should have result")
}
if len(result.Builders) != 1 {
t.Fatalf("bad: %#v", result.Builders)
}
builder, ok := result.Builders["bob"]
assert.True(ok, "should have bob builder")
assert.Equal(builder.Type, "amazon-ebs", "builder should be amazon-ebs")
if !ok {
t.Fatal("should be ok")
}
if builder.Type != "amazon-ebs" {
t.Fatalf("bad: %#v", builder.Type)
}
RawConfig := builder.RawConfig
if RawConfig == nil {
@@ -230,8 +253,6 @@ func TestParseTemplate_BuilderWithName(t *testing.T) {
}
func TestParseTemplate_BuilderWithConflictingName(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -248,12 +269,12 @@ func TestParseTemplate_BuilderWithConflictingName(t *testing.T) {
`
_, err := ParseTemplate([]byte(data))
assert.NotNil(err, "should have error")
if err == nil {
t.Fatal("should have error")
}
}
func TestParseTemplate_Hooks(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
@@ -266,13 +287,23 @@ func TestParseTemplate_Hooks(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
assert.NotNil(result, "template should not be nil")
assert.Length(result.Hooks, 1, "should have one hook")
if err != nil {
t.Fatalf("err: %s", err)
}
if result == nil {
t.Fatal("should have result")
}
if len(result.Hooks) != 1 {
t.Fatalf("bad: %#v", result.Hooks)
}
hooks, ok := result.Hooks["event"]
assert.True(ok, "should have hook")
assert.Equal(hooks, []string{"foo", "bar"}, "hooks should be correct")
if !ok {
t.Fatal("should be okay")
}
if !reflect.DeepEqual(hooks, []string{"foo", "bar"}) {
t.Fatalf("bad: %#v", hooks)
}
}
func TestParseTemplate_PostProcessors(t *testing.T) {
@@ -332,8 +363,6 @@ func TestParseTemplate_PostProcessors(t *testing.T) {
}
func TestParseTemplate_ProvisionerWithoutType(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [{"type": "foo"}],
@@ -343,12 +372,12 @@ func TestParseTemplate_ProvisionerWithoutType(t *testing.T) {
`
_, err := ParseTemplate([]byte(data))
assert.NotNil(err, "should have error")
if err == nil {
t.Fatal("err should not be nil")
}
}
func TestParseTemplate_ProvisionerWithNonStringType(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [{"type": "foo"}],
@@ -360,12 +389,12 @@ func TestParseTemplate_ProvisionerWithNonStringType(t *testing.T) {
`
_, err := ParseTemplate([]byte(data))
assert.NotNil(err, "should have error")
if err == nil {
t.Fatal("should have error")
}
}
func TestParseTemplate_Provisioners(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [{"type": "foo"}],
@@ -379,11 +408,21 @@ func TestParseTemplate_Provisioners(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
assert.NotNil(result, "template should not be nil")
assert.Length(result.Provisioners, 1, "should have one provisioner")
assert.Equal(result.Provisioners[0].Type, "shell", "provisioner should be shell")
assert.NotNil(result.Provisioners[0].RawConfig, "should have raw config")
if err != nil {
t.Fatal("err: %s", err)
}
if result == nil {
t.Fatal("should have result")
}
if len(result.Provisioners) != 1 {
t.Fatalf("bad: %#v", result.Provisioners)
}
if result.Provisioners[0].Type != "shell" {
t.Fatalf("bad: %#v", result.Provisioners[0].Type)
}
if result.Provisioners[0].RawConfig == nil {
t.Fatal("should have raw config")
}
}
func TestParseTemplate_Variables(t *testing.T) {
@@ -391,7 +430,8 @@ func TestParseTemplate_Variables(t *testing.T) {
{
"variables": {
"foo": "bar",
"bar": null
"bar": null,
"baz": 27
},
"builders": [{"type": "something"}]
@@ -403,7 +443,7 @@ func TestParseTemplate_Variables(t *testing.T) {
t.Fatalf("err: %s", err)
}
if result.Variables == nil || len(result.Variables) != 2 {
if result.Variables == nil || len(result.Variables) != 3 {
t.Fatalf("bad vars: %#v", result.Variables)
}
@@ -422,6 +462,14 @@ func TestParseTemplate_Variables(t *testing.T) {
if !result.Variables["bar"].Required {
t.Fatal("bar should be required")
}
if result.Variables["baz"].Default != "27" {
t.Fatal("default should be empty")
}
if result.Variables["baz"].Required {
t.Fatal("baz should not be required")
}
}
func TestParseTemplate_variablesBadDefault(t *testing.T) {
@@ -442,8 +490,6 @@ func TestParseTemplate_variablesBadDefault(t *testing.T) {
}
func TestTemplate_BuildNames(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -460,16 +506,18 @@ func TestTemplate_BuildNames(t *testing.T) {
`
result, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
buildNames := result.BuildNames()
sort.Strings(buildNames)
assert.Equal(buildNames, []string{"bob", "chris"}, "should have proper builds")
if !reflect.DeepEqual(buildNames, []string{"bob", "chris"}) {
t.Fatalf("bad: %#v", buildNames)
}
}
func TestTemplate_BuildUnknown(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -482,16 +530,20 @@ func TestTemplate_BuildUnknown(t *testing.T) {
`
template, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("bad: %s", err)
}
build, err := template.Build("nope", nil)
assert.Nil(build, "build should be nil")
assert.NotNil(err, "should have error")
if build != nil {
t.Fatalf("build should be nil: %#v", build)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestTemplate_BuildUnknownBuilder(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -504,18 +556,22 @@ func TestTemplate_BuildUnknownBuilder(t *testing.T) {
`
template, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
builderFactory := func(string) (Builder, error) { return nil, nil }
components := &ComponentFinder{Builder: builderFactory}
build, err := template.Build("test1", components)
assert.Nil(build, "build should be nil")
assert.NotNil(err, "should have error")
if err == nil {
t.Fatal("should have error")
}
if build != nil {
t.Fatalf("bad: %#v", build)
}
}
func TestTemplate_Build_NilBuilderFunc(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -534,14 +590,18 @@ func TestTemplate_Build_NilBuilderFunc(t *testing.T) {
`
template, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
defer func() {
p := recover()
assert.NotNil(p, "should panic")
if p == nil {
t.Fatal("should panic")
}
if p != nil {
assert.Equal(p.(string), "no builder function", "right panic")
if p.(string) != "no builder function" {
t.Fatalf("bad panic: %s", p.(string))
}
}()
@@ -549,8 +609,6 @@ func TestTemplate_Build_NilBuilderFunc(t *testing.T) {
}
func TestTemplate_Build_NilProvisionerFunc(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -569,14 +627,18 @@ func TestTemplate_Build_NilProvisionerFunc(t *testing.T) {
`
template, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
defer func() {
p := recover()
assert.NotNil(p, "should panic")
if p == nil {
t.Fatal("should panic")
}
if p != nil {
assert.Equal(p.(string), "no provisioner function", "right panic")
if p.(string) != "no provisioner function" {
t.Fatalf("bad panic: %s", p.(string))
}
}()
@@ -586,8 +648,6 @@ func TestTemplate_Build_NilProvisionerFunc(t *testing.T) {
}
func TestTemplate_Build_NilProvisionerFunc_WithNoProvisioners(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -602,7 +662,9 @@ func TestTemplate_Build_NilProvisionerFunc_WithNoProvisioners(t *testing.T) {
`
template, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
template.Build("test1", &ComponentFinder{
Builder: func(string) (Builder, error) { return nil, nil },
@@ -610,8 +672,6 @@ func TestTemplate_Build_NilProvisionerFunc_WithNoProvisioners(t *testing.T) {
}
func TestTemplate_Build(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -642,7 +702,9 @@ func TestTemplate_Build(t *testing.T) {
}
template, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
builder := testBuilder()
builderMap := map[string]Builder{
@@ -671,18 +733,40 @@ func TestTemplate_Build(t *testing.T) {
// Get the build, verifying we can get it without issue, but also
// that the proper builder was looked up and used for the build.
build, err := template.Build("test1", components)
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
coreBuild, ok := build.(*coreBuild)
assert.True(ok, "should be a core build")
assert.Equal(coreBuild.builder, builder, "should have the same builder")
assert.Equal(coreBuild.builderConfig, expectedConfig, "should have proper config")
assert.Equal(len(coreBuild.provisioners), 1, "should have one provisioner")
assert.Equal(len(coreBuild.postProcessors), 2, "should have pps")
assert.Equal(len(coreBuild.postProcessors[0]), 1, "should have correct number")
assert.Equal(len(coreBuild.postProcessors[1]), 2, "should have correct number")
assert.False(coreBuild.postProcessors[1][0].keepInputArtifact, "shoule be correct")
assert.True(coreBuild.postProcessors[1][1].keepInputArtifact, "shoule be correct")
if !ok {
t.Fatal("should be ok")
}
if coreBuild.builder != builder {
t.Fatalf("bad: %#v", coreBuild.builder)
}
if !reflect.DeepEqual(coreBuild.builderConfig, expectedConfig) {
t.Fatalf("bad: %#v", coreBuild.builderConfig)
}
if len(coreBuild.provisioners) != 1 {
t.Fatalf("bad: %#v", coreBuild.provisioners)
}
if len(coreBuild.postProcessors) != 2 {
t.Fatalf("bad: %#v", coreBuild.postProcessors)
}
if len(coreBuild.postProcessors[0]) != 1 {
t.Fatalf("bad: %#v", coreBuild.postProcessors[0])
}
if len(coreBuild.postProcessors[1]) != 2 {
t.Fatalf("bad: %#v", coreBuild.postProcessors[1])
}
if coreBuild.postProcessors[1][0].keepInputArtifact {
t.Fatal("postProcessors[1][0] should not keep input artifact")
}
if !coreBuild.postProcessors[1][1].keepInputArtifact {
t.Fatal("postProcessors[1][1] should keep input artifact")
}
config := coreBuild.postProcessors[1][1].config
if _, ok := config["keep_input_artifact"]; ok {
@@ -1071,8 +1155,6 @@ func TestTemplateBuild_onlyProv(t *testing.T) {
}
func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
data := `
{
"builders": [
@@ -1132,12 +1214,20 @@ func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
// Get the build, verifying we can get it without issue, but also
// that the proper builder was looked up and used for the build.
build, err := template.Build("test1", components)
assert.Nil(err, "should not error")
if err != nil {
t.Fatalf("err: %s", err)
}
coreBuild, ok := build.(*coreBuild)
assert.True(ok, "should be a core build")
assert.Equal(len(coreBuild.provisioners), 1, "should have one provisioner")
assert.Equal(len(coreBuild.provisioners[0].config), 2, "should have two configs on the provisioner")
if !ok {
t.Fatal("should be okay")
}
if len(coreBuild.provisioners) != 1 {
t.Fatalf("bad: %#v", coreBuild.provisioners)
}
if len(coreBuild.provisioners[0].config) != 2 {
t.Fatalf("bad: %#v", coreBuild.provisioners[0].config)
}
}
func TestTemplate_Build_ProvisionerOverrideBad(t *testing.T) {
+44 -15
View File
@@ -2,7 +2,6 @@ package packer
import (
"bytes"
"cgl.tideland.biz/asserts"
"strings"
"testing"
)
@@ -38,25 +37,40 @@ func TestColoredUi(t *testing.T) {
}
func TestTargettedUi(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi()
targettedUi := &TargettedUi{
Target: "foo",
Ui: bufferUi,
}
var actual, expected string
targettedUi.Say("foo")
assert.Equal(readWriter(bufferUi), "==> foo: foo\n", "should have prefix")
actual = readWriter(bufferUi)
expected = "==> foo: foo\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
targettedUi.Message("foo")
assert.Equal(readWriter(bufferUi), " foo: foo\n", "should have prefix")
actual = readWriter(bufferUi)
expected = " foo: foo\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
targettedUi.Error("bar")
assert.Equal(readWriter(bufferUi), "==> foo: bar\n", "should have prefix")
actual = readWriter(bufferUi)
expected = "==> foo: bar\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
targettedUi.Say("foo\nbar")
assert.Equal(readWriter(bufferUi), "==> foo: foo\n==> foo: bar\n", "should multiline")
actual = readWriter(bufferUi)
expected = "==> foo: foo\n==> foo: bar\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
}
func TestColoredUi_ImplUi(t *testing.T) {
@@ -84,27 +98,42 @@ func TestBasicUi_ImplUi(t *testing.T) {
}
func TestBasicUi_Error(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi()
var actual, expected string
bufferUi.Error("foo")
assert.Equal(readWriter(bufferUi), "foo\n", "basic output")
actual = readWriter(bufferUi)
expected = "foo\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
bufferUi.Error("5")
assert.Equal(readWriter(bufferUi), "5\n", "formatting")
actual = readWriter(bufferUi)
expected = "5\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
}
func TestBasicUi_Say(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
bufferUi := testUi()
var actual, expected string
bufferUi.Say("foo")
assert.Equal(readWriter(bufferUi), "foo\n", "basic output")
actual = readWriter(bufferUi)
expected = "foo\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
bufferUi.Say("5")
assert.Equal(readWriter(bufferUi), "5\n", "formatting")
actual = readWriter(bufferUi)
expected = "5\n"
if actual != expected {
t.Fatalf("bad: %#v", actual)
}
}
func TestMachineReadableUi_ImplUi(t *testing.T) {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var GitCommit string
// The version of packer.
const Version = "0.3.8"
const Version = "0.3.10"
// Any pre-release marker for the version. If this is "" (empty string),
// then it means that it is a final release. Otherwise, this is the
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/packer/provisioner/ansible-local"
)
func main() {
plugin.ServeProvisioner(new(ansiblelocal.Provisioner))
}
@@ -0,0 +1 @@
package main
+13 -1
View File
@@ -1,6 +1,7 @@
package vagrant
import (
"compress/flate"
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
@@ -8,6 +9,7 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"strings"
)
@@ -16,6 +18,7 @@ type AWSBoxConfig struct {
OutputPath string `mapstructure:"output"`
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
CompressionLevel string `mapstructure:"compression_level"`
tpl *packer.ConfigTemplate
}
@@ -46,6 +49,7 @@ func (p *AWSBoxPostProcessor) Configure(raws ...interface{}) error {
validates := map[string]*string{
"output": &p.config.OutputPath,
"vagrantfile_template": &p.config.VagrantfileTemplate,
"compression_level": &p.config.CompressionLevel,
}
for n, ptr := range validates {
@@ -127,6 +131,14 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
vf.Write([]byte(vagrantfileContents))
vf.Close()
var level int = flate.DefaultCompression
if p.config.CompressionLevel != "" {
level, err = strconv.Atoi(p.config.CompressionLevel)
if err != nil {
return nil, false, err
}
}
// Create the metadata
metadata := map[string]string{"provider": "aws"}
if err := WriteMetadata(dir, metadata); err != nil {
@@ -134,7 +146,7 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
}
// Compress the directory to the given output path
if err := DirToBox(outputPath, dir, ui); err != nil {
if err := DirToBox(outputPath, dir, ui, level); err != nil {
err = fmt.Errorf("error creating box: %s", err)
return nil, false, err
}
+4 -3
View File
@@ -12,9 +12,10 @@ import (
)
var builtins = map[string]string{
"mitchellh.amazonebs": "aws",
"mitchellh.virtualbox": "virtualbox",
"mitchellh.vmware": "vmware",
"mitchellh.amazonebs": "aws",
"mitchellh.amazon.instance": "aws",
"mitchellh.virtualbox": "virtualbox",
"mitchellh.vmware": "vmware",
}
type Config struct {
+5 -2
View File
@@ -44,7 +44,7 @@ func CopyContents(dst, src string) error {
// DirToBox takes the directory and compresses it into a Vagrant-compatible
// box. This function does not perform checks to verify that dir is
// actually a proper box. This is an expected precondition.
func DirToBox(dst, dir string, ui packer.Ui) error {
func DirToBox(dst, dir string, ui packer.Ui, level int) error {
log.Printf("Turning dir into box: %s => %s", dir, dst)
dstF, err := os.Create(dst)
if err != nil {
@@ -52,7 +52,10 @@ func DirToBox(dst, dir string, ui packer.Ui) error {
}
defer dstF.Close()
gzipWriter := gzip.NewWriter(dstF)
gzipWriter, err := gzip.NewWriterLevel(dstF, level)
if err != nil {
return err
}
defer gzipWriter.Close()
tarWriter := tar.NewWriter(gzipWriter)
+14 -2
View File
@@ -2,6 +2,7 @@ package vagrant
import (
"archive/tar"
"compress/flate"
"errors"
"fmt"
"github.com/mitchellh/packer/common"
@@ -12,6 +13,7 @@ import (
"os"
"path/filepath"
"regexp"
"strconv"
)
type VBoxBoxConfig struct {
@@ -19,6 +21,7 @@ type VBoxBoxConfig struct {
OutputPath string `mapstructure:"output"`
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
CompressionLevel string `mapstructure:"compression_level"`
tpl *packer.ConfigTemplate
}
@@ -49,6 +52,7 @@ func (p *VBoxBoxPostProcessor) Configure(raws ...interface{}) error {
validates := map[string]*string{
"output": &p.config.OutputPath,
"vagrantfile_template": &p.config.VagrantfileTemplate,
"compression_level": &p.config.CompressionLevel,
}
for n, ptr := range validates {
@@ -92,7 +96,7 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
// directory so we can get the resulting disk and OVF.
if extension := filepath.Ext(path); extension == ".ova" {
ui.Message(fmt.Sprintf("Unpacking OVA: %s", path))
if err := DecompressOva(dir, filepath.Base(path)); err != nil {
if err := DecompressOva(dir, path); err != nil {
return nil, false, err
}
} else {
@@ -141,6 +145,14 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
vf.Write([]byte(vagrantfileContents))
vf.Close()
var level int = flate.DefaultCompression
if p.config.CompressionLevel != "" {
level, err = strconv.Atoi(p.config.CompressionLevel)
if err != nil {
return nil, false, err
}
}
// Create the metadata
metadata := map[string]string{"provider": "virtualbox"}
if err := WriteMetadata(dir, metadata); err != nil {
@@ -155,7 +167,7 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
// Compress the directory to the given output path
ui.Message(fmt.Sprintf("Compressing box..."))
if err := DirToBox(outputPath, dir, ui); err != nil {
if err := DirToBox(outputPath, dir, ui, level); err != nil {
return nil, false, err
}
+13 -1
View File
@@ -1,12 +1,14 @@
package vagrant
import (
"compress/flate"
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
"path/filepath"
"strconv"
)
type VMwareBoxConfig struct {
@@ -14,6 +16,7 @@ type VMwareBoxConfig struct {
OutputPath string `mapstructure:"output"`
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
CompressionLevel string `mapstructure:"compression_level"`
tpl *packer.ConfigTemplate
}
@@ -40,6 +43,7 @@ func (p *VMwareBoxPostProcessor) Configure(raws ...interface{}) error {
validates := map[string]*string{
"output": &p.config.OutputPath,
"vagrantfile_template": &p.config.VagrantfileTemplate,
"compression_level": &p.config.CompressionLevel,
}
for n, ptr := range validates {
@@ -111,6 +115,14 @@ func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artif
vf.Close()
}
var level int = flate.DefaultCompression
if p.config.CompressionLevel != "" {
level, err = strconv.Atoi(p.config.CompressionLevel)
if err != nil {
return nil, false, err
}
}
// Create the metadata
metadata := map[string]string{"provider": "vmware_desktop"}
if err := WriteMetadata(dir, metadata); err != nil {
@@ -119,7 +131,7 @@ func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artif
// Compress the directory to the given output path
ui.Message(fmt.Sprintf("Compressing box..."))
if err := DirToBox(outputPath, dir, ui); err != nil {
if err := DirToBox(outputPath, dir, ui, level); err != nil {
return nil, false, err
}
+225
View File
@@ -0,0 +1,225 @@
package ansiblelocal
import (
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"os"
"path/filepath"
)
const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
tpl *packer.ConfigTemplate
// The main playbook file to execute.
PlaybookFile string `mapstructure:"playbook_file"`
// An array of local paths of playbook files to upload.
PlaybookPaths []string `mapstructure:"playbook_paths"`
// An array of local paths of roles to upload.
RolePaths []string `mapstructure:"role_paths"`
// The directory where files will be uploaded. Packer requires write
// permissions in this directory.
StagingDir string `mapstructure:"staging_directory"`
}
type Provisioner struct {
config Config
}
func (p *Provisioner) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&p.config, raws...)
if err != nil {
return err
}
p.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return err
}
p.config.tpl.UserVars = p.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
if p.config.StagingDir == "" {
p.config.StagingDir = DefaultStagingDir
}
// Templates
templates := map[string]*string{
"staging_dir": &p.config.StagingDir,
}
for n, ptr := range templates {
var err error
*ptr, err = p.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
// Validation
err = validateFileConfig(p.config.PlaybookFile, "playbook_file", true)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
for _, path := range p.config.PlaybookPaths {
err := validateFileConfig(path, "playbook_paths", false)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
for _, path := range p.config.RolePaths {
if err := validateDirConfig(path, "role_paths"); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with Ansible...")
ui.Message("Creating Ansible staging directory...")
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
return fmt.Errorf("Error creating staging directory: %s", err)
}
ui.Message("Uploading main Playbook file...")
src := p.config.PlaybookFile
dst := filepath.Join(p.config.StagingDir, filepath.Base(src))
if err := p.uploadFile(ui, comm, dst, src); err != nil {
return fmt.Errorf("Error uploading main playbook: %s", err)
}
if len(p.config.RolePaths) > 0 {
ui.Message("Uploading role directories...")
for _, src := range p.config.RolePaths {
dst := filepath.Join(p.config.StagingDir, "roles", filepath.Base(src))
if err := p.uploadDir(ui, comm, dst, src); err != nil {
return fmt.Errorf("Error uploading roles: %s", err)
}
}
}
if len(p.config.PlaybookPaths) > 0 {
ui.Message("Uploading additional Playbooks...")
if err := p.createDir(ui, comm, filepath.Join(p.config.StagingDir, "playbooks")); err != nil {
return fmt.Errorf("Error creating playbooks directory: %s", err)
}
for _, src := range p.config.PlaybookPaths {
dst := filepath.Join(p.config.StagingDir, "playbooks", filepath.Base(src))
if err := p.uploadFile(ui, comm, dst, src); err != nil {
return fmt.Errorf("Error uploading playbooks: %s", err)
}
}
}
if err := p.executeAnsible(ui, comm); err != nil {
return fmt.Errorf("Error executing Ansible: %s", err)
}
return nil
}
func (p *Provisioner) Cancel() {
// Just hard quit. It isn't a big deal if what we're doing keeps
// running on the other side.
os.Exit(0)
}
func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) error {
playbook := filepath.Join(p.config.StagingDir, filepath.Base(p.config.PlaybookFile))
// The inventory must be set to "127.0.0.1,". The comma is important
// as its the only way to override the ansible inventory when dealing
// with a single host.
command := fmt.Sprintf("ansible-playbook %s -c local -i %s", playbook, `"127.0.0.1,"`)
ui.Message(fmt.Sprintf("Executing Ansible: %s", command))
cmd := &packer.RemoteCmd{
Command: command,
}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
}
return nil
}
func validateDirConfig(path string, config string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("%s: %s is invalid: %s", config, path, err)
} else if !info.IsDir() {
return fmt.Errorf("%s: %s must point to a directory", config, path)
}
return nil
}
func validateFileConfig(name string, config string, req bool) error {
if req {
if name == "" {
return fmt.Errorf("%s must be specified.", config)
}
}
info, err := os.Stat(name)
if err != nil {
return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
} else if info.IsDir() {
return fmt.Errorf("%s: %s must point to a file", config, name)
}
return nil
}
func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error {
f, err := os.Open(src)
if err != nil {
return fmt.Errorf("Error opening: %s", err)
}
defer f.Close()
if err = comm.Upload(dst, f); err != nil {
return fmt.Errorf("Error uploading %s: %s", src, err)
}
return nil
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("mkdir -p '%s'", dir),
}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status.")
}
return nil
}
func (p *Provisioner) uploadDir(ui packer.Ui, comm packer.Communicator, dst, src string) error {
if err := p.createDir(ui, comm, dst); err != nil {
return err
}
// Make sure there is a trailing "/" so that the directory isn't
// created on the other side.
if src[len(src)-1] != '/' {
src = src + "/"
}
return comm.UploadDir(dst, src, nil)
}
@@ -0,0 +1,71 @@
package ansiblelocal
import (
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
"testing"
)
func testConfig() map[string]interface{} {
m := make(map[string]interface{})
return m
}
func TestProvisioner_Impl(t *testing.T) {
var raw interface{}
raw = &Provisioner{}
if _, ok := raw.(packer.Provisioner); !ok {
t.Fatalf("must be a Provisioner")
}
}
func TestProvisionerPrepare_Defaults(t *testing.T) {
var p Provisioner
config := testConfig()
playbook_file, err := ioutil.TempFile("", "playbook")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(playbook_file.Name())
config["playbook_file"] = playbook_file.Name()
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if p.config.StagingDir != DefaultStagingDir {
t.Fatalf("unexpected staging dir %s, expected %s",
p.config.StagingDir, DefaultStagingDir)
}
}
func TestProvisionerPrepare_PlaybookFile(t *testing.T) {
var p Provisioner
config := testConfig()
err := p.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
config["playbook_file"] = ""
err = p.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
playbook_file, err := ioutil.TempFile("", "playbook")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(playbook_file.Name())
config["playbook_file"] = playbook_file.Name()
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
+49 -18
View File
@@ -18,10 +18,12 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ChefEnvironment string `mapstructure:"chef_environment"`
ConfigTemplate string `mapstructure:"config_template"`
CookbookPaths []string `mapstructure:"cookbook_paths"`
RolesPath string `mapstructure:"roles_path"`
DataBagsPath string `mapstructure:"data_bags_path"`
EnvironmentsPath string `mapstructure:"environments_path"`
ExecuteCommand string `mapstructure:"execute_command"`
InstallCommand string `mapstructure:"install_command"`
RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
@@ -39,15 +41,18 @@ type Provisioner struct {
}
type ConfigTemplate struct {
CookbookPaths string
DataBagsPath string
RolesPath string
CookbookPaths string
DataBagsPath string
RolesPath string
EnvironmentsPath string
ChefEnvironment string
// Templates don't support boolean statements until Go 1.2. In the
// mean time, we do this.
// TODO(mitchellh): Remove when Go 1.2 is released
HasDataBagsPath bool
HasRolesPath bool
HasDataBagsPath bool
HasRolesPath bool
HasEnvironmentsPath bool
}
type ExecuteTemplate struct {
@@ -92,10 +97,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
errs := common.CheckUnusedConfig(md)
templates := map[string]*string{
"config_template": &p.config.ConfigTemplate,
"data_bags_path": &p.config.DataBagsPath,
"roles_path": &p.config.RolesPath,
"staging_dir": &p.config.StagingDir,
"config_template": &p.config.ConfigTemplate,
"data_bags_path": &p.config.DataBagsPath,
"roles_path": &p.config.RolesPath,
"staging_dir": &p.config.StagingDir,
"environments_path": &p.config.EnvironmentsPath,
"chef_environment": &p.config.ChefEnvironment,
}
for n, ptr := range templates {
@@ -174,6 +181,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
}
if p.config.EnvironmentsPath != "" {
pFileInfo, err := os.Stat(p.config.EnvironmentsPath)
if err != nil || !pFileInfo.IsDir() {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Bad environments path '%s': %s", p.config.EnvironmentsPath, err))
}
}
// Process the user variables within the JSON and set the JSON.
// Do this early so that we can validate and show errors.
p.config.Json, err = p.processJsonUserVars()
@@ -212,7 +228,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
rolesPath := ""
if p.config.RolesPath != "" {
rolesPath := fmt.Sprintf("%s/roles", p.config.StagingDir)
rolesPath = fmt.Sprintf("%s/roles", p.config.StagingDir)
if err := p.uploadDirectory(ui, comm, rolesPath, p.config.RolesPath); err != nil {
return fmt.Errorf("Error uploading roles: %s", err)
}
@@ -220,13 +236,21 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
dataBagsPath := ""
if p.config.DataBagsPath != "" {
dataBagsPath := fmt.Sprintf("%s/data_bags", p.config.StagingDir)
dataBagsPath = fmt.Sprintf("%s/data_bags", p.config.StagingDir)
if err := p.uploadDirectory(ui, comm, dataBagsPath, p.config.DataBagsPath); err != nil {
return fmt.Errorf("Error uploading data bags: %s", err)
}
}
configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath)
environmentsPath := ""
if p.config.EnvironmentsPath != "" {
environmentsPath = fmt.Sprintf("%s/environments", p.config.StagingDir)
if err := p.uploadDirectory(ui, comm, environmentsPath, p.config.EnvironmentsPath); err != nil {
return fmt.Errorf("Error uploading environments: %s", err)
}
}
configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath, environmentsPath, p.config.ChefEnvironment)
if err != nil {
return fmt.Errorf("Error creating Chef config file: %s", err)
}
@@ -263,7 +287,7 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil)
}
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string) (string, error) {
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string, environmentsPath string, chefEnvironment string) (string, error) {
ui.Message("Creating configuration file 'solo.rb'")
cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks))
@@ -294,11 +318,14 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
}
configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
CookbookPaths: strings.Join(cookbook_paths, ","),
RolesPath: rolesPath,
DataBagsPath: dataBagsPath,
HasRolesPath: rolesPath != "",
HasDataBagsPath: dataBagsPath != "",
CookbookPaths: strings.Join(cookbook_paths, ","),
RolesPath: rolesPath,
DataBagsPath: dataBagsPath,
EnvironmentsPath: environmentsPath,
HasRolesPath: rolesPath != "",
HasDataBagsPath: dataBagsPath != "",
HasEnvironmentsPath: environmentsPath != "",
ChefEnvironment: chefEnvironment,
})
if err != nil {
return "", err
@@ -456,4 +483,8 @@ role_path "{{.RolesPath}}"
{{if .HasDataBagsPath}}
data_bag_path "{{.DataBagsPath}}"
{{end}}
{{if .HasEnvironmentsPath}}
environments_path "{{.EnvironmentsPath}}"
chef_environment "{{.ChefEnvironment}}"
{{end}}
`
+38
View File
@@ -19,6 +19,22 @@ func TestProvisioner_Impl(t *testing.T) {
}
}
func TestProvisionerPrepare_chefEnvironment(t *testing.T) {
var p Provisioner
config := testConfig()
config["chef_environment"] = "some-env"
err := p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if p.config.ChefEnvironment != "some-env" {
t.Fatalf("unexpected: %#v", p.config.ChefEnvironment)
}
}
func TestProvisionerPrepare_configTemplate(t *testing.T) {
var err error
var p Provisioner
@@ -139,6 +155,28 @@ func TestProvisionerPrepare_dataBagsPath(t *testing.T) {
}
}
func TestProvisionerPrepare_environmentsPath(t *testing.T) {
var p Provisioner
environmentsPath, err := ioutil.TempDir("", "environments")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(environmentsPath)
config := testConfig()
config["environments_path"] = environmentsPath
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if p.config.EnvironmentsPath != environmentsPath {
t.Fatalf("unexpected: %#v", p.config.EnvironmentsPath)
}
}
func TestProvisionerPrepare_rolesPath(t *testing.T) {
var p Provisioner
+6 -54
View File
@@ -8,8 +8,6 @@ import (
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"os"
"path/filepath"
"strings"
)
const DefaultTempConfigDir = "/tmp/salt"
@@ -146,12 +144,13 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
}
ui.Message(fmt.Sprintf("Uploading local state tree: %s", p.config.LocalStateTree))
if err = UploadLocalDirectory(p.config.LocalStateTree, fmt.Sprintf("%s/states", p.config.TempConfigDir), comm, ui); err != nil {
if err = comm.UploadDir(fmt.Sprintf("%s/states", p.config.TempConfigDir),
p.config.LocalStateTree, []string{".git"}); err != nil {
return fmt.Errorf("Error uploading local state tree to remote: %s", err)
}
ui.Message(fmt.Sprintf("Moving %s to /srv/salt", p.config.TempConfigDir))
cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/states /srv/salt/", p.config.TempConfigDir)}
ui.Message(fmt.Sprintf("Moving %s/states to /srv/salt", p.config.TempConfigDir))
cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/states /srv/salt", p.config.TempConfigDir)}
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
if err == nil {
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
@@ -161,18 +160,9 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
}
if p.config.LocalPillarRoots != "" {
ui.Message(fmt.Sprintf("Creating remote pillar directory: %s/pillar", p.config.TempConfigDir))
cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s/pillar", p.config.TempConfigDir)}
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
if err == nil {
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
}
return fmt.Errorf("Error creating remote pillar directory: %s", err)
}
ui.Message(fmt.Sprintf("Uploading local pillar roots: %s", p.config.LocalPillarRoots))
if err = UploadLocalDirectory(p.config.LocalPillarRoots, fmt.Sprintf("%s/pillar", p.config.TempConfigDir), comm, ui); err != nil {
if err = comm.UploadDir(fmt.Sprintf("%s/pillar", p.config.TempConfigDir),
p.config.LocalPillarRoots, []string{".git"}); err != nil {
return fmt.Errorf("Error uploading local pillar roots to remote: %s", err)
}
@@ -206,44 +196,6 @@ func (p *Provisioner) Cancel() {
os.Exit(0)
}
func UploadLocalDirectory(localDir string, remoteDir string, comm packer.Communicator, ui packer.Ui) (err error) {
visitPath := func(localPath string, f os.FileInfo, err error) (err2 error) {
localRelPath := strings.Replace(localPath, localDir, "", 1)
localRelPath = strings.Replace(localRelPath, "\\", "/", -1)
remotePath := filepath.Join(remoteDir, localRelPath)
if f.IsDir() && f.Name() == ".git" {
return filepath.SkipDir
}
if f.IsDir() {
// Make remote directory
cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", remotePath)}
if err = cmd.StartWithUi(comm, ui); err != nil {
return err
}
} else {
// Upload file to existing directory
file, err := os.Open(localPath)
if err != nil {
return fmt.Errorf("Error opening file: %s", err)
}
defer file.Close()
ui.Message(fmt.Sprintf("Uploading file %s: %s", localPath, remotePath))
if err = comm.Upload(remotePath, file); err != nil {
return fmt.Errorf("Error uploading file: %s", err)
}
}
return
}
err = filepath.Walk(localDir, visitPath)
if err != nil {
return fmt.Errorf("Error uploading local directory %s: %s", localDir, err)
}
return nil
}
func uploadMinionConfig(comm packer.Communicator, dst string, src string) error {
f, err := os.Open(src)
if err != nil {
+1 -1
View File
@@ -269,7 +269,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
r = &UnixReader{Reader: r}
}
if err := comm.Upload(p.config.RemotePath, f); err != nil {
if err := comm.Upload(p.config.RemotePath, r); err != nil {
return fmt.Errorf("Error uploading script: %s", err)
}
+7 -30
View File
@@ -2,7 +2,6 @@ package shell
import (
"bufio"
"bytes"
"io"
"sync"
)
@@ -54,35 +53,13 @@ func (r *UnixReader) Read(p []byte) (n int, err error) {
// only returns unix-style lines. So even if the line is "one\r\n", the
// token returned will be "one\n".
func scanUnixLine(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
advance, token, err = bufio.ScanLines(data, atEOF)
if advance == 0 {
// If we reached the end of a line without a newline, then
// just return as it is. Otherwise the Scanner will keep trying
// to scan, blocking forever.
return
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
// We have a new-line terminated line. Return the line with the newline
return i + 1, dropCR(data[0 : i+1]), nil
}
if atEOF {
// We have a final, non-terminated line
return len(data), dropCR(data), nil
}
if data[len(data)-1] != '\r' {
// We have a normal line, just let it tokenize
return len(data), data, nil
}
// We need more data
return 0, nil, nil
}
func dropCR(data []byte) []byte {
if len(data) > 0 && data[len(data)-2] == '\r' {
// Trim off the last byte and replace it with a '\n'
data = data[0 : len(data)-1]
data[len(data)-1] = '\n'
}
return data
return advance, append(token, '\n'), err
}
+20 -2
View File
@@ -15,9 +15,27 @@ func TestUnixReader_impl(t *testing.T) {
}
func TestUnixReader(t *testing.T) {
input := "one\r\ntwo\nthree\r\n"
expected := "one\ntwo\nthree\n"
input := "one\r\ntwo\n\r\nthree\r\n"
expected := "one\ntwo\n\nthree\n"
unixReaderTest(t, input, expected)
}
func TestUnixReader_unixOnly(t *testing.T) {
input := "\none\n\ntwo\nthree\n\n"
expected := "\none\n\ntwo\nthree\n\n"
unixReaderTest(t, input, expected)
}
func TestUnixReader_readsLastLine(t *testing.T) {
input := "one\ntwo"
expected := "one\ntwo\n"
unixReaderTest(t, input, expected)
}
func unixReaderTest(t *testing.T, input string, expected string) {
r := &UnixReader{
Reader: bytes.NewReader([]byte(input)),
}
+7
View File
@@ -72,6 +72,8 @@ go build \
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
-v \
-o bin/packer${EXTENSION} .
cp bin/packer${EXTENSION} ${GOPATH}/bin
) &
waitSingle
@@ -86,9 +88,14 @@ for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
-v \
-o bin/packer-${PLUGIN_NAME}${EXTENSION} ${PLUGIN}
cp bin/packer-${PLUGIN_NAME}${EXTENSION} ${GOPATH}/bin
) &
waitSingle
done
waitAll
# Reset signal trapping to avoid "Terminated: 15" at the end
trap - SIGINT SIGTERM EXIT
@@ -111,9 +111,11 @@ Optional:
of the source AMI will be attached. This defaults to "" (empty string),
which forces Packer to find an open device automatically.
* `mount_command` (string) - The command to use to mount devices. This
defaults to "mount". This may be useful to set if you want to set
environmental variables or perhaps run it with `sudo` or so on.
* `command_wrapper` (string) - How to run shell commands. This
defaults to "{{.Command}}". This may be useful to set if you want to set
environmental variables or perhaps run it with `sudo` or so on. This is a
configuration template where the `.Command` variable is replaced with the
command to be run..
* `mount_path` (string) - The path where the volume will be mounted. This is
where the chroot environment will be. This defaults to
@@ -123,9 +125,6 @@ Optional:
* `tags` (object of key/value strings) - Tags applied to the AMI.
* `unmount_command` (string) - Just like `mount_command`, except this is
the command to unmount devices.
## Basic Example
Here is a basic example. It is completely valid except for the access keys:
@@ -184,3 +183,37 @@ out of your AMI builds.
Packer properly obtains a process lock for the parallelism-sensitive parts
of its internals such as finding an available device.
## Using an IAM Instance Profile
If AWS keys are not specified in the template or through environment variables
Packer will use credentials provided by the instance's IAM profile, if it has one.
The following policy document provides the minimal set permissions necessary for Packer to work:
<pre class="prettyprint">
{
"Statement": [{
"Effect": "Allow",
"Action" : [
"ec2:AttachVolume",
"ec2:CreateVolume",
"ec2:DeleteVolume",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:DescribeInstances",
"ec2:CreateSnapshot",
"ec2:DeleteSnapshot",
"ec2:DescribeSnapshots",
"ec2:DescribeImages",
"ec2:RegisterImage",
"ec2:CreateTags"
],
"Resource" : "*"
}]
}
</pre>
@@ -35,11 +35,6 @@ Required:
Optional:
* `event_delay` (string) - The delay, as a duration string, before checking
the status of an event. DigitalOcean's current API has consistency issues
where events take time to appear after being created. This defaults to "5s"
and generally shouldn't have to be changed.
* `image_id` (int) - The ID of the base image to use. This is the image that
will be used to launch a new droplet and provision it. Defaults to "284203",
which happens to be "Ubuntu 12.04 x64 Server."
@@ -28,11 +28,16 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
"iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9",
"iso_checksum_type": "md5",
"ssh_username": "packer",
"ssh_password": "packer",
"ssh_wait_timeout": "30s",
"shutdown_command": "shutdown -P now"
"shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
}
</pre>
It is important to add a `shutdown_command`. By default Packer halts the
virtual machine and the file system may not be sync'd. Thus, changes made in a
provisioner might not be saved.
## Configuration Reference
There are many configuration options available for the VirtualBox builder.
@@ -42,6 +42,8 @@ usage: packer [--version] [--help] <command> [<args>]
Available commands are:
build build image(s) from template
fix fixes templates from old versions of packer
inspect see components of a template
validate check that a template is valid
```
@@ -68,6 +68,11 @@ The AWS provider itself can be configured with specific options:
this is a template that simply sets the AMIs for the various regions
of the AWS build.
* `compression_level` (integer) - An integer repesenting the
compression level to use when creating the Vagrant box. Valid
values range from 0 to 9, with 0 being no compression and 9 being
the best compression.
The `vagrantfile_template` has the `Images` variable which is a map
of region (string) to AMI ID (string). An example Vagrantfile template for
AWS is shown below. The example simply sets the AMI for each region.
@@ -91,6 +96,11 @@ The VirtualBox provider itself can be configured with specific options:
[text template](http://golang.org/pkg/text/template). By default this is
a template that just sets the base MAC address so that networking works.
* `compression_level` (integer) - An integer repesenting the
compression level to use when creating the Vagrant box. Valid
values range from 0 to 9, with 0 being no compression and 9 being
the best compression.
The `vagrantfile_template` has the `BaseMACAddress` variable which is a string
containing the MAC address of the first network interface. This must be set
in the Vagrantfile for networking to work properly with Vagrant. An example
@@ -111,3 +121,8 @@ The VMware provider itself can be configured with specific options:
[text template](http://golang.org/pkg/text/template). By default no
Vagrantfile is packaged with the box. Note that currently no variables
are available in the template, but this may change in the future.
* `compression_level` (integer) - An integer repesenting the
compression level to use when creating the Vagrant box. Valid
values range from 0 to 9, with 0 being no compression and 9 being
the best compression.
@@ -0,0 +1,50 @@
---
layout: "docs"
page_title: "Ansible (Local) Provisioner"
---
# Ansible Local Provisioner
Type: `ansible-local`
The `ansible-local` provisioner configures Ansible to run on the machine by
Packer from local Playbook and Role files. Playbooks and Roles can be uploaded
from your local machine to the remote machine. Ansible is run in [local mode](http://www.ansibleworks.com/docs/playbooks2.html#local-playbooks) via the `ansible-playbook` command.
## Basic Example
The example below is fully functional.
<pre class="prettyprint">
{
"type": "ansible-local",
"playbook_file": "local.yml"
}
</pre>
## Configuration Reference
The reference of available configuration options is listed below.
Required:
* `playbook_file` (string) - The playbook file to be executed by ansible.
This file must exist on your local system and will be uploaded to the
remote machine.
Optional:
* `playbook_paths` (array of strings) - An array of paths to playbook files on
your local system. These will be uploaded to the remote machine under
`staging_directory`/playbooks. By default, this is empty.
* `role_paths` (array of strings) - An array of paths to role directories on
your local system. These will be uploaded to the remote machine under
`staging_directory`/roles. By default, this is empty.
* `staging_directory` (string) - The directory where all the configuration of
Ansible by Packer will be placed. By default this is "/tmp/packer-provisioner-ansible-local".
This directory doesn't need to exist but must have proper permissions so that
the SSH user that Packer uses is able to create directories and write into
this folder. If the permissions are not correct, use a shell provisioner prior
to this to configure it properly.
@@ -39,7 +39,7 @@ Optional:
[salt bootstrap](https://github.com/saltstack/salt-bootstrap) to install
salt. Set this to true to skip this step.
* `boostrap_args` (string) - Arguments to send to the bootstrap script. Usage
* `bootstrap_args` (string) - Arguments to send to the bootstrap script. Usage
is somewhat documented on [github](https://github.com/saltstack/salt-bootstrap),
but the [script itself](https://github.com/saltstack/salt-bootstrap/blob/develop/bootstrap-salt.sh)
has more detailed usage instructions. By default, no arguments are sent to
@@ -102,7 +102,7 @@ root privileges without worrying about password prompts.
## Default Environmental Variables
In addition to being able to specify custom environmental variables using
the `environmental_vars` configuration, the provisioner automatically
the `environment_vars` configuration, the provisioner automatically
defines certain commonly useful environmental variables:
* `PACKER_BUILD_NAME` is set to the name of the build that Packer is running.
@@ -45,6 +45,8 @@ usage: packer [--version] [--help] <command> [<args>]
Available commands are:
build build image(s) from template
fix fixes templates from old versions of packer
inspect see components of a template
validate check that a template is valid
```
+1
View File
@@ -42,6 +42,7 @@
<li><h4>Provisioners</h4></li>
<li><a href="/docs/provisioners/shell.html">Shell Scripts</a></li>
<li><a href="/docs/provisioners/file.html">File Uploads</a></li>
<li><a href="/docs/provisioners/ansible-local.html">Ansible</a></li>
<li><a href="/docs/provisioners/chef-solo.html">Chef Solo</a></li>
<li><a href="/docs/provisioners/puppet-masterless.html">Puppet</a></li>
<li><a href="/docs/provisioners/salt-masterless.html">Salt</a></li>