Compare commits

...

185 Commits

Author SHA1 Message Date
Mitchell Hashimoto caaf679f27 v0.3.6 2013-09-02 10:19:27 -07:00
Mitchell Hashimoto 659383618f Update CHANGELOG 2013-09-02 10:18:48 -07:00
Mark Peek 84b0bf6dba Merge pull request #379 from justinsb/justinsb-openstack_ipv6_support
builder/openstack: Support IPv6 access address for Openstack
2013-09-02 09:56:25 -07:00
Mitchell Hashimoto 86c43a1bc9 Merge pull request #381 from greenalto/typo
builder/vmware: fix typo
2013-09-02 08:37:56 -07:00
Greg Karékinian 6cd10286db Fix typo
s/Detatching/Detaching/
2013-09-02 12:46:47 +02:00
Mitchell Hashimoto 4e87275f87 Merge pull request #378 from justinsb/increase_ssh_timeout
builder/openstack: Increase default SSH timeout from 1m -> 5m
2013-09-01 23:49:13 -07:00
Justin Santa Barbara 3001c16eee Increase default SSH timeout for OpenStack from 1m -> 5m
Many cloud providers have a minimum charge of 1 hour, and if there are DNS problems we can hit the 1 minute timeout easily.

Waiting five minutes gives more of a margin of error.
2013-09-01 14:36:59 -07:00
Mark Peek 5f3ae970c0 builder/openstack: region is now required 2013-09-01 13:46:57 -07:00
Mark Peek 5be22de623 website: add openstack to the intro platforms list 2013-09-01 13:41:48 -07:00
Mark Peek 15132a9f17 website: fixup some underscores to not be emphasis in the AWS docs 2013-09-01 13:37:19 -07:00
Mark Peek ffaca0a226 website: document required openstack region setting 2013-09-01 13:30:36 -07:00
Mark Peek eea558d2a5 builder/openstack: don't hardcode "DFW" and make region required 2013-09-01 13:22:22 -07:00
Mitchell Hashimoto 4370726ba7 website: fix typo s/script/shell/ provisioner 2013-09-01 10:11:28 -07:00
Mitchell Hashimoto 73ac1fc82c website: page titles on provisioner docs 2013-09-01 10:02:46 -07:00
Justin Santa Barbara 375eb57077 Support IPv6 access address for Openstack
We still prefer the IPv4 address, but if there is an IPv6 address and no IPv4 address, use IPv6.
2013-08-31 22:39:30 -07:00
Mitchell Hashimoto e6881cee6a Update CHANGELOG 2013-08-31 22:06:47 -07:00
Mitchell Hashimoto d4c4581885 Merge pull request #376 from justinsb/try_parse_pkcs8
communicator/ssh: If PKCS1 parsing of the SSH key fails, try PKCS8
2013-08-31 22:05:34 -07:00
Mitchell Hashimoto 0c11cf7c88 Update CHANGELOG 2013-08-31 22:02:36 -07:00
Mitchell Hashimoto 67887c11e4 Merge pull request #375 from justinsb/avoid_nil_pointer_if_no_openstack_image
builder/openstack: If no image is generated, just return nil
2013-08-31 22:01:55 -07:00
Justin Santa Barbara f1eae75389 If no image is generated, just return nil for the Openstack provider
This mirrors what is done in other providers e.g. Amazon providers
2013-08-31 21:55:52 -07:00
Justin Santa Barbara 7681d618d6 If PKCS1 parsing of the SSH key fails, try PKCS8 2013-08-31 21:54:23 -07:00
Mitchell Hashimoto 90b4dc7860 builder/digitalocean: retry destroy a few times 2013-08-31 21:32:35 -07:00
Mitchell Hashimoto b842e53028 command/inspect: cleaner output if no required vars 2013-08-31 17:49:22 -07:00
Mitchell Hashimoto 3abacee131 command/inspect: show required variables differently 2013-08-31 17:47:38 -07:00
Mitchell Hashimoto 4e2936d270 website: document required user vars 2013-08-31 17:38:38 -07:00
Mitchell Hashimoto 570be4d581 packer: fix required var check to work properly 2013-08-31 17:36:43 -07:00
Mitchell Hashimoto ab99d424ef packer: required user variables [GH-374] 2013-08-31 17:33:17 -07:00
Mitchell Hashimoto a1b4887324 website: add docs for -debug on Amazon builder 2013-08-31 16:07:55 -07:00
Mitchell Hashimoto 23b21ff4d0 Update CHANGELOG 2013-08-31 13:07:30 -07:00
Mitchell Hashimoto 8e7c2796fc builder/amazon/instance: new multistep API 2013-08-31 13:03:13 -07:00
Mitchell Hashimoto b5606af9e2 builder/amazon/ebs: new multistep API 2013-08-31 13:00:43 -07:00
Mitchell Hashimoto 1b8646c963 builder/amazon/chroot: new multistep API 2013-08-31 12:58:55 -07:00
Mitchell Hashimoto 36698d4b6d builder/vmware: new multistep API 2013-08-31 12:50:25 -07:00
Mitchell Hashimoto 8d5f8dc423 builder/virtualbox: new multistep API 2013-08-31 12:44:58 -07:00
Mitchell Hashimoto ea5361a9ac builder/openstack: new multistep API 2013-08-31 12:37:07 -07:00
Mitchell Hashimoto a82f1c18ee builder/digitalocean: new multistep API 2013-08-31 12:25:08 -07:00
Mitchell Hashimoto 98f3bc57a0 common: Use new multistep API 2013-08-31 12:17:59 -07:00
Mitchell Hashimoto 964546e050 Update CHANGELOG 2013-08-30 23:58:03 -07:00
Mitchell Hashimoto 1719cf8006 Merge branch 'cancel-next'
This implements more robust interrupt handling by propagating cancels
throughout more core Packer components.
2013-08-30 23:56:06 -07:00
Mitchell Hashimoto 6e098d1aaf packer: implement Cancel in ProvisionHook 2013-08-30 23:39:29 -07:00
Mitchell Hashimoto 66f7f5aad5 common: detect ctrl-c in Provision 2013-08-30 23:28:31 -07:00
Mitchell Hashimoto 84001c7c76 provisioner/*: implement Cancel 2013-08-30 23:23:36 -07:00
Mitchell Hashimoto 21171db836 packer: Add Cancel method to Provisioner 2013-08-30 23:21:15 -07:00
Mitchell Hashimoto 2e90660afc packer/rpc: test concurrent cancel/run 2013-08-30 23:10:16 -07:00
Mitchell Hashimoto cd12c3e030 packer/rpc: implement hook Cancel 2013-08-30 23:03:43 -07:00
Mitchell Hashimoto 017d27d7eb packer: reset runningHook when done 2013-08-30 22:59:13 -07:00
Mitchell Hashimoto 27a6dac7fd packer: implement Cancel on DispatchHook 2013-08-30 17:26:51 -07:00
Mitchell Hashimoto 98ddf043cc packer: Add Cancel() method to hook 2013-08-30 17:03:55 -07:00
Mitchell Hashimoto dfb44a2abe packer/plugin: Interrupted() to check for i nterrupts 2013-08-30 16:39:28 -07:00
Mitchell Hashimoto dbae49f0c8 provisioner/chef-solo: code re-org 2013-08-30 16:35:57 -07:00
Mitchell Hashimoto a03c66272f packer/plugin: Count number of interrupts atomically 2013-08-30 16:29:21 -07:00
Mitchell Hashimoto 65eb05384f packer: pass tests 2013-08-30 16:07:06 -07:00
Mitchell Hashimoto c157da38c9 Update CHANGELOG 2013-08-30 16:06:12 -07:00
Mitchell Hashimoto 4c4020f723 provisioner/chef-solo: properly escape user vars for JSON
/cc @mwhooker - I moved the processing up into Prepare so that any
errors will be shown during a validate pass. Also, I escape some stuff
in keys. Tests moved as well.
2013-08-30 16:05:04 -07:00
Mitchell Hashimoto e4c22472bb Merge pull request #364 from mwhooker/362
provisioner/chef-solo: template expansion of `json`
2013-08-30 15:26:39 -07:00
Mitchell Hashimoto dc9a803efd builder/amazon: don't chmod on windows 2013-08-30 15:04:23 -07:00
Mitchell Hashimoto d1fff21045 builder/amazon: chmod debug key so it is ssh-ready 2013-08-30 15:03:29 -07:00
Mitchell Hashimoto f97b88654f builder/amazon: output DNS name if debug mode 2013-08-30 14:55:56 -07:00
Mitchell Hashimoto e613b0cdcd builder/amazon: drop private key if debug mode [GH-373] 2013-08-30 14:48:50 -07:00
Mitchell Hashimoto 7ed2498407 provisioner/salt-masterless: join paths properly 2013-08-30 14:26:29 -07:00
Mark Peek 5d8a211774 website: add tip about using apt-get/yum -y to prevent hangs 2013-08-29 22:19:13 -07:00
Mark Peek 1b91110a59 Merge pull request #370 from TFDuesing/patch-1
website: openstack s/droplet/server/
2013-08-29 19:30:05 -07:00
Thomas Duesing 729232c7c7 s/droplet/server/ 2013-08-29 21:21:40 -05:00
Mitchell Hashimoto 293db31a0f Merge pull request #369 from celkins/fix-staging-dir-2
website: Fix incorrect config key for Chef staging directory
2013-08-28 21:25:41 -07:00
Christopher Elkins 9f55695964 Fix incorrect config key for staging directory 2013-08-28 21:23:57 -07:00
Matthew Hooker a1893b0bce reuse jsonString instead of opaque result var. 2013-08-28 20:16:06 -07:00
Matthew Hooker fc595de0fd clean up and retab. 2013-08-28 17:35:43 -07:00
Matthew Hooker e21e980647 template expansion of json in chef-solo
Fixes #362
2013-08-28 17:27:22 -07:00
Mitchell Hashimoto 332d666180 packer/plugin: fix nil deref during ctrl-c of provisioner 2013-08-28 11:21:02 -07:00
Mitchell Hashimoto 87f47ba6de Up vesrion for dev 2013-08-28 11:16:28 -07:00
Mitchell Hashimoto a000e24dc7 v0.3.5 2013-08-28 10:47:19 -07:00
Mitchell Hashimoto 6941f72b21 website: document shuting down network interfaces for reboot 2013-08-28 10:46:45 -07:00
Mitchell Hashimoto 4540285b15 common: support SHA512 as checksum type [Gh-356] 2013-08-28 09:09:43 -07:00
Mitchell Hashimoto a14d64f0ed website: update website for ssh_key_path stuff 2013-08-27 22:59:36 -07:00
Mitchell Hashimoto e7b2751d63 builder/vmware: support key based SSH auth [GH-70] 2013-08-27 22:57:42 -07:00
Mitchell Hashimoto 19bafd8da8 builder/virtualbox: support ssh keys as auth mechanism [GH-70] 2013-08-27 22:55:13 -07:00
Mark Peek f717edd644 builder/openstack: return artifacts 2013-08-27 22:37:06 -07:00
Mark Peek ca1388f7bc builder/openstack: simplify by passing csp instead of accessor and api 2013-08-27 22:37:06 -07:00
Mitchell Hashimoto 62fbf8fef8 communicator/ssh: Reset failure count if heartbeat works 2013-08-27 22:14:31 -07:00
Mitchell Hashimoto c16c467e63 communicator/ssh: heartbeat the SSH connection to detect drops [GH-200] 2013-08-27 22:12:21 -07:00
Mitchell Hashimoto 6805d62b44 packer: template error if override specified for bad builder [GH-336] 2013-08-27 21:34:55 -07:00
Mitchell Hashimoto caa4fce2d3 Merge branch 'fix-minion-config-with-non-tmp-dir' of https://github.com/rach/packer into rach-fix-minion-config-with-non-tmp-dir
Conflicts:
	provisioner/salt-masterless/provisioner.go
2013-08-27 20:21:02 -07:00
Mitchell Hashimoto d08019c1a7 website: Fix typo in docs [GH-340] 2013-08-27 17:55:09 -07:00
Mitchell Hashimoto 6f1675ab6b provisioner/chef-solo: template process the configuration 2013-08-27 17:41:49 -07:00
Mitchell Hashimoto ec8ba97c11 fmt 2013-08-27 17:37:29 -07:00
Mitchell Hashimoto c186d2dc3e Update CHANGELOG 2013-08-27 17:37:07 -07:00
Mitchell Hashimoto 06643423b1 Merge pull request #353 from rach/add-setting-for-pillar-folder
provisioner/salt-masterless: Add setting for pillar folder
2013-08-27 17:36:28 -07:00
Mitchell Hashimoto e50a15c4ee builder/vmware: support vmx_template_path [GH-270] 2013-08-27 17:23:28 -07:00
Rachid Belaid 99a7ae8792 Add doc and change pillar tree in roots to respect salt vocabulary 2013-08-28 01:01:37 +01:00
Mitchell Hashimoto 20541a7eda builder/vmware: ability to not request a PTY for SSH [GH-270] 2013-08-27 16:51:05 -07:00
Rachid Belaid 2c03c1a64e Add setting for pillar folder to respect saltstack structure 2013-08-28 00:38:36 +01:00
Mitchell Hashimoto f626790edd Update CHANGELOG 2013-08-27 16:36:36 -07:00
Mitchell Hashimoto 9865dd0161 Merge pull request #339 from mitchellh/do-soft-shutdown
builder/digitalocean: send a "shutdown" before snapshotting
2013-08-27 16:35:30 -07:00
Rachid Belaid c680fe3909 Fix minion config with non tmp dir 2013-08-27 23:49:04 +01:00
Mitchell Hashimoto 95682f6dfc command/build: build error machine-readable is targetted to the build 2013-08-27 15:03:49 -07:00
Mitchell Hashimoto 869c0b94ff provisioner/chef-solo: use proper exceute command 2013-08-27 14:35:11 -07:00
Mitchell Hashimoto e6960e3e04 plugin/provisioner-chef-solo: compile 2013-08-27 14:31:42 -07:00
Mitchell Hashimoto 2746139591 website: document chef solo provisioner 2013-08-27 14:29:52 -07:00
Mitchell Hashimoto f536e351fa command/build: "end" sentinel in artifact machine-readable output 2013-08-27 13:40:19 -07:00
Mitchell Hashimoto 53890e9de3 command/build: put build errors in machine readable output 2013-08-27 13:37:31 -07:00
Mitchell Hashimoto 56a309941b provisioner/chef-solo: remove unused constants and variables 2013-08-27 13:32:59 -07:00
Mitchell Hashimoto 286c36b3c4 Update CHANGELOG 2013-08-27 13:27:53 -07:00
Mitchell Hashimoto fa2347e711 provisioner/chef-solo: upload cookbooks 2013-08-27 13:27:20 -07:00
Mitchell Hashimoto 5e841befee provisioner/chef-solo: massive refactor and style nitpick 2013-08-27 13:27:20 -07:00
Mitchell Hashimoto e43d08a3fc provisioner/chef-solo: installChef fixed up to use tpl and StartWithUi 2013-08-27 13:27:20 -07:00
Mark Peek 2e760ee57b Add new openstack builder 2013-08-26 22:05:21 -07:00
Mark Peek 9bc3702adc website: add openstack page 2013-08-26 22:02:03 -07:00
Mark Peek e7140cf405 Add base support for openstack [GH-155]
This change adds base support for an openstack builder.
Thank you to Rackspace for providing cloud assets to complete this work
and @sam-falvo for working with us on the perigee/gophercloud changes.
2013-08-26 21:57:23 -07:00
Mitchell Hashimoto 99dd3ccec5 Update CHANGELOG 2013-08-25 20:51:51 -07:00
Mitchell Hashimoto d6fde7dcb1 Merge branch 'jvandyke-provisioner-chef-solo'
Chef-solo provisioner first pass.
2013-08-25 20:50:06 -07:00
Mitchell Hashimoto da1de12b78 Merge branch 'provisioner-chef-solo' of https://github.com/jvandyke/packer into jvandyke-provisioner-chef-solo
Conflicts:
	config.go
2013-08-25 20:49:53 -07:00
Mitchell Hashimoto 5eb97908c2 communicator/ssh: Trailing slash won't create destination dir 2013-08-25 20:47:10 -07:00
Mitchell Hashimoto aa47487960 communicator/ssh: UploadDir works properly 2013-08-25 20:30:56 -07:00
Mitchell Hashimoto 1948350d20 communicator/ssh: refactor to upload directories properly 2013-08-25 20:30:56 -07:00
Mitchell Hashimoto e75d3c1fbb communicator/ssh: refactor scpSession that we'll reuse for UploadDir 2013-08-25 20:30:56 -07:00
Mitchell Hashimoto 7895df8c8f builder/amazon/chroot: implement UploadDir for chroot communicator 2013-08-25 20:30:56 -07:00
Mitchell Hashimoto 8a194a3750 packer/rpc: implement UploadDir 2013-08-25 20:30:56 -07:00
Mitchell Hashimoto dbfe8e21e1 packer/rpc: use public MockCommunicator, tests pass 2013-08-25 20:30:56 -07:00
Mitchell Hashimoto f5c7c6ea90 packer: Add UploadDir to the Communicator interface 2013-08-25 20:30:56 -07:00
Mitchell Hashimoto e589bf2724 Merge pull request #343 from masteinhauser/patch-1
website: Actual md5sum as Ubuntu Hashesh webpage is out of date.
2013-08-24 21:07:50 -07:00
Myles A. K. Steinhauser 8311b3508c Actual md5sum as Ubuntu Hashesh webpage is out of date. 2013-08-24 20:43:49 -04:00
Mitchell Hashimoto 894ca6deca Update CHANGELOG 2013-08-24 17:14:45 -07:00
Mitchell Hashimoto 32f1092608 Merge pull request #338 from mitchellh/signal-confirm
main: confirm cleanup at first signal received
2013-08-24 17:13:25 -07:00
Mitchell Hashimoto c365dd2f9f Merge pull request #342 from masteinhauser/patch-1
website: bump Ubuntu example from 12.04.2 to 12.04.3
2013-08-24 17:05:48 -07:00
Myles A. K. Steinhauser d6d54cc406 Bump Ubuntu 12.04.2 to 12.04.3
Ubuntu has removed the 12.04.2 release ISOs from their mirrors when they released 12.04.3. This impacted the URL and the Checksum contained in the example.json which returned an HTTP 404 from Ubuntu's Mirrors.
2013-08-24 20:04:45 -04:00
Jack Pearkes 43dd2bd2c9 builder/digitalocean: send a "shutdown" before snapshotting
Instead of pulling the plug on a droplet with the "poweroff" command,
we first send a soft shutdown to the API, then we poweroff to allow
the snapshot to properly complete.

Sending just a shutdown and then snapshotting wasn't as reliable as
sending the poweroff manually, for reasons unknown to me.

This fixes #332.
2013-08-24 13:04:51 +02:00
Jack Pearkes 088665840f packer/plugin: confirm cleanup at first signal received 2013-08-24 12:55:25 +02:00
Mitchell Hashimoto 14517e6fa5 provisioner/salt-masterless: fix the build 2013-08-23 15:58:30 -07:00
Mitchell Hashimoto 9527f60884 builder/amazon/instance: remove check for AMI tools [GH-330] 2013-08-23 15:53:54 -07:00
Mitchell Hashimoto 788fe84053 Update CHANGELOG 2013-08-23 15:42:30 -07:00
Mitchell Hashimoto f56601fd64 website: delete salt.html.markdown 2013-08-23 15:41:55 -07:00
Rachid Belaid 4b2d1efb13 Fix #264 to upload local minion config
Conflicts:
	provisioner/salt-masterless/provisioner.go
	website/source/docs/provisioners/salt.html.markdown
2013-08-23 15:41:11 -07:00
Mitchell Hashimoto da70359949 Update CHANGELOG 2013-08-23 15:32:05 -07:00
Mitchell Hashimoto 3a19127f53 post-processor/vagrant: don't error if unused 2013-08-23 15:31:21 -07:00
Mitchell Hashimoto 303a003d2f packer: no need to check if nil since we're appending to slice 2013-08-23 15:25:02 -07:00
Mitchell Hashimoto 5d7dc6e1c9 Merge pull request #329 from jasonberanek/vagrant-output-fix
post-processor/vagrant: proper output ConfigTemplate validation [GH-324]
2013-08-23 15:15:55 -07:00
Mitchell Hashimoto 012b534873 post-processor/vagrant: nitpick format 2013-08-23 15:14:58 -07:00
Mitchell Hashimoto 701b867a95 Update CHANGELOG 2013-08-23 15:07:13 -07:00
Mitchell Hashimoto e550c4503e Merge pull request #331 from jasonberanek/vbox-vagrant-ova
post-processor/vagrant: support for Virtualbox OVA artifacts
2013-08-23 15:06:44 -07:00
Mitchell Hashimoto d3fff7d145 packer/rpc: don't panic on failed Ui calls 2013-08-23 14:39:59 -07:00
Mitchell Hashimoto 4513a2509e Update CHANGELOG 2013-08-23 14:34:31 -07:00
Mitchell Hashimoto 2b797bb1ed packer/plugin: swallowInterrupts in plugin should swallow all 2013-08-23 14:22:32 -07:00
Mitchell Hashimoto c7e932ee6e packer/rpc: error instead of panic cancelling builder 2013-08-23 13:58:00 -07:00
Mitchell Hashimoto a7d3638146 packer/rpc: less panics 2013-08-23 13:57:26 -07:00
Mitchell Hashimoto 5a1073a980 communicator/ssh: more error handlingin Upload 2013-08-23 11:27:59 -07:00
Mitchell Hashimoto 99a7116939 provisioner/shell: retry the upload if command fails as well
This improves reboot handling robustness
2013-08-23 11:21:20 -07:00
Jason A. Beranek dec7f3f2ef post-processor/vagrant/virtualbox: support ova artifacts [GH-315] 2013-08-22 21:47:50 -05:00
Jason A. Beranek b2157b87b9 post-processor/vagrant: fix output ConfigTemplate validation [GH-324]
-apply same output validation steps found in builder specific Configure
functions
2013-08-22 20:19:03 -05:00
Mitchell Hashimoto f4f433d0d8 builder/amazon: modify attributes before region copy
/cc @jmassara
2013-08-22 15:35:47 -07:00
Mitchell Hashimoto d9e80803c9 builder/amazon/common: modify output for AMI copy 2013-08-22 15:32:24 -07:00
Mitchell Hashimoto 9810766373 website: document ami_regions
/cc @jmassara
2013-08-22 15:20:44 -07:00
Mitchell Hashimoto 385ca4cc97 website: document tags on every amazon builder 2013-08-22 15:17:07 -07:00
Mitchell Hashimoto 0306759840 builder/amazon: support tags in chroot and instance 2013-08-22 15:11:54 -07:00
Mitchell Hashimoto 50e2535bd8 builder/amazon/common: move tags into ami_config 2013-08-22 15:09:21 -07:00
Mitchell Hashimoto f197c2b5f0 builder/amazon: region copy must go before modify attributes
/cc @jmassara - This has to go before the modify attributes so that
the attributes are properly modified on all resulting AMIs.
2013-08-22 15:03:30 -07:00
Mitchell Hashimoto 374f2fb647 builder/amazon/common: remove duplicates from ami_region
/cc @jmassara
2013-08-22 14:58:21 -07:00
Mitchell Hashimoto cd4fb50cb7 Update CHANGELOG 2013-08-22 14:44:48 -07:00
Mitchell Hashimoto 4522db5d7d Merge pull request #322 from jmassara/ami_region_copy
amazon/common: Added AMI CopyImage support
2013-08-22 14:44:03 -07:00
Mitchell Hashimoto 16df9a2c69 Update CHANGELOG 2013-08-22 14:29:33 -07:00
Mitchell Hashimoto 6fd17791e6 Merge pull request #328 from jbronn/vmdk_type
builder/vmware: Enable customization of VMDK type with `disk_type_id`
2013-08-22 14:28:43 -07:00
Mitchell Hashimoto db01abc008 packer/rpc: fix build by missing import 2013-08-22 11:53:54 -07:00
Mitchell Hashimoto 3375c9e4fd communicator/ssh: respect interrupts by not looping on retyr [GH-327] 2013-08-22 11:53:31 -07:00
Justin Bronn 53b13869b3 Document the disk_type_id parameter. 2013-08-22 11:51:53 -07:00
Mitchell Hashimoto 24b12993b7 packer/rpc: get rid of panicable case 2013-08-22 11:45:58 -07:00
Justin Bronn ffe63b8bd8 Make it possible to customize the VMware virtual disk type id. 2013-08-22 11:40:56 -07:00
Mitchell Hashimoto 58a48ed171 common/command: error if only/except points to bad build 2013-08-22 11:40:30 -07:00
Mitchell Hashimoto 2cf112c0ba common/command: more tests for filtering builds 2013-08-22 11:34:51 -07:00
Mitchell Hashimoto cc0f91b41b packer: add mock implementations and more template tests 2013-08-22 11:32:59 -07:00
James Massara e2ce81ce5e Make sure ami_regions are valid 2013-08-21 18:44:14 -07:00
James Massara 1e27feb6c6 amazon/common: Added AMI CopyImage support 2013-08-21 13:53:07 -07:00
Mitchell Hashimoto cfb53005ff Update CHANGELOG 2013-08-21 11:56:02 -07:00
Mitchell Hashimoto ba900fc2e8 up version for dev 2013-08-21 11:55:45 -07:00
James Van Dyke 428bc4d745 Remove debugging statement. 2013-07-15 16:38:26 -04:00
James Van Dyke ba5d7a9d72 Remove redundant code and clean up some string concatenation.
Clean up Say statements.
2013-07-12 11:09:52 -04:00
James Van Dyke a6f3bb3bb2 Improve logging.
Correct behavior for undefined recipe list.
Correct package name to upstream repo.
2013-07-11 12:22:15 -04:00
James Van Dyke 1fadd03837 Merge remote-tracking branch 'upstream/master' into provisioner-chef-solo 2013-07-11 10:48:53 -04:00
James Van Dyke a84f26bfdf Add basic test file for provisioner-chef-solo. 2013-07-11 10:47:51 -04:00
James Van Dyke 24c4b5334b Merge remote-tracking branch 'upstream/master' into provisioner-chef-solo
Conflicts:
	config.go

Add the chef-solo provisioner back to config.go.
Fix import path for chef-solo provisioner.
2013-07-10 08:58:00 -04:00
James Van Dyke 69f0049a44 Rename configuration avoid_sudo to prevent_sudo 2013-07-06 00:37:59 -04:00
James Van Dyke 3416f0760c Remove errant die statement. 2013-07-06 00:17:28 -04:00
James Van Dyke e5e97f3cc1 Implement skip_install configuration to skip Chef installation. 2013-07-06 00:15:21 -04:00
James Van Dyke 552298b683 Implement avoid_sudo configuration option. 2013-07-05 23:56:51 -04:00
James Van Dyke bb3ff62623 Rename configuration cookbook_paths to cookbooks_paths to
conform to Vagrant wording.
Remove configuration `cookbook_path` to simplify some things.
2013-07-05 22:33:24 -04:00
James Van Dyke 608d874b45 Make JSON attributes work with real JSON! 2013-07-05 21:57:56 -04:00
James Van Dyke 45a16fceac First pass at Chef Solo provisioner. 2013-07-05 00:26:48 -04:00
179 changed files with 5010 additions and 1292 deletions
+78
View File
@@ -1,3 +1,81 @@
## 0.3.6 (September 2, 2013)
FEATURES:
* User variables can now be specified as "required", meaning the user
MUST specify a value. Just set the default value to "null". [GH-374]
IMPROVEMENTS:
* core: Much improved interrupt handling. For example, interrupts now
cancel much more quickly within provisioners.
* builder/amazon: In `-debug` mode, the keypair used will be saved to
the current directory so you can access the machine. [GH-373]
* builder/amazon: In `-debug` mode, the DNS is outputted.
* builder/openstack: IPv6 addresses supported for SSH. [GH-379]
* communicator/ssh: Support for private keys encrypted using PKCS8. [GH-376]
* provisioner/chef-solo: You can now use user variables in the `json`
configuration for Chef. [GH-362]
BUG FIXES:
* core: Concurrent map access is completely gone, fixing rare issues
with runtime memory corruption. [GH-307]
* core: Fix possible panic when ctrl-C during provisioner run.
* builder/digitalocean: Retry destroy a few times because DO sometimes
gives false errors.
* builder/openstack: Properly handle the case no image is made. [GH-375]
* builder/openstack: Specifying a region is now required in a template.
* provisioners/salt-masterless: Use filepath join to properly join paths.
## 0.3.5 (August 28, 2013)
FEATURES:
* **NEW BUILDER:** `openstack`. You can now build on OpenStack. [GH-155]
* **NEW PROVISIONER:** `chef-solo`. You can now provision with Chef
using `chef-solo` from local cookbooks.
* builder/amazon: Copy AMI to multiple regions with `ami_regions`. [GH-322]
* builder/virtualbox,vmware: Can now use SSH keys as an auth mechanism for
SSH using `ssh_key_path`. [GH-70]
* builder/virtualbox,vmware: Support SHA512 as a checksum type. [GH-356]
* builder/vmware: The root hard drive type can now be specified with
"disk_type_id" for advanced users. [GH-328]
* provisioner/salt-masterless: Ability to specfy a minion config. [GH-264]
* provisioner/salt-masterless: Ability to upload pillars. [GH-353]
IMPROVEMENTS:
* core: Output message when Ctrl-C received that we're cleaning up. [GH-338]
* builder/amazon: Tagging now works with all amazon builder types.
* builder/vmware: Option `ssh_skip_request_pty` for not requesting a PTY
for the SSH connection. [GH-270]
* builder/vmware: Specify a `vmx_template_path` in order to customize
the generated VMX. [GH-270]
* command/build: Machine-readable output now contains build errors, if any.
* command/build: An "end" sentinel is outputted in machine-readable output
for artifact listing so it is easier to know when it is over.
BUG FIXES:
* core: Fixed a couple cases where a double ctrl-C could panic.
* core: Template validation fails if an override is specified for a
non-existent builder. [GH-336]
* core: The SSH connection is heartbeated so that drops can be
detected. [GH-200]
* builder/amazon/instance: Remove check for ec2-ami-tools because it
didn't allow absolute paths to work properly. [GH-330]
* builder/digitalocean: Send a soft shutdown request so that files
are properly synced before shutdown. [GH-332]
* command/build,command/validate: If a non-existent build is specified to
'-only' or '-except', it is now an error. [GH-326]
* post-processor/vagrant: Setting OutputPath with a timestamp now
always works properly. [GH-324]
* post-processor/vagrant: VirtualBox OVA formats now turn into
Vagrant boxes properly. [GH-331]
* provisioner/shell: Retry upload if start command fails, making reboot
handling much more robust.
## 0.3.4 (August 21, 2013)
IMPROVEMENTS:
+15 -8
View File
@@ -167,11 +167,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := make(map[string]interface{})
state["config"] = &b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
@@ -194,6 +194,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
Tags: b.config.AMITags,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -209,18 +216,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state["error"]; ok {
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state["amis"]; !ok {
if _, ok := state.GetOk("amis"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state["amis"].(map[string]string),
Amis: state.Get("amis").(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+5 -1
View File
@@ -1,6 +1,10 @@
package chroot
import (
"github.com/mitchellh/multistep"
)
// Cleanup is an interface that some steps implement for early cleanup.
type Cleanup interface {
CleanupFunc(map[string]interface{}) error
CleanupFunc(multistep.StateBag) error
}
+32
View File
@@ -70,6 +70,38 @@ func (c *Communicator) Upload(dst string, r io.Reader) error {
return nil
}
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)
}
log.Printf("Uploading directory '%s' to '%s'", src, dst)
return filepath.Walk(src, walkFn)
}
func (c *Communicator) Download(src string, w io.Writer) error {
src = filepath.Join(c.Chroot, src)
log.Printf("Downloading from chroot dir: %s", src)
+14 -14
View File
@@ -21,12 +21,12 @@ type StepAttachVolume struct {
volumeId string
}
func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
device := state["device"].(string)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
volumeId := state["volume_id"].(string)
func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
device := state.Get("device").(string)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
// For the API call, it expects "sd" prefixed devices.
attachVolume := strings.Replace(device, "/xvd", "/sd", 1)
@@ -35,7 +35,7 @@ func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepActio
_, err := ec2conn.AttachVolume(volumeId, instance.InstanceId, attachVolume)
if err != nil {
err := fmt.Errorf("Error attaching volume: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -67,29 +67,29 @@ func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepActio
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["attach_cleanup"] = s
state.Put("attach_cleanup", s)
return multistep.ActionContinue
}
func (s *StepAttachVolume) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
func (s *StepAttachVolume) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepAttachVolume) CleanupFunc(state map[string]interface{}) error {
func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
if !s.attached {
return nil
}
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Detaching EBS volume...")
_, err := ec2conn.DetachVolume(s.volumeId)
@@ -11,10 +11,10 @@ type StepChrootProvision struct {
mounts []string
}
func (s *StepChrootProvision) Run(state map[string]interface{}) multistep.StepAction {
hook := state["hook"].(packer.Hook)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
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)
// Create our communicator
comm := &Communicator{
@@ -24,11 +24,11 @@ func (s *StepChrootProvision) Run(state map[string]interface{}) multistep.StepAc
// Provision
log.Println("Running the provision hook")
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
state["error"] = err
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepChrootProvision) Cleanup(state map[string]interface{}) {}
func (s *StepChrootProvision) Cleanup(state multistep.StateBag) {}
+9 -9
View File
@@ -19,10 +19,10 @@ type StepCopyFiles struct {
files []string
}
func (s *StepCopyFiles) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
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)
s.files = make([]string, 0, len(config.CopyFiles))
if len(config.CopyFiles) > 0 {
@@ -34,7 +34,7 @@ func (s *StepCopyFiles) Run(state map[string]interface{}) multistep.StepAction {
if err := s.copySingle(chrootPath, path); err != nil {
err := fmt.Errorf("Error copying file: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -43,18 +43,18 @@ func (s *StepCopyFiles) Run(state map[string]interface{}) multistep.StepAction {
}
}
state["copy_files_cleanup"] = s
state.Put("copy_files_cleanup", s)
return multistep.ActionContinue
}
func (s *StepCopyFiles) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepCopyFiles) CleanupFunc(map[string]interface{}) error {
func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error {
if s.files != nil {
for _, file := range s.files {
log.Printf("Removing: %s", file)
+12 -12
View File
@@ -18,11 +18,11 @@ type StepCreateVolume struct {
volumeId string
}
func (s *StepCreateVolume) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
image := state["source_image"].(*ec2.Image)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
// Determine the root device snapshot
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
@@ -36,7 +36,7 @@ func (s *StepCreateVolume) Run(state map[string]interface{}) multistep.StepActio
if rootDevice == nil {
err := fmt.Errorf("Couldn't find root device!")
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -54,7 +54,7 @@ func (s *StepCreateVolume) Run(state map[string]interface{}) multistep.StepActio
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
if err != nil {
err := fmt.Errorf("Error creating root volume: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -82,22 +82,22 @@ func (s *StepCreateVolume) Run(state map[string]interface{}) multistep.StepActio
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["volume_id"] = s.volumeId
state.Put("volume_id", s.volumeId)
return multistep.ActionContinue
}
func (s *StepCreateVolume) Cleanup(state map[string]interface{}) {
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
if s.volumeId == "" {
return
}
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting the created EBS volume...")
_, err := ec2conn.DeleteVolume(s.volumeId)
+5 -5
View File
@@ -11,8 +11,8 @@ import (
// prepare for snapshotting and creating an AMI.
type StepEarlyCleanup struct{}
func (s *StepEarlyCleanup) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
cleanupKeys := []string{
"copy_files_cleanup",
"mount_extra_cleanup",
@@ -21,11 +21,11 @@ func (s *StepEarlyCleanup) Run(state map[string]interface{}) multistep.StepActio
}
for _, key := range cleanupKeys {
c := state[key].(Cleanup)
c := state.Get(key).(Cleanup)
log.Printf("Running cleanup func: %s", key)
if err := c.CleanupFunc(state); err != nil {
err := fmt.Errorf("Error cleaning up: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -34,4 +34,4 @@ func (s *StepEarlyCleanup) Run(state map[string]interface{}) multistep.StepActio
return multistep.ActionContinue
}
func (s *StepEarlyCleanup) Cleanup(state map[string]interface{}) {}
func (s *StepEarlyCleanup) Cleanup(state multistep.StateBag) {}
+5 -5
View File
@@ -10,14 +10,14 @@ import (
// StepEarlyUnflock unlocks the flock.
type StepEarlyUnflock struct{}
func (s *StepEarlyUnflock) Run(state map[string]interface{}) multistep.StepAction {
cleanup := state["flock_cleanup"].(Cleanup)
ui := state["ui"].(packer.Ui)
func (s *StepEarlyUnflock) Run(state multistep.StateBag) multistep.StepAction {
cleanup := state.Get("flock_cleanup").(Cleanup)
ui := state.Get("ui").(packer.Ui)
log.Println("Unlocking file lock...")
if err := cleanup.CleanupFunc(state); err != nil {
err := fmt.Errorf("Error unlocking file lock: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -25,4 +25,4 @@ func (s *StepEarlyUnflock) Run(state map[string]interface{}) multistep.StepActio
return multistep.ActionContinue
}
func (s *StepEarlyUnflock) Cleanup(state map[string]interface{}) {}
func (s *StepEarlyUnflock) Cleanup(state multistep.StateBag) {}
+8 -8
View File
@@ -17,13 +17,13 @@ type StepFlock struct {
fh *os.File
}
func (s *StepFlock) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
lockfile := "/var/lock/packer-chroot/lock"
if err := os.MkdirAll(filepath.Dir(lockfile), 0755); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -32,7 +32,7 @@ func (s *StepFlock) Run(state map[string]interface{}) multistep.StepAction {
f, err := os.Create(lockfile)
if err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -40,7 +40,7 @@ func (s *StepFlock) Run(state map[string]interface{}) multistep.StepAction {
// LOCK!
if err := lockFile(f); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -49,15 +49,15 @@ func (s *StepFlock) Run(state map[string]interface{}) multistep.StepAction {
// the lock.
s.fh = f
state["flock_cleanup"] = s
state.Put("flock_cleanup", s)
return multistep.ActionContinue
}
func (s *StepFlock) Cleanup(state map[string]interface{}) {
func (s *StepFlock) Cleanup(state multistep.StateBag) {
s.CleanupFunc(state)
}
func (s *StepFlock) CleanupFunc(state map[string]interface{}) error {
func (s *StepFlock) CleanupFunc(state multistep.StateBag) error {
if s.fh == nil {
return nil
}
+8 -8
View File
@@ -12,9 +12,9 @@ import (
// StepInstanceInfo verifies that this builder is running on an EC2 instance.
type StepInstanceInfo struct{}
func (s *StepInstanceInfo) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
// Get our own instance ID
ui.Say("Gathering information about this EC2 instance...")
@@ -24,7 +24,7 @@ func (s *StepInstanceInfo) Run(state map[string]interface{}) multistep.StepActio
err := fmt.Errorf(
"Error retrieving the ID of the instance Packer is running on.\n" +
"Please verify Packer is running on a proper AWS EC2 instance.")
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -36,22 +36,22 @@ func (s *StepInstanceInfo) Run(state map[string]interface{}) multistep.StepActio
instancesResp, err := ec2conn.Instances([]string{instanceId}, ec2.NewFilter())
if err != nil {
err := fmt.Errorf("Error getting instance data: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(instancesResp.Reservations) == 0 {
err := fmt.Errorf("Error getting instance data: no instance found.")
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instance := &instancesResp.Reservations[0].Instances[0]
state["instance"] = instance
state.Put("instance", instance)
return multistep.ActionContinue
}
func (s *StepInstanceInfo) Cleanup(map[string]interface{}) {}
func (s *StepInstanceInfo) Cleanup(multistep.StateBag) {}
+15 -15
View File
@@ -24,10 +24,10 @@ type StepMountDevice struct {
mountPath string
}
func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
device := state["device"].(string)
func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
Device: filepath.Base(device),
@@ -35,7 +35,7 @@ func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -43,7 +43,7 @@ func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction
mountPath, err = filepath.Abs(mountPath)
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -52,7 +52,7 @@ func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction
if err := os.MkdirAll(mountPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -65,33 +65,33 @@ func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting root volume: %s\nStderr: %s", err, stderr.String())
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the mount path so we remember to unmount it later
s.mountPath = mountPath
state["mount_path"] = s.mountPath
state["mount_device_cleanup"] = s
state.Put("mount_path", s.mountPath)
state.Put("mount_device_cleanup", s)
return multistep.ActionContinue
}
func (s *StepMountDevice) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
func (s *StepMountDevice) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepMountDevice) CleanupFunc(state map[string]interface{}) error {
func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
if s.mountPath == "" {
return nil
}
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unmounting the root device...")
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, s.mountPath)
+11 -11
View File
@@ -17,10 +17,10 @@ type StepMountExtra struct {
mounts []string
}
func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
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)
s.mounts = make([]string, 0, len(config.ChrootMounts))
@@ -30,7 +30,7 @@ func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction
if err := os.MkdirAll(innerPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -53,7 +53,7 @@ func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting: %s\nStderr: %s", err, stderr.String())
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -61,12 +61,12 @@ func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction
s.mounts = append(s.mounts, innerPath)
}
state["mount_extra_cleanup"] = s
state.Put("mount_extra_cleanup", s)
return multistep.ActionContinue
}
func (s *StepMountExtra) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
func (s *StepMountExtra) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
@@ -74,12 +74,12 @@ func (s *StepMountExtra) Cleanup(state map[string]interface{}) {
}
}
func (s *StepMountExtra) CleanupFunc(state map[string]interface{}) error {
func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
if s.mounts == nil {
return nil
}
config := state["config"].(*Config)
config := state.Get("config").(*Config)
for len(s.mounts) > 0 {
var path string
lastIndex := len(s.mounts) - 1
+7 -7
View File
@@ -13,9 +13,9 @@ type StepPrepareDevice struct {
mounts []string
}
func (s *StepPrepareDevice) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := config.DevicePath
if device == "" {
@@ -24,7 +24,7 @@ func (s *StepPrepareDevice) Run(state map[string]interface{}) multistep.StepActi
device, err = AvailableDevice()
if err != nil {
err := fmt.Errorf("Error finding available device: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -32,14 +32,14 @@ func (s *StepPrepareDevice) Run(state map[string]interface{}) multistep.StepActi
if _, err := os.Stat(device); err == nil {
err := fmt.Errorf("Device is in use: %s", device)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Device: %s", device)
state["device"] = device
state.Put("device", device)
return multistep.ActionContinue
}
func (s *StepPrepareDevice) Cleanup(state map[string]interface{}) {}
func (s *StepPrepareDevice) Cleanup(state multistep.StateBag) {}
+11 -11
View File
@@ -11,12 +11,12 @@ import (
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct{}
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
image := state["source_image"].(*ec2.Image)
snapshotId := state["snapshot_id"].(string)
ui := state["ui"].(packer.Ui)
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Registering the AMI...")
blockDevices := make([]ec2.BlockDeviceMapping, len(image.BlockDevices))
@@ -40,8 +40,8 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf("Error registering AMI: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
@@ -49,13 +49,13 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = registerResp.ImageId
state["amis"] = amis
state.Put("amis", amis)
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -63,4 +63,4 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
return multistep.ActionContinue
}
func (s *StepRegisterAMI) Cleanup(state map[string]interface{}) {}
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
+12 -12
View File
@@ -17,16 +17,16 @@ type StepSnapshot struct {
snapshotId string
}
func (s *StepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
volumeId := state["volume_id"].(string)
func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
ui.Say("Creating snapshot...")
createSnapResp, err := ec2conn.CreateSnapshot(volumeId, "")
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -58,26 +58,26 @@ func (s *StepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["snapshot_id"] = s.snapshotId
state.Put("snapshot_id", s.snapshotId)
return multistep.ActionContinue
}
func (s *StepSnapshot) Cleanup(state map[string]interface{}) {
func (s *StepSnapshot) Cleanup(state multistep.StateBag) {
if s.snapshotId == "" {
return
}
_, cancelled := state[multistep.StateCancelled]
_, halted := state[multistep.StateHalted]
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Removing snapshot since we cancelled or halted...")
_, err := ec2conn.DeleteSnapshots([]string{s.snapshotId})
if err != nil {
@@ -14,23 +14,23 @@ import (
// source_image *ec2.Image - the source AMI info
type StepSourceAMIInfo struct{}
func (s *StepSourceAMIInfo) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Inspecting the source AMI...")
imageResp, err := ec2conn.Images([]string{config.SourceAmi}, ec2.NewFilter())
if err != nil {
err := fmt.Errorf("Error querying AMI: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Source AMI '%s' was not found!", config.SourceAmi)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -40,13 +40,13 @@ func (s *StepSourceAMIInfo) Run(state map[string]interface{}) multistep.StepActi
// It must be EBS-backed otherwise the build won't work
if image.RootDeviceType != "ebs" {
err := fmt.Errorf("The root device of the source AMI must be EBS-backed.")
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["source_image"] = image
state.Put("source_image", image)
return multistep.ActionContinue
}
func (s *StepSourceAMIInfo) Cleanup(map[string]interface{}) {}
func (s *StepSourceAMIInfo) Cleanup(multistep.StateBag) {}
+55 -5
View File
@@ -2,16 +2,19 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/packer/packer"
)
// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions"`
AMITags map[string]string `mapstructure:"tags"`
}
func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
@@ -42,6 +45,7 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
"ami_users": c.AMIUsers,
"ami_groups": c.AMIGroups,
"ami_product_codes": c.AMIProductCodes,
"ami_regions": c.AMIRegions,
}
for n, slice := range sliceTemplates {
@@ -59,6 +63,52 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
errs = append(errs, fmt.Errorf("ami_name must be specified"))
}
if len(c.AMIRegions) > 0 {
regionSet := make(map[string]struct{})
regions := make([]string, 0, len(c.AMIRegions))
for _, region := range c.AMIRegions {
// If we already saw the region, then don't look again
if _, ok := regionSet[region]; ok {
continue
}
// Mark that we saw the region
regionSet[region] = struct{}{}
// Verify the region is real
if _, ok := aws.Regions[region]; !ok {
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
continue
}
regions = append(regions, region)
}
c.AMIRegions = regions
}
newTags := make(map[string]string)
for k, v := range c.AMITags {
k, err := t.Process(k, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err := t.Process(v, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
c.AMITags = newTags
if len(errs) > 0 {
return errs
}
+25 -1
View File
@@ -1,6 +1,7 @@
package common
import (
"reflect"
"testing"
)
@@ -10,7 +11,7 @@ func testAMIConfig() *AMIConfig {
}
}
func TestAMIConfigPrepare_Region(t *testing.T) {
func TestAMIConfigPrepare_name(t *testing.T) {
c := testAMIConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
@@ -21,3 +22,26 @@ func TestAMIConfigPrepare_Region(t *testing.T) {
t.Fatal("should have error")
}
}
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAMIConfig()
c.AMIRegions = nil
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIRegions = []string{"foo"}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
if err := c.Prepare(nil); err != nil {
t.Fatalf("bad: %s", err)
}
expected := []string{"us-east-1", "us-west-1"}
if !reflect.DeepEqual(c.AMIRegions, expected) {
t.Fatalf("bad: %#v", c.AMIRegions)
}
}
+2 -2
View File
@@ -26,7 +26,7 @@ type StateChangeConf struct {
Conn *ec2.EC2
Pending []string
Refresh StateRefreshFunc
StepState map[string]interface{}
StepState multistep.StateBag
Target string
}
@@ -62,7 +62,7 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
}
if conf.StepState != nil {
if _, ok := conf.StepState[multistep.StateCancelled]; ok {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted")
}
}
+8 -7
View File
@@ -5,17 +5,18 @@ import (
"errors"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"time"
)
// SSHAddress returns a function that can be given to the SSH communicator
// for determining the SSH address based on the instance DNS name.
func SSHAddress(e *ec2.EC2, port int) func(map[string]interface{}) (string, error) {
return func(state map[string]interface{}) (string, error) {
func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
for j := 0; j < 2; j++ {
var host string
i := state["instance"].(*ec2.Instance)
i := state.Get("instance").(*ec2.Instance)
if i.DNSName != "" {
host = i.DNSName
} else if i.VpcId != "" {
@@ -35,7 +36,7 @@ func SSHAddress(e *ec2.EC2, port int) func(map[string]interface{}) (string, erro
return "", fmt.Errorf("instance not found: %s", i.InstanceId)
}
state["instance"] = &r.Reservations[0].Instances[0]
state.Put("instance", &r.Reservations[0].Instances[0])
time.Sleep(1 * time.Second)
}
@@ -46,9 +47,9 @@ func SSHAddress(e *ec2.EC2, port int) func(map[string]interface{}) (string, erro
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(map[string]interface{}) (*gossh.ClientConfig, error) {
return func(state map[string]interface{}) (*gossh.ClientConfig, error) {
privateKey := state["privateKey"].(string)
func SSHConfig(username string) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
@@ -0,0 +1,80 @@
package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepAMIRegionCopy struct {
Regions []string
Tags map[string]string
}
func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
ami := amis[ec2conn.Region.Name]
if len(s.Regions) == 0 {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Copying AMI (%s) to other regions...", ami))
for _, region := range s.Regions {
ui.Message(fmt.Sprintf("Copying to: %s", region))
// Connect to the region where the AMI will be copied to
regionconn := ec2.New(ec2conn.Auth, aws.Regions[region])
resp, err := regionconn.CopyImage(&ec2.CopyImage{
SourceRegion: ec2conn.Region.Name,
SourceImageId: ami,
})
if err != nil {
err := fmt.Errorf("Error Copying AMI (%s) to region (%s): %s", ami, region, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...", resp.ImageId, region))
if err := WaitForAMI(regionconn, resp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", resp.ImageId, region, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Need to re-apply Tags since they are not copied with the AMI
if len(s.Tags) > 0 {
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", resp.ImageId))
var ec2Tags []ec2.Tag
for key, value := range s.Tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
}
_, err := regionconn.CreateTags([]string{resp.ImageId}, ec2Tags)
if err != nil {
err := fmt.Errorf("Error adding tags to AMI (%s): %s", resp.ImageId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
amis[region] = resp.ImageId
}
state.Put("amis", amis)
return multistep.ActionContinue
}
func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
// No cleanup...
}
+6 -6
View File
@@ -11,10 +11,10 @@ type StepCreateTags struct {
Tags map[string]string
}
func (s *StepCreateTags) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
amis := state["amis"].(map[string]string)
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
ami := amis[ec2conn.Region.Name]
if len(s.Tags) > 0 {
@@ -29,7 +29,7 @@ func (s *StepCreateTags) Run(state map[string]interface{}) multistep.StepAction
_, err := ec2conn.CreateTags([]string{ami}, ec2Tags)
if err != nil {
err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -38,6 +38,6 @@ func (s *StepCreateTags) Run(state map[string]interface{}) multistep.StepAction
return multistep.ActionContinue
}
func (s *StepCreateTags) Cleanup(state map[string]interface{}) {
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
// No cleanup...
}
+40 -9
View File
@@ -8,22 +8,27 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os"
"runtime"
)
type StepKeyPair struct {
Debug bool
DebugKeyPath string
keyName string
}
func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
log.Printf("temporary keypair name: %s", keyName)
keyResp, err := ec2conn.CreateKeyPair(keyName)
if err != nil {
state["error"] = fmt.Errorf("Error creating temporary keypair: %s", err)
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
}
@@ -31,20 +36,46 @@ func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
s.keyName = keyName
// Set some state data for use in future steps
state["keyPair"] = keyName
state["privateKey"] = keyResp.KeyMaterial
state.Put("keyPair", keyName)
state.Put("privateKey", keyResp.KeyMaterial)
// If we're in debug mode, output the private key to the working
// directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(keyResp.KeyMaterial)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepKeyPair) Cleanup(state map[string]interface{}) {
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return
if s.keyName == "" {
return
}
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary keypair...")
_, err := ec2conn.DeleteKeyPair(s.keyName)
@@ -14,10 +14,10 @@ type StepModifyAMIAttributes struct {
Description string
}
func (s *StepModifyAMIAttributes) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
amis := state["amis"].(map[string]string)
func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
ami := amis[ec2conn.Region.Name]
// Determine if there is any work to do.
@@ -65,7 +65,7 @@ func (s *StepModifyAMIAttributes) Run(state map[string]interface{}) multistep.St
_, err := ec2conn.ModifyImageAttribute(ami, opts)
if err != nil {
err := fmt.Errorf("Error modify AMI attributes: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -74,6 +74,6 @@ func (s *StepModifyAMIAttributes) Run(state map[string]interface{}) multistep.St
return multistep.ActionContinue
}
func (s *StepModifyAMIAttributes) Cleanup(state map[string]interface{}) {
func (s *StepModifyAMIAttributes) Cleanup(state multistep.StateBag) {
// No cleanup...
}
@@ -10,6 +10,7 @@ import (
)
type StepRunSourceInstance struct {
Debug bool
ExpectedRootDevice string
InstanceType string
UserData string
@@ -22,17 +23,17 @@ type StepRunSourceInstance struct {
instance *ec2.Instance
}
func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
keyName := state["keyPair"].(string)
securityGroupId := state["securityGroupId"].(string)
ui := state["ui"].(packer.Ui)
func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
keyName := state.Get("keyPair").(string)
securityGroupId := state.Get("securityGroupId").(string)
ui := state.Get("ui").(packer.Ui)
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
state["error"] = fmt.Errorf("Problem reading user data file: %s", err)
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
return multistep.ActionHalt
}
@@ -55,27 +56,27 @@ func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.Step
ui.Say("Launching a source AWS instance...")
imageResp, err := ec2conn.Images([]string{s.SourceAMI}, ec2.NewFilter())
if err != nil {
state["error"] = fmt.Errorf("There was a problem with the source AMI: %s", err)
state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err))
return multistep.ActionHalt
}
if len(imageResp.Images) != 1 {
state["error"] = fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI)
state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI))
return multistep.ActionHalt
}
if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice {
state["error"] = fmt.Errorf(
state.Put("error", fmt.Errorf(
"The provided source AMI has an invalid root device type.\n"+
"Expected '%s', got '%s'.",
s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType)
s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType))
return multistep.ActionHalt
}
runResp, err := ec2conn.RunInstances(runOpts)
if err != nil {
err := fmt.Errorf("Error launching source instance: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -94,24 +95,35 @@ func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.Step
latestInstance, err := WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.instance = latestInstance.(*ec2.Instance)
state["instance"] = s.instance
if s.Debug {
if s.instance.DNSName != "" {
ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName))
}
if s.instance.PrivateIpAddress != "" {
ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress))
}
}
state.Put("instance", s.instance)
return multistep.ActionContinue
}
func (s *StepRunSourceInstance) Cleanup(state map[string]interface{}) {
func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
if s.instance == nil {
return
}
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Terminating the source AWS instance...")
if _, err := ec2conn.TerminateInstances([]string{s.instance.InstanceId}); err != nil {
+9 -9
View File
@@ -19,13 +19,13 @@ type StepSecurityGroup struct {
createdGroupId string
}
func (s *StepSecurityGroup) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
if s.SecurityGroupId != "" {
log.Printf("Using specified security group: %s", s.SecurityGroupId)
state["securityGroupId"] = s.SecurityGroupId
state.Put("securityGroupId", s.SecurityGroupId)
return multistep.ActionContinue
}
@@ -64,24 +64,24 @@ func (s *StepSecurityGroup) Run(state map[string]interface{}) multistep.StepActi
ui.Say("Authorizing SSH access on the temporary security group...")
if _, err := ec2conn.AuthorizeSecurityGroup(groupResp.SecurityGroup, perms); err != nil {
err := fmt.Errorf("Error creating temporary security group: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set some state data for use in future steps
state["securityGroupId"] = s.createdGroupId
state.Put("securityGroupId", s.createdGroupId)
return multistep.ActionContinue
}
func (s *StepSecurityGroup) Cleanup(state map[string]interface{}) {
func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
if s.createdGroupId == "" {
return
}
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary security group...")
+20 -35
View File
@@ -25,9 +25,6 @@ type config struct {
awscommon.BlockDevices `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
// Tags for the AMI
Tags map[string]string
tpl *packer.ConfigTemplate
}
@@ -54,28 +51,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
// Accumulate any errors
newTags := make(map[string]string)
for k, v := range b.config.Tags {
k, err = b.config.tpl.Process(k, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err = b.config.tpl.Process(v, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
b.config.Tags = newTags
if errs != nil && len(errs.Errors) > 0 {
return errs
}
@@ -98,21 +73,25 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := make(map[string]interface{})
state["config"] = b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&awscommon.StepKeyPair{},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
},
&awscommon.StepSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
@@ -130,12 +109,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&common.StepProvision{},
&stepStopInstance{},
&stepCreateAMI{},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
Tags: b.config.AMITags,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -151,18 +136,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state["error"]; ok {
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state["amis"]; !ok {
if _, ok := state.GetOk("amis"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state["amis"].(map[string]string),
Amis: state.Get("amis").(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+9 -9
View File
@@ -10,11 +10,11 @@ import (
type stepCreateAMI struct{}
func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(config)
ec2conn := state["ec2"].(*ec2.EC2)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
// Create the image
ui.Say(fmt.Sprintf("Creating the AMI: %s", config.AMIName))
@@ -27,7 +27,7 @@ func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
createResp, err := ec2conn.CreateImage(createOpts)
if err != nil {
err := fmt.Errorf("Error creating AMI: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -36,13 +36,13 @@ func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
ui.Say(fmt.Sprintf("AMI: %s", createResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = createResp.ImageId
state["amis"] = amis
state.Put("amis", amis)
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := awscommon.WaitForAMI(ec2conn, createResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -50,6 +50,6 @@ func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepCreateAMI) Cleanup(map[string]interface{}) {
func (s *stepCreateAMI) Cleanup(multistep.StateBag) {
// No cleanup...
}
+7 -7
View File
@@ -10,17 +10,17 @@ import (
type stepStopInstance struct{}
func (s *stepStopInstance) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
// Stop the instance so we can create an AMI from it
ui.Say("Stopping the source instance...")
_, err := ec2conn.StopInstances(instance.InstanceId)
if err != nil {
err := fmt.Errorf("Error stopping instance: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -37,7 +37,7 @@ func (s *stepStopInstance) Run(state map[string]interface{}) multistep.StepActio
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -45,6 +45,6 @@ func (s *stepStopInstance) Run(state map[string]interface{}) multistep.StepActio
return multistep.ActionContinue
}
func (s *stepStopInstance) Cleanup(map[string]interface{}) {
func (s *stepStopInstance) Cleanup(multistep.StateBag) {
// No cleanup...
}
+20 -11
View File
@@ -33,7 +33,6 @@ type Config struct {
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
BundleVolCommand string `mapstructure:"bundle_vol_command"`
S3Bucket string `mapstructure:"s3_bucket"`
Tags map[string]string
X509CertPath string `mapstructure:"x509_cert_path"`
X509KeyPath string `mapstructure:"x509_key_path"`
X509UploadPath string `mapstructure:"x509_upload_path"`
@@ -177,21 +176,25 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := make(map[string]interface{})
state["config"] = &b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&awscommon.StepKeyPair{},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
},
&awscommon.StepSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
@@ -211,13 +214,19 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepBundleVolume{},
&StepUploadBundle{},
&StepRegisterAMI{},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
Tags: b.config.AMITags,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -233,18 +242,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state["error"]; ok {
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state["amis"]; !ok {
if _, ok := state.GetOk("amis"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state["amis"].(map[string]string),
Amis: state.Get("amis").(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+19 -38
View File
@@ -19,32 +19,13 @@ type bundleCmdData struct {
type StepBundleVolume struct{}
func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
x509RemoteCertPath := state["x509RemoteCertPath"].(string)
x509RemoteKeyPath := state["x509RemoteKeyPath"].(string)
// Verify the AMI tools are available
ui.Say("Checking for EC2 AMI tools...")
cmd := &packer.RemoteCmd{Command: "ec2-ami-tools-version"}
if err := comm.Start(cmd); err != nil {
state["error"] = fmt.Errorf("Error checking for AMI tools: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
cmd.Wait()
if cmd.ExitStatus != 0 {
state["error"] = fmt.Errorf(
"The EC2 AMI tools could not be detected. These must be manually\n" +
"via a provisioner or some other means and are required for Packer\n" +
"to create an instance-store AMI.")
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
x509RemoteCertPath := state.Get("x509RemoteCertPath").(string)
x509RemoteKeyPath := state.Get("x509RemoteKeyPath").(string)
// Bundle the volume
var err error
@@ -59,35 +40,35 @@ func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepActio
})
if err != nil {
err := fmt.Errorf("Error processing bundle volume command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Bundling the volume...")
cmd = new(packer.RemoteCmd)
cmd := new(packer.RemoteCmd)
cmd.Command = config.BundleVolCommand
if err := cmd.StartWithUi(comm, ui); err != nil {
state["error"] = fmt.Errorf("Error bundling volume: %s", err)
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf("Error bundling volume: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
state["error"] = fmt.Errorf(
"Volume bundling failed. Please see the output above for more\n" +
"details on what went wrong.")
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf(
"Volume bundling failed. Please see the output above for more\n"+
"details on what went wrong."))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
// Store the manifest path
manifestName := config.BundlePrefix + ".manifest.xml"
state["manifest_name"] = manifestName
state["manifest_path"] = fmt.Sprintf(
"%s/%s", config.BundleDestination, manifestName)
state.Put("manifest_name", manifestName)
state.Put("manifest_path", fmt.Sprintf(
"%s/%s", config.BundleDestination, manifestName))
return multistep.ActionContinue
}
func (s *StepBundleVolume) Cleanup(map[string]interface{}) {}
func (s *StepBundleVolume) Cleanup(multistep.StateBag) {}
+10 -10
View File
@@ -10,11 +10,11 @@ import (
type StepRegisterAMI struct{}
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
manifestPath := state["remote_manifest_path"].(string)
ui := state["ui"].(packer.Ui)
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
manifestPath := state.Get("remote_manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Registering the AMI...")
registerOpts := &ec2.RegisterImage{
@@ -25,8 +25,8 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf("Error registering AMI: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
@@ -34,13 +34,13 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = registerResp.ImageId
state["amis"] = amis
state.Put("amis", amis)
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -48,4 +48,4 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
return multistep.ActionContinue
}
func (s *StepRegisterAMI) Cleanup(map[string]interface{}) {}
func (s *StepRegisterAMI) Cleanup(multistep.StateBag) {}
+16 -16
View File
@@ -16,12 +16,12 @@ type uploadCmdData struct {
type StepUploadBundle struct{}
func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
manifestName := state["manifest_name"].(string)
manifestPath := state["manifest_path"].(string)
ui := state["ui"].(packer.Ui)
func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
manifestName := state.Get("manifest_name").(string)
manifestPath := state.Get("manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
var err error
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
@@ -33,7 +33,7 @@ func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepActio
})
if err != nil {
err := fmt.Errorf("Error processing bundle upload command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -41,23 +41,23 @@ func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepActio
ui.Say("Uploading the bundle...")
cmd := &packer.RemoteCmd{Command: config.BundleUploadCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
state["error"] = fmt.Errorf("Error uploading volume: %s", err)
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf("Error uploading volume: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
state["error"] = fmt.Errorf(
"Bundle upload failed. Please see the output above for more\n" +
"details on what went wrong.")
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf(
"Bundle upload failed. Please see the output above for more\n"+
"details on what went wrong."))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
state["remote_manifest_path"] = fmt.Sprintf(
"%s/%s", config.S3Bucket, manifestName)
state.Put("remote_manifest_path", fmt.Sprintf(
"%s/%s", config.S3Bucket, manifestName))
return multistep.ActionContinue
}
func (s *StepUploadBundle) Cleanup(state map[string]interface{}) {}
func (s *StepUploadBundle) Cleanup(state multistep.StateBag) {}
@@ -9,34 +9,34 @@ import (
type StepUploadX509Cert struct{}
func (s *StepUploadX509Cert) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
func (s *StepUploadX509Cert) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
x509RemoteCertPath := config.X509UploadPath + "/cert.pem"
x509RemoteKeyPath := config.X509UploadPath + "/key.pem"
ui.Say("Uploading X509 Certificate...")
if err := s.uploadSingle(comm, x509RemoteCertPath, config.X509CertPath); err != nil {
state["error"] = fmt.Errorf("Error uploading X509 cert: %s", err)
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf("Error uploading X509 cert: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
if err := s.uploadSingle(comm, x509RemoteKeyPath, config.X509KeyPath); err != nil {
state["error"] = fmt.Errorf("Error uploading X509 cert: %s", err)
ui.Error(state["error"].(error).Error())
state.Put("error", fmt.Errorf("Error uploading X509 cert: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
state["x509RemoteCertPath"] = x509RemoteCertPath
state["x509RemoteKeyPath"] = x509RemoteKeyPath
state.Put("x509RemoteCertPath", x509RemoteCertPath)
state.Put("x509RemoteKeyPath", x509RemoteKeyPath)
return multistep.ActionContinue
}
func (s *StepUploadX509Cert) Cleanup(map[string]interface{}) {}
func (s *StepUploadX509Cert) Cleanup(multistep.StateBag) {}
func (s *StepUploadX509Cert) uploadSingle(comm packer.Communicator, dst, src string) error {
f, err := os.Open(src)
+9
View File
@@ -115,6 +115,15 @@ func (d DigitalOceanClient) PowerOffDroplet(id uint) error {
return err
}
// Shutsdown a droplet. This is a "soft" shutdown.
func (d DigitalOceanClient) ShutdownDroplet(id uint) error {
path := fmt.Sprintf("droplets/%v/shutdown", id)
_, err := NewRequest(d, path, url.Values{})
return err
}
// Creates a snaphot of a droplet by it's ID
func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error {
path := fmt.Sprintf("droplets/%v/snapshot", id)
+10 -9
View File
@@ -189,11 +189,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey)
// Set up the state
state := make(map[string]interface{})
state["config"] = b.config
state["client"] = client
state["hook"] = hook
state["ui"] = ui
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("client", client)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
@@ -206,6 +206,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SSHWaitTimeout: 5 * time.Minute,
},
new(common.StepProvision),
new(stepShutdown),
new(stepPowerOff),
new(stepSnapshot),
}
@@ -223,18 +224,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state["error"]; ok {
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
if _, ok := state["snapshot_name"]; !ok {
if _, ok := state.GetOk("snapshot_name"); !ok {
log.Println("Failed to find snapshot_name in state. Bug?")
return nil, nil
}
artifact := &Artifact{
snapshotName: state["snapshot_name"].(string),
snapshotId: state["snapshot_image_id"].(uint),
snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_image_id").(uint),
client: client,
}
+7 -6
View File
@@ -3,18 +3,19 @@ package digitalocean
import (
gossh "code.google.com/p/go.crypto/ssh"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
)
func sshAddress(state map[string]interface{}) (string, error) {
config := state["config"].(config)
ipAddress := state["droplet_ip"].(string)
func sshAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(config)
ipAddress := state.Get("droplet_ip").(string)
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
}
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
config := state["config"].(config)
privateKey := state["privateKey"].(string)
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(config)
privateKey := state.Get("privateKey").(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
+22 -14
View File
@@ -14,11 +14,11 @@ type stepCreateDroplet struct {
dropletId uint
}
func (s *stepCreateDroplet) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
sshKeyId := state["ssh_key_id"].(uint)
func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
sshKeyId := state.Get("ssh_key_id").(uint)
ui.Say("Creating droplet...")
@@ -29,7 +29,7 @@ func (s *stepCreateDroplet) Run(state map[string]interface{}) multistep.StepActi
dropletId, err := client.CreateDroplet(name, c.SizeID, c.ImageID, c.RegionID, sshKeyId)
if err != nil {
err := fmt.Errorf("Error creating droplet: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -38,20 +38,20 @@ func (s *stepCreateDroplet) Run(state map[string]interface{}) multistep.StepActi
s.dropletId = dropletId
// Store the droplet id for later
state["droplet_id"] = dropletId
state.Put("droplet_id", dropletId)
return multistep.ActionContinue
}
func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
// If the dropletid isn't there, we probably never created it
if s.dropletId == 0 {
return
}
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
// Destroy the droplet we just created
ui.Say("Destroying droplet...")
@@ -62,12 +62,20 @@ func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
time.Sleep(c.eventDelay)
err := client.DestroyDroplet(s.dropletId)
var err error
for i := 0; i < 5; i++ {
err = client.DestroyDroplet(s.dropletId)
if err == nil {
break
}
curlstr := fmt.Sprintf("curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'",
DIGITALOCEAN_API_URL, s.dropletId, c.ClientID, c.APIKey)
time.Sleep(2 * time.Second)
}
if err != nil {
curlstr := fmt.Sprintf("curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'",
DIGITALOCEAN_API_URL, s.dropletId, c.ClientID, c.APIKey)
ui.Error(fmt.Sprintf(
"Error destroying droplet. Please destroy it manually: %v", curlstr))
}
+10 -10
View File
@@ -18,9 +18,9 @@ type stepCreateSSHKey struct {
keyId uint
}
func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary ssh key for droplet...")
@@ -35,7 +35,7 @@ func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepActio
}
// Set the private key in the statebag for later
state["privateKey"] = string(pem.EncodeToMemory(&priv_blk))
state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
// Marshal the public key into SSH compatible format
pub := priv.PublicKey
@@ -48,7 +48,7 @@ func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepActio
keyId, err := client.CreateKey(name, pub_sshformat)
if err != nil {
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -59,20 +59,20 @@ func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepActio
log.Printf("temporary ssh key name: %s", name)
// Remember some state for the future
state["ssh_key_id"] = keyId
state.Put("ssh_key_id", keyId)
return multistep.ActionContinue
}
func (s *stepCreateSSHKey) Cleanup(state map[string]interface{}) {
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return
if s.keyId == 0 {
return
}
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
ui.Say("Deleting temporary ssh key...")
err := client.DestroyKey(s.keyId)
+9 -9
View File
@@ -8,18 +8,18 @@ import (
type stepDropletInfo struct{}
func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
dropletId := state["droplet_id"].(uint)
func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
ui.Say("Waiting for droplet to become active...")
err := waitForDropletState("active", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -28,16 +28,16 @@ func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction
ip, _, err := client.DropletStatus(dropletId)
if err != nil {
err := fmt.Errorf("Error retrieving droplet ID: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["droplet_ip"] = ip
state.Put("droplet_ip", ip)
return multistep.ActionContinue
}
func (s *stepDropletInfo) Cleanup(state map[string]interface{}) {
func (s *stepDropletInfo) Cleanup(state multistep.StateBag) {
// no cleanup
}
+14 -15
View File
@@ -10,11 +10,11 @@ import (
type stepPowerOff struct{}
func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
c := state["config"].(config)
ui := state["ui"].(packer.Ui)
dropletId := state["droplet_id"].(uint)
func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
c := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint)
// Sleep arbitrarily before sending power off request
// Otherwise we get "pending event" errors, even though there isn't
@@ -27,24 +27,23 @@ func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction {
if err != nil {
err := fmt.Errorf("Error powering off droplet: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Waiting for droplet to power off...")
log.Println("Waiting for poweroff event to complete...")
err = waitForDropletState("off", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// This arbitrary sleep is because we can't wait for the state
// of the droplet to be 'off', as stepShutdown should already
// have accomplished that, and the state indicator is the same.
// We just have to assume that this event will process quickly.
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
time.Sleep(c.eventDelay)
return multistep.ActionContinue
}
func (s *stepPowerOff) Cleanup(state map[string]interface{}) {
func (s *stepPowerOff) Cleanup(state multistep.StateBag) {
// no cleanup
}
+49
View File
@@ -0,0 +1,49 @@
package digitalocean
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type stepShutdown struct{}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
c := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint)
// Sleep arbitrarily before sending the request
// Otherwise we get "pending event" errors, even though there isn't
// one.
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
time.Sleep(c.eventDelay)
err := client.ShutdownDroplet(dropletId)
if err != nil {
err := fmt.Errorf("Error shutting down droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Waiting for droplet to shutdown...")
err = waitForDropletState("off", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
// no cleanup
}
+12 -12
View File
@@ -10,17 +10,17 @@ import (
type stepSnapshot struct{}
func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
dropletId := state["droplet_id"].(uint)
func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
err := client.CreateSnapshot(dropletId, c.SnapshotName)
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -29,7 +29,7 @@ func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
err = waitForDropletState("active", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot to complete: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -38,7 +38,7 @@ func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
images, err := client.Images()
if err != nil {
err := fmt.Errorf("Error looking up snapshot ID: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -53,19 +53,19 @@ func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
if imageId == 0 {
err := errors.New("Couldn't find snapshot to get the image ID. Bug?")
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Snapshot image ID: %d", imageId)
state["snapshot_image_id"] = imageId
state["snapshot_name"] = c.SnapshotName
state.Put("snapshot_image_id", imageId)
state.Put("snapshot_name", c.SnapshotName)
return multistep.ActionContinue
}
func (s *stepSnapshot) Cleanup(state map[string]interface{}) {
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
// no cleanup
}
+82
View File
@@ -0,0 +1,82 @@
package openstack
import (
"fmt"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"os"
)
// AccessConfig is for common configuration related to openstack access
type AccessConfig struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Provider string `mapstructure:"provider"`
RawRegion string `mapstructure:"region"`
}
// Auth returns a valid Auth object for access to openstack services, or
// an error if the authentication couldn't be resolved.
func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) {
username := c.Username
password := c.Password
provider := c.Provider
if username == "" {
username = os.Getenv("SDK_USERNAME")
}
if password == "" {
password = os.Getenv("SDK_PASSWORD")
}
if provider == "" {
provider = os.Getenv("SDK_PROVIDER")
}
authoptions := gophercloud.AuthOptions{
Username: username,
Password: password,
AllowReauth: true,
}
return gophercloud.Authenticate(provider, authoptions)
}
func (c *AccessConfig) Region() string {
return c.RawRegion
}
func (c *AccessConfig) Prepare(t *packer.ConfigTemplate) []error {
if t == nil {
var err error
t, err = packer.NewConfigTemplate()
if err != nil {
return []error{err}
}
}
templates := map[string]*string{
"username": &c.Username,
"password": &c.Password,
"provider": &c.Provider,
}
errs := make([]error, 0)
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if c.RawRegion == "" {
errs = append(errs, fmt.Errorf("region must be specified"))
}
if len(errs) > 0 {
return errs
}
return nil
}
+28
View File
@@ -0,0 +1,28 @@
package openstack
import (
"testing"
)
func testAccessConfig() *AccessConfig {
return &AccessConfig{}
}
func TestAccessConfigPrepare_NoRegion(t *testing.T) {
c := testAccessConfig()
if err := c.Prepare(nil); err == nil {
t.Fatalf("shouldn't have err: %s", err)
}
}
func TestAccessConfigPrepare_Region(t *testing.T) {
dfw := "DFW"
c := testAccessConfig()
c.RawRegion = dfw
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
if dfw != c.Region() {
t.Fatalf("Regions do not match: %s %s", dfw, c.Region())
}
}
+41
View File
@@ -0,0 +1,41 @@
package openstack
import (
"fmt"
"github.com/rackspace/gophercloud"
"log"
)
// Artifact is an artifact implementation that contains built images.
type Artifact struct {
// ImageId of built image
ImageId string
// BuilderId is the unique ID for the builder that created this image
BuilderIdValue string
// OpenStack connection for performing API stuff.
Conn gophercloud.CloudServersProvider
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
func (a *Artifact) Id() string {
return a.ImageId
}
func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: %v", a.ImageId)
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d", a.ImageId)
return a.Conn.DeleteImageById(a.ImageId)
}
+39
View File
@@ -0,0 +1,39 @@
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")
}
func TestArtifactId(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `b8cdf55b-c916-40bd-b190-389ec144c4ed`
a := &Artifact{
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
}
result := a.Id()
assert.Equal(result, expected, "should match output")
}
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")
}
+136
View File
@@ -0,0 +1,136 @@
// The openstack package contains a packer.Builder implementation that
// builds Images for openstack.
package openstack
import (
//"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
)
// The unique ID for this builder
const BuilderId = "mitchellh.openstack"
type config struct {
common.PackerConfig `mapstructure:",squash"`
AccessConfig `mapstructure:",squash"`
ImageConfig `mapstructure:",squash"`
RunConfig `mapstructure:",squash"`
tpl *packer.ConfigTemplate
}
type Builder struct {
config config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.ImageConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
if errs != nil && len(errs.Errors) > 0 {
return errs
}
log.Printf("Config: %+v", b.config)
return nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
auth, err := b.config.AccessConfig.Auth()
if err != nil {
return nil, err
}
api := &gophercloud.ApiCriteria{
Name: "cloudServersOpenStack",
Region: b.config.AccessConfig.Region(),
VersionId: "2",
UrlChoice: gophercloud.PublicURL,
}
csp, err := gophercloud.ServersApi(auth, *api)
if err != nil {
log.Printf("Region: %s", b.config.AccessConfig.Region())
return nil, err
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("csp", csp)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&StepKeyPair{},
&StepRunSourceServer{
Name: b.config.ImageName,
Flavor: b.config.Flavor,
SourceImage: b.config.SourceImage,
},
&common.StepConnectSSH{
SSHAddress: SSHAddress(csp, b.config.SSHPort),
SSHConfig: SSHConfig(b.config.SSHUsername),
SSHWaitTimeout: b.config.SSHTimeout(),
},
&common.StepProvision{},
&stepCreateImage{},
}
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no images, then just return
if _, ok := state.GetOk("image"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &Artifact{
ImageId: state.Get("image").(string),
BuilderIdValue: BuilderId,
Conn: csp,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
+79
View File
@@ -0,0 +1,79 @@
package openstack
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"username": "foo",
"password": "bar",
"provider": "foo",
"region": "DFW",
"image_name": "foo",
"source_image": "foo",
"flavor": "foo",
"ssh_username": "root",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"password": []string{},
}
err := b.Prepare(c)
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_ImageName(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["image_name"] = "foo"
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["image_name"] = "foo {{"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "image_name")
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
+45
View File
@@ -0,0 +1,45 @@
package openstack
import (
"fmt"
"github.com/mitchellh/packer/packer"
)
// ImageConfig is for common configuration related to creating Images.
type ImageConfig struct {
ImageName string `mapstructure:"image_name"`
}
func (c *ImageConfig) Prepare(t *packer.ConfigTemplate) []error {
if t == nil {
var err error
t, err = packer.NewConfigTemplate()
if err != nil {
return []error{err}
}
}
templates := map[string]*string{
"image_name": &c.ImageName,
}
errs := make([]error, 0)
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if c.ImageName == "" {
errs = append(errs, fmt.Errorf("An image_name must be specified"))
}
if len(errs) > 0 {
return errs
}
return nil
}
+23
View File
@@ -0,0 +1,23 @@
package openstack
import (
"testing"
)
func testImageConfig() *ImageConfig {
return &ImageConfig{
ImageName: "foo",
}
}
func TestImageConfigPrepare_Region(t *testing.T) {
c := testImageConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.ImageName = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}
+86
View File
@@ -0,0 +1,86 @@
package openstack
import (
"errors"
"fmt"
"github.com/mitchellh/packer/packer"
"time"
)
// RunConfig contains configuration for running an instance from a source
// image and details on how to access that launched image.
type RunConfig struct {
SourceImage string `mapstructure:"source_image"`
Flavor string `mapstructure:"flavor"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
// Unexported fields that are calculated from others
sshTimeout time.Duration
}
func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
if t == nil {
var err error
t, err = packer.NewConfigTemplate()
if err != nil {
return []error{err}
}
}
// Defaults
if c.SSHUsername == "" {
c.SSHUsername = "root"
}
if c.SSHPort == 0 {
c.SSHPort = 22
}
if c.RawSSHTimeout == "" {
c.RawSSHTimeout = "5m"
}
// Validation
var err error
errs := make([]error, 0)
if c.SourceImage == "" {
errs = append(errs, errors.New("A source_image must be specified"))
}
if c.Flavor == "" {
errs = append(errs, errors.New("A flavor must be specified"))
}
if c.SSHUsername == "" {
errs = append(errs, errors.New("An ssh_username must be specified"))
}
templates := map[string]*string{
"flavlor": &c.Flavor,
"ssh_timeout": &c.RawSSHTimeout,
"ssh_username": &c.SSHUsername,
"source_image": &c.SourceImage,
}
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
return errs
}
func (c *RunConfig) SSHTimeout() time.Duration {
return c.sshTimeout
}
+88
View File
@@ -0,0 +1,88 @@
package openstack
import (
"os"
"testing"
)
func init() {
// Clear out the openstack env vars so they don't
// affect our tests.
os.Setenv("SDK_USERNAME", "")
os.Setenv("SDK_PASSWORD", "")
os.Setenv("SDK_PROVIDER", "")
}
func testRunConfig() *RunConfig {
return &RunConfig{
SourceImage: "abcd",
Flavor: "m1.small",
SSHUsername: "root",
}
}
func TestRunConfigPrepare(t *testing.T) {
c := testRunConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testRunConfig()
c.Flavor = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SourceImage(t *testing.T) {
c := testRunConfig()
c.SourceImage = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testRunConfig()
c.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.SSHPort)
}
c.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.SSHPort)
}
}
func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
c := testRunConfig()
c.RawSSHTimeout = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.RawSSHTimeout = "bad"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
c := testRunConfig()
c.SSHUsername = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
+87
View File
@@ -0,0 +1,87 @@
package openstack
import (
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/rackspace/gophercloud"
"log"
"time"
)
// StateRefreshFunc is a function type used for StateChangeConf that is
// responsible for refreshing the item being watched for a state change.
//
// It returns three results. `result` is any object that will be returned
// as the final object after waiting for state change. This allows you to
// return the final updated object, for example an openstack instance after
// refreshing it.
//
// `state` is the latest state of that object. And `err` is any error that
// may have happened while refreshing the state.
type StateRefreshFunc func() (result interface{}, state string, progress int, err error)
// StateChangeConf is the configuration struct used for `WaitForState`.
type StateChangeConf struct {
Pending []string
Refresh StateRefreshFunc
StepState multistep.StateBag
Target string
}
// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an openstacn server.
func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc {
return func() (interface{}, string, int, error) {
resp, err := csp.ServerById(s.Id)
if err != nil {
log.Printf("Error on ServerStateRefresh: %s", err)
return nil, "", 0, err
}
return resp, resp.Status, resp.Progress, nil
}
}
// WaitForState watches an object and waits for it to achieve a certain
// state.
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
for {
var currentProgress int
var currentState string
i, currentState, currentProgress, err = conf.Refresh()
if err != nil {
return
}
if currentState == conf.Target {
return
}
if conf.StepState != nil {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted")
}
}
found := false
for _, allowed := range conf.Pending {
if currentState == allowed {
found = true
break
}
}
if !found {
fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
return
}
log.Printf("Waiting for state to become: %s currently %s (%d%%)", conf.Target, currentState, currentProgress)
time.Sleep(2 * time.Second)
}
return
}
+58
View File
@@ -0,0 +1,58 @@
package openstack
import (
gossh "code.google.com/p/go.crypto/ssh"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"github.com/rackspace/gophercloud"
"time"
)
// SSHAddress returns a function that can be given to the SSH communicator
// for determining the SSH address based on the server AccessIPv4 setting..
func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
for j := 0; j < 2; j++ {
s := state.Get("server").(*gophercloud.Server)
if s.AccessIPv4 != "" {
return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil
}
if s.AccessIPv6 != "" {
return fmt.Sprintf("[%s]:%d", s.AccessIPv6, port), nil
}
serverState, err := csp.ServerById(s.Id)
if err != nil {
return "", err
}
state.Put("server", serverState)
time.Sleep(1 * time.Second)
}
return "", errors.New("couldn't determine IP address for server")
}
}
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return &gossh.ClientConfig{
User: username,
Auth: []gossh.ClientAuth{
gossh.ClientAuthKeyring(keyring),
},
}, nil
}
}
+68
View File
@@ -0,0 +1,68 @@
package openstack
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
"time"
)
type stepCreateImage struct{}
func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider)
config := state.Get("config").(config)
server := state.Get("server").(*gophercloud.Server)
ui := state.Get("ui").(packer.Ui)
// Create the image
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName))
createOpts := gophercloud.CreateImage{
Name: config.ImageName,
}
imageId, err := csp.CreateImage(server.Id, createOpts)
if err != nil {
err := fmt.Errorf("Error creating image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the Image ID in the state
ui.Say(fmt.Sprintf("Image: %s", imageId))
state.Put("image", imageId)
// Wait for the image to become ready
ui.Say("Waiting for image to become ready...")
if err := WaitForImage(csp, imageId); err != nil {
err := fmt.Errorf("Error waiting for image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepCreateImage) Cleanup(multistep.StateBag) {
// No cleanup...
}
// WaitForImage waits for the given Image ID to become ready.
func WaitForImage(csp gophercloud.CloudServersProvider, imageId string) error {
for {
image, err := csp.ImageById(imageId)
if err != nil {
return err
}
if image.Status == "ACTIVE" {
return nil
}
log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress)
time.Sleep(2 * time.Second)
}
}
+55
View File
@@ -0,0 +1,55 @@
package openstack
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
)
type StepKeyPair struct {
keyName string
}
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
log.Printf("temporary keypair name: %s", keyName)
keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName})
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
}
// Set the keyname so we know to delete it later
s.keyName = keyName
// Set some state data for use in future steps
state.Put("keyPair", keyName)
state.Put("privateKey", keyResp.PrivateKey)
return multistep.ActionContinue
}
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return
if s.keyName == "" {
return
}
csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary keypair...")
err := csp.DeleteKeyPair(s.keyName)
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
}
}
@@ -0,0 +1,86 @@
package openstack
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
)
type StepRunSourceServer struct {
Flavor string
Name string
SourceImage string
server *gophercloud.Server
}
func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider)
keyName := state.Get("keyPair").(string)
ui := state.Get("ui").(packer.Ui)
// XXX - validate image and flavor is available
server := gophercloud.NewServer{
Name: s.Name,
ImageRef: s.SourceImage,
FlavorRef: s.Flavor,
KeyPairName: keyName,
}
serverResp, err := csp.CreateServer(server)
if err != nil {
err := fmt.Errorf("Error launching source server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.server, err = csp.ServerById(serverResp.Id)
log.Printf("server id: %s", s.server.Id)
ui.Say(fmt.Sprintf("Waiting for server (%s) to become ready...", s.server.Id))
stateChange := StateChangeConf{
Pending: []string{"BUILD"},
Target: "ACTIVE",
Refresh: ServerStateRefreshFunc(csp, s.server),
StepState: state,
}
latestServer, err := WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.Id, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.server = latestServer.(*gophercloud.Server)
state.Put("server", s.server)
return multistep.ActionContinue
}
func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) {
if s.server == nil {
return
}
csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state.Get("ui").(packer.Ui)
ui.Say("Terminating the source server...")
if err := csp.DeleteServerById(s.server.Id); err != nil {
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
return
}
stateChange := StateChangeConf{
Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"},
Refresh: ServerStateRefreshFunc(csp, s.server),
Target: "DELETED",
}
WaitForState(&stateChange)
}
+20 -9
View File
@@ -43,6 +43,7 @@ type config struct {
ShutdownCommand string `mapstructure:"shutdown_command"`
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHUser string `mapstructure:"ssh_username"`
@@ -282,6 +283,16 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
}
if b.config.SSHKeyPath != "" {
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
@@ -357,12 +368,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Setup the state bag
state := make(map[string]interface{})
state["cache"] = cache
state["config"] = &b.config
state["driver"] = driver
state["hook"] = hook
state["ui"] = ui
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("config", &b.config)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
// Run
if b.config.PackerDebug {
@@ -377,16 +388,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state["error"]; ok {
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state[multistep.StateCancelled]; ok {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state[multistep.StateHalted]; ok {
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
+79
View File
@@ -8,6 +8,36 @@ import (
"testing"
)
var testPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
-----END RSA PRIVATE KEY-----
`
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "foo",
@@ -484,6 +514,55 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) {
}
}
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
var b Builder
config := testConfig()
config["ssh_key_path"] = ""
b = Builder{}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
config["ssh_key_path"] = "/i/dont/exist"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test good contents
tf.Seek(0, 0)
tf.Truncate(0)
tf.Write([]byte(testPem))
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuilderPrepare_SSHUser(t *testing.T) {
var b Builder
config := testConfig()
+43 -9
View File
@@ -3,23 +3,57 @@ package virtualbox
import (
gossh "code.google.com/p/go.crypto/ssh"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"io/ioutil"
"os"
)
func sshAddress(state map[string]interface{}) (string, error) {
sshHostPort := state["sshHostPort"].(uint)
func sshAddress(state multistep.StateBag) (string, error) {
sshHostPort := state.Get("sshHostPort").(uint)
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
}
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
config := state["config"].(*config)
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*config)
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
}
if config.SSHKeyPath != "" {
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
if err != nil {
return nil, err
}
auth = append(auth, gossh.ClientAuthKeyring(keyring))
}
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
},
Auth: auth,
}, nil
}
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
keyBytes, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
return nil, err
}
return keyring, nil
}
+11 -11
View File
@@ -20,10 +20,10 @@ type stepAttachFloppy struct {
floppyPath string
}
func (s *stepAttachFloppy) Run(state map[string]interface{}) multistep.StepAction {
func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
// Determine if we even have a floppy disk to attach
var floppyPath string
if floppyPathRaw, ok := state["floppy_path"]; ok {
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
floppyPath = floppyPathRaw.(string)
} else {
log.Println("No floppy disk, not attaching.")
@@ -35,13 +35,13 @@ func (s *stepAttachFloppy) Run(state map[string]interface{}) multistep.StepActio
// floppy.
floppyPath, err := s.copyFloppy(floppyPath)
if err != nil {
state["error"] = fmt.Errorf("Error preparing floppy: %s", err)
state.Put("error", fmt.Errorf("Error preparing floppy: %s", err))
return multistep.ActionHalt
}
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Attaching floppy disk...")
@@ -52,7 +52,7 @@ func (s *stepAttachFloppy) Run(state map[string]interface{}) multistep.StepActio
"--add", "floppy",
}
if err := driver.VBoxManage(command...); err != nil {
state["error"] = fmt.Errorf("Error creating floppy controller: %s", err)
state.Put("error", fmt.Errorf("Error creating floppy controller: %s", err))
return multistep.ActionHalt
}
@@ -66,7 +66,7 @@ func (s *stepAttachFloppy) Run(state map[string]interface{}) multistep.StepActio
"--medium", floppyPath,
}
if err := driver.VBoxManage(command...); err != nil {
state["error"] = fmt.Errorf("Error attaching floppy: %s", err)
state.Put("error", fmt.Errorf("Error attaching floppy: %s", err))
return multistep.ActionHalt
}
@@ -76,7 +76,7 @@ func (s *stepAttachFloppy) Run(state map[string]interface{}) multistep.StepActio
return multistep.ActionContinue
}
func (s *stepAttachFloppy) Cleanup(state map[string]interface{}) {
func (s *stepAttachFloppy) Cleanup(state multistep.StateBag) {
if s.floppyPath == "" {
return
}
@@ -84,8 +84,8 @@ func (s *stepAttachFloppy) Cleanup(state map[string]interface{}) {
// Delete the floppy disk
defer os.Remove(s.floppyPath)
driver := state["driver"].(Driver)
vmName := state["vmName"].(string)
driver := state.Get("driver").(Driver)
vmName := state.Get("vmName").(string)
command := []string{
"storageattach", vmName,
+10 -10
View File
@@ -15,11 +15,11 @@ type stepAttachISO struct {
diskPath string
}
func (s *stepAttachISO) Run(state map[string]interface{}) multistep.StepAction {
driver := state["driver"].(Driver)
isoPath := state["iso_path"].(string)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
// Attach the disk to the controller
command := []string{
@@ -32,7 +32,7 @@ func (s *stepAttachISO) Run(state map[string]interface{}) multistep.StepAction {
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error attaching ISO: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -43,14 +43,14 @@ func (s *stepAttachISO) Run(state map[string]interface{}) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepAttachISO) Cleanup(state map[string]interface{}) {
func (s *stepAttachISO) Cleanup(state multistep.StateBag) {
if s.diskPath == "" {
return
}
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
command := []string{
"storageattach", vmName,
+9 -9
View File
@@ -13,11 +13,11 @@ import (
// hard drive for the virtual machine.
type stepCreateDisk struct{}
func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
format := "VDI"
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, strings.ToLower(format)))
@@ -34,7 +34,7 @@ func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -44,7 +44,7 @@ func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction
err = driver.VBoxManage("storagectl", vmName, "--name", controllerName, "--add", "ide")
if err != nil {
err := fmt.Errorf("Error creating disk controller: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -60,7 +60,7 @@ func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error attaching hard drive: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -68,4 +68,4 @@ func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction
return multistep.ActionContinue
}
func (s *stepCreateDisk) Cleanup(state map[string]interface{}) {}
func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {}
+9 -9
View File
@@ -14,10 +14,10 @@ type stepCreateVM struct {
vmName string
}
func (s *stepCreateVM) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
name := config.VMName
@@ -38,7 +38,7 @@ func (s *stepCreateVM) Run(state map[string]interface{}) multistep.StepAction {
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error creating VM: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -50,18 +50,18 @@ func (s *stepCreateVM) Run(state map[string]interface{}) multistep.StepAction {
}
// Set the final name in the state bag so others can use it
state["vmName"] = s.vmName
state.Put("vmName", s.vmName)
return multistep.ActionContinue
}
func (s *stepCreateVM) Cleanup(state map[string]interface{}) {
func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
if s.vmName == "" {
return
}
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unregistering and deleting virtual machine...")
if err := driver.VBoxManage("unregistervm", s.vmName, "--delete"); err != nil {
@@ -29,16 +29,16 @@ type guestAdditionsUrlTemplate struct {
// guest_additions_path string - Path to the guest additions.
type stepDownloadGuestAdditions struct{}
func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep.StepAction {
func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
var action multistep.StepAction
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
config := state["config"].(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*config)
// Get VBox version
version, err := driver.Version()
if err != nil {
state["error"] = fmt.Errorf("Error reading version for guest additions download: %s", err)
state.Put("error", fmt.Errorf("Error reading version for guest additions download: %s", err))
return multistep.ActionHalt
}
@@ -71,7 +71,7 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
url, err = config.tpl.Process(url, tplData)
if err != nil {
err := fmt.Errorf("Error preparing guest additions url: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -85,7 +85,7 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
url, err = common.DownloadableURL(url)
if err != nil {
err := fmt.Errorf("Error preparing guest additions url: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -103,9 +103,9 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
return downStep.Run(state)
}
func (s *stepDownloadGuestAdditions) Cleanup(state map[string]interface{}) {}
func (s *stepDownloadGuestAdditions) Cleanup(state multistep.StateBag) {}
func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state map[string]interface{}, additionsVersion string, additionsName string) (string, multistep.StepAction) {
func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state multistep.StateBag, additionsVersion string, additionsName string) (string, multistep.StepAction) {
// First things first, we get the list of checksums for the files available
// for this version.
checksumsUrl := fmt.Sprintf(
@@ -114,9 +114,9 @@ func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state map[string]in
checksumsFile, err := ioutil.TempFile("", "packer")
if err != nil {
state["error"] = fmt.Errorf(
state.Put("error", fmt.Errorf(
"Failed creating temporary file to store guest addition checksums: %s",
err)
err))
return "", multistep.ActionHalt
}
defer os.Remove(checksumsFile.Name())
@@ -136,9 +136,9 @@ func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state map[string]in
// Next, we find the checksum for the file we're looking to download.
// It is an error if the checksum cannot be found.
checksumsF, err := os.Open(state["guest_additions_checksums_path"].(string))
checksumsF, err := os.Open(state.Get("guest_additions_checksums_path").(string))
if err != nil {
state["error"] = fmt.Errorf("Error opening guest addition checksums: %s", err)
state.Put("error", fmt.Errorf("Error opening guest addition checksums: %s", err))
return "", multistep.ActionHalt
}
defer checksumsF.Close()
@@ -166,8 +166,8 @@ func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state map[string]in
}
if checksum == "" {
state["error"] = fmt.Errorf(
"The checksum for the file '%s' could not be found.", additionsName)
state.Put("error", fmt.Errorf(
"The checksum for the file '%s' could not be found.", additionsName))
return "", multistep.ActionHalt
}
+12 -12
View File
@@ -15,25 +15,25 @@ import (
// exportPath string - The path to the resulting export.
type stepExport struct{}
func (s *stepExport) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
// 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["sshHostPort"]))
ui.Message(fmt.Sprintf("Deleting forwarded port mapping for SSH (host port %d)", state.Get("sshHostPort")))
command := []string{"modifyvm", vmName, "--natpf1", "delete", "packerssh"}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error deleting port forwarding rule: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Remove the attached floppy disk, if it exists
if _, ok := state["floppy_path"]; ok {
if _, ok := state.GetOk("floppy_path"); ok {
ui.Message("Removing floppy drive...")
command := []string{
"storageattach", vmName,
@@ -43,7 +43,7 @@ func (s *stepExport) Run(state map[string]interface{}) multistep.StepAction {
"--medium", "none",
}
if err := driver.VBoxManage(command...); err != nil {
state["error"] = fmt.Errorf("Error removing floppy: %s", err)
state.Put("error", fmt.Errorf("Error removing floppy: %s", err))
return multistep.ActionHalt
}
@@ -63,14 +63,14 @@ func (s *stepExport) Run(state map[string]interface{}) multistep.StepAction {
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error exporting virtual machine: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["exportPath"] = outputPath
state.Put("exportPath", outputPath)
return multistep.ActionContinue
}
func (s *stepExport) Cleanup(state map[string]interface{}) {}
func (s *stepExport) Cleanup(state multistep.StateBag) {}
+8 -8
View File
@@ -17,11 +17,11 @@ import (
// Produces:
type stepForwardSSH struct{}
func (s *stepForwardSSH) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
var sshHostPort uint
@@ -45,15 +45,15 @@ func (s *stepForwardSSH) Run(state map[string]interface{}) multistep.StepAction
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error creating port forwarding rule: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Save the port we're using so that future steps can use it
state["sshHostPort"] = sshHostPort
state.Put("sshHostPort", sshHostPort)
return multistep.ActionContinue
}
func (s *stepForwardSSH) Cleanup(state map[string]interface{}) {}
func (s *stepForwardSSH) Cleanup(state multistep.StateBag) {}
+6 -6
View File
@@ -23,13 +23,13 @@ type stepHTTPServer struct {
l net.Listener
}
func (s *stepHTTPServer) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
var httpPort uint = 0
if config.HTTPDir == "" {
state["http_port"] = httpPort
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
@@ -62,12 +62,12 @@ func (s *stepHTTPServer) Run(state map[string]interface{}) multistep.StepAction
go server.Serve(s.l)
// Save the address into the state so it can be accessed in the future
state["http_port"] = httpPort
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
func (s *stepHTTPServer) Cleanup(map[string]interface{}) {
func (s *stepHTTPServer) Cleanup(multistep.StateBag) {
if s.l != nil {
// Close the listener so that the HTTP server stops
s.l.Close()
@@ -10,9 +10,9 @@ import (
type stepPrepareOutputDir struct{}
func (stepPrepareOutputDir) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
@@ -20,20 +20,20 @@ func (stepPrepareOutputDir) Run(state map[string]interface{}) multistep.StepActi
}
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
state["error"] = err
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state map[string]interface{}) {
_, cancelled := state[multistep.StateCancelled]
_, halted := state[multistep.StateHalted]
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
+9 -9
View File
@@ -16,11 +16,11 @@ type stepRun struct {
vmName string
}
func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Starting the virtual machine...")
guiArgument := "gui"
@@ -33,7 +33,7 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
command := []string{"startvm", vmName, "--type", guiArgument}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error starting VM: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -48,13 +48,13 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepRun) Cleanup(state map[string]interface{}) {
func (s *stepRun) Cleanup(state multistep.StateBag) {
if s.vmName == "" {
return
}
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if running, _ := driver.IsRunning(s.vmName); running {
if err := driver.VBoxManage("controlvm", s.vmName, "poweroff"); err != nil {
+10 -10
View File
@@ -23,12 +23,12 @@ import (
// <nothing>
type stepShutdown struct{}
func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
if config.ShutdownCommand != "" {
ui.Say("Gracefully halting virtual machine...")
@@ -36,7 +36,7 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -53,7 +53,7 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
select {
case <-shutdownTimer:
err := errors.New("Timeout while waiting for machine to shut down.")
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
default:
@@ -64,7 +64,7 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
ui.Say("Halting the virtual machine...")
if err := driver.Stop(vmName); err != nil {
err := fmt.Errorf("Error stopping VM: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -74,4 +74,4 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {}
+5 -5
View File
@@ -11,14 +11,14 @@ import (
// pop-up messages don't exist.
type stepSuppressMessages struct{}
func (stepSuppressMessages) Run(state map[string]interface{}) multistep.StepAction {
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
func (stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
log.Println("Suppressing annoying messages in VirtualBox")
if err := driver.SuppressMessages(); err != nil {
err := fmt.Errorf("Error configuring VirtualBox to suppress messages: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -26,4 +26,4 @@ func (stepSuppressMessages) Run(state map[string]interface{}) multistep.StepActi
return multistep.ActionContinue
}
func (stepSuppressMessages) Cleanup(map[string]interface{}) {}
func (stepSuppressMessages) Cleanup(multistep.StateBag) {}
+10 -10
View File
@@ -32,12 +32,12 @@ type bootCommandTemplateData struct {
// <nothing>
type stepTypeBootCommand struct{}
func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
httpPort := state["http_port"].(uint)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
tplData := &bootCommandTemplateData{
"10.0.2.2",
@@ -50,7 +50,7 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
command, err := config.tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -73,13 +73,13 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
// Since typing is sometimes so slow, we check for an interrupt
// in between each character.
if _, ok := state[multistep.StateCancelled]; ok {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
if err := driver.VBoxManage("controlvm", vmName, "keyboardputscancode", code); err != nil {
err := fmt.Errorf("Error sending boot command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -89,7 +89,7 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
return multistep.ActionContinue
}
func (*stepTypeBootCommand) Cleanup(map[string]interface{}) {}
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func scancodes(message string) []string {
special := make(map[string][]string)
@@ -14,22 +14,22 @@ type guestAdditionsPathTemplate struct {
// This step uploads the guest additions ISO to the VM.
type stepUploadGuestAdditions struct{}
func (s *stepUploadGuestAdditions) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*config)
driver := state["driver"].(Driver)
guestAdditionsPath := state["guest_additions_path"].(string)
ui := state["ui"].(packer.Ui)
func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
guestAdditionsPath := state.Get("guest_additions_path").(string)
ui := state.Get("ui").(packer.Ui)
version, err := driver.Version()
if err != nil {
state["error"] = fmt.Errorf("Error reading version for guest additions upload: %s", err)
state.Put("error", fmt.Errorf("Error reading version for guest additions upload: %s", err))
return multistep.ActionHalt
}
f, err := os.Open(guestAdditionsPath)
if err != nil {
state["error"] = fmt.Errorf("Error opening guest additions ISO: %s", err)
state.Put("error", fmt.Errorf("Error opening guest additions ISO: %s", err))
return multistep.ActionHalt
}
@@ -40,18 +40,18 @@ func (s *stepUploadGuestAdditions) Run(state map[string]interface{}) multistep.S
config.GuestAdditionsPath, err = config.tpl.Process(config.GuestAdditionsPath, tplData)
if err != nil {
err := fmt.Errorf("Error preparing guest additions path: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Uploading VirtualBox guest additions ISO...")
if err := comm.Upload(config.GuestAdditionsPath, f); err != nil {
state["error"] = fmt.Errorf("Error uploading guest additions: %s", err)
state.Put("error", fmt.Errorf("Error uploading guest additions: %s", err))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepUploadGuestAdditions) Cleanup(state map[string]interface{}) {}
func (s *stepUploadGuestAdditions) Cleanup(state multistep.StateBag) {}
+8 -8
View File
@@ -12,11 +12,11 @@ import (
// can be useful for various provisioning reasons.
type stepUploadVersion struct{}
func (s *stepUploadVersion) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if config.VBoxVersionFile == "" {
log.Println("VBoxVersionFile is empty. Not uploading.")
@@ -25,7 +25,7 @@ func (s *stepUploadVersion) Run(state map[string]interface{}) multistep.StepActi
version, err := driver.Version()
if err != nil {
state["error"] = fmt.Errorf("Error reading version for metadata upload: %s", err)
state.Put("error", fmt.Errorf("Error reading version for metadata upload: %s", err))
return multistep.ActionHalt
}
@@ -33,11 +33,11 @@ func (s *stepUploadVersion) Run(state map[string]interface{}) multistep.StepActi
var data bytes.Buffer
data.WriteString(version)
if err := comm.Upload(config.VBoxVersionFile, &data); err != nil {
state["error"] = fmt.Errorf("Error uploading VirtualBox version: %s", err)
state.Put("error", fmt.Errorf("Error uploading VirtualBox version: %s", err))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepUploadVersion) Cleanup(state map[string]interface{}) {}
func (s *stepUploadVersion) Cleanup(state multistep.StateBag) {}
+8 -8
View File
@@ -19,11 +19,11 @@ type commandTemplate struct {
// Produces:
type stepVBoxManage struct{}
func (s *stepVBoxManage) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
if len(config.VBoxManage) > 0 {
ui.Say("Executing custom VBoxManage commands...")
@@ -42,7 +42,7 @@ func (s *stepVBoxManage) Run(state map[string]interface{}) multistep.StepAction
command[i], err = config.tpl.Process(arg, tplData)
if err != nil {
err := fmt.Errorf("Error preparing vboxmanage command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -51,7 +51,7 @@ func (s *stepVBoxManage) Run(state map[string]interface{}) multistep.StepAction
ui.Message(fmt.Sprintf("Executing: %s", strings.Join(command, " ")))
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error executing command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -60,4 +60,4 @@ func (s *stepVBoxManage) Run(state map[string]interface{}) multistep.StepAction
return multistep.ActionContinue
}
func (s *stepVBoxManage) Cleanup(state map[string]interface{}) {}
func (s *stepVBoxManage) Cleanup(state multistep.StateBag) {}
+54 -9
View File
@@ -6,6 +6,7 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"math/rand"
"os"
@@ -27,6 +28,7 @@ type config struct {
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
FloppyFiles []string `mapstructure:"floppy_files"`
GuestOSType string `mapstructure:"guest_os_type"`
ISOChecksum string `mapstructure:"iso_checksum"`
@@ -42,11 +44,14 @@ type config struct {
SkipCompaction bool `mapstructure:"skip_compaction"`
ShutdownCommand string `mapstructure:"shutdown_command"`
SSHUser string `mapstructure:"ssh_username"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"`
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
ToolsUploadPath string `mapstructure:"tools_upload_path"`
VMXData map[string]string `mapstructure:"vmx_data"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"`
@@ -84,6 +89,11 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.DiskSize = 40000
}
if b.config.DiskTypeId == "" {
// Default is growable virtual disk split in 2GB files.
b.config.DiskTypeId = "1"
}
if b.config.FloppyFiles == nil {
b.config.FloppyFiles = make([]string, 0)
}
@@ -145,6 +155,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"boot_wait": &b.config.RawBootWait,
"shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
"vmx_template_path": &b.config.VMXTemplatePath,
}
for n, ptr := range templates {
@@ -253,6 +264,16 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
}
if b.config.SSHKeyPath != "" {
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
if b.config.SSHUser == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("An ssh_username must be specified."))
@@ -291,6 +312,14 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs, fmt.Errorf("tools_upload_path invalid: %s", err))
}
if b.config.VMXTemplatePath != "" {
if err := b.validateVMXTemplatePath(); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
}
}
if b.config.VNCPortMin > b.config.VNCPortMax {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
@@ -336,6 +365,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SSHAddress: sshAddress,
SSHConfig: sshConfig,
SSHWaitTimeout: b.config.sshWaitTimeout,
NoPty: b.config.SSHSkipRequestPty,
},
&stepUploadTools{},
&common.StepProvision{},
@@ -346,12 +376,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Setup the state bag
state := make(map[string]interface{})
state["cache"] = cache
state["config"] = &b.config
state["driver"] = driver
state["hook"] = hook
state["ui"] = ui
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("config", &b.config)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
// Run!
if b.config.PackerDebug {
@@ -366,16 +396,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state["error"]; ok {
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state[multistep.StateCancelled]; ok {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state[multistep.StateHalted]; ok {
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
@@ -406,3 +436,18 @@ func (b *Builder) Cancel() {
b.runner.Cancel()
}
}
func (b *Builder) validateVMXTemplatePath() error {
f, err := os.Open(b.config.VMXTemplatePath)
if err != nil {
return err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return err
}
return b.config.tpl.Validate(string(data))
}
+129
View File
@@ -9,6 +9,36 @@ import (
"time"
)
var testPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
-----END RSA PRIVATE KEY-----
`
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "foo",
@@ -339,6 +369,55 @@ func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
}
}
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
var b Builder
config := testConfig()
config["ssh_key_path"] = ""
b = Builder{}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
config["ssh_key_path"] = "/i/dont/exist"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test good contents
tf.Seek(0, 0)
tf.Truncate(0)
tf.Write([]byte(testPem))
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuilderPrepare_SSHUser(t *testing.T) {
var b Builder
config := testConfig()
@@ -437,6 +516,56 @@ func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
}
}
func TestBuilderPrepare_VMXTemplatePath(t *testing.T) {
var b Builder
config := testConfig()
// Test bad
config["vmx_template_path"] = "/i/dont/exist/forreal"
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test good
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["vmx_template_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Bad template
tf2, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf2.Name())
defer tf2.Close()
if _, err := tf2.Write([]byte("{{foo}")); err != nil {
t.Fatalf("err: %s", err)
}
config["vmx_template_path"] = tf2.Name()
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_VNCPort(t *testing.T) {
var b Builder
config := testConfig()
+1 -1
View File
@@ -15,7 +15,7 @@ type Driver interface {
CompactDisk(string) error
// CreateDisk creates a virtual disk with the given size.
CreateDisk(string, string) error
CreateDisk(string, string, string) error
// Checks if the VMX file at the given path is running.
IsRunning(string) (bool, error)
+2 -2
View File
@@ -28,8 +28,8 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error {
return nil
}
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
func (d *Fusion5Driver) CreateDisk(output string, size string, type_id string) error {
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
+2 -2
View File
@@ -51,12 +51,12 @@ func (d *Player5LinuxDriver) qemuCompactDisk(diskPath string) error {
return nil
}
func (d *Player5LinuxDriver) CreateDisk(output string, size string) error {
func (d *Player5LinuxDriver) CreateDisk(output string, size string, type_id string) error {
var cmd *exec.Cmd
if d.QemuImgPath != "" {
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
} else {
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
}
if _, _, err := runAndLog(cmd); err != nil {
return err
+2 -2
View File
@@ -31,8 +31,8 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error {
return nil
}
func (d *Workstation9Driver) CreateDisk(output string, size string) error {
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
func (d *Workstation9Driver) CreateDisk(output string, size string, type_id string) error {
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
+43 -11
View File
@@ -4,16 +4,17 @@ import (
gossh "code.google.com/p/go.crypto/ssh"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"io/ioutil"
"log"
"os"
)
func sshAddress(state map[string]interface{}) (string, error) {
config := state["config"].(*config)
driver := state["driver"].(Driver)
vmxPath := state["vmx_path"].(string)
func sshAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
vmxPath := state.Get("vmx_path").(string)
log.Println("Lookup up IP information...")
f, err := os.Open(vmxPath)
@@ -58,15 +59,46 @@ func sshAddress(state map[string]interface{}) (string, error) {
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
}
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
config := state["config"].(*config)
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*config)
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
}
if config.SSHKeyPath != "" {
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
if err != nil {
return nil, err
}
auth = append(auth, gossh.ClientAuthKeyring(keyring))
}
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
},
Auth: auth,
}, nil
}
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
keyBytes, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
return nil, err
}
return keyring, nil
}
+5 -5
View File
@@ -23,9 +23,9 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
// <nothing>
type stepCleanFiles struct{}
func (stepCleanFiles) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting unnecessary VMware files...")
visit := func(path string, info os.FileInfo, err error) error {
@@ -55,11 +55,11 @@ func (stepCleanFiles) Run(state map[string]interface{}) multistep.StepAction {
}
if err := filepath.Walk(config.OutputDir, visit); err != nil {
state["error"] = err
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCleanFiles) Cleanup(map[string]interface{}) {}
func (stepCleanFiles) Cleanup(multistep.StateBag) {}
+9 -9
View File
@@ -22,20 +22,20 @@ import (
// <nothing>
type stepCleanVMX struct{}
func (s stepCleanVMX) Run(state map[string]interface{}) multistep.StepAction {
isoPath := state["iso_path"].(string)
ui := state["ui"].(packer.Ui)
vmxPath := state["vmx_path"].(string)
func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
ui.Say("Cleaning VMX prior to finishing up...")
vmxData, err := s.readVMX(vmxPath)
if err != nil {
state["error"] = fmt.Errorf("Error reading VMX: %s", err)
state.Put("error", fmt.Errorf("Error reading VMX: %s", err))
return multistep.ActionHalt
}
if _, ok := state["floppy_path"]; ok {
if _, ok := state.GetOk("floppy_path"); ok {
// Delete the floppy0 entries so the floppy is no longer mounted
ui.Message("Unmounting floppy from VMX...")
for k, _ := range vmxData {
@@ -47,7 +47,7 @@ func (s stepCleanVMX) Run(state map[string]interface{}) multistep.StepAction {
vmxData["floppy0.present"] = "FALSE"
}
ui.Message("Detatching ISO from CD-ROM device...")
ui.Message("Detaching ISO from CD-ROM device...")
devRe := regexp.MustCompile(`^ide\d:\d\.`)
for k, _ := range vmxData {
match := devRe.FindString(k)
@@ -67,14 +67,14 @@ func (s stepCleanVMX) Run(state map[string]interface{}) multistep.StepAction {
// Rewrite the VMX
if err := WriteVMX(vmxPath, vmxData); err != nil {
state["error"] = fmt.Errorf("Error writing VMX: %s", err)
state.Put("error", fmt.Errorf("Error writing VMX: %s", err))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCleanVMX) Cleanup(map[string]interface{}) {}
func (stepCleanVMX) Cleanup(multistep.StateBag) {}
func (stepCleanVMX) readVMX(vmxPath string) (map[string]string, error) {
vmxF, err := os.Open(vmxPath)
+7 -7
View File
@@ -20,11 +20,11 @@ import (
// <nothing>
type stepCompactDisk struct{}
func (stepCompactDisk) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
full_disk_path := state["full_disk_path"].(string)
func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
full_disk_path := state.Get("full_disk_path").(string)
if config.SkipCompaction == true {
log.Println("Skipping disk compaction step...")
@@ -33,11 +33,11 @@ func (stepCompactDisk) Run(state map[string]interface{}) multistep.StepAction {
ui.Say("Compacting the disk image")
if err := driver.CompactDisk(full_disk_path); err != nil {
state["error"] = fmt.Errorf("Error compacting disk: %s", err)
state.Put("error", fmt.Errorf("Error compacting disk: %s", err))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCompactDisk) Cleanup(map[string]interface{}) {}
func (stepCompactDisk) Cleanup(multistep.StateBag) {}
+9 -9
View File
@@ -22,15 +22,15 @@ import (
// vnc_port uint - The port that VNC is configured to listen on.
type stepConfigureVNC struct{}
func (stepConfigureVNC) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
vmxPath := state["vmx_path"].(string)
func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
f, err := os.Open(vmxPath)
if err != nil {
err := fmt.Errorf("Error reading VMX data: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -38,7 +38,7 @@ func (stepConfigureVNC) Run(state map[string]interface{}) multistep.StepAction {
vmxBytes, err := ioutil.ReadAll(f)
if err != nil {
err := fmt.Errorf("Error reading VMX data: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -67,15 +67,15 @@ func (stepConfigureVNC) Run(state map[string]interface{}) multistep.StepAction {
if err := WriteVMX(vmxPath, vmxData); err != nil {
err := fmt.Errorf("Error writing VMX data: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["vnc_port"] = vncPort
state.Put("vnc_port", vncPort)
return multistep.ActionContinue
}
func (stepConfigureVNC) Cleanup(map[string]interface{}) {
func (stepConfigureVNC) Cleanup(multistep.StateBag) {
}
+8 -8
View File
@@ -18,23 +18,23 @@ import (
// full_disk_path (string) - The full path to the created disk.
type stepCreateDisk struct{}
func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
func (stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating virtual machine disk")
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskTypeId); err != nil {
err := fmt.Errorf("Error creating disk: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["full_disk_path"] = full_disk_path
state.Put("full_disk_path", full_disk_path)
return multistep.ActionContinue
}
func (stepCreateDisk) Cleanup(map[string]interface{}) {}
func (stepCreateDisk) Cleanup(multistep.StateBag) {}
+40 -14
View File
@@ -1,13 +1,13 @@
package vmware
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"os"
"path/filepath"
"text/template"
)
type vmxTemplateData struct {
@@ -28,10 +28,10 @@ type vmxTemplateData struct {
// vmx_path string - The path to the VMX file.
type stepCreateVMX struct{}
func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
isoPath := state["iso_path"].(string)
ui := state["ui"].(packer.Ui)
func (stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Building and writing VMX file")
@@ -42,11 +42,37 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
ISOPath: isoPath,
}
var buf bytes.Buffer
t := template.Must(template.New("vmx").Parse(DefaultVMXTemplate))
t.Execute(&buf, tplData)
vmxTemplate := DefaultVMXTemplate
if config.VMXTemplatePath != "" {
f, err := os.Open(config.VMXTemplatePath)
if err != nil {
err := fmt.Errorf("Error reading VMX template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
defer f.Close()
vmxData := ParseVMX(buf.String())
rawBytes, err := ioutil.ReadAll(f)
if err != nil {
err := fmt.Errorf("Error reading VMX template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vmxTemplate = string(rawBytes)
}
vmxContents, err := config.tpl.Process(vmxTemplate, tplData)
if err != nil {
err := fmt.Errorf("Error procesing VMX template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vmxData := ParseVMX(vmxContents)
if config.VMXData != nil {
log.Println("Setting custom VMX data...")
for k, v := range config.VMXData {
@@ -55,7 +81,7 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
}
}
if floppyPathRaw, ok := state["floppy_path"]; ok {
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
log.Println("Floppy path present, setting in VMX")
vmxData["floppy0.present"] = "TRUE"
vmxData["floppy0.fileType"] = "file"
@@ -65,17 +91,17 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
vmxPath := filepath.Join(config.OutputDir, config.VMName+".vmx")
if err := WriteVMX(vmxPath, vmxData); err != nil {
err := fmt.Errorf("Error creating VMX file: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state["vmx_path"] = vmxPath
state.Put("vmx_path", vmxPath)
return multistep.ActionContinue
}
func (stepCreateVMX) Cleanup(map[string]interface{}) {
func (stepCreateVMX) Cleanup(multistep.StateBag) {
}
// This is the default VMX template used if no other template is given.
+6 -6
View File
@@ -23,13 +23,13 @@ type stepHTTPServer struct {
l net.Listener
}
func (s *stepHTTPServer) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
var httpPort uint = 0
if config.HTTPDir == "" {
state["http_port"] = httpPort
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
@@ -62,12 +62,12 @@ func (s *stepHTTPServer) Run(state map[string]interface{}) multistep.StepAction
go server.Serve(s.l)
// Save the address into the state so it can be accessed in the future
state["http_port"] = httpPort
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
func (s *stepHTTPServer) Cleanup(map[string]interface{}) {
func (s *stepHTTPServer) Cleanup(multistep.StateBag) {
if s.l != nil {
// Close the listener so that the HTTP server stops
s.l.Close()
+9 -9
View File
@@ -10,9 +10,9 @@ import (
type stepPrepareOutputDir struct{}
func (stepPrepareOutputDir) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
@@ -20,20 +20,20 @@ func (stepPrepareOutputDir) Run(state map[string]interface{}) multistep.StepActi
}
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
state["error"] = err
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state map[string]interface{}) {
_, cancelled := state[multistep.StateCancelled]
_, halted := state[multistep.StateHalted]
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
+7 -7
View File
@@ -8,9 +8,9 @@ import (
type stepPrepareTools struct{}
func (*stepPrepareTools) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
func (*stepPrepareTools) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
if config.ToolsUploadFlavor == "" {
return multistep.ActionContinue
@@ -18,16 +18,16 @@ func (*stepPrepareTools) Run(state map[string]interface{}) multistep.StepAction
path := driver.ToolsIsoPath(config.ToolsUploadFlavor)
if _, err := os.Stat(path); err != nil {
state["error"] = fmt.Errorf(
state.Put("error", fmt.Errorf(
"Couldn't find VMware tools for '%s'! VMware often downloads these\n"+
"tools on-demand. However, to do this, you need to create a fake VM\n"+
"of the proper type then click the 'install tools' option in the\n"+
"VMware GUI.", config.ToolsUploadFlavor)
"VMware GUI.", config.ToolsUploadFlavor))
return multistep.ActionHalt
}
state["tools_upload_source"] = path
state.Put("tools_upload_source", path)
return multistep.ActionContinue
}
func (*stepPrepareTools) Cleanup(map[string]interface{}) {}
func (*stepPrepareTools) Cleanup(multistep.StateBag) {}
+10 -10
View File
@@ -22,12 +22,12 @@ type stepRun struct {
vmxPath string
}
func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmxPath := state["vmx_path"].(string)
vncPort := state["vnc_port"].(uint)
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
vncPort := state.Get("vnc_port").(uint)
// Set the VMX path so that we know we started the machine
s.bootTime = time.Now()
@@ -43,7 +43,7 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
if err := driver.Start(vmxPath, config.Headless); err != nil {
err := fmt.Errorf("Error starting VM: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -57,9 +57,9 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepRun) Cleanup(state map[string]interface{}) {
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
func (s *stepRun) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
// If we started the machine... stop it.
if s.vmxPath != "" {
+12 -12
View File
@@ -27,12 +27,12 @@ import (
// <nothing>
type stepShutdown struct{}
func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmxPath := state["vmx_path"].(string)
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
if config.ShutdownCommand != "" {
ui.Say("Gracefully halting virtual machine...")
@@ -46,7 +46,7 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
}
if err := comm.Start(cmd); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -56,9 +56,9 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
// If the command failed to run, notify the user in some way.
if cmd.ExitStatus != 0 {
state["error"] = fmt.Errorf(
state.Put("error", fmt.Errorf(
"Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s",
stdout.String(), stderr.String())
stdout.String(), stderr.String()))
return multistep.ActionHalt
}
@@ -77,7 +77,7 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
select {
case <-shutdownTimer:
err := errors.New("Timeout while waiting for machine to shut down.")
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
default:
@@ -87,7 +87,7 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
} else {
if err := driver.Stop(vmxPath); err != nil {
err := fmt.Errorf("Error stopping VM: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -132,4 +132,4 @@ LockWaitLoop:
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {}
+11 -11
View File
@@ -34,18 +34,18 @@ type bootCommandTemplateData struct {
// <nothing>
type stepTypeBootCommand struct{}
func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
httpPort := state["http_port"].(uint)
ui := state["ui"].(packer.Ui)
vncPort := state["vnc_port"].(uint)
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
vncPort := state.Get("vnc_port").(uint)
// Connect to VNC
ui.Say("Connecting to VM via VNC")
nc, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", vncPort))
if err != nil {
err := fmt.Errorf("Error connecting to VNC: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -54,7 +54,7 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
c, err := vnc.Client(nc, &vnc.ClientConfig{Exclusive: true})
if err != nil {
err := fmt.Errorf("Error handshaking with VNC: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -73,7 +73,7 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
hostIp, err := ipFinder.HostIP()
if err != nil {
err := fmt.Errorf("Error detecting host IP: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -91,14 +91,14 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
command, err := config.tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Check for interrupts between typing things so we can cancel
// since this isn't the fastest thing.
if _, ok := state[multistep.StateCancelled]; ok {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
@@ -108,7 +108,7 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
return multistep.ActionContinue
}
func (*stepTypeBootCommand) Cleanup(map[string]interface{}) {}
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func vncSendString(c *vnc.ClientConn, original string) {
special := make(map[string]uint32)
+9 -9
View File
@@ -13,20 +13,20 @@ type toolsUploadPathTemplate struct {
type stepUploadTools struct{}
func (*stepUploadTools) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
func (*stepUploadTools) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
if config.ToolsUploadFlavor == "" {
return multistep.ActionContinue
}
comm := state["communicator"].(packer.Communicator)
tools_source := state["tools_upload_source"].(string)
ui := state["ui"].(packer.Ui)
comm := state.Get("communicator").(packer.Communicator)
tools_source := state.Get("tools_upload_source").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Uploading the '%s' VMware Tools", config.ToolsUploadFlavor))
f, err := os.Open(tools_source)
if err != nil {
state["error"] = fmt.Errorf("Error opening VMware Tools ISO: %s", err)
state.Put("error", fmt.Errorf("Error opening VMware Tools ISO: %s", err))
return multistep.ActionHalt
}
defer f.Close()
@@ -35,17 +35,17 @@ func (*stepUploadTools) Run(state map[string]interface{}) multistep.StepAction {
config.ToolsUploadPath, err = config.tpl.Process(config.ToolsUploadPath, tplData)
if err != nil {
err := fmt.Errorf("Error preparing upload path: %s", err)
state["error"] = err
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := comm.Upload(config.ToolsUploadPath, f); err != nil {
state["error"] = fmt.Errorf("Error uploading VMware Tools: %s", err)
state.Put("error", fmt.Errorf("Error uploading VMware Tools: %s", err))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (*stepUploadTools) Cleanup(map[string]interface{}) {}
func (*stepUploadTools) Cleanup(multistep.StateBag) {}
+11
View File
@@ -187,8 +187,18 @@ func (c Command) Run(env packer.Environment, args []string) int {
}
if len(errors) > 0 {
env.Ui().Machine("error-count", strconv.FormatInt(int64(len(errors)), 10))
env.Ui().Error("\n==> Some builds didn't complete successfully and had errors:")
for name, err := range errors {
// Create a UI for the machine readable stuff to be targetted
ui := &packer.TargettedUi{
Target: name,
Ui: env.Ui(),
}
ui.Machine("error", err.Error())
env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err))
}
}
@@ -233,6 +243,7 @@ func (c Command) Run(env packer.Environment, args []string) int {
ui.Machine("artifact", iStr, "nil")
}
ui.Machine("artifact", iStr, "end")
env.Ui().Say(message.String())
}
}

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