Compare commits

...

173 Commits

Author SHA1 Message Date
Mitchell Hashimoto 3adc5e03d7 v0.1.5 2013-07-07 21:09:47 -07:00
Mitchell Hashimoto ef1ec3708b packer/rpc: Properly close net.conn when remote process ends 2013-07-07 20:59:43 -07:00
Mitchell Hashimoto ee1ad5fa56 provisioner/shell: close source script file handle 2013-07-07 20:50:53 -07:00
Mitchell Hashimoto 65d74f90c1 builder/amazonebs: retry SSH handshakes [GH-130] 2013-07-07 20:37:43 -07:00
Mitchell Hashimoto 27bada1ba9 builder/virtualbox: whitespace 2013-07-07 18:04:30 -07:00
Mitchell Hashimoto f7587be617 website: document the inline_shebang stuff 2013-07-07 17:55:02 -07:00
Mitchell Hashimoto 62430fdcea provisioner/shell: inline_shebang for inline scripts 2013-07-07 17:52:20 -07:00
Mitchell Hashimoto b25bed634f Merge pull request #133 from ubermuda/provisioner/shell/script-execution
provisioner/shell: execute script directly instead of using /bin/sh
2013-07-07 17:47:25 -07:00
Mitchell Hashimoto 8c01d2c226 post-processor/vagrant: properly close file handles [GH-100] 2013-07-07 17:44:13 -07:00
Mitchell Hashimoto f59dfa90a9 Started on a contributing doc 2013-07-07 17:37:44 -07:00
Mitchell Hashimoto eb009ca4e3 communicator/ssh: show more descriptive error if SCP not avail [GH-127] 2013-07-07 12:23:32 -07:00
Mitchell Hashimoto abc2bc60ab builder/common: Error on non-200 download responses [GH-141] 2013-07-07 12:16:31 -07:00
Mitchell Hashimoto 308205cc6f website: document the guest additinos URL and SHA256
/cc @sgirones
2013-07-07 12:06:13 -07:00
Mitchell Hashimoto cd6e154bd1 builder/virtualbox: style nitpicks and CHANGELOG 2013-07-07 09:17:27 -07:00
Mitchell Hashimoto 4ecb92358b fmt 2013-07-07 09:14:41 -07:00
Mitchell Hashimoto c1ad88aaa6 builder/virtualbox: test the configuration
/cc @sgirones
2013-07-07 09:14:16 -07:00
Mitchell Hashimoto ee9aa9b071 builder/virtualbox: typo in test 2013-07-07 09:09:22 -07:00
Mitchell Hashimoto 8d5c2091d4 Merge pull request #145 from sgirones/feature/local-mode-guest-additions
builder/virtualbox: Local mode for VBoxGuestAdditions. Provide local path and SHA256
2013-07-07 09:07:56 -07:00
Mitchell Hashimoto baa539d413 Merge pull request #147 from mitchellh/shutdown-docs
website: Shutdown Command in Examples
2013-07-07 09:05:58 -07:00
Jack Pearkes 70155fc695 website: add a shutdown command to the vm builder config examples 2013-07-07 15:37:22 +02:00
Jack Pearkes c46aae5b09 website: specify ruby version in the gemfile 2013-07-07 15:31:37 +02:00
Salvador Gironès d5731a13d4 Local mode for VBoxGuestAdditions. Provide local path and SHA256 2013-07-06 11:28:56 +02:00
Mitchell Hashimoto 248f18da37 Merge pull request #143 from priteau/master
website: Fix some typos and an error in the website documentation
2013-07-05 11:30:59 -07:00
Pierre Riteau 62199c8c73 Fix some typos and an error in the documentation 2013-07-05 19:23:24 +01:00
Mitchell Hashimoto 324a2229a6 post-processor/vagrant: provider PPs get properly configured 2013-07-05 11:11:54 -07:00
Mitchell Hashimoto 8b25acfab6 fmt 2013-07-05 11:00:18 -07:00
Mitchell Hashimoto f5003c4e19 post-processor/vagrant: properly forward config into provider [GH-129] 2013-07-05 10:50:57 -07:00
Mitchell Hashimoto 534c923a44 post-processor/vagrant: Extra logging 2013-07-05 10:41:54 -07:00
Mitchell Hashimoto da415312ee Merge pull request #142 from philipcristiano/master
website: Use free-tier instance size.
2013-07-05 10:37:16 -07:00
Mitchell Hashimoto c0cdec92db Merge pull request #139 from sit/clarify-gopath
README: Clarify repository placement relative to GOPATH
2013-07-05 10:35:38 -07:00
Philip Cristiano 04c4d7f7a9 docs: Use free-tier instance size 2013-07-05 13:35:31 -04:00
Mitchell Hashimoto 6d4cf32982 Merge pull request #140 from philipcristiano/master
website: Fix introduction basic example
2013-07-05 10:33:28 -07:00
Philip Cristiano fb0af12c22 docs: Use free-tier instance size 2013-07-05 13:22:38 -04:00
Philip Cristiano c7ce4bf74c docs: Fix invalid example 2013-07-05 08:42:22 -04:00
Emil Sit 8735ac99db README: Clarify repository placement relative to GOPATH
If you don't put the repository in the right place in GOPATH,
changes to your sources don't get built.

Signed-off-by: Emil Sit <sit@emilsit.net>
2013-07-04 23:59:52 -04:00
Mitchell Hashimoto 3068d0e3fd Update CHANGELOG 2013-07-04 15:47:06 -07:00
Mitchell Hashimoto 30cb058c81 Merge pull request #137 from bdon/forwarded-port-cleanup
builder/virtualbox: clean up port forwarding before exporting OVF.
2013-07-04 15:46:32 -07:00
Brandon Liu cf191350cd Virtualbox: clean up port forwarding before exporting OVF. 2013-07-04 14:46:39 -07:00
Mitchell Hashimoto f1c8f0a8cd website: add more info to the file provisioner page 2013-07-04 12:58:11 -07:00
Mitchell Hashimoto 49dc2c4cba Update CHANGELOG 2013-07-04 12:50:49 -07:00
Mitchell Hashimoto 4f2344f48b provisioner/file: style nitpicks
/cc @ericlathrop
2013-07-04 12:50:00 -07:00
Mitchell Hashimoto 7f9c755a42 fmt 2013-07-04 12:44:36 -07:00
Mitchell Hashimoto 6d6d8795df Merge pull request #136 from ericlathrop/file-provisioner
File upload provisioner [GH-118]
2013-07-04 12:42:19 -07:00
Eric Lathrop 05c7d08bb2 Add file provisioner documentation. 2013-07-04 15:17:32 -04:00
Eric Lathrop c259f30e58 Implement file upload provisioner per #118. 2013-07-04 15:16:17 -04:00
Mitchell Hashimoto ab574af78c Merge pull request #132 from ubermuda/patch-1
builder/virtualbox: Typo
2013-07-03 18:10:11 -07:00
Geoffrey Bachelet e8d31bc21a provisioner/shell: execute script directly instead of using /bin/sh 2013-07-03 20:17:31 -04:00
Geoffrey Bachelet 94c0fac5ad Typo 2013-07-03 19:11:37 -04:00
Mitchell Hashimoto 73154286fc Up version for dev 2013-07-03 11:06:39 -07:00
Steven Merrill d891e578e2 Merge pull request #124 from priteau/master
Fix small typos
2013-07-03 04:55:15 -07:00
Pierre Riteau d7dccaeea2 Fix small typos 2013-07-03 10:55:25 +01:00
Mitchell Hashimoto df560dbbe1 v0.1.4 2013-07-02 22:16:13 -07:00
Mitchell Hashimoto 259986e7e5 builder/virtualbox, builder/vmware: keyboard-interactive [GH-121] 2013-07-02 22:02:58 -07:00
Mitchell Hashimoto 40e9f9a76f communicator/ssh: add keyboard interactive auth [GH-121] 2013-07-02 22:00:31 -07:00
Mitchell Hashimoto 2f698cca77 Update CHANGELOG 2013-07-02 21:29:28 -07:00
Mitchell Hashimoto 7247a69581 website: nitpick compaction docs 2013-07-02 21:28:54 -07:00
Mitchell Hashimoto 2958b5d9c2 Merge pull request #122 from smerrill/compaction-option
builder/vmware: Add a 'skip_compaction' configuration option
2013-07-02 21:27:20 -07:00
Mitchell Hashimoto a8da773e40 Merge pull request #123 from smerrill/headless-documentation
website: Document 'headless' option for Fusion and VBox
2013-07-02 21:26:37 -07:00
Steven Merrill 663cce5aaa Remove confusing duplicate references. 2013-07-02 23:56:56 -04:00
Steven Merrill 82a3aba0d3 builder/vmware, builder/virtualbox: Document 'headless' option. 2013-07-02 23:53:31 -04:00
Steven Merrill 2e15bed7b6 Add documentation of the skip_compaction parameter. 2013-07-02 23:48:04 -04:00
Steven Merrill 9264f76053 Add a 'skip_compaction' step. 2013-07-02 20:22:11 -04:00
Mitchell Hashimoto ae279a8b61 Merge pull request #120 from bdon/master
helptext has command [options] in it
2013-07-02 13:16:39 -07:00
Brandon Liu f31f5ed759 Command helpText indicates that options must come before template. 2013-07-02 13:07:39 -07:00
Mitchell Hashimoto 8ce8171ec1 packer: PrefixedUi properly prefixes each line of output 2013-07-02 12:28:25 -07:00
Mitchell Hashimoto b4b9469d3c builder/vmware: output VNC info if headless
/cc @smerrill
2013-07-02 12:23:19 -07:00
Mitchell Hashimoto 925734d06b builder/virtualbox: Warning about headless mode
/cc @smerrill
2013-07-02 12:20:26 -07:00
Mitchell Hashimoto 2e88ddc965 Update CHANGELOG 2013-07-02 12:18:31 -07:00
Mitchell Hashimoto b729ba0e00 Merge pull request #108 from smerrill/headless-mode
builder/vmware, builder/virtualbox: Headless mode
2013-07-02 12:16:40 -07:00
Mitchell Hashimoto 4bb5f8a6aa Merge pull request #115 from bdon/wait
builder/virtualbox, builder/vmware: wait5 and wait10 timers
2013-07-02 12:14:17 -07:00
Brandon Liu 2fc795d299 Update command to run locally in website README. 2013-07-02 12:12:42 -07:00
Brandon Liu f931b28851 Document wait5 and wait10 boot command keys. 2013-07-02 10:44:49 -07:00
Brandon Liu cf13f57528 Add wait5 and wait10 special keys for boot command. 2013-07-02 10:31:22 -07:00
Mitchell Hashimoto aff4389fc8 Update CHANGELOG 2013-07-02 09:38:39 -07:00
Mitchell Hashimoto 4d4dd85ef4 builder/vmware: Improve check for VMware cleanup 2013-07-02 09:30:24 -07:00
Mitchell Hashimoto be40fb7abb builder/vmware: No redundant errors 2013-07-02 09:10:44 -07:00
Mitchell Hashimoto 00a5ca32ae builder/vmware: Style nitpick
/cc @smerrill: I prefer to do state changes at the end. :)
2013-07-02 09:09:55 -07:00
Mitchell Hashimoto 93e5082d04 Merge pull request #107 from smerrill/compact-disk
builder/vmware: Compact disk at end of the run
2013-07-02 09:09:15 -07:00
Mitchell Hashimoto f36972f52a builder/vmware: wait for shutdown to avoid corrupt disk files [GH-111] 2013-07-02 09:05:17 -07:00
Mitchell Hashimoto 8ece9a60d5 post-processor/vagrant: check for error when tarring earlier 2013-07-02 08:46:20 -07:00
Steven Merrill 432cab3fcf Small fix to be sure "full_disk_path" is put into state. 2013-07-02 01:32:50 -04:00
Steven Merrill 586a9a7c32 Small doc fixes. 2013-07-02 01:15:03 -04:00
Steven Merrill 359ba01c6a Integrate code review comments. 2013-07-02 01:12:57 -04:00
Mitchell Hashimoto dbbbc38af6 Merge pull request #109 from razzintown/patch-1
website: capitalize "Packer"
2013-07-01 22:12:49 -07:00
Razz Fox f2111b6d12 capitalize "Packer" 2013-07-01 22:01:22 -07:00
Steven Merrill f27ea933a4 Add VirtualBox headless mode. 2013-07-02 00:13:24 -04:00
Steven Merrill 4aa55c40de Add VMWare headless mode. 2013-07-02 00:13:13 -04:00
Mitchell Hashimoto 06108b4f96 Merge pull request #106 from bdon/master
website: typos
2013-07-01 21:09:59 -07:00
Brandon Liu 0e3b1e59a8 Doc fixes. 2013-07-01 19:53:04 -07:00
Steven Merrill 29fa621907 Finish initial compaction feature. 2013-07-01 22:45:46 -04:00
Steven Merrill d4cd9352d6 First try at a compaction step. 2013-07-01 22:25:33 -04:00
Steven Merrill 4de76ccd3f Add a computed FullDiskPath config option. 2013-07-01 22:22:00 -04:00
Mitchell Hashimoto ac5c90bd7e Merge pull request #101 from ericlathrop/fix-make-test
Fix import paths in "make test" dependency fetching
2013-07-01 16:37:07 -07:00
Eric Lathrop 7bff7f8a22 Remove newline from test import paths.
When running "make test" on my machine, I got many unrecognized import
path errors, and the path showed a trailing "\n". Changing the go list
format to remove the newline fixed these.
2013-07-01 19:25:23 -04:00
Mitchell Hashimoto 126f2bc07f website: more notes on AWS env vars 2013-07-01 15:26:08 -07:00
Mitchell Hashimoto 0b1b76a4db website: update website with new AWS env vars 2013-07-01 15:24:36 -07:00
Mitchell Hashimoto 9ca3d05241 builder/amazonebs: tests for env vars 2013-07-01 15:22:35 -07:00
Mitchell Hashimoto 2a7a7173f1 Merge pull request #40 from justincampbell/amazon-env-vars
builder/amazonebs: Default to AWS env vars for key and secret
2013-07-01 15:11:19 -07:00
Mitchell Hashimoto 8e231f9264 fmt 2013-07-01 15:07:37 -07:00
Mitchell Hashimoto 3f9df2992c post-processor/vagrant: make output contain build name by default
[GH-92]
2013-07-01 15:07:09 -07:00
Mitchell Hashimoto 400faa57a5 packer: Send packerConfig into PostProcessors 2013-07-01 15:00:21 -07:00
Mitchell Hashimoto fc78bf3dd6 packer: PostProcessor can take multiple configs 2013-07-01 14:59:23 -07:00
Mitchell Hashimoto 8cf21324dc packer: extra comments on JSON syntax error calc 2013-07-01 14:47:49 -07:00
Mitchell Hashimoto 974ac26c9c packer: Clean up some of the JSON syntax stuff 2013-07-01 14:46:32 -07:00
Mitchell Hashimoto b6884da2a1 Merge pull request #95 from markpeek/markpeek-jsonerror
packer: Provide line number for invalid json syntax [GH-56]
2013-07-01 14:38:01 -07:00
Mitchell Hashimoto 9f17257f19 Up version for development 2013-07-01 14:37:43 -07:00
Mark Peek 5bf33a0e91 Provide line number for invalid json syntax [GH-56] 2013-07-01 13:30:08 -07:00
Mitchell Hashimoto f623c0cb3b v0.1.3 2013-07-01 11:40:49 -07:00
Mitchell Hashimoto 4ad77b9d9a website: update latest post-processor API 2013-07-01 11:35:09 -07:00
Mitchell Hashimoto f270701858 packer: PostProcessor API change so they can keep artifacts [GH-55] 2013-07-01 11:30:39 -07:00
Mitchell Hashimoto 855e8aab85 website: update website with latest vm_name default changes 2013-07-01 11:15:13 -07:00
Mitchell Hashimoto da225c0d06 builder/virtualbox, builder/vmware: output and VM name based on build
[GH-91]
2013-07-01 11:11:51 -07:00
Mitchell Hashimoto b781077ed9 fmt 2013-07-01 11:07:25 -07:00
Mitchell Hashimoto 8120cf9adf packer: Add BuildNameConfigKey to all configs which has the build name 2013-07-01 11:06:28 -07:00
Mitchell Hashimoto 222b15e8f3 packer/plugin: Println should be Printf 2013-07-01 11:01:23 -07:00
Mitchell Hashimoto 7f17c58335 builder/virtualbox: trim whitespace when getting version 2013-07-01 10:59:04 -07:00
Mitchell Hashimoto 8da6ea720e website: document disk_size config for virtualbox 2013-07-01 10:51:40 -07:00
Mitchell Hashimoto 05c6c87eb2 website: document the vmware tools stuff 2013-07-01 10:48:50 -07:00
Mitchell Hashimoto 60c0cb6fc8 packer/rpc: Tests passing again 2013-07-01 10:45:51 -07:00
Mitchell Hashimoto cf99d640e3 Update CHANGELOG 2013-07-01 10:45:51 -07:00
Mitchell Hashimoto d306fe742c builder/vmware: tools_upload_flavor and path
/cc @smerrill
2013-07-01 10:45:50 -07:00
Mitchell Hashimoto 2708c5e3eb Merge pull request #89 from mitchellh/debug-plat-arch
Log Packer OS/Arch to Debug
2013-07-01 10:00:59 -07:00
Mitchell Hashimoto 986a66ee19 Update CHANGELOG 2013-07-01 09:41:10 -07:00
Mitchell Hashimoto e9f7f94599 fmt 2013-07-01 09:39:34 -07:00
Mitchell Hashimoto 6f705d5bcb packer/rpc: Listen on 127.0.0.1 specifically to avoid hangs 2013-07-01 09:39:34 -07:00
Jack Pearkes 280d176f73 packer: prints packers OS and architecture to the log during startup 2013-07-01 18:34:43 +02:00
Mitchell Hashimoto 832223f321 Merge pull request #88 from mitchellh/debug-version
Log Packer Version to Debug
2013-07-01 08:53:16 -07:00
Jack Pearkes c7d9e88db3 packer: print packers version to the log during startup 2013-07-01 16:46:54 +02:00
Jack Pearkes d9ab4e210d builder/digitalocean: display friendler API error messages
Fixes #85
2013-07-01 09:29:45 +02:00
Mitchell Hashimoto 666a8e11cb Update README 2013-06-30 21:03:24 -07:00
Mitchell Hashimoto 05eab61df4 Merge pull request #86 from markjaquith/requirements
Note that Mercurial (hg) is a dependency for developing on Packer
2013-06-30 21:02:23 -07:00
Mark Jaquith 373f093a98 Note that Mercurial (hg) is a dependency for developing on Packer. 2013-06-30 22:46:22 -04:00
Mitchell Hashimoto b60d0ad834 Merge pull request #83 from priteau/master
website: FIx a typo
2013-06-30 15:03:02 -07:00
Pierre Riteau 9d8977fe54 Fix a small typo 2013-06-30 21:22:32 +01:00
Mitchell Hashimoto a7b684cc7e Merge pull request #75 from smerrill/upload-vmware-tools
builders/vmware: Upload vmware tools (only Linux for now)
2013-06-30 08:44:49 -07:00
Steven Merrill 8f72a06ef6 Incorporate code review feedback. 2013-06-30 11:10:26 -04:00
Mitchell Hashimoto 6878c75c84 Merge pull request #79 from qur/master
Fix building on Ubuntu
2013-06-30 07:57:22 -07:00
Julian Phillips d997224cc3 Fix building on Ubuntu
1) /bin/sh is not /bin/bash, so build.sh needs to explictly use /bin/bash
2) dash's echo does not support \x, but it does support \0 (since dash is
   /bin/sh, it gets used by Makefiles).
2013-06-30 13:15:19 +01:00
Steven Merrill eb905d30e3 Add a method to get the VMware Tools ISO path. 2013-06-29 23:20:38 -04:00
Steven Merrill 3cda96f9a6 Upload the linux.iso to /tmp/linux.iso. 2013-06-29 23:08:27 -04:00
Mitchell Hashimoto 177a374667 Merge pull request #74 from markjaquith/ignore-src-directory
.gitignore the /src directory
2013-06-29 19:19:26 -07:00
Mark Jaquith 8325fbb2f5 .gitignore the /src directory 2013-06-29 22:16:31 -04:00
Mitchell Hashimoto f155f40f2c Merge pull request #73 from jcrosby/master
website: Grammar tweak for veewee-to-packer docs
2013-06-29 18:06:32 -07:00
Jon Crosby 31106086fe Update veewee-to-packer docs 2013-06-29 16:43:36 -07:00
Mitchell Hashimoto ba483bce71 Update README to note GOPATH [GH-71] 2013-06-29 14:32:27 -07:00
Mitchell Hashimoto 0021f31931 Up version for development 2013-06-29 14:16:26 -07:00
Mitchell Hashimoto 552ee0b8ee website: switch all download URLs to https 2013-06-29 14:16:12 -07:00
Mitchell Hashimoto 00a16a7ce9 v0.1.2 2013-06-29 14:03:39 -07:00
Mitchell Hashimoto 377f7ba3cd packer: Template requires builders 2013-06-29 14:02:20 -07:00
Mitchell Hashimoto 57c53ea2d7 post-processor/vagrant: use proper provider type for VMware boxes 2013-06-29 13:52:39 -07:00
Mitchell Hashimoto 5ad40dd184 builder/virtualbox: add more logging to version detection 2013-06-29 13:45:30 -07:00
Mitchell Hashimoto a35cbfc4da packer/plugin: Assume any listen error is a port collision [GH-58] 2013-06-29 13:35:24 -07:00
Mitchell Hashimoto 86df78a7be builder/vmware: delete unnecessary files from artifact 2013-06-29 13:28:08 -07:00
Mitchell Hashimoto 958502bbdc builder/vmware: populate files properly in artifact [GH-63] 2013-06-29 13:00:34 -07:00
Mitchell Hashimoto 014ed0e507 post-processor/vagrant: rename OVF to box.ovf [GH-64] 2013-06-29 12:52:22 -07:00
Mitchell Hashimoto 45f6f6f297 Merge pull request #68 from antonlindstrom/master
website: Update HTML characters for the <enter> command in Virtualbox builder documentation
2013-06-29 12:49:27 -07:00
Anton Lindström 7c7a8a7f27 fix html characters in virtualbox builder doc
This commit removes <, > and instead uses <,> to describe the
enter command in the boot command code block in virtualbox
builder documentation. Noticed this after fixing the VMware doc.
2013-06-29 21:43:53 +02:00
Mitchell Hashimoto 1beec2b713 Merge pull request #67 from mitchellh/website-text-selection
website: change color of text on selection
2013-06-29 12:31:04 -07:00
Mitchell Hashimoto 42d0980228 Update CHANGELOG 2013-06-29 12:30:10 -07:00
Mitchell Hashimoto a45f843ccb Up version for dev 2013-06-29 12:26:15 -07:00
Jack Pearkes f3bcdbdf34 website: change color of text on selection 2013-06-29 21:25:28 +02:00
Mitchell Hashimoto bb1aae39b0 Merge pull request #62 from antonlindstrom/master
website: Update HTML characters for the <enter> command in VMware builder documentation
2013-06-29 12:23:40 -07:00
Mitchell Hashimoto 2e1032a36b Merge pull request #61 from gkarekinian/patch-1
website: Fix typo in the Veewee to Packer doc page
2013-06-29 12:22:56 -07:00
Mitchell Hashimoto f3ece7ceae Merge pull request #66 from sergiotapia/fix-typo-in-readme
Fix typo in README file.
2013-06-29 12:21:41 -07:00
Mitchell Hashimoto 0543d238dd Merge pull request #60 from mitchellh/amazon-request-limit
builder/amazonebs: sleep between checks for ami create
2013-06-29 12:19:31 -07:00
Sergio Tapia Gutierrez c420dc6e19 Add new paragraph to make readme easier to read. 2013-06-29 12:31:00 -04:00
Sergio Tapia Gutierrez 15206f53e8 Add a new paragraph to make it easier to read. 2013-06-29 12:29:17 -04:00
Sergio Tapia Gutierrez b4cca743cf Fix typo in README file. 2013-06-29 12:22:32 -04:00
Justin Campbell 4b2b23c32f Add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY 2013-06-29 09:32:07 -04:00
Anton Lindström c3bbb82c5c fix html characters in vmware builder doc
This commit removes <, > and instead uses &lt;,&gt; to describe the
enter command in the boot command code block in vmware builder
documentation.
2013-06-29 15:19:37 +02:00
Greg Karékinian 32f6dd17a3 Fix typo 2013-06-29 13:02:57 +02:00
Jack Pearkes c3363e48ed builder/amazonebs: sleep between checks for ami create
fixes #50
2013-06-29 12:35:48 +02:00
Justin Campbell 9c9c60aabf Default to AWS_ env vars for key and secret 2013-06-28 13:45:15 -04:00
93 changed files with 1842 additions and 448 deletions
+1
View File
@@ -1,5 +1,6 @@
/bin
/local
/pkg
/src
/website/.sass-cache
/website/build
+99
View File
@@ -1,3 +1,102 @@
## 0.1.5 (July 7, 2013)
FEATURES:
* "file" uploader will upload files from the machine running Packer to the
remote machine.
* VirtualBox guest additions URL and checksum can now be specified, allowing
the VirtualBox builder to have the ability to be used completely offline.
IMPROVEMENTS:
* core: If SCP is not available, a more descriptive error message
is shown telling the user. [GH-127]
* shell: Scripts are now executed by default according to their shebang,
not with `/bin/sh`. [GH-105]
* shell: You can specify what interpreter you want inline scripts to
run with `inline_shebang`.
* virtualbox: Delete the packer-made SSH port forwarding prior to
exporting the VM.
BUG FIXES:
* core: Non-200 response codes on downloads now show proper errors.
[GH-141]
* amazon-ebs: SSH handshake is retried. [GH-130]
* vagrant: The `BuildName` template propery works properly in
the output path.
* vagrant: Properly configure the provider-specific post-processors so
things like `vagrantfile_template` work. [GH-129]
* vagrant: Close filehandles when copying files so Windows can
rename files. [GH-100]
## 0.1.4 (July 2, 2013)
FEATURES:
* virtualbox: Can now be built headless with the "Headless" option. [GH-99]
* virtualbox: <wait5> and <wait10> codes for waiting 5 and 10 seconds
during the boot sequence, respectively. [GH-97]
* vmware: Can now be built headless with the "Headless" option. [GH-99]
* vmware: <wait5> and <wait10> codes for waiting 5 and 10 seconds
during the boot sequence, respectively. [GH-97]
* vmware: Disks are defragmented and compacted at the end of the build.
This can be disabled using "skip_compaction"
IMPROVEMENTS:
* core: Template syntax errors now show line and character number. [GH-56]
* amazon-ebs: Access key and secret access key default to
environmental variables. [GH-40]
* virtualbox: Send password for keyboard-interactive auth [GH-121]
* vmware: Send password for keyboard-interactive auth [GH-121]
BUG FIXES:
* vmware: Wait until shut down cleans up properly to avoid corrupt
disk files [GH-111]
## 0.1.3 (July 1, 2013)
FEATURES:
* The VMware builder can now upload the VMware tools for you into
the VM. This is opt-in, you must specify the `tools_upload_flavor`
option. See the website for more documentation.
IMPROVEMENTS:
* digitalocean: Errors contain human-friendly error messages. [GH-85]
BUG FIXES:
* core: More plugin server fixes that avoid hangs on OS X 10.7 [GH-87]
* vagrant: AWS boxes will keep the AMI artifact around [GH-55]
* virtualbox: More robust version parsing for uploading guest additions. [GH-69]
* virtualbox: Output dir and VM name defaults depend on build name,
avoiding collisions. [GH-91]
* vmware: Output dir and VM name defaults depend on build name,
avoiding collisions. [GH-91]
## 0.1.2 (June 29, 2013)
IMPROVEMENTS:
* core: Template doesn't validate if there are no builders.
* vmware: Delete any VMware files in the VM that aren't necessary for
it to function.
BUG FIXES:
* core: Plugin servers consider a port in use if there is any
error listening to it. This fixes I18n issues and Windows. [GH-58]
* amazon-ebs: Sleep between checking instance state to avoid
RequestLimitExceeded [GH-50]
* vagrant: Rename VirtualBox ovf to "box.ovf" [GH-64]
* vagrant: VMware boxes have the correct provider type.
* vmware: Properly populate files in artifact so that the Vagrant
post-processor works. [GH-63]
## 0.1.1 (June 28, 2013)
BUG FIXES:
+43
View File
@@ -0,0 +1,43 @@
# Contributing to Packer
**First:** if you're unsure or afraid of _anything_, just ask
or submit the issue or pull request anyways. You won't be yelled at for
giving your best effort. The worst that can happen is that you'll be
politely asked to change something. We appreciate any sort of contributions,
and don't want a wall of rules to get in the way of that.
However, for those individuals who want a bit more guidance on the
best way to contribute to the project, read on. This document will cover
what we're looking for. By addressing all the points we're looking for,
it raises the chances we can quickly merge or address your contributions.
## Issues
### Reporting an Issue
* Make sure you test against the latest released version. It is possible
we already fixed the bug you're experiencing.
* Provide a reproducible test case. If a contributor can't reproduce an
issue, then it dramatically lowers the chances it'll get fixed. And in
some cases, the issue will eventually be closed.
* Respond promptly to any questions made by the Packer team to your issue.
Stale issues will be closed.
### Issue Lifecycle
1. The issue is reported.
2. The issue is verified and categorized by a Packer collaborator.
Categorization is done via tags. For example, bugs are marked as "bugs"
and easy fixes are marked as "easy".
3. Unless it is critical, the issue is left for a period of time (sometimes
many weeks), giving outside contributors a chance to address the issue.
4. The issue is addressed in a pull request or commit. The issue will be
referenced in the commit message so that the code that fixes it is clearly
linked.
5. The issue is closed.
+5 -6
View File
@@ -1,7 +1,7 @@
NO_COLOR=\x1b[0m
OK_COLOR=\x1b[32;01m
ERROR_COLOR=\x1b[31;01m
WARN_COLOR=\x1b[33;01m
NO_COLOR=\033[0m
OK_COLOR=\033[32;01m
ERROR_COLOR=\033[31;01m
WARN_COLOR=\033[33;01m
all:
@mkdir -p bin/
@@ -15,8 +15,7 @@ format:
test:
@echo "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)"
@go list -f '{{range .TestImports}}{{.}}\
{{end}}' ./... | xargs -n1 go get -d
@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d
go test ./...
.PHONY: all format test
+15 -5
View File
@@ -11,7 +11,9 @@ Packer is lightweight, runs on every major operating system, and is highly
performant, creating machine images for multiple platforms in parallel.
Packer comes out of the box with support for creating AMIs (EC2), VMware
images, and VirtualBox images. Support for more platforms can be added via
plugins. The images that Packer creates an easily be turned into
plugins.
The images that Packer creates can easily be turned into
[Vagrant](http://www.vagrantup.com) boxes.
## Quick Start
@@ -68,8 +70,15 @@ http://www.packer.io/docs
## Developing Packer
If you wish to work on Packer itself, you'll first need [Go](http://golang.org)
installed (version 1.1+ is _required_). Next, clone this repository then just type `make`.
In a few moments, you'll have a working `packer` executable:
installed (version 1.1+ is _required_). Make sure you have Go properly installed,
including setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH).
For some additional dependencies, Go needs [Mercurial](http://mercurial.selenic.com/)
to be installed. Packer itself doesn't require this but a dependency of a
dependency does.
Next, clone this repository into `$GOPATH/src/github.com/mitchellh/packer` and
then just type `make`. In a few moments, you'll have a working `packer` executable:
```
$ make
@@ -78,5 +87,6 @@ $ bin/packer
...
```
You can run tests by typing `make test`. This will run tests for Packer core
along with all the core builders and commands and such that come with Packer.
You can run tests by typing `make test`.
This will run tests for Packer core along with all the core builders and commands and such that come with Packer.
+17
View File
@@ -15,6 +15,7 @@ import (
"github.com/mitchellh/packer/builder/common"
"github.com/mitchellh/packer/packer"
"log"
"os"
"text/template"
"time"
)
@@ -57,6 +58,22 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
}
if b.config.AccessKey == "" {
b.config.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
}
if b.config.AccessKey == "" {
b.config.AccessKey = os.Getenv("AWS_ACCESS_KEY")
}
if b.config.SecretKey == "" {
b.config.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
}
if b.config.SecretKey == "" {
b.config.SecretKey = os.Getenv("AWS_SECRET_KEY")
}
if b.config.SSHPort == 0 {
b.config.SSHPort = 22
}
+60
View File
@@ -2,9 +2,19 @@ package amazonebs
import (
"github.com/mitchellh/packer/packer"
"os"
"testing"
)
func init() {
// Clear out the AWS access key env vars so they don't
// affect our tests.
os.Setenv("AWS_ACCESS_KEY_ID", "")
os.Setenv("AWS_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
os.Setenv("AWS_SECRET_KEY", "")
}
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
@@ -59,6 +69,31 @@ func TestBuilderPrepare_AccessKey(t *testing.T) {
if err == nil {
t.Fatal("should have error")
}
// Test env
delete(config, "access_key")
os.Setenv("AWS_ACCESS_KEY_ID", "foo")
defer os.Setenv("AWS_ACCESS_KEY_ID", "")
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.AccessKey != "foo" {
t.Errorf("access key invalid: %s", b.config.AccessKey)
}
delete(config, "access_key")
os.Setenv("AWS_ACCESS_KEY", "foo")
defer os.Setenv("AWS_ACCESS_KEY", "")
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.AccessKey != "foo" {
t.Errorf("access key invalid: %s", b.config.AccessKey)
}
}
func TestBuilderPrepare_AMIName(t *testing.T) {
@@ -167,6 +202,31 @@ func TestBuilderPrepare_SecretKey(t *testing.T) {
if err == nil {
t.Fatal("should have error")
}
// Test env
delete(config, "secret_key")
os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
defer os.Setenv("AWS_SECRET_ACCESS_KEY", "")
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.SecretKey != "foo" {
t.Errorf("access key invalid: %s", b.config.SecretKey)
}
delete(config, "secret_key")
os.Setenv("AWS_SECRET_KEY", "foo")
defer os.Setenv("AWS_SECRET_KEY", "")
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.SecretKey != "foo" {
t.Errorf("access key invalid: %s", b.config.SecretKey)
}
}
func TestBuilderPrepare_SourceAmi(t *testing.T) {
+116 -90
View File
@@ -14,10 +14,64 @@ import (
)
type stepConnectSSH struct {
conn net.Conn
cancel bool
conn net.Conn
}
func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(config)
ui := state["ui"].(packer.Ui)
var comm packer.Communicator
var err error
waitDone := make(chan bool, 1)
go func() {
comm, err = s.waitForSSH(state)
waitDone <- true
}()
log.Printf("Waiting for SSH, up to timeout: %s", config.SSHTimeout.String())
timeout := time.After(config.SSHTimeout)
WaitLoop:
for {
// Wait for either SSH to become available, a timeout to occur,
// or an interrupt to come through.
select {
case <-waitDone:
if err != nil {
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
return multistep.ActionHalt
}
state["communicator"] = comm
break WaitLoop
case <-timeout:
ui.Error("Timeout waiting for SSH.")
s.cancel = true
return multistep.ActionHalt
case <-time.After(1 * time.Second):
if _, ok := state[multistep.StateCancelled]; ok {
log.Println("Interrupt detected, quitting waiting for SSH.")
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *stepConnectSSH) Cleanup(map[string]interface{}) {
if s.conn != nil {
s.conn.Close()
s.conn = nil
}
}
// This blocks until SSH becomes available, and sends the communicator
// on the given channel.
func (s *stepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
config := state["config"].(config)
instance := state["instance"].(*ec2.Instance)
privateKey := state["privateKey"].(string)
@@ -28,98 +82,70 @@ func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction
keyring := &ssh.SimpleKeychain{}
err := keyring.AddPEMKey(privateKey)
if err != nil {
err := fmt.Errorf("Error setting up SSH config: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Build the actual SSH client configuration
sshConfig := &gossh.ClientConfig{
User: config.SSHUsername,
Auth: []gossh.ClientAuth{
gossh.ClientAuthKeyring(keyring),
},
}
// Start trying to connect to SSH
connected := make(chan bool, 1)
connectQuit := make(chan bool, 1)
defer func() {
connectQuit <- true
}()
go func() {
var err error
ui.Say("Connecting to the instance via SSH...")
attempts := 0
for {
select {
case <-connectQuit:
return
default:
}
attempts += 1
log.Printf(
"Opening TCP conn for SSH to %s:%d (attempt %d)",
instance.DNSName, config.SSHPort, attempts)
s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort))
if err == nil {
break
}
// A brief sleep so we're not being overly zealous attempting
// to connect to the instance.
time.Sleep(500 * time.Millisecond)
}
connected <- true
}()
log.Printf("Waiting up to %s for SSH connection", config.SSHTimeout)
timeout := time.After(config.SSHTimeout)
ConnectWaitLoop:
for {
select {
case <-connected:
// We connected. Just break the loop.
break ConnectWaitLoop
case <-timeout:
err := errors.New("Timeout waiting for SSH to become available.")
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
case <-time.After(1 * time.Second):
if _, ok := state[multistep.StateCancelled]; ok {
log.Println("Interrupt detected, quitting waiting for SSH.")
return multistep.ActionHalt
}
}
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
ui.Say("Waiting for SSH to become available...")
var comm packer.Communicator
if err == nil {
comm, err = ssh.New(s.conn, sshConfig)
var nc net.Conn
for {
if nc != nil {
nc.Close()
}
time.Sleep(5 * time.Second)
if s.cancel {
log.Println("SSH wait cancelled. Exiting loop.")
return nil, errors.New("SSH wait cancelled")
}
// Attempt to connect to SSH port
log.Printf(
"Opening TCP conn for SSH to %s:%d",
instance.DNSName, config.SSHPort)
nc, err := net.Dial("tcp",
fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort))
if err != nil {
log.Printf("TCP connection to SSH ip/port failed: %s", err)
continue
}
// Build the actual SSH client configuration
sshConfig := &gossh.ClientConfig{
User: config.SSHUsername,
Auth: []gossh.ClientAuth{
gossh.ClientAuthKeyring(keyring),
},
}
sshConnectSuccess := make(chan bool, 1)
go func() {
comm, err = ssh.New(nc, sshConfig)
if err != nil {
log.Printf("SSH connection fail: %s", err)
sshConnectSuccess <- false
return
}
sshConnectSuccess <- true
}()
select {
case success := <-sshConnectSuccess:
if !success {
continue
}
case <-time.After(5 * time.Second):
log.Printf("SSH handshake timeout. Trying again.")
continue
}
ui.Say("Connected via SSH!")
break
}
if err != nil {
err := fmt.Errorf("Error connecting to SSH: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the communicator on the state bag so it can be used later
state["communicator"] = comm
return multistep.ActionContinue
}
func (s *stepConnectSSH) Cleanup(map[string]interface{}) {
if s.conn != nil {
s.conn.Close()
}
// Store the connection so we can close it later
s.conn = nc
return comm, nil
}
+6
View File
@@ -6,6 +6,7 @@ import (
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"strconv"
"text/template"
"time"
@@ -68,6 +69,11 @@ func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
if imageResp.Images[0].State == "available" {
break
}
log.Printf("Image in state %s, sleeping 2s before checking again",
imageResp.Images[0].State)
time.Sleep(2 * time.Second)
}
return multistep.ActionContinue
+14
View File
@@ -107,6 +107,9 @@ func (d *DownloadClient) Get() (string, error) {
log.Printf("Downloading: %s", url.String())
err = d.downloader.Download(f, url)
if err != nil {
return "", err
}
}
if d.config.Hash != nil {
@@ -160,11 +163,22 @@ func (*HTTPDownloader) Cancel() {
}
func (d *HTTPDownloader) Download(dst io.Writer, src *url.URL) error {
log.Printf("Starting download: %s", src.String())
resp, err := http.Get(src.String())
if err != nil {
return err
}
if resp.StatusCode != 200 {
log.Printf(
"Non-200 status code: %d. Getting error body.", resp.StatusCode)
errorBody := new(bytes.Buffer)
io.Copy(errorBody, resp.Body)
return fmt.Errorf("HTTP error '%d'! Remote side responded:\n%s",
resp.StatusCode, errorBody.String())
}
d.progress = 0
d.total = uint(resp.ContentLength)
+7 -10
View File
@@ -189,28 +189,25 @@ func NewRequest(d DigitalOceanClient, path string, params string) (map[string]in
return decodedResponse, err
}
log.Printf("response from digitalocean: %s", body)
err = json.Unmarshal(body, &decodedResponse)
log.Printf("response from digitalocean: %v", decodedResponse)
// Catch all non-200 status and return an error
if resp.StatusCode != 200 {
err = errors.New(fmt.Sprintf("Received non-200 HTTP status from DigitalOcean: %v", resp.StatusCode))
return decodedResponse, err
}
// Check for bad JSON
if err != nil {
err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
resp.StatusCode, body))
return decodedResponse, err
}
// Catch all non-OK statuses from DO and return an error
// Check for errors sent by digitalocean
status := decodedResponse["status"]
if status != "OK" {
// Get the actual error message if there is one
if status == "ERROR" {
status = decodedResponse["error_message"]
}
err = errors.New(fmt.Sprintf("Received bad status from DigitalOcean: %v", status))
err = errors.New(fmt.Sprintf("Received bad response (HTTP %v) from DigitalOcean: %s", resp.StatusCode, status))
return decodedResponse, err
}
+70 -25
View File
@@ -25,30 +25,34 @@ type Builder struct {
}
type config struct {
BootCommand []string `mapstructure:"boot_command"`
BootWait time.Duration ``
DiskSize uint `mapstructure:"disk_size"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestOSType string `mapstructure:"guest_os_type"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
ISOMD5 string `mapstructure:"iso_md5"`
ISOUrl string `mapstructure:"iso_url"`
OutputDir string `mapstructure:"output_directory"`
ShutdownCommand string `mapstructure:"shutdown_command"`
ShutdownTimeout time.Duration ``
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHUser string `mapstructure:"ssh_username"`
SSHWaitTimeout time.Duration ``
VBoxVersionFile string `mapstructure:"virtualbox_version_file"`
VBoxManage [][]string `mapstructure:"vboxmanage"`
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
BootWait time.Duration ``
DiskSize uint `mapstructure:"disk_size"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
GuestOSType string `mapstructure:"guest_os_type"`
Headless bool `mapstructure:"headless"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
ISOMD5 string `mapstructure:"iso_md5"`
ISOUrl string `mapstructure:"iso_url"`
OutputDir string `mapstructure:"output_directory"`
ShutdownCommand string `mapstructure:"shutdown_command"`
ShutdownTimeout time.Duration ``
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHUser string `mapstructure:"ssh_username"`
SSHWaitTimeout time.Duration ``
VBoxVersionFile string `mapstructure:"virtualbox_version_file"`
VBoxManage [][]string `mapstructure:"vboxmanage"`
VMName string `mapstructure:"vm_name"`
PackerDebug bool `mapstructure:"packer_debug"`
PackerBuildName string `mapstructure:"packer_build_name"`
PackerDebug bool `mapstructure:"packer_debug"`
RawBootWait string `mapstructure:"boot_wait"`
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
@@ -86,7 +90,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
if b.config.OutputDir == "" {
b.config.OutputDir = "virtualbox"
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
}
if b.config.RawBootWait == "" {
@@ -114,7 +118,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
if b.config.VMName == "" {
b.config.VMName = "packer"
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
errs := make([]error, 0)
@@ -168,6 +172,47 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
}
if b.config.GuestAdditionsSHA256 != "" {
b.config.GuestAdditionsSHA256 = strings.ToLower(b.config.GuestAdditionsSHA256)
}
if b.config.GuestAdditionsURL != "" {
url, err := url.Parse(b.config.GuestAdditionsURL)
if err != nil {
errs = append(errs, fmt.Errorf("guest_additions_url is not a valid URL: %s", err))
} else {
if url.Scheme == "" {
url.Scheme = "file"
}
if url.Scheme == "file" {
if _, err := os.Stat(url.Path); err != nil {
errs = append(errs, fmt.Errorf("guest_additions_url points to bad file: %s", err))
}
} else {
supportedSchemes := []string{"file", "http", "https"}
scheme := strings.ToLower(url.Scheme)
found := false
for _, supported := range supportedSchemes {
if scheme == supported {
found = true
break
}
}
if !found {
errs = append(errs, fmt.Errorf("Unsupported URL scheme in guest_additions_url: %s", scheme))
}
}
}
if len(errs) == 0 {
// Put the URL back together since we may have modified it
b.config.GuestAdditionsURL = url.String()
}
}
if _, err := os.Stat(b.config.OutputDir); err == nil {
errs = append(errs, errors.New("Output directory already exists. It must not exist."))
}
+80 -3
View File
@@ -13,6 +13,8 @@ func testConfig() map[string]interface{} {
"iso_md5": "foo",
"iso_url": "http://www.google.com/",
"ssh_username": "foo",
packer.BuildNameConfigKey: "foo",
}
}
@@ -36,7 +38,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
}
if b.config.OutputDir != "virtualbox" {
if b.config.OutputDir != "output-foo" {
t.Errorf("bad output dir: %s", b.config.OutputDir)
}
@@ -52,7 +54,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Errorf("bad ssh port: %d", b.config.SSHPort)
}
if b.config.VMName != "packer" {
if b.config.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", b.config.VMName)
}
}
@@ -117,7 +119,7 @@ func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "disk_size")
delete(config, "guest_additions_path")
err := b.Prepare(config)
if err != nil {
t.Fatalf("bad err: %s", err)
@@ -139,6 +141,81 @@ func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) {
}
}
func TestBuilderPrepare_GuestAdditionsSHA256(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "guest_additions_sha256")
err := b.Prepare(config)
if err != nil {
t.Fatalf("bad err: %s", err)
}
if b.config.GuestAdditionsSHA256 != "" {
t.Fatalf("bad: %s", b.config.GuestAdditionsSHA256)
}
config["guest_additions_sha256"] = "FOO"
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.GuestAdditionsSHA256 != "foo" {
t.Fatalf("bad size: %s", b.config.GuestAdditionsSHA256)
}
}
func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) {
var b Builder
config := testConfig()
config["guest_additions_url"] = ""
err := b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.GuestAdditionsURL != "" {
t.Fatalf("should be empty: %s", b.config.GuestAdditionsURL)
}
config["guest_additions_url"] = "i/am/a/file/that/doesnt/exist"
err = b.Prepare(config)
if err == nil {
t.Error("should have error")
}
config["guest_additions_url"] = "file:i/am/a/file/that/doesnt/exist"
err = b.Prepare(config)
if err == nil {
t.Error("should have error")
}
config["guest_additions_url"] = "http://www.packer.io"
err = b.Prepare(config)
if err != nil {
t.Errorf("should not have error: %s", err)
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf.Name())
config["guest_additions_url"] = tf.Name()
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.GuestAdditionsURL != "file://"+tf.Name() {
t.Fatalf("guest_additions_url should be modified: %s", b.config.GuestAdditionsURL)
}
}
func TestBuilderPrepare_HTTPPort(t *testing.T) {
var b Builder
config := testConfig()
+5 -2
View File
@@ -124,11 +124,14 @@ func (d *VBox42Driver) Version() (string, error) {
return "", err
}
versionOutput := strings.TrimSpace(stdout.String())
log.Printf("VBoxManage --version output: %s", versionOutput)
versionRe := regexp.MustCompile("[^.0-9]")
matches := versionRe.Split(stdout.String(), 2)
matches := versionRe.Split(versionOutput, 2)
if len(matches) == 0 {
return "", fmt.Errorf("No version found: %s", stdout.String())
return "", fmt.Errorf("No version found: %s", versionOutput)
}
log.Printf("VirtualBox version: %s", matches[0])
return matches[0], nil
}
+4 -1
View File
@@ -22,7 +22,10 @@ func (s *stepCreateVM) Run(state map[string]interface{}) multistep.StepAction {
name := config.VMName
commands := make([][]string, 4)
commands[0] = []string{"createvm", "--name", name, "--ostype", config.GuestOSType, "--register"}
commands[0] = []string{
"createvm", "--name", name,
"--ostype", config.GuestOSType, "--register",
}
commands[1] = []string{
"modifyvm", name,
"--boot1", "disk", "--boot2", "dvd", "--boot3", "none", "--boot4", "none",
@@ -33,7 +33,9 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
cache := state["cache"].(packer.Cache)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
config := state["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)
@@ -45,68 +47,18 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
version = newVersion
}
// First things first, we get the list of checksums for the files available
// for this version.
checksumsUrl := fmt.Sprintf("http://download.virtualbox.org/virtualbox/%s/SHA256SUMS", version)
checksumsFile, err := ioutil.TempFile("", "packer")
if err != nil {
state["error"] = fmt.Errorf(
"Failed creating temporary file to store guest addition checksums: %s",
err)
return multistep.ActionHalt
}
checksumsFile.Close()
defer os.Remove(checksumsFile.Name())
downloadConfig := &common.DownloadConfig{
Url: checksumsUrl,
TargetPath: checksumsFile.Name(),
Hash: nil,
}
log.Printf("Downloading guest addition checksums: %s", checksumsUrl)
download := common.NewDownloadClient(downloadConfig)
checksumsPath, action := s.progressDownload(download, state)
if action != multistep.ActionContinue {
return action
}
additionsName := fmt.Sprintf("VBoxGuestAdditions_%s.iso", version)
// 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(checksumsPath)
if err != nil {
state["error"] = fmt.Errorf("Error opening guest addition checksums: %s", err)
return multistep.ActionHalt
}
defer checksumsF.Close()
// Use provided version or get it from virtualbox.org
var checksum string
// We copy the contents of the file into memory. In general this file
// is quite small so that is okay. In the future, we probably want to
// use bufio and iterate line by line.
var contents bytes.Buffer
io.Copy(&contents, checksumsF)
checksum := ""
for _, line := range strings.Split(contents.String(), "\n") {
parts := strings.Fields(line)
log.Printf("Checksum file parts: %#v", parts)
if len(parts) != 2 {
// Bogus line
continue
if config.GuestAdditionsSHA256 != "" {
checksum = config.GuestAdditionsSHA256
} else {
checksum, action = s.downloadAdditionsSHA256(state, version, additionsName)
if action != multistep.ActionContinue {
return action
}
if strings.HasSuffix(parts[1], additionsName) {
checksum = parts[0]
log.Printf("Guest additions checksum: %s", checksum)
break
}
}
if checksum == "" {
state["error"] = fmt.Errorf("The checksum for the file '%s' could not be found.", additionsName)
return multistep.ActionHalt
}
checksumBytes, err := hex.DecodeString(checksum)
@@ -115,23 +67,29 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
return multistep.ActionHalt
}
url := fmt.Sprintf(
"http://download.virtualbox.org/virtualbox/%s/%s",
version, additionsName)
// Use the provided source (URL or file path) or generate it
url := config.GuestAdditionsURL
if url == "" {
url = fmt.Sprintf(
"http://download.virtualbox.org/virtualbox/%s/%s",
version,
additionsName)
}
log.Printf("Guest additions URL: %s", url)
log.Printf("Acquiring lock to download the guest additions ISO.")
cachePath := cache.Lock(url)
defer cache.Unlock(url)
downloadConfig = &common.DownloadConfig{
downloadConfig := &common.DownloadConfig{
Url: url,
TargetPath: cachePath,
Hash: sha256.New(),
Checksum: checksumBytes,
}
download = common.NewDownloadClient(downloadConfig)
download := common.NewDownloadClient(downloadConfig)
ui.Say("Downloading VirtualBox guest additions. Progress will be shown periodically.")
state["guest_additions_path"], action = s.progressDownload(download, state)
return action
@@ -179,3 +137,72 @@ DownloadWaitLoop:
return result, multistep.ActionContinue
}
func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state map[string]interface{}, 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("http://download.virtualbox.org/virtualbox/%s/SHA256SUMS", additionsVersion)
checksumsFile, err := ioutil.TempFile("", "packer")
if err != nil {
state["error"] = fmt.Errorf(
"Failed creating temporary file to store guest addition checksums: %s",
err)
return "", multistep.ActionHalt
}
defer os.Remove(checksumsFile.Name())
checksumsFile.Close()
downloadConfig := &common.DownloadConfig{
Url: checksumsUrl,
TargetPath: checksumsFile.Name(),
Hash: nil,
}
log.Printf("Downloading guest addition checksums: %s", checksumsUrl)
download := common.NewDownloadClient(downloadConfig)
checksumsPath, action := s.progressDownload(download, state)
if action != multistep.ActionContinue {
return "", action
}
// 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(checksumsPath)
if err != nil {
state["error"] = fmt.Errorf("Error opening guest addition checksums: %s", err)
return "", multistep.ActionHalt
}
defer checksumsF.Close()
// We copy the contents of the file into memory. In general this file
// is quite small so that is okay. In the future, we probably want to
// use bufio and iterate line by line.
var contents bytes.Buffer
io.Copy(&contents, checksumsF)
checksum := ""
for _, line := range strings.Split(contents.String(), "\n") {
parts := strings.Fields(line)
log.Printf("Checksum file parts: %#v", parts)
if len(parts) != 2 {
// Bogus line
continue
}
if strings.HasSuffix(parts[1], additionsName) {
checksum = parts[0]
log.Printf("Guest additions checksum: %s", checksum)
break
}
}
if checksum == "" {
state["error"] = fmt.Errorf("The checksum for the file '%s' could not be found.", additionsName)
return "", multistep.ActionHalt
}
return checksum, multistep.ActionContinue
}
+13 -3
View File
@@ -7,8 +7,7 @@ import (
"path/filepath"
)
// This step creates the virtual disk that will be used as the
// hard drive for the virtual machine.
// This step cleans up forwarded ports and exports the VM to an OVF.
//
// Uses:
//
@@ -22,9 +21,20 @@ func (s *stepExport) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
// Clear out the Packer-created forwarding rule
ui.Say(fmt.Sprintf("Deleting forwarded port mapping for SSH (host port %d)", state["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
ui.Error(err.Error())
return multistep.ActionHalt
}
// Export the VM to an OVF
outputPath := filepath.Join(config.OutputDir, "packer.ovf")
command := []string{
command = []string{
"export",
vmName,
"--output",
+1 -1
View File
@@ -36,7 +36,7 @@ func (s *stepForwardSSH) Run(state map[string]interface{}) multistep.StepAction
}
}
// Attach the disk to the controller
// Create a forwarded port mapping to the VM
ui.Say(fmt.Sprintf("Creating forwarded port mapping for SSH (host port %d)", sshHostPort))
command := []string{
"modifyvm", vmName,
+8 -1
View File
@@ -23,7 +23,14 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
vmName := state["vmName"].(string)
ui.Say("Starting the virtual machine...")
command := []string{"startvm", vmName, "--type", "gui"}
guiArgument := "gui"
if config.Headless == true {
ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" +
"In headless mode, errors during the boot sequence or OS setup\n" +
"won't be easily visible. Use at your own discretion.")
guiArgument = "headless"
}
command := []string{"startvm", vmName, "--type", guiArgument}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error starting VM: %s", err)
state["error"] = err
+23 -1
View File
@@ -59,6 +59,16 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
continue
}
if code == "wait5" {
time.Sleep(5 * time.Second)
continue
}
if code == "wait10" {
time.Sleep(10 * time.Second)
continue
}
// Since typing is sometimes so slow, we check for an interrupt
// in between each character.
if _, ok := state[multistep.StateCancelled]; ok {
@@ -116,11 +126,23 @@ func scancodes(message string) []string {
var scancode []string
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep at this point.")
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = []string{"wait"}
message = message[len("<wait>"):]
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
scancode = []string{"wait5"}
message = message[len("<wait5>"):]
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
scancode = []string{"wait10"}
message = message[len("<wait10>"):]
}
if scancode == nil {
for specialCode, specialValue := range special {
if strings.HasPrefix(message, specialCode) {
+2
View File
@@ -112,6 +112,8 @@ func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Commun
User: config.SSHUser,
Auth: []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
},
}
+51 -26
View File
@@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
@@ -25,29 +26,34 @@ type Builder struct {
}
type config struct {
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
GuestOSType string `mapstructure:"guest_os_type"`
ISOMD5 string `mapstructure:"iso_md5"`
ISOUrl string `mapstructure:"iso_url"`
VMName string `mapstructure:"vm_name"`
OutputDir string `mapstructure:"output_directory"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
BootCommand []string `mapstructure:"boot_command"`
BootWait time.Duration ``
ShutdownCommand string `mapstructure:"shutdown_command"`
ShutdownTimeout time.Duration ``
SSHUser string `mapstructure:"ssh_username"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHWaitTimeout time.Duration ``
VMXData map[string]string `mapstructure:"vmx_data"`
VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"`
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
GuestOSType string `mapstructure:"guest_os_type"`
ISOMD5 string `mapstructure:"iso_md5"`
ISOUrl string `mapstructure:"iso_url"`
VMName string `mapstructure:"vm_name"`
OutputDir string `mapstructure:"output_directory"`
Headless bool `mapstructure:"headless"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
BootCommand []string `mapstructure:"boot_command"`
BootWait time.Duration ``
SkipCompaction bool `mapstructure:"skip_compaction"`
ShutdownCommand string `mapstructure:"shutdown_command"`
ShutdownTimeout time.Duration ``
SSHUser string `mapstructure:"ssh_username"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHWaitTimeout time.Duration ``
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
ToolsUploadPath string `mapstructure:"tools_upload_path"`
VMXData map[string]string `mapstructure:"vmx_data"`
VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"`
PackerDebug bool `mapstructure:"packer_debug"`
PackerBuildName string `mapstructure:"packer_build_name"`
PackerDebug bool `mapstructure:"packer_debug"`
RawBootWait string `mapstructure:"boot_wait"`
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
@@ -75,7 +81,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
if b.config.VMName == "" {
b.config.VMName = "packer"
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
if b.config.HTTPPortMin == 0 {
@@ -99,13 +105,17 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
if b.config.OutputDir == "" {
b.config.OutputDir = "vmware"
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
}
if b.config.SSHPort == 0 {
b.config.SSHPort = 22
}
if b.config.ToolsUploadPath == "" {
b.config.ToolsUploadPath = "{{ .Flavor }}.iso"
}
// Accumulate any errors
var err error
errs := make([]error, 0)
@@ -192,6 +202,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
}
if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil {
errs = append(errs, fmt.Errorf("tools_upload_path invalid: %s", err))
}
if b.config.VNCPortMin > b.config.VNCPortMax {
errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
}
@@ -213,6 +227,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
rand.Seed(time.Now().UTC().UnixNano())
steps := []multistep.Step{
&stepPrepareTools{},
&stepDownloadISO{},
&stepPrepareOutputDir{},
&stepCreateDisk{},
@@ -222,8 +237,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&stepRun{},
&stepTypeBootCommand{},
&stepWaitForSSH{},
&stepUploadTools{},
&stepProvision{},
&stepShutdown{},
&stepCleanFiles{},
&stepCompactDisk{},
}
// Setup the state bag
@@ -263,8 +281,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Compile the artifact list
files := make([]string, 0, 10)
visit := func(path string, info os.FileInfo, err error) error {
files = append(files, path)
return err
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
}
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
+34 -2
View File
@@ -13,6 +13,8 @@ func testConfig() map[string]interface{} {
"iso_md5": "foo",
"iso_url": "http://www.packer.io",
"ssh_username": "foo",
packer.BuildNameConfigKey: "foo",
}
}
@@ -66,7 +68,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Errorf("bad disk name: %s", b.config.DiskName)
}
if b.config.OutputDir != "vmware" {
if b.config.OutputDir != "output-foo" {
t.Errorf("bad output dir: %s", b.config.OutputDir)
}
@@ -74,7 +76,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout)
}
if b.config.VMName != "packer" {
if b.config.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", b.config.VMName)
}
}
@@ -309,6 +311,36 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
}
}
func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
var b Builder
config := testConfig()
// Test a default
delete(config, "tools_upload_path")
err := b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.ToolsUploadPath == "" {
t.Fatalf("bad value: %s", b.config.ToolsUploadPath)
}
// Test with a bad value
config["tools_upload_path"] = "{{{nope}"
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["tools_upload_path"] = "hey"
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_VNCPort(t *testing.T) {
var b Builder
config := testConfig()
+32 -3
View File
@@ -12,6 +12,9 @@ import (
// A driver is able to talk to VMware, control virtual machines, etc.
type Driver interface {
// CompactDisk compacts a virtual disk.
CompactDisk(string) error
// CreateDisk creates a virtual disk with the given size.
CreateDisk(string, string) error
@@ -19,11 +22,14 @@ type Driver interface {
IsRunning(string) (bool, error)
// Start starts a VM specified by the path to the VMX given.
Start(string) error
Start(string, bool) error
// Stop stops a VM specified by the path to the VMX given.
Stop(string) error
// Get the path to the VMware ISO for the given flavor.
ToolsIsoPath(string) string
// Verify checks to make sure that this driver should function
// properly. This should check that all the files it will use
// appear to exist and so on. If everything is okay, this doesn't
@@ -37,6 +43,20 @@ type Fusion5Driver struct {
AppPath string
}
func (d *Fusion5Driver) CompactDisk(diskPath string) error {
defragCmd := exec.Command(d.vdiskManagerPath(), "-d", diskPath)
if _, _, err := d.runAndLog(defragCmd); err != nil {
return err
}
shrinkCmd := exec.Command(d.vdiskManagerPath(), "-k", diskPath)
if _, _, err := d.runAndLog(shrinkCmd); err != nil {
return err
}
return nil
}
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
if _, _, err := d.runAndLog(cmd); err != nil {
@@ -67,8 +87,13 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
return false, nil
}
func (d *Fusion5Driver) Start(vmxPath string) error {
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, "gui")
func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
guiArgument := "gui"
if headless == true {
guiArgument = "nogui"
}
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, guiArgument)
if _, _, err := d.runAndLog(cmd); err != nil {
return err
}
@@ -121,6 +146,10 @@ func (d *Fusion5Driver) vmrunPath() string {
return filepath.Join(d.AppPath, "Contents", "Library", "vmrun")
}
func (d *Fusion5Driver) ToolsIsoPath(k string) string {
return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", k+".iso")
}
func (d *Fusion5Driver) runAndLog(cmd *exec.Cmd) (string, string, error) {
var stdout, stderr bytes.Buffer
+65
View File
@@ -0,0 +1,65 @@
package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"path/filepath"
)
// These are the extensions of files that are important for the function
// of a VMware virtual machine. Any other file is discarded as part of the
// build.
var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
// This step removes unnecessary files from the final result.
//
// Uses:
// config *config
// ui packer.Ui
//
// Produces:
// <nothing>
type stepCleanFiles struct{}
func (stepCleanFiles) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting unnecessary VMware files...")
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
// If the file isn't critical to the function of the
// virtual machine, we get rid of it.
keep := false
ext := filepath.Ext(path)
for _, goodExt := range KeepFileExtensions {
if goodExt == ext {
keep = true
break
}
}
if !keep {
ui.Message(fmt.Sprintf("Deleting: %s", path))
return os.Remove(path)
}
}
return nil
}
if err := filepath.Walk(config.OutputDir, visit); err != nil {
state["error"] = err
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCleanFiles) Cleanup(map[string]interface{}) {}
+43
View File
@@ -0,0 +1,43 @@
package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
// This step compacts the virtual disk for the VM unless the "skip_compaction"
// boolean is true.
//
// Uses:
// config *config
// driver Driver
// full_disk_path string
// ui packer.Ui
//
// Produces:
// <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)
if config.SkipCompaction == true {
log.Println("Skipping disk compaction step...")
return multistep.ActionContinue
}
ui.Say("Compacting the disk image")
if err := driver.CompactDisk(full_disk_path); err != nil {
state["error"] = fmt.Errorf("Error compacting disk: %s", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCompactDisk) Cleanup(map[string]interface{}) {}
+5 -3
View File
@@ -15,7 +15,7 @@ import (
// ui packer.Ui
//
// Produces:
// <nothing>
// full_disk_path (string) - The full path to the created disk.
type stepCreateDisk struct{}
func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
@@ -24,14 +24,16 @@ func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
ui.Say("Creating virtual machine disk")
output := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
if err := driver.CreateDisk(output, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
err := fmt.Errorf("Error creating disk: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state["full_disk_path"] = full_disk_path
return multistep.ActionContinue
}
+33
View File
@@ -0,0 +1,33 @@
package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"os"
)
type stepPrepareTools struct{}
func (*stepPrepareTools) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
if config.ToolsUploadFlavor == "" {
return multistep.ActionContinue
}
path := driver.ToolsIsoPath(config.ToolsUploadFlavor)
if _, err := os.Stat(path); err != nil {
state["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)
return multistep.ActionHalt
}
state["tools_upload_source"] = path
return multistep.ActionContinue
}
func (*stepPrepareTools) Cleanup(map[string]interface{}) {}
+9 -1
View File
@@ -27,13 +27,21 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmxPath := state["vmx_path"].(string)
vncPort := state["vnc_port"].(uint)
// Set the VMX path so that we know we started the machine
s.bootTime = time.Now()
s.vmxPath = vmxPath
ui.Say("Starting virtual machine...")
if err := driver.Start(vmxPath); err != nil {
if config.Headless {
ui.Message(fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC without a password to\n"+
"127.0.0.1:%d", vncPort))
}
if err := driver.Start(vmxPath, config.Headless); err != nil {
err := fmt.Errorf("Error starting VM: %s", err)
state["error"] = err
ui.Error(err.Error())
+30
View File
@@ -6,6 +6,8 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"path/filepath"
"strings"
"time"
)
@@ -72,6 +74,34 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
}
}
ui.Message("Waiting for VMware to clean up after itself...")
lockPattern := filepath.Join(config.OutputDir, "*.lck")
timer := time.After(15 * time.Second)
LockWaitLoop:
for {
locks, err := filepath.Glob(lockPattern)
if err == nil {
if len(locks) == 0 {
log.Println("No more lock files found. VMware is clean.")
break
}
if len(locks) == 1 && strings.HasSuffix(locks[0], ".vmx.lck") {
log.Println("Only waiting on VMX lock. VMware is clean.")
break
}
log.Printf("Waiting on lock files: %#v", locks)
}
select {
case <-timer:
log.Println("Reached timeout on waiting for clean VMware. Assuming clean.")
break LockWaitLoop
case <-time.After(1 * time.Second):
}
}
log.Println("VM shut down.")
return multistep.ActionContinue
}
+14
View File
@@ -114,6 +114,20 @@ func vncSendString(c *vnc.ClientConn, original string) {
continue
}
if strings.HasPrefix(original, "<wait5>") {
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
time.Sleep(5 * time.Second)
original = original[len("<wait5>"):]
continue
}
if strings.HasPrefix(original, "<wait10>") {
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
time.Sleep(10 * time.Second)
original = original[len("<wait10>"):]
continue
}
for specialCode, specialValue := range special {
if strings.HasPrefix(original, specialCode) {
log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue)
+49
View File
@@ -0,0 +1,49 @@
package vmware
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"text/template"
)
type toolsUploadPathTemplate struct {
Flavor string
}
type stepUploadTools struct{}
func (*stepUploadTools) Run(state map[string]interface{}) multistep.StepAction {
config := state["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)
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)
return multistep.ActionHalt
}
defer f.Close()
tplData := &toolsUploadPathTemplate{Flavor: config.ToolsUploadFlavor}
var processedPath bytes.Buffer
t := template.Must(template.New("path").Parse(config.ToolsUploadPath))
t.Execute(&processedPath, tplData)
if err := comm.Upload(processedPath.String(), f); err != nil {
state["error"] = fmt.Errorf("Error uploading VMware Tools: %s", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (*stepUploadTools) Cleanup(map[string]interface{}) {}
+2
View File
@@ -157,6 +157,8 @@ func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Commun
User: config.SSHUser,
Auth: []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
},
}
+1 -1
View File
@@ -1,7 +1,7 @@
package build
const helpText = `
Usage: packer build TEMPLATE
Usage: packer build [options] TEMPLATE
Will execute multiple builds in parallel as defined in the template.
The various artifacts created by the template will be outputted.
+1 -1
View File
@@ -1,7 +1,7 @@
package validate
const helpString = `
Usage: packer validate TEMPLATE
Usage: packer validate [options] TEMPLATE
Checks the template is valid by parsing the template and also
checking the configuration with the various builders, provisioners, etc.
+9
View File
@@ -3,6 +3,7 @@ package ssh
import (
"bytes"
"code.google.com/p/go.crypto/ssh"
"errors"
"fmt"
"github.com/mitchellh/packer/packer"
"io"
@@ -145,6 +146,14 @@ func (c *comm) Upload(path string, input io.Reader) error {
// Otherwise, we have an ExitErorr, meaning we can just read
// the exit status
log.Printf("non-zero exit status: %d", exitErr.ExitStatus())
// If we exited with status 127, it means SCP isn't available.
// Return a more descriptive error for that.
if exitErr.ExitStatus() == 127 {
return errors.New(
"SCP failed to start. This usually means that SCP is not\n" +
"properly installed on the remote system.")
}
}
return err
+23
View File
@@ -1,5 +1,7 @@
package ssh
import "log"
// An implementation of ssh.ClientPassword so that you can use a static
// string password for the password to ClientAuthPassword.
type Password string
@@ -7,3 +9,24 @@ type Password string
func (p Password) Password(user string) (string, error) {
return string(p), nil
}
// An implementation of ssh.ClientKeyboardInteractive that simply sends
// back the password for all questions. The questions are logged.
type PasswordKeyboardInteractive string
func (p PasswordKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) ([]string, error) {
log.Printf("Keyboard interactive challenge: ")
log.Printf("-- User: %s", user)
log.Printf("-- Instructions: %s", instruction)
for i, question := range questions {
log.Printf("-- Question %d: %s", i+1, question)
}
// Just send the password back for all questions
answers := make([]string, len(questions))
for i, _ := range answers {
answers[i] = string(p)
}
return answers, nil
}
+21
View File
@@ -2,6 +2,7 @@ package ssh
import (
"code.google.com/p/go.crypto/ssh"
"reflect"
"testing"
)
@@ -24,3 +25,23 @@ func TestPasswordPassword(t *testing.T) {
t.Fatalf("invalid password: %s", result)
}
}
func TestPasswordKeyboardInteractive_Impl(t *testing.T) {
var raw interface{}
raw = PasswordKeyboardInteractive("foo")
if _, ok := raw.(ssh.ClientKeyboardInteractive); !ok {
t.Fatal("PasswordKeyboardInteractive must implement ClientKeyboardInteractive")
}
}
func TestPasswordKeybardInteractive_Challenge(t *testing.T) {
p := PasswordKeyboardInteractive("foo")
result, err := p.Challenge("foo", "bar", []string{"one", "two"}, nil)
if err != nil {
t.Fatalf("err not nil: %s", err)
}
if !reflect.DeepEqual(result, []string{"foo", "foo"}) {
t.Fatalf("invalid password: %#v", result)
}
}
+1
View File
@@ -35,6 +35,7 @@ const defaultConfig = `
},
"provisioners": {
"file": "packer-provisioner-file",
"shell": "packer-provisioner-shell"
}
}
+3
View File
@@ -27,6 +27,9 @@ func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
log.Printf("Packer Version: %s %s", packer.Version, packer.VersionPrerelease)
log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
config, err := loadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
+15 -8
View File
@@ -6,6 +6,10 @@ import (
"sync"
)
// This is the key in configurations that is set to the name of the
// build.
const BuildNameConfigKey = "packer_build_name"
// This is the key in configurations that is set to "true" when Packer
// debugging is enabled.
const DebugConfigKey = "packer_debug"
@@ -91,12 +95,13 @@ func (b *coreBuild) Prepare() (err error) {
b.prepareCalled = true
debugConfig := map[string]interface{}{
DebugConfigKey: b.debug,
packerConfig := map[string]interface{}{
BuildNameConfigKey: b.name,
DebugConfigKey: b.debug,
}
// Prepare the builder
err = b.builder.Prepare(b.builderConfig, debugConfig)
err = b.builder.Prepare(b.builderConfig, packerConfig)
if err != nil {
log.Printf("Build '%s' prepare failure: %s\n", b.name, err)
return
@@ -106,7 +111,7 @@ func (b *coreBuild) Prepare() (err error) {
for _, coreProv := range b.provisioners {
configs := make([]interface{}, len(coreProv.config), len(coreProv.config)+1)
copy(configs, coreProv.config)
configs = append(configs, debugConfig)
configs = append(configs, packerConfig)
if err = coreProv.provisioner.Prepare(configs...); err != nil {
return
@@ -116,7 +121,8 @@ func (b *coreBuild) Prepare() (err error) {
// Prepare the post-processors
for _, ppSeq := range b.postProcessors {
for _, corePP := range ppSeq {
if err = corePP.processor.Configure(corePP.config); err != nil {
err = corePP.processor.Configure(corePP.config, packerConfig)
if err != nil {
return
}
}
@@ -189,7 +195,7 @@ PostProcessorRunSeqLoop:
}
builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.processorType))
artifact, err := corePP.processor.PostProcess(ppUi, priorArtifact)
artifact, keep, err := corePP.processor.PostProcess(ppUi, priorArtifact)
if err != nil {
errors = append(errors, fmt.Errorf("Post-processor failed: %s", err))
continue PostProcessorRunSeqLoop
@@ -200,11 +206,12 @@ PostProcessorRunSeqLoop:
continue PostProcessorRunSeqLoop
}
keep = keep || corePP.keepInputArtifact
if i == 0 {
// This is the first post-processor. We handle deleting
// previous artifacts a bit different because multiple
// post-processors may be using the original and need it.
if !keepOriginalArtifact && corePP.keepInputArtifact {
if !keepOriginalArtifact && keep {
log.Printf(
"Flagging to keep original artifact from post-processor '%s'",
corePP.processorType)
@@ -213,7 +220,7 @@ PostProcessorRunSeqLoop:
} else {
// We have a prior artifact. If we want to keep it, we append
// it to the results list. Otherwise, we destroy it.
if corePP.keepInputArtifact {
if keep {
artifacts = append(artifacts, priorArtifact)
} else {
log.Printf("Deleting prior artifact from post-processor '%s'", corePP.processorType)
+40 -7
View File
@@ -39,24 +39,27 @@ func TestBuild_Name(t *testing.T) {
func TestBuild_Prepare(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
debugFalseConfig := map[string]interface{}{DebugConfigKey: false}
packerConfig := map[string]interface{}{
BuildNameConfigKey: "test",
DebugConfigKey: false,
}
build := testBuild()
builder := build.builder.(*TestBuilder)
build.Prepare()
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, debugFalseConfig}, "prepare config should be 42")
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*TestProvisioner)
assert.True(prov.prepCalled, "prepare should be called")
assert.Equal(prov.prepConfigs, []interface{}{42, debugFalseConfig}, "prepare should be called with proper config")
assert.Equal(prov.prepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config")
corePP := build.postProcessors[0][0]
pp := corePP.processor.(*TestPostProcessor)
assert.True(pp.configCalled, "config should be called")
assert.Equal(pp.configVal, 42, "config should have right value")
assert.Equal(pp.configVal, []interface{}{42, packerConfig}, "config should have right value")
}
func TestBuild_Prepare_Twice(t *testing.T) {
@@ -82,7 +85,10 @@ func TestBuild_Prepare_Twice(t *testing.T) {
func TestBuild_Prepare_Debug(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
debugConfig := map[string]interface{}{DebugConfigKey: true}
packerConfig := map[string]interface{}{
BuildNameConfigKey: "test",
DebugConfigKey: true,
}
build := testBuild()
builder := build.builder.(*TestBuilder)
@@ -90,12 +96,12 @@ func TestBuild_Prepare_Debug(t *testing.T) {
build.SetDebug(true)
build.Prepare()
assert.True(builder.prepareCalled, "prepare should be called")
assert.Equal(builder.prepareConfig, []interface{}{42, debugConfig}, "prepare config should be 42")
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
coreProv := build.provisioners[0]
prov := coreProv.provisioner.(*TestProvisioner)
assert.True(prov.prepCalled, "prepare should be called")
assert.Equal(prov.prepConfigs, []interface{}{42, debugConfig}, "prepare should be called with proper config")
assert.Equal(prov.prepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config")
}
func TestBuild_Run(t *testing.T) {
@@ -239,6 +245,33 @@ func TestBuild_Run_Artifacts(t *testing.T) {
if !reflect.DeepEqual(artifactIds, expectedIds) {
t.Fatalf("unexpected ids: %#v", artifactIds)
}
// Test case: Test that with a single post-processor that forcibly
// keeps inputs, that the artifacts are kept.
build = testBuild()
build.postProcessors = [][]coreBuildPostProcessor{
[]coreBuildPostProcessor{
coreBuildPostProcessor{
&TestPostProcessor{artifactId: "pp", keep: true}, "pp", 42, false,
},
},
}
build.Prepare()
artifacts, err = build.Run(ui, cache)
if err != nil {
t.Fatalf("err: %s", err)
}
expectedIds = []string{"b", "pp"}
artifactIds = make([]string, len(artifacts))
for i, artifact := range artifacts {
artifactIds[i] = artifact.Id()
}
if !reflect.DeepEqual(artifactIds, expectedIds) {
t.Fatalf("unexpected ids: %#v", artifactIds)
}
}
func TestBuild_RunBeforePrepare(t *testing.T) {
+1 -1
View File
@@ -252,7 +252,7 @@ func (c *Client) Start() (address string, err error) {
timeout := time.After(c.config.StartTimeout)
// Start looking for the address
log.Println("Waiting for RPC address for: %s", cmd.Path)
log.Printf("Waiting for RPC address for: %s", cmd.Path)
for done := false; !done; {
select {
case <-timeout:
+2 -9
View File
@@ -19,7 +19,6 @@ import (
"os/signal"
"runtime"
"strconv"
"strings"
)
const MagicCookieKey = "PACKER_PLUGIN_MAGIC_COOKIE"
@@ -59,14 +58,8 @@ func serve(server *rpc.Server) (err error) {
address = fmt.Sprintf("127.0.0.1:%d", port)
listener, err = net.Listen("tcp", address)
if err != nil {
if !strings.Contains(err.Error(), "address already in use") {
// Not an address already in use error, return.
return
} else {
// Address is in use, just try another
err = nil
continue
}
err = nil
continue
}
break
+3 -3
View File
@@ -10,16 +10,16 @@ type cmdPostProcessor struct {
client *Client
}
func (c *cmdPostProcessor) Configure(config interface{}) error {
func (c *cmdPostProcessor) Configure(config ...interface{}) error {
defer func() {
r := recover()
c.checkExit(r, nil)
}()
return c.p.Configure(config)
return c.p.Configure(config...)
}
func (c *cmdPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, error) {
func (c *cmdPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
defer func() {
r := recover()
c.checkExit(r, nil)
+3 -3
View File
@@ -8,12 +8,12 @@ import (
type helperPostProcessor byte
func (helperPostProcessor) Configure(interface{}) error {
func (helperPostProcessor) Configure(...interface{}) error {
return nil
}
func (helperPostProcessor) PostProcess(packer.Ui, packer.Artifact) (packer.Artifact, error) {
return nil, nil
func (helperPostProcessor) PostProcess(packer.Ui, packer.Artifact) (packer.Artifact, bool, error) {
return nil, false, nil
}
func TestPostProcessor_NoExist(t *testing.T) {
+4 -3
View File
@@ -9,9 +9,10 @@ type PostProcessor interface {
// Configure is responsible for setting up configuration, storing
// the state for later, and returning and errors, such as validation
// errors.
Configure(interface{}) error
Configure(...interface{}) error
// PostProcess takes a previously created Artifact and produces another
// Artifact. If an error occurs, it should return that error.
PostProcess(Ui, Artifact) (Artifact, error)
// Artifact. If an error occurs, it should return that error. If `keep`
// is to true, then the previous artifact is forcibly kept.
PostProcess(Ui, Artifact) (a Artifact, keep bool, err error)
}
+5 -4
View File
@@ -2,22 +2,23 @@ package packer
type TestPostProcessor struct {
artifactId string
keep bool
configCalled bool
configVal interface{}
configVal []interface{}
ppCalled bool
ppArtifact Artifact
ppUi Ui
}
func (pp *TestPostProcessor) Configure(v interface{}) error {
func (pp *TestPostProcessor) Configure(v ...interface{}) error {
pp.configCalled = true
pp.configVal = v
return nil
}
func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, error) {
func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) {
pp.ppCalled = true
pp.ppArtifact = a
pp.ppUi = ui
return &TestArtifact{id: pp.artifactId}, nil
return &TestArtifact{id: pp.artifactId}, pp.keep, nil
}
+7
View File
@@ -153,12 +153,14 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
var cmd packer.RemoteCmd
cmd.Command = args.Command
toClose := make([]net.Conn, 0)
if args.StdinAddress != "" {
stdinC, err := net.Dial("tcp", args.StdinAddress)
if err != nil {
return err
}
toClose = append(toClose, stdinC)
cmd.Stdin = stdinC
}
@@ -168,6 +170,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
return err
}
toClose = append(toClose, stdoutC)
cmd.Stdout = stdoutC
}
@@ -177,6 +180,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
return err
}
toClose = append(toClose, stderrC)
cmd.Stderr = stderrC
}
@@ -196,6 +200,9 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
// exit. When it does, report it back to caller...
go func() {
defer responseC.Close()
for _, conn := range toClose {
defer conn.Close()
}
for !cmd.Exited {
time.Sleep(50 * time.Millisecond)
+1 -1
View File
@@ -20,7 +20,7 @@ func PortRange(min, max int) {
// bound to that port.
func netListenerInRange(min, max int) net.Listener {
for port := min; port <= max; port++ {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err == nil {
return l
}
+1 -1
View File
@@ -16,7 +16,7 @@ func Test_netListenerInRange(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Open up port 10000 so that we take up a port
L1000, err := net.Listen("tcp", ":11000")
L1000, err := net.Listen("tcp", "127.0.0.1:11000")
defer L1000.Close()
assert.Nil(err, "should be able to bind to port 10000")
+18 -11
View File
@@ -17,50 +17,56 @@ type PostProcessorServer struct {
p packer.PostProcessor
}
type PostProcessorConfigureArgs struct {
Configs []interface{}
}
type PostProcessorProcessResponse struct {
Err error
Keep bool
RPCAddress string
}
func PostProcessor(client *rpc.Client) *postProcessor {
return &postProcessor{client}
}
func (p *postProcessor) Configure(raw interface{}) (err error) {
if cerr := p.client.Call("PostProcessor.Configure", &raw, &err); cerr != nil {
func (p *postProcessor) Configure(raw ...interface{}) (err error) {
args := &PostProcessorConfigureArgs{Configs: raw}
if cerr := p.client.Call("PostProcessor.Configure", args, &err); cerr != nil {
err = cerr
}
return
}
func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, error) {
func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
server := rpc.NewServer()
RegisterArtifact(server, a)
RegisterUi(server, ui)
var response PostProcessorProcessResponse
if err := p.client.Call("PostProcessor.PostProcess", serveSingleConn(server), &response); err != nil {
return nil, err
return nil, false, err
}
if response.Err != nil {
return nil, response.Err
return nil, false, response.Err
}
if response.RPCAddress == "" {
return nil, nil
return nil, false, nil
}
client, err := rpc.Dial("tcp", response.RPCAddress)
if err != nil {
return nil, err
return nil, false, err
}
return Artifact(client), nil
return Artifact(client), response.Keep, nil
}
func (p *PostProcessorServer) Configure(raw *interface{}, reply *error) error {
*reply = p.p.Configure(*raw)
func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply *error) error {
*reply = p.p.Configure(args.Configs...)
if *reply != nil {
*reply = NewBasicError(*reply)
}
@@ -76,7 +82,7 @@ func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorPr
responseAddress := ""
artifact, err := p.p.PostProcess(&Ui{client}, Artifact(client))
artifact, keep, err := p.p.PostProcess(&Ui{client}, Artifact(client))
if err == nil && artifact != nil {
server := rpc.NewServer()
RegisterArtifact(server, artifact)
@@ -89,6 +95,7 @@ func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorPr
*reply = PostProcessorProcessResponse{
Err: err,
Keep: keep,
RPCAddress: responseAddress,
}
+7 -6
View File
@@ -3,6 +3,7 @@ package rpc
import (
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
"testing"
)
@@ -10,23 +11,23 @@ var testPostProcessorArtifact = new(testArtifact)
type TestPostProcessor struct {
configCalled bool
configVal interface{}
configVal []interface{}
ppCalled bool
ppArtifact packer.Artifact
ppUi packer.Ui
}
func (pp *TestPostProcessor) Configure(v interface{}) error {
func (pp *TestPostProcessor) Configure(v ...interface{}) error {
pp.configCalled = true
pp.configVal = v
return nil
}
func (pp *TestPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, error) {
func (pp *TestPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
pp.ppCalled = true
pp.ppArtifact = a
pp.ppUi = ui
return testPostProcessorArtifact, nil
return testPostProcessorArtifact, false, nil
}
func TestPostProcessorRPC(t *testing.T) {
@@ -56,14 +57,14 @@ func TestPostProcessorRPC(t *testing.T) {
t.Fatal("config should be called")
}
if p.configVal != 42 {
if !reflect.DeepEqual(p.configVal, []interface{}{42}) {
t.Fatalf("unknown config value: %#v", p.configVal)
}
// Test PostProcess
a := new(testArtifact)
ui := new(testUi)
artifact, err := pClient.PostProcess(ui, a)
artifact, _, err := pClient.PostProcess(ui, a)
if err != nil {
t.Fatalf("err: %s", err)
}
+28
View File
@@ -1,6 +1,7 @@
package packer
import (
"bytes"
"encoding/json"
"fmt"
"github.com/mitchellh/mapstructure"
@@ -65,6 +66,29 @@ func ParseTemplate(data []byte) (t *Template, err error) {
var rawTpl rawTemplate
err = json.Unmarshal(data, &rawTpl)
if err != nil {
syntaxErr, ok := err.(*json.SyntaxError)
if !ok {
return
}
// We have a syntax error. Extract out the line number and friends.
// https://groups.google.com/forum/#!topic/golang-nuts/fizimmXtVfc
newline := []byte{'\x0a'}
// Calculate the start/end position of the line where the error is
start := bytes.LastIndex(data[:syntaxErr.Offset], newline) + 1
end := len(data)
if idx := bytes.Index(data[start:], newline); idx >= 0 {
end = start + idx
}
// Count the line number we're on plus the offset in the line
line := bytes.Count(data[:start], newline) + 1
pos := int(syntaxErr.Offset) - start - 1
err = fmt.Errorf("Error in line %d, char %d: %s\n%s",
line, pos, syntaxErr, data[start:end])
return
}
@@ -172,6 +196,10 @@ func ParseTemplate(data []byte) (t *Template, err error) {
raw.rawConfig = v
}
if len(t.Builders) == 0 {
errors = append(errors, fmt.Errorf("No builders are defined in the template."))
}
// If there were errors, we put it into a MultiError and return
if len(errors) > 0 {
err = &MultiError{errors}
+12 -2
View File
@@ -11,14 +11,14 @@ func TestParseTemplate_Basic(t *testing.T) {
data := `
{
"builders": []
"builders": [{"type": "something"}]
}
`
result, err := ParseTemplate([]byte(data))
assert.Nil(err, "should not error")
assert.NotNil(result, "template should not be nil")
assert.Length(result.Builders, 0, "no builders")
assert.Length(result.Builders, 1, "one builder")
}
func TestParseTemplate_Invalid(t *testing.T) {
@@ -140,6 +140,8 @@ func TestParseTemplate_Hooks(t *testing.T) {
data := `
{
"builders": [{"type": "foo"}],
"hooks": {
"event": ["foo", "bar"]
}
@@ -159,6 +161,8 @@ func TestParseTemplate_Hooks(t *testing.T) {
func TestParseTemplate_PostProcessors(t *testing.T) {
data := `
{
"builders": [{"type": "foo"}],
"post-processors": [
"simple",
@@ -215,6 +219,8 @@ func TestParseTemplate_ProvisionerWithoutType(t *testing.T) {
data := `
{
"builders": [{"type": "foo"}],
"provisioners": [{}]
}
`
@@ -228,6 +234,8 @@ func TestParseTemplate_ProvisionerWithNonStringType(t *testing.T) {
data := `
{
"builders": [{"type": "foo"}],
"provisioners": [{
"type": 42
}]
@@ -243,6 +251,8 @@ func TestParseTemplate_Provisioners(t *testing.T) {
data := `
{
"builders": [{"type": "foo"}],
"provisioners": [
{
"type": "shell"
+16 -4
View File
@@ -1,12 +1,14 @@
package packer
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"os"
"os/signal"
"strings"
"sync"
)
@@ -86,19 +88,29 @@ func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
}
func (u *PrefixedUi) Ask(query string) (string, error) {
return u.Ui.Ask(fmt.Sprintf("%s: %s", u.SayPrefix, query))
return u.Ui.Ask(u.prefixLines(u.SayPrefix, query))
}
func (u *PrefixedUi) Say(message string) {
u.Ui.Say(fmt.Sprintf("%s: %s", u.SayPrefix, message))
u.Ui.Say(u.prefixLines(u.SayPrefix, message))
}
func (u *PrefixedUi) Message(message string) {
u.Ui.Message(fmt.Sprintf("%s: %s", u.MessagePrefix, message))
u.Ui.Message(u.prefixLines(u.MessagePrefix, message))
}
func (u *PrefixedUi) Error(message string) {
u.Ui.Error(fmt.Sprintf("%s: %s", u.SayPrefix, message))
u.Ui.Error(u.prefixLines(u.SayPrefix, message))
}
func (u *PrefixedUi) prefixLines(prefix, message string) string {
var result bytes.Buffer
for _, line := range strings.Split(message, "\n") {
result.WriteString(fmt.Sprintf("%s: %s\n", prefix, line))
}
return strings.TrimSpace(result.String())
}
func (rw *ReaderWriterUi) Ask(query string) (string, error) {
+3
View File
@@ -50,6 +50,9 @@ func TestPrefixedUi(t *testing.T) {
prefixUi.Error("bar")
assert.Equal(readWriter(bufferUi), "mitchell: bar\n", "should have prefix")
prefixUi.Say("foo\nbar")
assert.Equal(readWriter(bufferUi), "mitchell: foo\nmitchell: bar\n", "should multiline")
}
func TestColoredUi_ImplUi(t *testing.T) {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
// The version of packer.
const Version = "0.1.1"
const Version = "0.1.5"
// Any pre-release marker for the version. If this is "" (empty string),
// then it means that it is a final release. Otherwise, this is the
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/packer/provisioner/file"
)
func main() {
plugin.ServeProvisioner(new(file.Provisioner))
}
+25 -15
View File
@@ -5,6 +5,7 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
@@ -14,6 +15,8 @@ import (
type AWSBoxConfig struct {
OutputPath string `mapstructure:"output"`
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
PackerBuildName string `mapstructure:"packer_build_name"`
}
type AWSVagrantfileTemplate struct {
@@ -24,16 +27,18 @@ type AWSBoxPostProcessor struct {
config AWSBoxConfig
}
func (p *AWSBoxPostProcessor) Configure(raw interface{}) error {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
func (p *AWSBoxPostProcessor) Configure(raws ...interface{}) error {
for _, raw := range raws {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
}
}
return nil
}
func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
// Determine the regions...
tplData := &AWSVagrantfileTemplate{
Images: make(map[string]string),
@@ -42,43 +47,47 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
for _, regions := range strings.Split(artifact.Id(), ",") {
parts := strings.Split(regions, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
return nil, false, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
}
tplData.Images[parts[0]] = parts[1]
}
// Compile the output path
outputPath, err := ProcessOutputPath(p.config.OutputPath, "aws", artifact)
outputPath, err := ProcessOutputPath(p.config.OutputPath,
p.config.PackerBuildName, "aws", artifact)
if err != nil {
return nil, err
return nil, false, err
}
// Create a temporary directory for us to build the contents of the box in
dir, err := ioutil.TempDir("", "packer")
if err != nil {
return nil, err
return nil, false, err
}
defer os.RemoveAll(dir)
// Create the Vagrantfile from the template
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
if err != nil {
return nil, err
return nil, false, err
}
defer vf.Close()
vagrantfileContents := defaultAWSVagrantfile
if p.config.VagrantfileTemplate != "" {
log.Printf("Using vagrantfile template: %s", p.config.VagrantfileTemplate)
f, err := os.Open(p.config.VagrantfileTemplate)
if err != nil {
return nil, err
err = fmt.Errorf("error opening vagrantfile template: %s", err)
return nil, false, err
}
defer f.Close()
contents, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
err = fmt.Errorf("error reading vagrantfile template: %s", err)
return nil, false, err
}
vagrantfileContents = string(contents)
@@ -91,15 +100,16 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
// Create the metadata
metadata := map[string]string{"provider": "aws"}
if err := WriteMetadata(dir, metadata); err != nil {
return nil, err
return nil, false, err
}
// Compress the directory to the given output path
if err := DirToBox(outputPath, dir); err != nil {
return nil, err
err = fmt.Errorf("error creating box: %s", err)
return nil, false, err
}
return NewArtifact("aws", outputPath), nil
return NewArtifact("aws", outputPath), true, nil
}
var defaultAWSVagrantfile = `
+34 -16
View File
@@ -19,30 +19,44 @@ var builtins = map[string]string{
type Config struct {
OutputPath string `mapstructure:"output"`
PackerBuildName string `mapstructure:"packer_build_name"`
}
type PostProcessor struct {
config Config
premade map[string]packer.PostProcessor
config Config
premade map[string]packer.PostProcessor
rawConfigs []interface{}
}
func (p *PostProcessor) Configure(raw interface{}) error {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
func (p *PostProcessor) Configure(raws ...interface{}) error {
// Store the raw configs for usage later
p.rawConfigs = raws
for _, raw := range raws {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
}
}
ppExtraConfig := make(map[string]interface{})
if p.config.OutputPath == "" {
p.config.OutputPath = "packer_{{.Provider}}.box"
p.config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
ppExtraConfig["output"] = p.config.OutputPath
}
_, err = template.New("output").Parse(p.config.OutputPath)
_, err := template.New("output").Parse(p.config.OutputPath)
if err != nil {
return fmt.Errorf("output invalid template: %s", err)
}
// Store the extra configuration for post-processors
p.rawConfigs = append(p.rawConfigs, ppExtraConfig)
// TODO(mitchellh): Properly handle multiple raw configs
var mapConfig map[string]interface{}
if err := mapstructure.Decode(raw, &mapConfig); err != nil {
if err := mapstructure.Decode(raws[0], &mapConfig); err != nil {
return err
}
@@ -54,7 +68,12 @@ func (p *PostProcessor) Configure(raw interface{}) error {
continue
}
if err := pp.Configure(raw); err != nil {
// Create the proper list of configurations
ppConfigs := make([]interface{}, 0, len(p.rawConfigs)+1)
copy(ppConfigs, p.rawConfigs)
ppConfigs = append(ppConfigs, raw)
if err := pp.Configure(ppConfigs...); err != nil {
errors = append(errors, err)
}
@@ -68,10 +87,10 @@ func (p *PostProcessor) Configure(raw interface{}) error {
return nil
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
ppName, ok := builtins[artifact.BuilderId()]
if !ok {
return nil, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
return nil, false, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
}
// Use the premade PostProcessor if we have one. Otherwise, we
@@ -81,12 +100,11 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
log.Printf("Premade post-processor for '%s' not found. Creating.", ppName)
pp = keyToPostProcessor(ppName)
if pp == nil {
return nil, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
return nil, false, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
}
config := map[string]string{"output": p.config.OutputPath}
if err := pp.Configure(config); err != nil {
return nil, err
if err := pp.Configure(p.rawConfigs...); err != nil {
return nil, false, err
}
}
+31 -3
View File
@@ -17,14 +17,36 @@ import (
// OutputPath variables.
type OutputPathTemplate struct {
ArtifactId string
BuildName string
Provider string
}
// Copies a file by copying the contents of the file to another place.
func CopyContents(dst, src string) error {
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
dstF, err := os.Create(dst)
if err != nil {
return err
}
defer dstF.Close()
if _, err := io.Copy(dstF, srcF); err != nil {
return err
}
return nil
}
// DirToBox takes the directory and compresses it into a Vagrant-compatible
// box. This function does not perform checks to verify that dir is
// actually a proper box. This is an expected precondition.
func DirToBox(dst, dir string) error {
log.Printf("Turning dir into box: %s", dir)
log.Printf("Turning dir into box: %s => %s", dir, dst)
dstF, err := os.Create(dst)
if err != nil {
return err
@@ -39,9 +61,14 @@ func DirToBox(dst, dir string) error {
// This is the walk func that tars each of the files in the dir
tarWalk := func(path string, info os.FileInfo, prevErr error) error {
// If there was a prior error, return it
if prevErr != nil {
return prevErr
}
// Skip directories
if info.IsDir() {
log.Printf("Skiping directory '%s' for box '%s'", path, dst)
log.Printf("Skipping directory '%s' for box '%s'", path, dst)
return nil
}
@@ -83,11 +110,12 @@ func DirToBox(dst, dir string) error {
// ProcessOutputPath takes an output path template and executes it,
// replacing variables with their respective values.
func ProcessOutputPath(path string, provider string, artifact packer.Artifact) (string, error) {
func ProcessOutputPath(path string, buildName string, provider string, artifact packer.Artifact) (string, error) {
var buf bytes.Buffer
tplData := &OutputPathTemplate{
ArtifactId: artifact.Id(),
BuildName: buildName,
Provider: provider,
}
+45 -30
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
@@ -18,6 +17,8 @@ import (
type VBoxBoxConfig struct {
OutputPath string `mapstructure:"output"`
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
PackerBuildName string `mapstructure:"packer_build_name"`
}
type VBoxVagrantfileTemplate struct {
@@ -28,60 +29,53 @@ type VBoxBoxPostProcessor struct {
config VBoxBoxConfig
}
func (p *VBoxBoxPostProcessor) Configure(raw interface{}) error {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
func (p *VBoxBoxPostProcessor) Configure(raws ...interface{}) error {
for _, raw := range raws {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
}
}
return nil
}
func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
var err error
tplData := &VBoxVagrantfileTemplate{}
tplData.BaseMacAddress, err = p.findBaseMacAddress(artifact)
if err != nil {
return nil, err
return nil, false, err
}
// Compile the output path
outputPath, err := ProcessOutputPath(p.config.OutputPath, "virtualbox", artifact)
outputPath, err := ProcessOutputPath(p.config.OutputPath,
p.config.PackerBuildName, "virtualbox", artifact)
if err != nil {
return nil, err
return nil, false, err
}
// Create a temporary directory for us to build the contents of the box in
dir, err := ioutil.TempDir("", "packer")
if err != nil {
return nil, err
return nil, false, err
}
defer os.RemoveAll(dir)
// Copy all of the original contents into the temporary directory
for _, path := range artifact.Files() {
ui.Message(fmt.Sprintf("Copying: %s", path))
src, err := os.Open(path)
if err != nil {
return nil, err
}
defer src.Close()
dst, err := os.Create(filepath.Join(dir, filepath.Base(path)))
if err != nil {
return nil, err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return nil, err
dstPath := filepath.Join(dir, filepath.Base(path))
if err := CopyContents(dstPath, path); err != nil {
return nil, false, err
}
}
// Create the Vagrantfile from the template
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
if err != nil {
return nil, err
return nil, false, err
}
defer vf.Close()
@@ -89,13 +83,13 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
if p.config.VagrantfileTemplate != "" {
f, err := os.Open(p.config.VagrantfileTemplate)
if err != nil {
return nil, err
return nil, false, err
}
defer f.Close()
contents, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
return nil, false, err
}
vagrantfileContents = string(contents)
@@ -108,16 +102,22 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
// Create the metadata
metadata := map[string]string{"provider": "virtualbox"}
if err := WriteMetadata(dir, metadata); err != nil {
return nil, err
return nil, false, err
}
// Rename the OVF file to box.ovf, as required by Vagrant
ui.Message("Renaming the OVF to box.ovf...")
if err := p.renameOVF(dir); err != nil {
return nil, false, err
}
// Compress the directory to the given output path
ui.Message(fmt.Sprintf("Compressing box..."))
if err := DirToBox(outputPath, dir); err != nil {
return nil, err
return nil, false, err
}
return NewArtifact("virtualbox", outputPath), nil
return NewArtifact("virtualbox", outputPath), false, nil
}
func (p *VBoxBoxPostProcessor) findBaseMacAddress(a packer.Artifact) (string, error) {
@@ -156,8 +156,23 @@ func (p *VBoxBoxPostProcessor) findBaseMacAddress(a packer.Artifact) (string, er
return string(matches[1]), nil
}
func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
log.Println("Looking for OVF to rename...")
matches, err := filepath.Glob(filepath.Join(dir, "*.ovf"))
if err != nil {
return err
}
if len(matches) > 1 {
return errors.New("More than one OVF file in VirtualBox artifact.")
}
log.Printf("Renaming: '%s' => box.ovf", matches[0])
return os.Rename(matches[0], filepath.Join(dir, "box.ovf"))
}
var defaultVBoxVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.base_mac = "{{ .BaseMacAddress }}"
config.vm.base_mac = "{{ .BaseMacAddress }}"
end
`
+23 -29
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -14,71 +13,66 @@ import (
type VMwareBoxConfig struct {
OutputPath string `mapstructure:"output"`
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
PackerBuildName string `mapstructure:"packer_build_name"`
}
type VMwareBoxPostProcessor struct {
config VMwareBoxConfig
}
func (p *VMwareBoxPostProcessor) Configure(raw interface{}) error {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
func (p *VMwareBoxPostProcessor) Configure(raws ...interface{}) error {
for _, raw := range raws {
err := mapstructure.Decode(raw, &p.config)
if err != nil {
return err
}
}
return nil
}
func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
// Compile the output path
outputPath, err := ProcessOutputPath(p.config.OutputPath, "vmware", artifact)
outputPath, err := ProcessOutputPath(p.config.OutputPath,
p.config.PackerBuildName, "vmware", artifact)
if err != nil {
return nil, err
return nil, false, err
}
// Create a temporary directory for us to build the contents of the box in
dir, err := ioutil.TempDir("", "packer")
if err != nil {
return nil, err
return nil, false, err
}
defer os.RemoveAll(dir)
// Copy all of the original contents into the temporary directory
for _, path := range artifact.Files() {
ui.Message(fmt.Sprintf("Copying: %s", path))
src, err := os.Open(path)
if err != nil {
return nil, err
}
defer src.Close()
dst, err := os.Create(filepath.Join(dir, filepath.Base(path)))
if err != nil {
return nil, err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return nil, err
dstPath := filepath.Join(dir, filepath.Base(path))
if err := CopyContents(dstPath, path); err != nil {
return nil, false, err
}
}
if p.config.VagrantfileTemplate != "" {
f, err := os.Open(p.config.VagrantfileTemplate)
if err != nil {
return nil, err
return nil, false, err
}
defer f.Close()
contents, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
return nil, false, err
}
// Create the Vagrantfile from the template
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
if err != nil {
return nil, err
return nil, false, err
}
defer vf.Close()
@@ -88,16 +82,16 @@ func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artif
}
// Create the metadata
metadata := map[string]string{"provider": "vmware"}
metadata := map[string]string{"provider": "vmware_desktop"}
if err := WriteMetadata(dir, metadata); err != nil {
return nil, err
return nil, false, err
}
// Compress the directory to the given output path
ui.Message(fmt.Sprintf("Compressing box..."))
if err := DirToBox(outputPath, dir); err != nil {
return nil, err
return nil, false, err
}
return NewArtifact("vmware", outputPath), nil
return NewArtifact("vmware", outputPath), false, nil
}
+57
View File
@@ -0,0 +1,57 @@
package file
import (
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/packer"
"os"
)
type config struct {
// The local path of the file to upload.
Source string
// The remote path where the local file will be uploaded to.
Destination string
}
type Provisioner struct {
config config
}
func (p *Provisioner) Prepare(raws ...interface{}) error {
for _, raw := range raws {
if err := mapstructure.Decode(raw, &p.config); err != nil {
return err
}
}
errs := []error{}
if _, err := os.Stat(p.config.Source); err != nil {
errs = append(errs,
fmt.Errorf("Bad source '%s': %s", p.config.Source, err))
}
if p.config.Destination == "" {
errs = append(errs, errors.New("Destination must be specified."))
}
if len(errs) > 0 {
return &packer.MultiError{errs}
}
return nil
}
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say(fmt.Sprintf("Uploading %s => %s", p.config.Source, p.config.Destination))
f, err := os.Open(p.config.Source)
if err != nil {
return err
}
defer f.Close()
return comm.Upload(p.config.Destination, f)
}
+147
View File
@@ -0,0 +1,147 @@
package file
import (
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"os"
"strings"
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"destination": "something",
}
}
func TestProvisioner_Impl(t *testing.T) {
var raw interface{}
raw = &Provisioner{}
if _, ok := raw.(packer.Provisioner); !ok {
t.Fatalf("must be a provisioner")
}
}
func TestProvisionerPrepare_InvalidSource(t *testing.T) {
var p Provisioner
config := testConfig()
config["source"] = "/this/should/not/exist"
err := p.Prepare(config)
if err == nil {
t.Fatalf("should require existing file")
}
}
func TestProvisionerPrepare_ValidSource(t *testing.T) {
var p Provisioner
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf.Name())
config := testConfig()
config["source"] = tf.Name()
err = p.Prepare(config)
if err != nil {
t.Fatalf("should allow valid file: %s", err)
}
}
func TestProvisionerPrepare_EmptyDestination(t *testing.T) {
var p Provisioner
config := testConfig()
delete(config, "destination")
err := p.Prepare(config)
if err == nil {
t.Fatalf("should require destination path")
}
}
type stubUploadCommunicator struct {
dest string
data []byte
}
func (suc *stubUploadCommunicator) Download(src string, data io.Writer) error {
return nil
}
func (suc *stubUploadCommunicator) Upload(dest string, data io.Reader) error {
var err error
suc.dest = dest
suc.data, err = ioutil.ReadAll(data)
return err
}
func (suc *stubUploadCommunicator) Start(cmd *packer.RemoteCmd) error {
return nil
}
type stubUi struct {
sayMessages string
}
func (su *stubUi) Ask(string) (string, error) {
return "", nil
}
func (su *stubUi) Error(string) {
}
func (su *stubUi) Message(string) {
}
func (su *stubUi) Say(msg string) {
su.sayMessages += msg
}
func TestProvisionerProvision_SendsFile(t *testing.T) {
var p Provisioner
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf.Name())
if _, err = tf.Write([]byte("hello")); err != nil {
t.Fatalf("error writing tempfile: %s", err)
}
config := map[string]interface{}{
"source": tf.Name(),
"destination": "something",
}
if err := p.Prepare(config); err != nil {
t.Fatalf("err: %s", err)
}
ui := &stubUi{}
comm := &stubUploadCommunicator{}
err = p.Provision(ui, comm)
if err != nil {
t.Fatalf("should successfully provision: %s", err)
}
if !strings.Contains(ui.sayMessages, tf.Name()) {
t.Fatalf("should print source filename")
}
if !strings.Contains(ui.sayMessages, "something") {
t.Fatalf("should print destination filename")
}
if comm.dest != "something" {
t.Fatalf("should upload to configured destination")
}
if string(comm.data) != "hello" {
t.Fatalf("should upload with source file's data")
}
}
+13 -1
View File
@@ -25,6 +25,9 @@ type config struct {
// in the context of a single shell.
Inline []string
// The shebang value used when running inline scripts.
InlineShebang string `mapstructure:"inline_shebang"`
// The local path of the shell script to upload and execute.
Script string
@@ -62,13 +65,17 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = "{{.Vars}} sh {{.Path}}"
p.config.ExecuteCommand = "chmod +x {{.Path}}; {{.Vars}} {{.Path}}"
}
if p.config.Inline != nil && len(p.config.Inline) == 0 {
p.config.Inline = nil
}
if p.config.InlineShebang == "" {
p.config.InlineShebang = "/bin/sh"
}
if p.config.RemotePath == "" {
p.config.RemotePath = DefaultRemotePath
}
@@ -136,6 +143,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// Write our contents to it
writer := bufio.NewWriter(tf)
writer.WriteString(fmt.Sprintf("#!%s\n", p.config.InlineShebang))
for _, command := range p.config.Inline {
if _, err := writer.WriteString(command + "\n"); err != nil {
return fmt.Errorf("Error preparing shell script: %s", err)
@@ -157,6 +165,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
if err != nil {
return fmt.Errorf("Error opening shell script: %s", err)
}
defer f.Close()
log.Printf("Uploading %s => %s", path, p.config.RemotePath)
err = comm.Upload(p.config.RemotePath, f)
@@ -164,6 +173,9 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return fmt.Errorf("Error uploading shell script: %s", err)
}
// Close the original file since we copied it
f.Close()
// Flatten the environment variables
flattendVars := strings.Join(p.config.Vars, " ")
+27
View File
@@ -35,6 +35,33 @@ func TestProvisionerPrepare_Defaults(t *testing.T) {
}
}
func TestProvisionerPrepare_InlineShebang(t *testing.T) {
config := testConfig()
delete(config, "inline_shebang")
p := new(Provisioner)
err := p.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if p.config.InlineShebang != "/bin/sh" {
t.Fatalf("bad value: %s", p.config.InlineShebang)
}
// Test with a good one
config["inline_shebang"] = "foo"
p = new(Provisioner)
err = p.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if p.config.InlineShebang != "foo" {
t.Fatalf("bad value: %s", p.config.InlineShebang)
}
}
func TestProvisionerPrepare_Script(t *testing.T) {
config := testConfig()
delete(config, "inline")
+3 -3
View File
@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
set -e
NO_COLOR="\x1b[0m"
@@ -15,12 +15,12 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
cd $DIR
# Compile the main Packer app
echo "${OK_COLOR}--> Compiling Packer${NO_COLOR}"
echo -e "${OK_COLOR}--> Compiling Packer${NO_COLOR}"
go build -v -o bin/packer .
# Go over each plugin and build it
for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
PLUGIN_NAME=$(basename ${PLUGIN})
echo "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}"
echo -e "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}"
go build -v -o bin/packer-${PLUGIN_NAME} ${PLUGIN}
done
+2
View File
@@ -1,5 +1,7 @@
source 'https://rubygems.org'
ruby '1.9.3'
gem "middleman", "~> 3.0.6"
gem "middleman-minify-html", "~> 3.0.0"
gem "rack-contrib", "~> 1.1.0"
+1 -1
View File
@@ -17,7 +17,7 @@ commands:
```
$ bundle
$ bundle exec middleman server
$ PACKER_DISABLE_DOWNLOAD_FETCH=true PACKER_VERSION=1.0 bundle exec middleman server
```
Then open up `localhost:4567`. Note that some URLs you may need to append
+2 -2
View File
@@ -1,6 +1,5 @@
require "net/http"
raise "BINTRAY_API_KEY must be set." if !ENV["BINTRAY_API_KEY"]
raise "PACKER_VERSION must be set." if !ENV["PACKER_VERSION"]
#-------------------------------------------------------------------------
@@ -11,6 +10,7 @@ $packer_files = {}
$packer_os = []
if !ENV["PACKER_DISABLE_DOWNLOAD_FETCH"]
raise "BINTRAY_API_KEY must be set." if !ENV["BINTRAY_API_KEY"]
http = Net::HTTP.new("dl.bintray.com", 80)
req = Net::HTTP::Get.new("/mitchellh/packer")
req.basic_auth "mitchellh", ENV["BINTRAY_API_KEY"]
@@ -82,7 +82,7 @@ helpers do
end
def download_url(file)
"http://dl.bintray.com/mitchellh/packer/#{file}?direct"
"https://dl.bintray.com/mitchellh/packer/#{file}?direct"
end
def latest_version
@@ -4,7 +4,7 @@ layout: "docs"
# Packer Terminology
There are a handful of terms used throughout Packer documentation where
There are a handful of terms used throughout the Packer documentation where
the meaning may not be immediately obvious if you haven't used Packer before.
Luckily, there are relatively few. This page documents all the terminology
required to understand and use Packer. The terminology is in alphabetical
@@ -37,6 +37,8 @@ each category, the available configuration keys are alphabetized.
Required:
* `access_key` (string) - The access key used to communicate with AWS.
If not specified, Packer will attempt to read this from environmental
variables `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order).
* `ami_name` (string) - The name of the resulting AMI that will appear
when managing AMIs in the AWS console or via APIs. This must be unique.
@@ -50,6 +52,8 @@ Required:
to launch the EC2 instance to create the AMI.
* `secret_key` (string) - The secret key used to communicate with AWS.
If not specified, Packer will attempt to read this from environmental
variables `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order).
* `source_ami` (string) - The initial AMI used as a base for the newly
created machine.
@@ -77,12 +81,19 @@ Here is a basic example. It is completely valid except for the access keys:
"secret_key": "YOUR SECRET KEY HERE",
"region": "us-east-1",
"source_ami": "ami-de0d9eb7",
"instance_type": "m1.small",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-quick-start {{.CreateTime}}"
}
</pre>
<div class="alert alert-block alert-info">
<strong>Note:</strong> Packer can also read the access key and secret
access key from environmental variables. See the configuration reference in
the section above for more information on what environmental variables Packer
will look for.
</div>
## AMI Name Variables
The AMI name specified by the `ami_name` configuration variable is actually
@@ -89,7 +89,7 @@ the prior linked page for information on syntax if you're unfamiliar with it.
The available variables are shown below:
* `CreateTime`- This will be replaced with the Unix timestamp of when the
* `CreateTime` - This will be replaced with the Unix timestamp of when the
image is created.
## Finding Image, Region, and Size IDs
@@ -27,7 +27,8 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
"iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.2-server-amd64.iso",
"iso_md5": "af5f788aee1b32c4b2634734309cc9e9",
"ssh_username": "packer",
"ssh_wait_timeout": "30s"
"ssh_wait_timeout": "30s",
"shutdown_command": "shutdown -P now"
}
</pre>
@@ -54,7 +55,7 @@ Required:
Optional:
* `boot_command` (array of strings) - This is an array of commands to type
when the virtual machine is firsted booted. The goal of these commands should
when the virtual machine is first booted. The goal of these commands should
be to type just enough to initialize the operating system installer. Special
keys can be typed as well, and are covered in the section below on the boot
command. If this is not specified, it is assumed the installer will start
@@ -66,12 +67,25 @@ Optional:
five seconds and one minute 30 seconds, respectively. If this isn't specified,
the default is 10 seconds.
* `disk_size` (int) - The size, in megabytes, of the hard disk to create
for the VM. By default, this is 40000 (40 GB).
* `guest_additions_path` (string) - The path on the guest virtual machine
where the VirtualBox guest additions ISO will be uploaded. By default this
is "VBoxGuestAdditions.iso" which should upload into the login directory
of the user. This is a [configuration template](/docs/templates/configuration-templates.html)
where the `Version` variable is replaced with the VirtualBox version.
* `guest_additions_sha256` (string) - The SHA256 checksum of the guest
additions ISO that will be uploaded to the guest VM. By default the
checksums will be downloaded from the VirtualBox website, so this only
needs to be set if you want to be explicit about the checksum.
* `guest_additions_url` (string) - The URL to the guest additions ISO
to upload. This can also be a file URL if the ISO is at a local path.
By default the VirtualBox builder will go and download the proper
guest additions ISO from the internet.
* `guest_os_type` (string) - The guest OS type being installed. By default
this is "other", but you can get _dramatic_ performance improvements by
setting this to the proper value. To view all available values for this
@@ -79,6 +93,11 @@ Optional:
how to optimize the virtual hardware to work best with that operating
system.
* `headless` (bool) - Packer defaults to building VirtualBox
virtual machines by launching a GUI that shows the console of the
machine being built. When this value is set to true, the machine will
start without a console.
* `http_directory` (string) - Path to a directory to serve using an HTTP
server. The files in this directory will be available over HTTP that will
be requestable from the virtual machine. This is useful for hosting
@@ -97,8 +116,9 @@ Optional:
* `output_directory` (string) - This is the path to the directory where the
resulting virtual machine will be created. This may be relative or absolute.
If relative, the path is relative to the working directory when `packer`
is executed. By default this is "virtualbox". This directory must not exist
or be empty prior to running the builder.
is executed. This directory must not exist or be empty prior to running the builder.
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
of the build.
* `shutdown_command` (string) - The command to use to gracefully shut down
the machine once all the provisioning is done. By default this is an empty
@@ -123,7 +143,7 @@ Optional:
* `ssh_wait_timeout` (string) - The duration to wait for SSH to become
available. By default this is "20m", or 20 minutes. Note that this should
be quite long since the timer begins as soon as virtual machine is booted.
be quite long since the timer begins as soon as the virtual machine is booted.
* `vboxmanage` (array of array of strings) - Custom `VBoxManage` commands to
execute in order to further customize the virtual machine being created.
@@ -142,7 +162,8 @@ Optional:
the home directory.
* `vm_name` (string) - This is the name of the VMX file for the new virtual
machine, without the file extension. By default this is "packer".
machine, without the file extension. By default this is "packer-BUILDNAME",
where "BUILDNAME" is the name of the build.
## Boot Command
@@ -166,7 +187,7 @@ will be replaced by the proper key:
* `<tab>` - Simulates pressing the tab key.
* `<wait>` - Adds a one second pause before sending any additional keys. This
* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This
is useful if you have to generally wait for the UI to update before typing more.
In addition to the special keys, each command to type is treated as a
@@ -191,7 +212,7 @@ an Ubuntu 12.04 installer:
"fb=false debconf/frontend=noninteractive ",
"keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
"keyboard-configuration/variant=USA console-setup/ask_detect=false ",
"initrd=/install/initrd.gz -- <enter>"
"initrd=/install/initrd.gz -- &lt;enter&gt;"
]
</pre>
@@ -28,7 +28,8 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
"iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.2-server-amd64.iso",
"iso_md5": "af5f788aee1b32c4b2634734309cc9e9",
"ssh_username": "packer",
"ssh_wait_timeout": "30s"
"ssh_wait_timeout": "30s",
"shutdown_command": "shutdown -P now"
}
</pre>
@@ -77,6 +78,13 @@ Optional:
OS type, VMware may perform some optimizations or virtual hardware changes
to better support the operating system running in the virtual machine.
* `headless` (bool) - Packer defaults to building VMware
virtual machines by launching a GUI that shows the console of the
machine being built. When this value is set to true, the machine will
start without a console. For VMware machines, Packer will output VNC
connection information in case you need to connect to the console to
debug the build process.
* `http_directory` (string) - Path to a directory to serve using an HTTP
server. The files in this directory will be available over HTTP that will
be requestable from the virtual machine. This is useful for hosting
@@ -95,8 +103,15 @@ Optional:
* `output_directory` (string) - This is the path to the directory where the
resulting virtual machine will be created. This may be relative or absolute.
If relative, the path is relative to the working directory when `packer`
is executed. By default this is "vmware". This directory must not exist
or be empty prior to running the builder.
is executed. This directory must not exist or be empty prior to running the builder.
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
of the build.
* `skip_compaction` (bool) - VMware-created disks are defragmented
and compacted at the end of the build process using `vmware-vdiskmanager`.
In certain rare cases, this might actually end up making the resulting disks
slightly larger. If you find this to be the case, you can disable compaction
using this configuration value.
* `shutdown_command` (string) - The command to use to gracefully shut down
the machine once all the provisioning is done. By default this is an empty
@@ -115,10 +130,22 @@ Optional:
* `ssh_wait_timeout` (string) - The duration to wait for SSH to become
available. By default this is "20m", or 20 minutes. Note that this should
be quite long since the timer begins as soon as virtual machine is booted.
be quite long since the timer begins as soon as the virtual machine is booted.
* `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to
upload into the VM. Valid values are "darwin", "linux", and "windows".
By default, this is empty, which means VMware tools won't be uploaded.
* `tools_upload_path` (string) - The path in the VM to upload the VMware
tools. This only takes effect if `tools_upload_flavor` is non-empty.
This is a [configuration template](/docs/templates/configuration-templates.html)
that has a single valid variable: `Flavor`, which will be the value of
`tools_upload_flavor`. By default the upload path is set to
`{{.Flavor}}.iso`.
* `vm_name` (string) - This is the name of the VMX file for the new virtual
machine, without the file extension. By default this is "packer".
machine, without the file extension. By default this is "packer-BUILDNAME",
where "BUILDNAME" is the name of the build.
* `vmdk_name` (string) - The filename of the virtual disk that'll be created,
without the extension. This defaults to "packer".
@@ -155,7 +182,7 @@ will be replaced by the proper key:
* `<tab>` - Simulates pressing the tab key.
* `<wait>` - Adds a one second pause before sending any additional keys. This
* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This
is useful if you have to generally wait for the UI to update before typing more.
In addition to the special keys, each command to type is treated as a
@@ -180,6 +207,6 @@ an Ubuntu 12.04 installer:
"fb=false debconf/frontend=noninteractive ",
"keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
"keyboard-configuration/variant=USA console-setup/ask_detect=false ",
"initrd=/install/initrd.gz -- <enter>"
"initrd=/install/initrd.gz -- &lt;enter&gt;"
]
</pre>
@@ -14,7 +14,7 @@ artifacts that are created will be outputted at the end of the build.
* `-debug` - Disables parallelization and enables debug mode. Debug mode flags
the builders that they should output debugging information. The exact behavior
of debug mode is left to the builder. In general, builders usually will stop
between each step, waiting keyboard input before continuing. This will allow
between each step, waiting for keyboard input before continuing. This will allow
the user to inspect state and so on.
* `-except=foo,bar,baz` - Builds all the builds except those with the given
@@ -52,7 +52,7 @@ it is not required. You're allowed to make the help look like anything
you please.
```
Usage: packer COMMAND ARGS...
Usage: packer COMMAND [options] ARGS...
Brief one or two sentence about the function of the command.
@@ -8,7 +8,7 @@ Plugins allow new functionality to be added to Packer without
modifying the core source code. Packer plugins are able to add new
commands, builders, provisioners, hooks, and more. In fact, much of Packer
itself is implemented by writing plugins that are simply distributed with
the Packer. For example, all the commands, builders, provisioners, and more
Packer. For example, all the commands, builders, provisioners, and more
that ship with Packer are implemented as Plugins that are simply hardcoded
to load with Packer.
@@ -38,7 +38,7 @@ what each method should do.
<pre class="prettyprint">
type PostProcessor interface {
Configure(interface{}) error
PostProcess(Ui, Artifact) (Artifact, error)
PostProcess(Ui, Artifact) (a Artifact, keep bool, err error)
}
</pre>
@@ -79,3 +79,15 @@ a "compress" post-processor that is responsible for compressing files,
the transformation would be taking the `Files()` from the original artifact,
compressing them, and creating a new artifact with a single file: the
compressed archive.
The result signature of this method is `(Artifact, bool, error)`. Each
return value is explained below:
* `Artifact` - The newly created artifact if no errors occurred.
* `bool` - If true, the input artifact will forcefully be kept. By default,
Packer typically deletes all input artifacts, since the user doesn't generally
want intermediary artifacts. However, some post-processors depend on the
previous artifact existing. If this is `true`, it forces packer to keep the
artifact around.
* `error` - Non-nil if there was an error in any way. If this is the case,
the other two return values are ignored.
@@ -20,7 +20,7 @@ for your system and download it. Packer is packaged as a "zip" file.
Next, unzip the downloaded package into a directory where Packer will be
installed. On Unix systems, `~/packer` or `/usr/local/packer` is generally good,
depending on whether you want to restrict the install to just your user
or install it system-wide. On Windows systems, you can put it whereever you'd
or install it system-wide. On Windows systems, you can put it wherever you'd
like.
After unzipping the package, the directory should contain a set of binary
@@ -47,6 +47,6 @@ Available commands are:
If you get an error that `packer` could not be found, then your PATH
environmental variable was not setup properly. Please go back and ensure
that yoru PATH variable contains the directory which has Packer installed.
that your PATH variable contains the directory which has Packer installed.
Otherwise, Packer is installed and you're ready to go!
@@ -0,0 +1,34 @@
---
layout: "docs"
---
# File Provisioner
Type: `file`
The file provisioner uploads files to machines built by Packer. The
recommended usage of the file provisioner is to use it to upload files,
and then use [shell provisioner](/docs/provisioners/shell.html) to move
them to the proper place, set permissions, etc.
## Basic Example
<pre class="prettyprint">
{
"type": "file",
"source": "app.tar.gz",
"destination": "/tmp/app.tar.gz"
}
</pre>
## Configuration Reference
The available configuration options are listed below. All elements are required.
* `source` (string) - The path to a local file to upload to the machine. The
path can be absolute or relative. If it is relative, it is relative to the
working directory when Packer is executed.
* `destination` (string) - The path where the file will be uploaded to in the
machine. This value must be a writable location and any parent directories
must already exist.
@@ -23,7 +23,7 @@ The example below is fully functional.
## Configuration Reference
The reference of available configuratin options is listed below. The only
The reference of available configuration options is listed below. The only
required element is either "inline" or "script". Every other option is optional.
Exactly _one_ of the following is required:
@@ -51,12 +51,17 @@ Optional parameters:
`key=value`.
* `execute_command` (string) - The command to use to execute the script.
By default this is `{{ .Vars }} sh {{ .Path }}`. The value of this is
By default this is `chmod +x {{ .Path }}; {{ .Vars }} {{ .Path }}`. The value of this is
treated as [configuration template](/docs/templates/configuration-
templates.html). There are two available variables: `Path`, which is
the path to the script to run, and `Vars`, which is the list of
`environment_vars`, if configured.
* `inline_shebang` (string) - The
[shebang](http://en.wikipedia.org/wiki/Shebang_(Unix)) value to use when
running commands specified by `inline`. By default, this is `/bin/sh`.
If you're not using `inline`, then this configuration has no effect.
* `remote_path` (string) - The path where the script will be uploaded to
in the machine. This defaults to "/tmp/script.sh". This value must be
a writable location and any parent directories must already exist.
+2 -1
View File
@@ -54,6 +54,7 @@ missing valid AWS access keys. Otherwise, it would work properly with
"secret_key": "...",
"region": "us-east-1",
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"ssh_username": "ubuntu",
"ami_name": "packer {{.CreateTime}}"
}
@@ -62,7 +63,7 @@ missing valid AWS access keys. Otherwise, it would work properly with
"provisioners": [
{
"type": "shell",
"path": "setup_things.sh"
"script": "setup_things.sh"
}
]
}
@@ -72,7 +72,7 @@ A **sequence definition** is a JSON array comprised of other **simple** or
**detailed** definitions. The post-processors defined in the array are run
in order, with the artifact of each feeding into the next, and any intermediary
artifacts being discarded. A sequence definition may not contain another
sequence definition. Sequnce definitions are used to chain together multiple
sequence definition. Sequence definitions are used to chain together multiple
post-processors. An example is shown below, where the artifact of a build is
compressed then uploaded, but the compressed result is not kept.
@@ -93,7 +93,7 @@ are simply shortcuts for a **sequence** definition of only one element.
## Input Artifacts
When using post-processors, the input artifact (coming from a builder or
another post-proccessor) is discarded by default after the post-processor runs.
another post-processor) is discarded by default after the post-processor runs.
This is because generally, you don't want the intermediary artifacts on the
way to the final artifact created.
+1 -1
View File
@@ -39,7 +39,7 @@ the `type` key. This key specifies the name of the provisioner to use.
Additional keys within the object are used to configure the provisioner,
with the exception of a handful of special keys, covered later.
As an example, the "shell" provisioner requires at least the `script` key,
As an example, the "shell" provisioner requires a key such as `script`
which specifies a path to a shell script to execute within the machines
being created.
@@ -14,7 +14,7 @@ of templates that can be readily used with Packer by simply converting them.
## Installation and Usage
Since Veewee itself is a Ruby project, so to is the veewee-to-packer
Since Veewee itself is a Ruby project, so too is the veewee-to-packer
application so that it can read the Veewee configurations. Install it using RubyGems:
```
@@ -22,7 +22,7 @@ $ gem install veewee-to-packer
...
```
Once installed, usage is easy! Just point call `veewee-to-packer` pointed
Once installed, usage is easy! Just point `veewee-to-packer`
at the `definition.rb` file of any template. The converter will output
any warnings or messages about the conversion. The example below converts
a CentOS template:
@@ -49,7 +49,7 @@ specific builder configurations.
None, really. The tool will tell you if it can't convert a part of a
template, and whether that is a critical error or just a warning.
Most of Veewees functions translate perfectly over to Packer. There are
Most of Veewee's functions translate perfectly over to Packer. There are
still a couple missing features in Packer, but they're minimal.
## Bugs
+1 -1
View File
@@ -17,7 +17,7 @@ page_title: "Downloads"
Below are all available downloads for the latest version of Packer
(<%= latest_version %>). Please download the proper package for your
operating system and architecture. You can find SHA256 checksums
for packages <a href="http://dl.bintray.com/mitchellh/packer/<%= latest_version %>_SHA256SUMS?direct">here</a>.
for packages <a href="https://dl.bintray.com/mitchellh/packer/<%= latest_version %>_SHA256SUMS?direct">here</a>.
</p>
</div>
</div>
@@ -73,7 +73,7 @@ The entire template should now [look like this](https://gist.github.com/mitchell
Additional builders are simply added to the `builders` array in the template.
This tells Packer to build multiple images. The builder `type` values don't
even need tdo be different! In fact, if you wanted to build multiple AMIs,
even need to be different! In fact, if you wanted to build multiple AMIs,
you can do that as well.
Validate the template with `packer validate`. This is always a good practice.
+1 -1
View File
@@ -9,7 +9,7 @@ next_title: "Why Use Packer?"
# Introduction to Packer
Welcome to the world of Packer! This introduction guide will show you what
packer is, explain why it exists, the benefits it has to offer, and how
Packer is, explain why it exists, the benefits it has to offer, and how
you can get started with it. If you're already familiar with Packer, the
[documentation](/docs) provides more of a reference for all available features.
+1
View File
@@ -36,6 +36,7 @@
<ul>
<li><h4>Provisioners</h4></li>
<li><a href="/docs/provisioners/shell.html">Shell Scripts</a></li>
<li><a href="/docs/provisioners/file.html">File Uploads</a></li>
<li><a href="/docs/provisioners/custom.html">Custom</a></li>
</ul>
+2
View File
@@ -167,9 +167,11 @@ table {
::selection {
background: #ffff00; /* Safari */
color: $black;
}
::-moz-selection {
background: #ffff00; /* Firefox */
color: $black;
}
input {