Compare commits

..

197 Commits

Author SHA1 Message Date
Mitchell Hashimoto 12ba7ae443 v0.4.1 2013-12-07 21:05:53 -08:00
Mitchell Hashimoto d125dc9244 builder/vmware: clarify guestiphack error message [GH-639] 2013-12-07 10:31:57 -08:00
Mitchell Hashimoto 013c1829e4 provisioner/puppet-masterless: check for hiera config path [GH-656] 2013-12-06 19:30:06 -08:00
Mitchell Hashimoto 3b2e3456f6 website: fix docs for vmware esxi discrep [GH-674] 2013-12-06 19:14:20 -08:00
Mitchell Hashimoto ea09c1105c builder/vmware: correct logic in cehcking if file is still there 2013-12-06 19:12:49 -08:00
Mitchell Hashimoto b12871dc28 builder/vmware: more resilient to racing deletes [GH-675] 2013-12-06 19:11:40 -08:00
Mitchell Hashimoto ece02bb19d Update CHANGELOG 2013-12-06 19:05:19 -08:00
Mitchell Hashimoto 5997573f37 builder/amazon/instance: add location to upload command [GH-679] 2013-12-06 19:04:40 -08:00
Mitchell Hashimoto a21b3cee23 common: fix tests 2013-12-06 18:36:16 -08:00
Mitchell Hashimoto 382f813694 builder/amazon/chroot: remove unused import 2013-12-06 18:32:21 -08:00
Mitchell Hashimoto 1e2ce0e3e3 common: allow files that don't exist to be URLs [GH-683] 2013-12-06 18:31:56 -08:00
Mitchell Hashimoto fa9804857a builder/amazon/chroot: block when obtaining lock [GH-689] 2013-12-06 18:28:48 -08:00
Mitchell Hashimoto b5503a81d6 Merge pull request #657 from mitchellh/docs-users-vars
website: user Variables in Intro
2013-12-06 18:18:26 -08:00
Mitchell Hashimoto 5e9cc73b1f Update CHANGELOG 2013-12-06 18:17:20 -08:00
Mitchell Hashimoto 68540cff09 Merge pull request #660 from CpuID/master
builder/amazon/ebs: AssociatePublicIpAddress for launching
2013-12-06 18:15:19 -08:00
Mitchell Hashimoto 85765dd386 Revert "Merge pull request #663 from ktruong7/public-AMI"
This reverts commit b2fc77870b, reversing
changes made to 4789d3acc8.

This is already possible by setting groups.
2013-12-06 18:09:05 -08:00
Mitchell Hashimoto b2fc77870b Merge pull request #663 from ktruong7/public-AMI
builder/amazon/all: added option to make AMI public
2013-12-06 18:05:45 -08:00
Mitchell Hashimoto 4789d3acc8 Update CHANGELOG 2013-12-06 17:59:45 -08:00
Mitchell Hashimoto d5341d7b00 Merge pull request #637 from johnbellone/master
builder/openstack: add ProxyUrl parameter for OpenStack builder
2013-12-06 17:58:15 -08:00
Mitchell Hashimoto 7ec1c8dc44 Merge pull request #681 from pcfens/qemu-img-formats
builder/qemu: qemu format img is invalid
2013-12-06 17:55:32 -08:00
Mitchell Hashimoto 0b0cada619 Merge pull request #685 from veshi/master
builder/vmware: trivial bug fix in mechanism to clean installation ISOs for VMware build...
2013-12-06 17:55:11 -08:00
adrian kalaveshi a2de093b72 Trivial bug fix in mechanism to clean installation ISOs for VMware builder
The resolution of Bug #608 still had issues.  This change removes a
superfluous period.

Testing Done:
- built packer with my change
- built VMware VMs and saw that the vmx file had the expected
  changes.
2013-12-05 21:36:34 +00:00
Phil Fenstermacher 43bf7b2859 Changed error message and docs to match code - qemu-img options are qcow2 and raw 2013-12-04 14:05:07 -05:00
Matthew Hooker 23c13e29b1 Merge pull request #671 from kyleconroy/patch-1
documentation/README: Point gox link to correct repository
2013-11-30 00:03:08 -08:00
Kyle Conroy b3dbdc8c04 Point gox link to correct repository 2013-11-30 00:01:18 -08:00
ktruong7 12245bd79c fixed format issue 2013-11-27 10:48:32 -05:00
ktruong7 a6a35e17df fixed format issues 2013-11-27 10:47:06 -05:00
ktruong7 892e718c52 Added option to make ami public automatically 2013-11-26 09:59:16 -05:00
Nathan Sullivan edff648f73 fix some debugging for PublicIpAddress 2013-11-26 15:03:45 +10:00
Nathan Sullivan 996a6bab01 remove excess debugging 2013-11-26 15:02:43 +10:00
Nathan Sullivan 53ea9b97bf revert whitespace changes - not necessary anymore 2013-11-26 15:01:57 +10:00
Nathan Sullivan 98f7e819fd working AssociatePublicIpAddress support 2013-11-26 14:45:06 +10:00
Nathan Sullivan 90f1e031fd goamz aspects of AssociatePublicIpAddress working now 2013-11-26 14:14:43 +10:00
Nathan Sullivan 2f53dbe03f reverting testing stuff 2013-11-26 13:46:32 +10:00
Nathan Sullivan 6e619d7351 lets try a string instead of a bool for AssociatePublicIpAddress 2013-11-26 13:44:39 +10:00
Nathan Sullivan 2ea61e69bc oops bugfix for work on https://github.com/mitchellh/packer/issues/578 2013-11-26 12:05:37 +10:00
Nathan Sullivan b3657db43e docs update for https://github.com/mitchellh/packer/issues/578 2013-11-26 11:41:39 +10:00
Nathan Sullivan da0edb2aa7 https://github.com/mitchellh/packer/issues/578 2013-11-26 11:32:08 +10:00
ktruong7 453dab7d26 Merge branch 'master' of github.com:mitchellh/packer 2013-11-25 10:54:15 -05:00
Jack Pearkes ecef4dc426 website: prettyprint variables examle in parallel builds 2013-11-25 13:51:32 +01:00
Jack Pearkes bdd75a6c6f website: user user varibles in the intro (parallel builds) 2013-11-25 13:47:23 +01:00
Jack Pearkes f36d53c60f website: use user variables in the intro (ec2) 2013-11-25 13:36:32 +01:00
Jack Pearkes 8776680b70 website: move iam policy documentation to the root amazon page 2013-11-25 13:27:38 +01:00
Mitchell Hashimoto f4f45b814f Merge pull request #649 from bpot/chroot_gotcha
website: document chroot running process gotcha
2013-11-22 09:44:41 -08:00
Mitchell Hashimoto 4c16865aaa Merge pull request #650 from mitchellh/readme-gox
readme: add a note about the gox dependency for compiling
2013-11-22 08:41:50 -08:00
Mitchell Hashimoto 62ddbec0f6 Merge pull request #651 from mwhooker/readme/bzr
README/developing packer: Mention the bazaar requirement.
2013-11-22 08:39:34 -08:00
Matthew Hooker 131686d10e README/developing packer
Mention the bazaar requirement.

https://launchpad.net/tomb is the only package which uses bazaar so far.
2013-11-22 03:17:16 -08:00
Jack Pearkes cc5fc6dfae readme: add a note about the gox dependency for compiling
I thought about putting it in compile.sh or devcompile.sh, but then
I realized if gox changes upstream it will be buried and potentially
confusing to update. Hence including the `-u` in the readme for
`go get`.
2013-11-22 11:31:02 +01:00
Bob Potter c454b19898 website: document chroot running process gotcha 2013-11-22 00:11:40 -06:00
Mitchell Hashimoto dc0b849891 Merge pull request #646 from jtopper/bundle_vol_command_website
website: Match default ec2-bundle-vol documented on the website to the one that's used by the code.
2013-11-21 14:42:02 -08:00
Jon Topper 09a978d97e Match ec2-bundle-vol on web to code
Issue #248 contained a PR which changes the way bundle_vol_command is
used by default. This change to the defaults was not reflected on the
website, so I encountered the same issue when making changes to the
bundle_vol_command in my config.
2013-11-21 18:29:19 +00:00
Mitchell Hashimoto b9be641f8c Merge pull request #641 from ubermuda/patch-1
website: typo in vmware docs' YAML front matter
2013-11-20 16:34:48 -08:00
Geoffrey Bachelet f2381d0735 Typo in vmware docs' YAML front matter 2013-11-20 22:05:22 +01:00
Mitchell Hashimoto 32e2623867 packer: don't change background color in UI 2013-11-19 21:31:54 -08:00
John Bellone 41b5a4f28a Website: Update timeout to reflect real default.
The default in the actual code is 5m.
2013-11-19 17:22:17 -05:00
Mitchell Hashimoto 01049413bd website: Fix typo with esxi config 2013-11-19 14:12:49 -08:00
Mitchell Hashimoto 879e8d3ef4 up version for dev 2013-11-19 13:49:26 -08:00
Mitchell Hashimoto 8a36f8c6d5 scripts: upload works with new build style 2013-11-19 13:30:09 -08:00
Mitchell Hashimoto fc4fffa38a v0.4.0 2013-11-19 12:53:45 -08:00
Mitchell Hashimoto c7d59342d2 builder/amazon/chroot: don't choose partition mount point if taken
[GH-635]
2013-11-19 12:50:33 -08:00
Mitchell Hashimoto 6427a1fe0d builder/virtualbox: use VBOX_INSTALL_PATH to find VBoxManage 2013-11-19 12:32:10 -08:00
John Bellone dbf6acf1e7 Add ProxyUrl parameter for OpenStack builder.
In my particular case I need to use a separate HTTP proxy to access my
OpenStack API than what is set in the environment. This commit adds an
optional parameter to the builder configuration and overrides the
net/http client that is used by the gophercloud library.
2013-11-19 13:55:55 -05:00
Mitchell Hashimoto 1b429acd2f website: vsphere endpoint docs 2013-11-18 16:23:28 -08:00
Mitchell Hashimoto 147265cfcf website: update chef solo docs 2013-11-18 16:14:33 -08:00
Mitchell Hashimoto 99605a0816 builder/vmware: ESX artifacts have a different builder ID
This is so that things like vagrant post-processors and vsphere
post processors don't work.
2013-11-18 16:10:55 -08:00
Mitchell Hashimoto 12478ee276 Update CHANGELOG 2013-11-18 16:06:05 -08:00
Mitchell Hashimoto 7d7e626e6f post-processor/vsphere: cleanups
/cc @mheidenr - Some basic cleanups. :)
2013-11-18 16:04:38 -08:00
Mitchell Hashimoto 4431257026 fmt 2013-11-18 15:57:31 -08:00
Mitchell Hashimoto 15ba287ceb Merge pull request #613 from mheidenr/master
added post-processor-vsphere
2013-11-18 15:57:12 -08:00
Mitchell Hashimoto 903c62e2e7 Merge pull request #619 from dougm/esx-paths
builder/vmware: Path related fixes in esx5 driver
2013-11-18 15:56:15 -08:00
Mitchell Hashimoto c32d4aee5c update changelog 2013-11-18 15:49:34 -08:00
Mitchell Hashimoto 37dbe0ba20 provisioner/chef-solo: test for encrypted data bag secret path config
/cc @matheeeny - Tests :)
2013-11-18 15:48:13 -08:00
Mitchell Hashimoto fadc1a22bb provisioner/chef-solo: naming nitpick
/cc @matheeeny - I prefer to be more explicit and say this is the path,
rather than the secret value itself.
2013-11-18 15:46:37 -08:00
Mitchell Hashimoto b38db14dcf Merge pull request #625 from matheeeny/encrypted-data-bags
provisioner/chef-solo: add support for chef-solo encrypted data bags
2013-11-18 15:44:55 -08:00
Mitchell Hashimoto b5d0385115 post-processor/vagrant: style nitpicks
/cc @rasa
2013-11-18 15:38:54 -08:00
Mitchell Hashimoto 50b93066b5 update makefile to latest 2013-11-18 15:37:14 -08:00
Mitchell Hashimoto d7ef5616c1 scripts: update to use gox 2013-11-18 15:37:14 -08:00
Mitchell Hashimoto 7a623fcd5d Merge pull request #634 from rasa/skip-gzip-compression-when-level-is-0-v2
post-processor/vagrant: skip gzip compression if compression_level=0
2013-11-18 15:36:52 -08:00
Ross Smith II f3a2aeeff9 Skip gzip compression if compression_level=0 2013-11-18 10:38:19 -08:00
Mitchell Hashimoto 5bec1d9ef2 Merge pull request #627 from jquast/patch-1
website: fix run-on italicizing for description of data_bags_path
2013-11-14 16:19:43 -08:00
Jeff Quast 67b0d69fb6 Update chef-solo.html.markdown
underscore of "data_bags" should be escaped; causes run-on italic
2013-11-14 16:13:20 -08:00
Andrew Matheny 15cf639c91 Add support for passing encrypted data bag secret to chef-solo
Adds additional option to chef-solo provisioner for an encrypted data bag
secret file.  Local file is copied up and referenced in solo.rb
2013-11-13 13:39:29 -05:00
Doug MacEachern 9c32d73f2e builder/vmware: Double check that OutputDirectory does not already exist
The initial check in Builder.Prepare does not use the OutputDir interface.
stepPrepareOutputDir also checks if OutputDir exists, error out there unless
using -force so we get the same behavior when RemoteType is esx5.
2013-11-12 13:11:02 -08:00
Doug MacEachern afa9f4aa3e builder/vmware: Add ListFiles and Remove methods to OutputDir interface
- Fixes builder/vmware when using RemoteType esx5,
  as there is no longer a local OutputDir
2013-11-12 12:49:57 -08:00
Doug MacEachern 2503f8a4ba builder/vmware: Path related fixes in esx5 driver
- Fix mkdir of packer_cache in ESX5Driver.UploadISO

- Fix vmx path in ESX5Driver.IsRunning
2013-11-12 09:17:28 -08:00
Mitchell Hashimoto 9e333e03db builder/docker: ctrl-C works during provisioning 2013-11-12 16:24:16 +00:00
Mitchell Hashimoto 7992693737 builder/vmware: ctrl-c works during wait for boot 2013-11-12 16:20:52 +00:00
Mitchell Hashimoto e28e827a16 builder/virtualbox: ctrl-c works during wait for boot 2013-11-12 16:19:38 +00:00
mheidenr 4743191d43 Merge https://github.com/mitchellh/packer into merge 2013-11-12 09:18:48 +01:00
Mitchell Hashimoto f8dbe5246c Merge pull request #615 from bbasata/fix-typo
website: Fix spelling in Intro documentation
2013-11-12 00:07:26 -08:00
mheidenr 852ae18fe9 Merge https://github.com/mitchellh/packer into merge 2013-11-12 08:26:24 +01:00
Baraa Basata 2951cc1070 Fix spelling in Intro documentation 2013-11-11 21:41:53 -05:00
Mitchell Hashimoto 34ba7c270a Merge pull request #614 from rasa/584-add-golang-version-to-log
core: log golang version in debug output
2013-11-11 14:35:21 -08:00
Ross Smith II 4337843907 fixed #584 log golang version in debug output 2013-11-11 08:42:27 -08:00
mheidenr f673aaee0e added post-processor-vsphere 2013-11-11 09:56:51 +01:00
Mitchell Hashimoto 80ed0d1d0c builder/docker: verify docker is available on path 2013-11-09 21:26:05 -08:00
Mitchell Hashimoto f18516a65a website: better document the dockerfile interaction with the builder 2013-11-09 19:40:38 -08:00
Mitchell Hashimoto f1de5dbcac builder/docker: say when killing the container 2013-11-09 19:17:27 -08:00
Mitchell Hashimoto 47040b5aef builder/docker: remove unused file 2013-11-09 19:11:07 -08:00
Mitchell Hashimoto d786e4e267 Update CHANGELOG 2013-11-09 19:09:58 -08:00
Mitchell Hashimoto 8cc09bcd56 Merge branch 'f-docker-builder': Docker builder
This introduces a Docker builder. The docker builder is able to create
containers by starting an existing Docker image, provisioning it using
standard practices, and then exporting it using `docker export`.
2013-11-09 19:08:05 -08:00
Mitchell Hashimoto 2da3c995a2 builder/docker: stream output from commands 2013-11-09 19:06:03 -08:00
Mitchell Hashimoto 167e91746f provisioner/chef-solo: Say using Chef-solo 2013-11-09 18:47:20 -08:00
Mitchell Hashimoto 5bae4980f8 builder/docker: UploadDir 2013-11-09 18:44:43 -08:00
Mitchell Hashimoto 2851e188e1 builder/docker: show stderr if docker fails to run 2013-11-09 17:27:30 -08:00
Mitchell Hashimoto 3bbe7024cd website: doc pull 2013-11-09 17:22:39 -08:00
Mitchell Hashimoto cff060a490 builder/docker: ability to disable pull 2013-11-09 17:21:24 -08:00
Mitchell Hashimoto 0e581bfec3 builder/docker: config refactor 2013-11-09 17:07:14 -08:00
Mitchell Hashimoto 1cf89afe06 website: document dockerfile 2013-11-09 13:59:34 -08:00
Mitchell Hashimoto 9438041fa3 website: start documenting the builder 2013-11-09 13:28:00 -08:00
Mitchell Hashimoto b4abf826da builder/docker: artifact 2013-11-09 13:22:13 -08:00
Mitchell Hashimoto e88a0a57b9 builder/docker: StepExport tests 2013-11-09 13:15:51 -08:00
Mitchell Hashimoto 20f76c6ffc builder/docker: StepRun tests 2013-11-09 13:03:01 -08:00
Mitchell Hashimoto b10abe30e0 builder/docker: StepPull test, driver abstraction for tests 2013-11-09 12:12:23 -08:00
Mitchell Hashimoto 13267c856f builder/docker: test StepTempDir 2013-11-09 11:58:20 -08:00
Mitchell Hashimoto b3dc5411ef builder/docker: config validation test 2013-11-09 11:47:32 -08:00
Mitchell Hashimoto 027d00b2fc builder/docker: perform cleanup in run method, not prematurely 2013-11-09 10:15:25 -08:00
Mitchell Hashimoto 5f752269df builder/docker: Communicator.Start doesn't block 2013-11-09 10:13:27 -08:00
Mitchell Hashimoto f2475baa7a builder/docker: export the final image 2013-11-09 09:48:36 -08:00
Matthew Hooker 9fb347b96a Merge branch 'patch-3' of https://github.com/fgo/packer
website: Fix missing whitespace. [ci skip]
2013-11-09 06:09:13 -08:00
Francis Go 032ab69691 Fix grammar in Terminology documentation 2013-11-10 00:30:54 +11:00
Mitchell Hashimoto 0bbb8116f5 builder/docker: remove the exit code file when we're done 2013-11-09 00:47:37 -08:00
Mitchell Hashimoto 13abefef9a builder/docker: command output and exit codes work
/cc @mwhooker - CCing you on this because it is also ridiculous. See the
big comments
2013-11-09 00:33:36 -08:00
Mitchell Hashimoto ac5bf3f3b0 builder/docker: comment the sleep on remote exec 2013-11-09 00:06:45 -08:00
Mitchell Hashimoto b06dd1567a builder/docker: fix upload for copy to work 2013-11-09 00:05:44 -08:00
Mitchell Hashimoto 41b17c3316 builder/docker: Remote execution works!
/cc @mwhooker - WOW. By luck, I had a hunch that maybe something like
this might be going on based on straces I was reading. Check:
https://github.com/dotcloud/docker/issues/2628

Anyways, this works now. No more blocker!
2013-11-08 23:59:25 -08:00
Mitchell Hashimoto 4cdd532a93 builder/docker: a non-working communicator 2013-11-08 23:43:41 -08:00
Mitchell Hashimoto 4e6d46bbd0 builder/docker: start a container 2013-11-08 22:17:46 -08:00
Mitchell Hashimoto ab1f83fa11 builder/docker: tests for some exec stuff 2013-11-08 22:02:47 -08:00
Mitchell Hashimoto 539d415a27 builder/docker: pull images 2013-11-08 22:00:57 -08:00
Mitchell Hashimoto 3213544e8f main: Default config has docker 2013-11-08 16:57:34 -08:00
Mitchell Hashimoto bb54551576 plugin/builder-docker 2013-11-08 16:57:13 -08:00
Mitchell Hashimoto 049e3adc5d builder/docker: boilerplate 2013-11-08 16:55:02 -08:00
Mitchell Hashimoto 352e26b6bf Ignore Vagrant stuff 2013-11-08 16:41:42 -08:00
Mitchell Hashimoto aa59cd55ae builder/vmware: downcase more vmx data 2013-11-08 14:21:31 -08:00
Mitchell Hashimoto 8062fd729d builder/vmware: Fusion suppresses upgrade requests 2013-11-08 12:19:09 -08:00
ktruong7 7760a9d746 Merge branch 'master' of github.com:mitchellh/packer 2013-11-08 15:10:19 -05:00
Mitchell Hashimoto 1915c00525 builder/vmware: fix local output dir exists check 2013-11-08 11:48:04 -08:00
Mitchell Hashimoto a436aa2e9c website: document need for AMI tools 2013-11-08 11:23:12 -08:00
Mitchell Hashimoto 9e9f31314d builder/amazon/chroot: fix compile error 2013-11-08 11:19:10 -08:00
Mitchell Hashimoto 756880cdd1 Update CHANGELOG 2013-11-08 11:05:21 -08:00
Mitchell Hashimoto 21c7d1c8b1 builder/amazon/chroot: allow no such file errors on upload [GH-588] 2013-11-08 10:53:54 -08:00
Mitchell Hashimoto e4b719a286 builder/amazon/chroot: quote commands and pass through a shell [GH-581] 2013-11-08 10:22:56 -08:00
Mitchell Hashimoto 0992c7bd19 Merge pull request #609 from fgo/patch-2
website: Fix spelling in Provision documentation [ci skip]
2013-11-07 23:03:45 -08:00
Mitchell Hashimoto 7495f5bd26 builder/vmware: case-insensitive VMX creation [GH-608] 2013-11-07 22:06:30 -08:00
Francis Go 4b3665e9c0 Fix spelling in Provision documentation [ci skip] 2013-11-08 17:03:15 +11:00
Mitchell Hashimoto aa3788e75b Merge pull request #607 from fgo/patch-1
website: Fix spelling in Use Cases documentation [ci skip]
2013-11-07 21:35:28 -08:00
Mitchell Hashimoto 226c244a46 builder/vmware: start/stop ESXi5 VMX with proper path 2013-11-07 21:22:12 -08:00
Mitchell Hashimoto f88301a890 builder/vmware: only unregister if registration success 2013-11-07 21:19:24 -08:00
Mitchell Hashimoto 5f380a614f builder/vmware: register the VMX to the proper path on esx 2013-11-07 21:18:25 -08:00
Mitchell Hashimoto 4fd6b3222f builder/vmware: log when writing a VMX 2013-11-07 21:03:15 -08:00
Mitchell Hashimoto d1134ea788 builder/vmware: for remote builds, put VMX in temp dir 2013-11-07 21:02:12 -08:00
Mitchell Hashimoto 8dba90e4ad builder/vmware: set proper default disk type for esx 2013-11-07 20:57:05 -08:00
Mitchell Hashimoto 13a420fe50 builder/vmware: make upload dst,src like normal go 2013-11-07 20:47:15 -08:00
Mitchell Hashimoto a665e01ba8 builder/vmware: esx file check works properly 2013-11-07 20:46:34 -08:00
Francis Go 0f936dac79 Fix spelling in Use Cases documentation 2013-11-08 13:22:25 +11:00
Mitchell Hashimoto 05bece2482 website: esxi docs 2013-11-07 12:44:40 -08:00
Mitchell Hashimoto a087f483be website: vmware esxi docs 2013-11-07 12:41:02 -08:00
Mitchell Hashimoto aba932b58b builder/vmware: upload the ISO to the ESXi machine 2013-11-07 12:28:41 -08:00
Mitchell Hashimoto 7ab4a0084b Update CHANGELOG 2013-11-07 12:03:11 -08:00
Mitchell Hashimoto 4d299ee41d builder/vmware: make things more Go-like
This commit currently breaks the builder though, since the ISo is now
uploaded back into ESX.
2013-11-07 12:01:18 -08:00
Doug MacEachern e7542ff33d builder/vmware: new driver to support building images directly on ESX
This driver talks directly to ESX over ssh, using vim-cmd, esxcli and sh;
no vCenter or VIM api required.

Remote* config properties added to support a remote driver

RemoteDriver interface extends Driver:
* SSHAddress - esx flavor uses esxcli to find the VM's ip address
* Download - esx flavor downloads iso files to a vmfs datastore

Driver can optionally implement the following interfaces:
* VNCAddressFinder - esx flavor needs to check remote ports
* OutputDir - esx driver needs a local and remote OutputDir
* Inventory - esx driver needs to register/unregister VMs
* HostIPFinder - esx flavor needs an address on the same network as esx itself
2013-11-07 12:01:18 -08:00
ktruong7 064e68fe80 update 2013-11-07 11:22:24 -05:00
ktruong7 c5ed552e33 Added a description field for the vmware builder that is written to the vm after a build 2013-11-07 11:13:52 -05:00
Matthew Hooker 24263bc878 Merge pull request #601 from vogxn/master
Update README.md to include updatedeps target
2013-11-07 05:22:06 -08:00
Prasanna Santhanam 72bbaf8295 Update README.md to include updatedeps target
When new dependencies are included builds on your localrepo will fail
until you have all the latest dependencies in the GOPATH. After doing
updatedeps, make will generate the packer binary.

Signed-off-by: Prasanna Santhanam <tsp@apache.org>
2013-11-07 18:48:01 +05:30
Mitchell Hashimoto 2e0ff556f2 builder/qemu: set proper HTTPIP for boot command [GH-597] 2013-11-05 22:05:56 -08:00
Mitchell Hashimoto 34c411eb12 builder/qemu: catch early exits of qemu 2013-11-05 22:03:43 -08:00
Mitchell Hashimoto 9439b980cb website: remove outdated qemu docs 2013-11-05 21:46:38 -08:00
Mitchell Hashimoto da120d3c07 builder/qemu: remove constructor, more Go-like 2013-11-05 21:43:20 -08:00
Mitchell Hashimoto ac9b643e81 builder/qemu: simplify driver, make things more Go-like 2013-11-05 21:40:49 -08:00
Mitchell Hashimoto 144a9d2ac7 builder/qemu: remove floppy support, not used currently 2013-11-05 18:11:38 -08:00
Mitchell Hashimoto 7370acf9e8 builder/qemu: log VM output 2013-11-05 17:59:09 -08:00
Mitchell Hashimoto 3e58ca4c98 builder/qemu: unnecssary log 2013-11-05 17:48:52 -08:00
Mitchell Hashimoto 1d70002719 builder/qemu: get rid of unnecessary step 2013-11-05 17:48:16 -08:00
Mitchell Hashimoto 1de263d3d8 builder/qemu: alphabetize some things 2013-11-05 17:47:22 -08:00
Mitchell Hashimoto 3683d7cd23 builder/qemu: passing tests 2013-11-05 15:44:38 -08:00
Mitchell Hashimoto 77b7169bd5 website: update QEMU docs 2013-11-05 15:34:09 -08:00
Mitchell Hashimoto 36166e2ba5 Update CHANGELOG 2013-11-05 15:28:53 -08:00
Mitchell Hashimoto 701f31ca4d Merge pull request #385 from TranscendComputing/master
builder/qemu: Qemu builder
2013-11-05 15:28:23 -08:00
Mitchell Hashimoto 2e40726807 Update CHANGELOG 2013-11-05 14:34:46 -08:00
Mitchell Hashimoto 8bbf9a8da8 Merge branch 'security-groups' of https://github.com/whiteley/packer into whiteley-security-groups
Conflicts:
	builder/amazon/common/run_config.go
2013-11-05 14:34:12 -08:00
Mitchell Hashimoto 5d7ccb5f78 Update changelog 2013-11-05 14:30:49 -08:00
Mitchell Hashimoto 1adc543d66 post-processor/vagrant: support compression level for DO 2013-11-05 14:30:03 -08:00
Ross Smith II bf79008abf fixed artifact test 2013-11-05 14:27:42 -08:00
Ross Smith II a479482d67 post-processor/vagrant: Adds vagrant support for digitalocean
Conflicts:
	post-processor/vagrant/post-processor.go
2013-11-05 14:27:28 -08:00
Tom Hite 2ac0f633ea Merge branch 'master' of http://github.com/mitchellh/packer 2013-10-17 23:32:18 -05:00
Tom Hite 75fe878633 Fixes #3 via minor documentation fix and setting default properly (in the net_device template value, virtio is incorrect -- must be virtio-net). 2013-10-09 07:11:10 -05:00
Tom Hite 6d37312733 Merge branch 'master' of https://github.com/mitchellh/packer 2013-10-08 18:30:58 +00:00
Tom Hite c1ed89f880 Fixes #1 and Fixes #2 by allowing qemuargs to operate and override defaults. 2013-10-07 20:58:08 -05:00
Matt Whiteley 28712b2648 builder/amazon: instances can be launched with a list of security groups 2013-10-04 10:42:45 -07:00
Tom Hite db10cc6e83 changed error string referring to 'ova' and 'ovf' to refer to 'qcow2' and 'img' as the former were stray leftovers from the virtualbox code used as a basis for this plugin. 2013-09-03 20:13:45 -05:00
Tom Hite f75378757a Merge remote-tracking branch 'upstream/master' 2013-09-03 18:13:27 -05:00
Tom Hite 2255a82300 removed a few stray 'VirtualBox' term uses in comments and docs. 2013-09-03 18:13:05 -05:00
Tom Hite a20cfa9cf9 Merge remote-tracking branch 'upstream/master' 2013-09-03 10:10:59 -05:00
Tom Hite 7a9baad5a2 added network and disk driver options, also a source comment on the kickstart file in the docs (I can't find the original source). 2013-09-03 10:08:04 -05:00
Tom Hite afc3f3be08 Initial checkin to GitHub -- has extensive changes to conform to the latest API model to match the 0.3.6 (Sept. 2, 2013) release. 2013-09-02 22:23:52 -05:00
129 changed files with 6067 additions and 479 deletions
+2
View File
@@ -4,3 +4,5 @@
/src
/website/.sass-cache
/website/build
.vagrant
Vagrantfile
+60
View File
@@ -1,3 +1,63 @@
## 0.4.1 (December 7, 2013)
IMPROVEMENTS:
* builder/amazon/ebs: New option allows associating a public IP with
non-default VPC instances. [GH-660]
* builder/openstack: A "proxy\_url" setting was added to define an HTTP
proxy to use when building with this builder. [GH-637]
BUG FIXES:
* core: Don't change background color on CLI anymore, making things look
a tad nicer in some terminals.
* core: multiple ISO URLs works properly in all builders. [GH-683]
* builder/amazon/chroot: Block when obtaining file lock to allow
parallel builds. [GH-689]
* builder/amazon/instance: Add location flag to upload bundle command
so that building AMIs works out of us-east-1 [GH-679]
* builder/vmware: Cleanup of VMX keys works properly so cd-rom won't
get stuck with ISO. [GH-685]
* builder/vmware: File cleanup is more resilient to file delete races
with the operating system. [GH-675]
* provisioner/puppet-masterless: Check for hiera config path existence
properly. [GH-656]
## 0.4.0 (November 19, 2013)
FEATURES:
* Docker builder: build and export Docker containers, easily provisioned
with any of the Packer built-in provisioners.
* QEMU builder: builds a new VM compatible with KVM or Xen using QEMU.
* Remote ESXi builder: builds a VMware VM using ESXi remotely using only
SSH to an ESXi machine directly.
* vSphere post-processor: Can upload VMware artifacts to vSphere
* Vagrant post-processor can now make DigitalOcean provider boxes. [GH-504]
IMPROVEMENTS:
* builder/amazon/all: Can now specify a list of multiple security group
IDs to apply. [GH-499]
* builder/amazon/all: AWS API requests are now retried when a temporary
network error occurs as well as 500 errors. [GH-559]
* builder/virtualbox: Use VBOX\_INSTALL\_PATH env var on Windows to find
VBoxManage. [GH-628]
* post-processor/vagrant: skips gzip compression when compression_level=0
* provisioner/chef-solo: Encrypted data bag support [GH-625]
BUG FIXES:
* builder/amazon/chroot: Copying empty directories works. [GH-588]
* builder/amazon/chroot: Chroot commands work with shell provisioners. [GH-581]
* builder/amazon/chroot: Don't choose a mount point that is a partition of
an already mounted device. [GH-635]
* builder/virtualbox: Ctrl-C interrupts during waiting for boot. [GH-618]
* builder/vmware: VMX modifications are now case-insensitive. [GH-608]
* builder/vmware: VMware Fusion won't ask for VM upgrade.
* builder/vmware: Ctrl-C interrupts during waiting for boot. [GH-618]
* provisioner/chef-solo: Output is slightly prettier and more informative.
## 0.3.11 (November 4, 2013)
FEATURES:
+1 -1
View File
@@ -7,7 +7,7 @@ DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
all: deps
@mkdir -p bin/
@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
@bash --norc -i ./scripts/build.sh
@bash --norc -i ./scripts/devcompile.sh
deps:
@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
+12 -2
View File
@@ -74,8 +74,15 @@ 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.
and [Bazaar](http://bazaar.canonical.com/en/) to be installed.
Packer itself doesn't require these, but a dependency of a dependency does.
You'll also need [`gox`](https://github.com/mitchellh/gox)
to compile packer. You can install that with:
```
$ go get -u github.com/mitchellh/gox
```
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:
@@ -93,3 +100,6 @@ This will run tests for Packer core along with all the core builders and command
If you make any changes to the code, run `make format` in order to automatically
format the code according to Go standards.
When new dependencies are added to packer you can use `make updatedeps` to
get the latest and subsequently use `make` to compile and generate the `packer` binary.
+19 -2
View File
@@ -1,6 +1,7 @@
package chroot
import (
"bytes"
"fmt"
"github.com/mitchellh/packer/packer"
"io"
@@ -9,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
)
@@ -21,7 +23,7 @@ type Communicator struct {
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
command, err := c.CmdWrapper(
fmt.Sprintf("chroot %s %s", c.Chroot, cmd.Command))
fmt.Sprintf("chroot %s /bin/sh -c \"%s\"", c.Chroot, cmd.Command))
if err != nil {
return err
}
@@ -85,7 +87,22 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error
return err
}
return ShellCommand(cpCmd).Run()
var stderr bytes.Buffer
cmd := ShellCommand(cpCmd)
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, "LANG=C")
cmd.Stderr = &stderr
err = cmd.Run()
if err == nil {
return err
}
if strings.Contains(stderr.String(), "No such file") {
// This just means that the directory was empty. Just ignore it.
return nil
}
return err
}
func (c *Communicator) Download(src string, w io.Writer) error {
+8
View File
@@ -19,6 +19,14 @@ func AvailableDevice() (string, error) {
letters := "fghijklmnop"
for _, letter := range letters {
device := fmt.Sprintf("/dev/%s%c", prefix, letter)
// If the block device itself, i.e. /dev/sf, exists, then we
// can't use any of the numbers either.
if _, err := os.Stat(device); err == nil {
continue
}
for i := 1; i < 16; i++ {
device := fmt.Sprintf("/dev/%s%c%d", prefix, letter, i)
if _, err := os.Stat(device); err != nil {
+1 -7
View File
@@ -3,7 +3,6 @@
package chroot
import (
"errors"
"os"
"syscall"
)
@@ -14,13 +13,8 @@ const LOCK_NB = 4
const LOCK_UN = 8
func lockFile(f *os.File) error {
err := syscall.Flock(int(f.Fd()), LOCK_EX|LOCK_NB)
err := syscall.Flock(int(f.Fd()), LOCK_EX)
if err != nil {
errno, ok := err.(syscall.Errno)
if ok && errno == syscall.EWOULDBLOCK {
return errors.New("file already locked")
}
return err
}
+1 -1
View File
@@ -39,7 +39,7 @@ func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
// LOCK!
if err := lockFile(f); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
err := fmt.Errorf("Error obtaining lock: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
+39 -14
View File
@@ -11,19 +11,21 @@ import (
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
SourceAmi string `mapstructure:"source_ami"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
VpcId string `mapstructure:"vpc_id"`
AvailabilityZone string `mapstructure:"availability_zone"`
SourceAmi string `mapstructure:"source_ami"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
VpcId string `mapstructure:"vpc_id"`
AvailabilityZone string `mapstructure:"availability_zone"`
// Unexported fields that are calculated from others
sshTimeout time.Duration
@@ -74,11 +76,19 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
}
}
if c.SecurityGroupId != "" {
if len(c.SecurityGroupIds) > 0 {
errs = append(errs, fmt.Errorf("Only one of security_group_id or security_group_ids can be specified."))
} else {
c.SecurityGroupIds = []string{c.SecurityGroupId}
c.SecurityGroupId = ""
}
}
templates := map[string]*string{
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"ssh_timeout": &c.RawSSHTimeout,
"security_group_id": &c.SecurityGroupId,
"ssh_username": &c.SSHUsername,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
@@ -96,6 +106,21 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
}
}
sliceTemplates := map[string][]string{
"security_group_ids": c.SecurityGroupIds,
}
for n, slice := range sliceTemplates {
for i, elem := range slice {
var err error
slice[i], err = t.Process(elem, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
}
}
}
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
+5 -1
View File
@@ -20,7 +20,11 @@ func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
if i.DNSName != "" {
host = i.DNSName
} else if i.VpcId != "" {
host = i.PrivateIpAddress
if i.PublicIpAddress != "" {
host = i.PublicIpAddress
} else {
host = i.PrivateIpAddress
}
}
if host != "" {
@@ -10,16 +10,17 @@ import (
)
type StepRunSourceInstance struct {
Debug bool
ExpectedRootDevice string
InstanceType string
UserData string
UserDataFile string
SourceAMI string
IamInstanceProfile string
SubnetId string
AvailabilityZone string
BlockDevices BlockDevices
Debug bool
ExpectedRootDevice string
InstanceType string
UserData string
UserDataFile string
SourceAMI string
IamInstanceProfile string
SubnetId string
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
instance *ec2.Instance
}
@@ -27,7 +28,7 @@ type StepRunSourceInstance struct {
func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
keyName := state.Get("keyPair").(string)
securityGroupId := state.Get("securityGroupId").(string)
securityGroupIds := state.Get("securityGroupIds").([]string)
ui := state.Get("ui").(packer.Ui)
userData := s.UserData
@@ -41,18 +42,24 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
userData = string(contents)
}
securityGroups := make([]ec2.SecurityGroup, len(securityGroupIds))
for n, securityGroupId := range securityGroupIds {
securityGroups[n] = ec2.SecurityGroup{Id: securityGroupId}
}
runOpts := &ec2.RunInstances{
KeyName: keyName,
ImageId: s.SourceAMI,
InstanceType: s.InstanceType,
UserData: []byte(userData),
MinCount: 0,
MaxCount: 0,
SecurityGroups: []ec2.SecurityGroup{ec2.SecurityGroup{Id: securityGroupId}},
IamInstanceProfile: s.IamInstanceProfile,
SubnetId: s.SubnetId,
BlockDevices: s.BlockDevices.BuildLaunchDevices(),
AvailZone: s.AvailabilityZone,
KeyName: keyName,
ImageId: s.SourceAMI,
InstanceType: s.InstanceType,
UserData: []byte(userData),
MinCount: 0,
MaxCount: 0,
SecurityGroups: securityGroups,
IamInstanceProfile: s.IamInstanceProfile,
SubnetId: s.SubnetId,
AssociatePublicIpAddress: s.AssociatePublicIpAddress,
BlockDevices: s.BlockDevices.BuildLaunchDevices(),
AvailZone: s.AvailabilityZone,
}
ui.Say("Launching a source AWS instance...")
@@ -109,6 +116,10 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName))
}
if s.instance.PublicIpAddress != "" {
ui.Message(fmt.Sprintf("Public IP: %s", s.instance.PublicIpAddress))
}
if s.instance.PrivateIpAddress != "" {
ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress))
}
+7 -7
View File
@@ -11,9 +11,9 @@ import (
)
type StepSecurityGroup struct {
SecurityGroupId string
SSHPort int
VpcId string
SecurityGroupIds []string
SSHPort int
VpcId string
createdGroupId string
}
@@ -22,9 +22,9 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
if s.SecurityGroupId != "" {
log.Printf("Using specified security group: %s", s.SecurityGroupId)
state.Put("securityGroupId", s.SecurityGroupId)
if len(s.SecurityGroupIds) > 0 {
log.Printf("Using specified security groups: %v", s.SecurityGroupIds)
state.Put("securityGroupIds", s.SecurityGroupIds)
return multistep.ActionContinue
}
@@ -69,7 +69,7 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
}
// Set some state data for use in future steps
state.Put("securityGroupId", s.createdGroupId)
state.Put("securityGroupIds", []string{s.createdGroupId})
return multistep.ActionContinue
}
+14 -13
View File
@@ -88,21 +88,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
KeyPairName: b.config.TemporaryKeyPairName,
},
&awscommon.StepSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
SecurityGroupIds: b.config.SecurityGroupIds,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
},
&common.StepConnectSSH{
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),
+15 -13
View File
@@ -74,6 +74,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"-s {{.SecretKey}} " +
"-d {{.BundleDirectory}} " +
"--batch " +
"--location {{.Region}} " +
"--retry"
}
@@ -191,21 +192,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
KeyPairName: b.config.TemporaryKeyPairName,
},
&awscommon.StepSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
SecurityGroupIds: b.config.SecurityGroupIds,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Debug: b.config.PackerDebug,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
},
&common.StepConnectSSH{
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),
+10 -1
View File
@@ -11,6 +11,7 @@ type uploadCmdData struct {
BucketName string
BundleDirectory string
ManifestPath string
Region string
SecretKey string
}
@@ -23,12 +24,20 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
manifestPath := state.Get("manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
var err error
region, err := config.Region()
if err != nil {
err := fmt.Errorf("Error retrieving region: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
AccessKey: config.AccessKey,
BucketName: config.S3Bucket,
BundleDirectory: config.BundleDestination,
ManifestPath: manifestPath,
Region: region.Name,
SecretKey: config.SecretKey,
})
if err != nil {
+42 -1
View File
@@ -29,6 +29,15 @@ type ImagesResp struct {
Images []Image
}
type Region struct {
Id uint
Name string
}
type RegionsResp struct {
Regions []Region
}
type DigitalOceanClient struct {
// The http client for communicating
client *http.Client
@@ -227,7 +236,7 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin
}
if status == "ERROR" {
statusRaw, ok := decodedResponse["message"]
statusRaw, ok := decodedResponse["error_message"]
if ok {
status = statusRaw.(string)
} else {
@@ -252,3 +261,35 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin
return nil, lastErr
}
// Returns all available regions.
func (d DigitalOceanClient) Regions() ([]Region, error) {
resp, err := NewRequest(d, "regions", url.Values{})
if err != nil {
return nil, err
}
var result RegionsResp
if err := mapstructure.Decode(resp, &result); err != nil {
return nil, err
}
return result.Regions, nil
}
func (d DigitalOceanClient) RegionName(region_id uint) (string, error) {
regions, err := d.Regions()
if err != nil {
return "", err
}
for _, region := range regions {
if region.Id == region_id {
return region.Name, nil
}
}
err = errors.New(fmt.Sprintf("Unknown region id %v", region_id))
return "", err
}
+10 -3
View File
@@ -12,6 +12,12 @@ type Artifact struct {
// The ID of the image
snapshotId uint
// The name of the region
regionName string
// The ID of the region
regionId uint
// The client for making API calls
client *DigitalOceanClient
}
@@ -26,14 +32,15 @@ func (*Artifact) Files() []string {
}
func (a *Artifact) Id() string {
return a.snapshotName
// mimicing the aws builder
return fmt.Sprintf("%s:%s", a.regionName, a.snapshotName)
}
func (a *Artifact) String() string {
return fmt.Sprintf("A snapshot was created: %v", a.snapshotName)
return fmt.Sprintf("A snapshot was created: '%v' in region '%v'", a.snapshotName, a.regionName)
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d", a.snapshotId)
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
return a.client.DestroyImage(a.snapshotId)
}
+2 -2
View File
@@ -14,8 +14,8 @@ func TestArtifact_Impl(t *testing.T) {
}
func TestArtifactString(t *testing.T) {
a := &Artifact{"packer-foobar", 42, nil}
expected := "A snapshot was created: packer-foobar"
a := &Artifact{"packer-foobar", 42, "San Francisco", 3, nil}
expected := "A snapshot was created: 'packer-foobar' in region 'San Francisco'"
if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
+9
View File
@@ -225,9 +225,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, nil
}
region_id := state.Get("region_id").(uint)
regionName, err := client.RegionName(region_id)
if err != nil {
return nil, err
}
artifact := &Artifact{
snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_image_id").(uint),
regionId: region_id,
regionName: regionName,
client: client,
}
+1
View File
@@ -62,6 +62,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
state.Put("snapshot_image_id", imageId)
state.Put("snapshot_name", c.SnapshotName)
state.Put("region_id", c.RegionID)
return multistep.ActionContinue
}
+1 -1
View File
@@ -44,7 +44,7 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce
}
}()
log.Printf("Waiting for up to %d seconds for droplet to become %s", timeout, desiredState)
log.Printf("Waiting for up to %d seconds for droplet to become %s", timeout/time.Second, desiredState)
select {
case err := <-result:
return err
+32
View File
@@ -0,0 +1,32 @@
package docker
import (
"fmt"
"os"
)
// ExportArtifact is an Artifact implementation for when a container is
// exported from docker into a single flat file.
type ExportArtifact struct {
path string
}
func (*ExportArtifact) BuilderId() string {
return BuilderId
}
func (a *ExportArtifact) Files() []string {
return []string{a.path}
}
func (*ExportArtifact) Id() string {
return "Container"
}
func (a *ExportArtifact) String() string {
return fmt.Sprintf("Exported Docker file: %s", a.path)
}
func (a *ExportArtifact) Destroy() error {
return os.Remove(a.path)
}
+10
View File
@@ -0,0 +1,10 @@
package docker
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestExportArtifact_impl(t *testing.T) {
var _ packer.Artifact = new(ExportArtifact)
}
+77
View File
@@ -0,0 +1,77 @@
package docker
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"log"
)
const BuilderId = "packer.docker"
type Builder struct {
config *Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
c, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = c
return warnings, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
driver := &DockerDriver{Ui: ui}
if err := driver.Verify(); err != nil {
return nil, err
}
steps := []multistep.Step{
&StepTempDir{},
&StepPull{},
&StepRun{},
&StepProvision{},
&StepExport{},
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("hook", hook)
state.Put("ui", ui)
// Setup the driver that will talk to Docker
state.Put("driver", driver)
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// No errors, must've worked
artifact := &ExportArtifact{path: b.config.ExportPath}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
+10
View File
@@ -0,0 +1,10 @@
package docker
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestBuilder_implBuilder(t *testing.T) {
var _ packer.Builder = new(Builder)
}
+290
View File
@@ -0,0 +1,290 @@
package docker
import (
"bytes"
"fmt"
"github.com/ActiveState/tail"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
)
type Communicator struct {
ContainerId string
HostDir string
ContainerDir string
lock sync.Mutex
}
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
// Create a temporary file to store the output. Because of a bug in
// Docker, sometimes all the output doesn't properly show up. This
// file will capture ALL of the output, and we'll read that.
//
// https://github.com/dotcloud/docker/issues/2625
outputFile, err := ioutil.TempFile(c.HostDir, "cmd")
if err != nil {
return err
}
outputFile.Close()
// This file will store the exit code of the command once it is complete.
exitCodePath := outputFile.Name() + "-exit"
cmd := exec.Command("docker", "attach", c.ContainerId)
stdin_w, err := cmd.StdinPipe()
if err != nil {
// We have to do some cleanup since run was never called
os.Remove(outputFile.Name())
os.Remove(exitCodePath)
return err
}
// Run the actual command in a goroutine so that Start doesn't block
go c.run(cmd, remote, stdin_w, outputFile, exitCodePath)
return nil
}
func (c *Communicator) Upload(dst string, src io.Reader) error {
// Create a temporary file to store the upload
tempfile, err := ioutil.TempFile(c.HostDir, "upload")
if err != nil {
return err
}
defer os.Remove(tempfile.Name())
// Copy the contents to the temporary file
_, err = io.Copy(tempfile, src)
tempfile.Close()
if err != nil {
return err
}
// Copy the file into place by copying the temporary file we put
// into the shared folder into the proper location in the container
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("cp %s/%s %s", c.ContainerDir,
filepath.Base(tempfile.Name()), dst),
}
if err := c.Start(cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
}
return nil
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
// Create the temporary directory that will store the contents of "src"
// for copying into the container.
td, err := ioutil.TempDir(c.HostDir, "dirupload")
if err != nil {
return err
}
defer os.RemoveAll(td)
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relpath, err := filepath.Rel(src, path)
if err != nil {
return err
}
hostpath := filepath.Join(td, relpath)
// If it is a directory, just create it
if info.IsDir() {
return os.MkdirAll(hostpath, info.Mode())
}
// It is a file, copy it over, including mode.
src, err := os.Open(path)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(hostpath)
if err != nil {
return err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return err
}
si, err := src.Stat()
if err != nil {
return err
}
return dst.Chmod(si.Mode())
}
// Copy the entire directory tree to the temporary directory
if err := filepath.Walk(src, walkFn); err != nil {
return err
}
// Determine the destination directory
containerSrc := filepath.Join(c.ContainerDir, filepath.Base(td))
containerDst := dst
if src[len(src)-1] != '/' {
containerDst = filepath.Join(dst, filepath.Base(src))
}
// Make the directory, then copy into it
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("set -e; mkdir -p %s; cp -R %s/* %s",
containerDst, containerSrc, containerDst),
}
if err := c.Start(cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
}
return nil
}
func (c *Communicator) Download(src string, dst io.Writer) error {
panic("not implemented")
}
// Runs the given command and blocks until completion
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) {
// For Docker, remote communication must be serialized since it
// only supports single execution.
c.lock.Lock()
defer c.lock.Unlock()
// Clean up after ourselves by removing our temporary files
defer os.Remove(outputFile.Name())
defer os.Remove(exitCodePath)
// Tail the output file and send the data to the stdout listener
tail, err := tail.TailFile(outputFile.Name(), tail.Config{
Poll: true,
ReOpen: true,
Follow: true,
})
if err != nil {
log.Printf("Error tailing output file: %s", err)
remote.SetExited(254)
return
}
defer tail.Stop()
// Modify the remote command so that all the output of the commands
// go to a single file and so that the exit code is redirected to
// a single file. This lets us determine both when the command
// is truly complete (because the file will have data), what the
// exit status is (because Docker loses it because of the pty, not
// Docker's fault), and get the output (Docker bug).
remoteCmd := fmt.Sprintf("(%s) >%s 2>&1; echo $? >%s",
remote.Command,
filepath.Join(c.ContainerDir, filepath.Base(outputFile.Name())),
filepath.Join(c.ContainerDir, filepath.Base(exitCodePath)))
// Start the command
log.Printf("Executing in container %s: %#v", c.ContainerId, remoteCmd)
if err := cmd.Start(); err != nil {
log.Printf("Error executing: %s", err)
remote.SetExited(254)
return
}
go func() {
defer stdin_w.Close()
// This sleep needs to be here because of the issue linked to below.
// Basically, without it, Docker will hang on reading stdin forever,
// and won't see what we write, for some reason.
//
// https://github.com/dotcloud/docker/issues/2628
time.Sleep(2 * time.Second)
stdin_w.Write([]byte(remoteCmd + "\n"))
}()
// Start a goroutine to read all the lines out of the logs
go func() {
for line := range tail.Lines {
if remote.Stdout != nil {
remote.Stdout.Write([]byte(line.Text + "\n"))
} else {
log.Printf("Command stdout: %#v", line.Text)
}
}
}()
err = cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus := 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
// Say that we ended, since if Docker itself failed, then
// the command must've not run, or so we assume
remote.SetExited(exitStatus)
return
}
// Wait for the exit code to appear in our file...
log.Println("Waiting for exit code to appear for remote command...")
for {
fi, err := os.Stat(exitCodePath)
if err == nil && fi.Size() > 0 {
break
}
time.Sleep(1 * time.Second)
}
// Read the exit code
exitRaw, err := ioutil.ReadFile(exitCodePath)
if err != nil {
log.Printf("Error executing: %s", err)
remote.SetExited(254)
return
}
exitStatus, err := strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0)
if err != nil {
log.Printf("Error executing: %s", err)
remote.SetExited(254)
return
}
log.Printf("Executed command exit status: %d", exitStatus)
// Finally, we're done
remote.SetExited(int(exitStatus))
}
+10
View File
@@ -0,0 +1,10 @@
package docker
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func TestCommunicator_impl(t *testing.T) {
var _ packer.Communicator = new(Communicator)
}
+75
View File
@@ -0,0 +1,75 @@
package docker
import (
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ExportPath string `mapstructure:"export_path"`
Image string
Pull bool
tpl *packer.ConfigTemplate
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
md, err := common.DecodeConfig(c, raws...)
if err != nil {
return nil, nil, err
}
c.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, nil, err
}
// Default Pull if it wasn't set
hasPull := false
for _, k := range md.Keys {
if k == "Pull" {
hasPull = true
break
}
}
if !hasPull {
c.Pull = true
}
errs := common.CheckUnusedConfig(md)
templates := map[string]*string{
"export_path": &c.ExportPath,
"image": &c.Image,
}
for n, ptr := range templates {
var err error
*ptr, err = c.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if c.ExportPath == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("export_path must be specified"))
}
if c.Image == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("image must be specified"))
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
return c, nil, nil
}
+90
View File
@@ -0,0 +1,90 @@
package docker
import (
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"export_path": "foo",
"image": "bar",
}
}
func testConfigStruct(t *testing.T) *Config {
c, warns, errs := NewConfig(testConfig())
if len(warns) > 0 {
t.Fatalf("bad: %#v", len(warns))
}
if errs != nil {
t.Fatalf("bad: %#v", errs)
}
return c
}
func testConfigErr(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should error")
}
}
func testConfigOk(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad: %s", err)
}
}
func TestConfigPrepare_exportPath(t *testing.T) {
raw := testConfig()
// No export path
delete(raw, "export_path")
_, warns, errs := NewConfig(raw)
testConfigErr(t, warns, errs)
// Good export path
raw["export_path"] = "good"
_, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs)
}
func TestConfigPrepare_image(t *testing.T) {
raw := testConfig()
// No image
delete(raw, "image")
_, warns, errs := NewConfig(raw)
testConfigErr(t, warns, errs)
// Good image
raw["image"] = "path"
_, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs)
}
func TestConfigPrepare_pull(t *testing.T) {
raw := testConfig()
// No pull set
delete(raw, "pull")
c, warns, errs := NewConfig(raw)
testConfigOk(t, warns, errs)
if !c.Pull {
t.Fatal("should pull by default")
}
// Pull set
raw["pull"] = false
c, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs)
if c.Pull {
t.Fatal("should not pull")
}
}
+32
View File
@@ -0,0 +1,32 @@
package docker
import (
"io"
)
// Driver is the interface that has to be implemented to communicate with
// Docker. The Driver interface also allows the steps to be tested since
// a mock driver can be shimmed in.
type Driver interface {
// Export exports the container with the given ID to the given writer.
Export(id string, dst io.Writer) error
// Pull should pull down the given image.
Pull(image string) error
// StartContainer starts a container and returns the ID for that container,
// along with a potential error.
StartContainer(*ContainerConfig) (string, error)
// StopContainer forcibly stops a container.
StopContainer(id string) error
// Verify verifies that the driver can run
Verify() error
}
// ContainerConfig is the configuration used to start a container.
type ContainerConfig struct {
Image string
Volumes map[string]string
}
+92
View File
@@ -0,0 +1,92 @@
package docker
import (
"bytes"
"fmt"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os/exec"
"strings"
)
type DockerDriver struct {
Ui packer.Ui
}
func (d *DockerDriver) Export(id string, dst io.Writer) error {
var stderr bytes.Buffer
cmd := exec.Command("docker", "export", id)
cmd.Stdout = dst
cmd.Stderr = &stderr
log.Printf("Exporting container: %s", id)
if err := cmd.Start(); err != nil {
return err
}
if err := cmd.Wait(); err != nil {
err = fmt.Errorf("Error exporting: %s\nStderr: %s",
err, stderr.String())
return err
}
return nil
}
func (d *DockerDriver) Pull(image string) error {
cmd := exec.Command("docker", "pull", image)
return runAndStream(cmd, d.Ui)
}
func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) {
// Args that we're going to pass to Docker
args := []string{"run", "-d", "-i", "-t"}
if len(config.Volumes) > 0 {
volumes := make([]string, 0, len(config.Volumes))
for host, guest := range config.Volumes {
volumes = append(volumes, fmt.Sprintf("%s:%s", host, guest))
}
args = append(args, "-v", strings.Join(volumes, ","))
}
args = append(args, config.Image, "/bin/bash")
// Start the container
var stdout, stderr bytes.Buffer
cmd := exec.Command("docker", args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
log.Printf("Starting container with args: %v", args)
if err := cmd.Start(); err != nil {
return "", err
}
log.Println("Waiting for container to finish starting")
if err := cmd.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("Docker exited with a non-zero exit status.\nStderr: %s",
stderr.String())
}
return "", err
}
// Capture the container ID, which is alone on stdout
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) StopContainer(id string) error {
return exec.Command("docker", "kill", id).Run()
}
func (d *DockerDriver) Verify() error {
if _, err := exec.LookPath("docker"); err != nil {
return err
}
return nil
}
+63
View File
@@ -0,0 +1,63 @@
package docker
import (
"io"
)
// MockDriver is a driver implementation that can be used for tests.
type MockDriver struct {
ExportReader io.Reader
ExportError error
PullError error
StartID string
StartError error
StopError error
VerifyError error
ExportCalled bool
ExportID string
PullCalled bool
PullImage string
StartCalled bool
StartConfig *ContainerConfig
StopCalled bool
StopID string
VerifyCalled bool
}
func (d *MockDriver) Export(id string, dst io.Writer) error {
d.ExportCalled = true
d.ExportID = id
if d.ExportReader != nil {
_, err := io.Copy(dst, d.ExportReader)
if err != nil {
return err
}
}
return d.ExportError
}
func (d *MockDriver) Pull(image string) error {
d.PullCalled = true
d.PullImage = image
return d.PullError
}
func (d *MockDriver) StartContainer(config *ContainerConfig) (string, error) {
d.StartCalled = true
d.StartConfig = config
return d.StartID, d.StartError
}
func (d *MockDriver) StopContainer(id string) error {
d.StopCalled = true
d.StopID = id
return d.StopError
}
func (d *MockDriver) Verify() error {
d.VerifyCalled = true
return d.VerifyError
}
+7
View File
@@ -0,0 +1,7 @@
package docker
import "testing"
func TestMockDriver_impl(t *testing.T) {
var _ Driver = new(MockDriver)
}
+7
View File
@@ -0,0 +1,7 @@
package docker
import "testing"
func TestDockerDriver_impl(t *testing.T) {
var _ Driver = new(DockerDriver)
}
+102
View File
@@ -0,0 +1,102 @@
package docker
import (
"fmt"
"github.com/mitchellh/iochan"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
)
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
defer stdout_w.Close()
defer stderr_w.Close()
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
if err := cmd.Start(); err != nil {
return err
}
// Create the channels we'll use for data
exitCh := make(chan int, 1)
stdoutCh := iochan.DelimReader(stdout_r, '\n')
stderrCh := iochan.DelimReader(stderr_r, '\n')
// Start the goroutine to watch for the exit
go func() {
defer stdout_w.Close()
defer stderr_w.Close()
exitStatus := 0
err := cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus = 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
}
exitCh <- exitStatus
}()
// This waitgroup waits for the streaming to end
var streamWg sync.WaitGroup
streamWg.Add(2)
streamFunc := func(ch <-chan string) {
defer streamWg.Done()
for data := range ch {
data = cleanOutputLine(data)
if data != "" {
ui.Message(data)
}
}
}
// Stream stderr/stdout
go streamFunc(stderrCh)
go streamFunc(stdoutCh)
// Wait for the process to end and then wait for the streaming to end
exitStatus := <-exitCh
streamWg.Wait()
if exitStatus != 0 {
return fmt.Errorf("Bad exit status: %d", exitStatus)
}
return nil
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func cleanOutputLine(line string) string {
// Build a regular expression that will get rid of shell codes
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
line = re.ReplaceAllString(line, "")
// Trim surrounding whitespace
line = strings.TrimSpace(line)
// Trim up to the first carriage return, since that text would be
// lost anyways.
idx := strings.LastIndex(line, "\r")
if idx > -1 {
line = line[idx+1:]
}
return line
}
+24
View File
@@ -0,0 +1,24 @@
package docker
import (
"testing"
)
func TestCleanLine(t *testing.T) {
cases := []struct {
input string
output string
}{
{
"\x1b[0A\x1b[2K\r8dbd9e392a96: Pulling image (precise) from ubuntu\r\x1b[0B\x1b[1A\x1b[2K\r8dbd9e392a96: Pulling image (precise) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/\r\x1b[1B",
"8dbd9e392a96: Pulling image (precise) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/",
},
}
for _, tc := range cases {
actual := cleanOutputLine(tc.input)
if actual != tc.output {
t.Fatalf("bad: %#v %#v", tc.input, actual)
}
}
}
+42
View File
@@ -0,0 +1,42 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
)
// StepExport exports the container to a flat tar file.
type StepExport struct{}
func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
containerId := state.Get("container_id").(string)
ui := state.Get("ui").(packer.Ui)
// Open the file that we're going to write to
f, err := os.Create(config.ExportPath)
if err != nil {
err := fmt.Errorf("Error creating output file: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Exporting the container")
if err := driver.Export(containerId, f); err != nil {
f.Close()
os.Remove(f.Name())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
f.Close()
return multistep.ActionContinue
}
func (s *StepExport) Cleanup(state multistep.StateBag) {}
+99
View File
@@ -0,0 +1,99 @@
package docker
import (
"bytes"
"errors"
"github.com/mitchellh/multistep"
"io/ioutil"
"os"
"testing"
)
func testStepExportState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("container_id", "foo")
return state
}
func TestStepExport_impl(t *testing.T) {
var _ multistep.Step = new(StepExport)
}
func TestStepExport(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportReader = bytes.NewReader([]byte("data!"))
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.ExportCalled {
t.Fatal("should've exported")
}
if driver.ExportID != "foo" {
t.Fatalf("bad: %#v", driver.ExportID)
}
// verify the data exported to the file
contents, err := ioutil.ReadFile(tf.Name())
if err != nil {
t.Fatalf("err: %s", err)
}
if string(contents) != "data!" {
t.Fatalf("bad: %#v", string(contents))
}
}
func TestStepExport_error(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
if err := os.Remove(tf.Name()); err != nil {
t.Fatalf("err: %s", err)
}
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify we have an error
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
// verify we didn't make that file
if _, err := os.Stat(tf.Name()); err == nil {
t.Fatal("export path shouldn't exist")
}
}
+26
View File
@@ -0,0 +1,26 @@
package docker
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
)
type StepProvision struct{}
func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
containerId := state.Get("container_id").(string)
tempDir := state.Get("temp_dir").(string)
// Create the communicator that talks to Docker via various
// os/exec tricks.
comm := &Communicator{
ContainerId: containerId,
HostDir: tempDir,
ContainerDir: "/packer-files",
}
prov := common.StepProvision{Comm: comm}
return prov.Run(state)
}
func (s *StepProvision) Cleanup(state multistep.StateBag) {}
+34
View File
@@ -0,0 +1,34 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type StepPull struct{}
func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if !config.Pull {
log.Println("Pull disabled, won't docker pull")
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image))
if err := driver.Pull(config.Image); err != nil {
err := fmt.Errorf("Error pulling Docker image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPull) Cleanup(state multistep.StateBag) {
}
+73
View File
@@ -0,0 +1,73 @@
package docker
import (
"errors"
"github.com/mitchellh/multistep"
"testing"
)
func TestStepPull_impl(t *testing.T) {
var _ multistep.Step = new(StepPull)
}
func TestStepPull(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.PullCalled {
t.Fatal("should've pulled")
}
if driver.PullImage != config.Image {
t.Fatalf("bad: %#v", driver.PullImage)
}
}
func TestStepPull_error(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.PullError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify we have an error
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
}
func TestStepPull_noPull(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
config.Pull = false
driver := state.Get("driver").(*MockDriver)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if driver.PullCalled {
t.Fatal("shouldn't have pulled")
}
}
+58
View File
@@ -0,0 +1,58 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepRun struct {
containerId string
}
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
tempDir := state.Get("temp_dir").(string)
ui := state.Get("ui").(packer.Ui)
runConfig := ContainerConfig{
Image: config.Image,
Volumes: map[string]string{
tempDir: "/packer-files",
},
}
ui.Say("Starting docker container with /bin/bash")
containerId, err := driver.StartContainer(&runConfig)
if err != nil {
err := fmt.Errorf("Error running container: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Save the container ID
s.containerId = containerId
state.Put("container_id", s.containerId)
ui.Message(fmt.Sprintf("Container ID: %s", s.containerId))
return multistep.ActionContinue
}
func (s *StepRun) Cleanup(state multistep.StateBag) {
if s.containerId == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
// Kill the container. We don't handle errors because errors usually
// just mean that the container doesn't exist anymore, which isn't a
// big deal.
ui.Say(fmt.Sprintf("Killing the container: %s", s.containerId))
driver.StopContainer(s.containerId)
// Reset the container ID so that we're idempotent
s.containerId = ""
}
+95
View File
@@ -0,0 +1,95 @@
package docker
import (
"errors"
"github.com/mitchellh/multistep"
"testing"
)
func testStepRunState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("temp_dir", "/foo")
return state
}
func TestStepRun_impl(t *testing.T) {
var _ multistep.Step = new(StepRun)
}
func TestStepRun(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
driver.StartID = "foo"
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.StartCalled {
t.Fatal("should've called")
}
if driver.StartConfig.Image != config.Image {
t.Fatalf("bad: %#v", driver.StartConfig.Image)
}
// verify the ID is saved
idRaw, ok := state.GetOk("container_id")
if !ok {
t.Fatal("should've saved ID")
}
id := idRaw.(string)
if id != "foo" {
t.Fatalf("bad: %#v", id)
}
// Verify we haven't called stop yet
if driver.StopCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if !driver.StopCalled {
t.Fatal("should've stopped")
}
if driver.StopID != id {
t.Fatalf("bad: %#v", driver.StopID)
}
}
func TestStepRun_error(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.StartError = errors.New("foo")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify the ID is not saved
if _, ok := state.GetOk("container_id"); ok {
t.Fatal("shouldn't save container ID")
}
// Verify we haven't called stop yet
if driver.StopCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if driver.StopCalled {
t.Fatal("should not have stopped")
}
}
+38
View File
@@ -0,0 +1,38 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
)
// StepTempDir creates a temporary directory that we use in order to
// share data with the docker container over the communicator.
type StepTempDir struct {
tempDir string
}
func (s *StepTempDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating a temporary directory for sharing data...")
td, err := ioutil.TempDir("", "packer-docker")
if err != nil {
err := fmt.Errorf("Error making temp dir: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.tempDir = td
state.Put("temp_dir", s.tempDir)
return multistep.ActionContinue
}
func (s *StepTempDir) Cleanup(state multistep.StateBag) {
if s.tempDir != "" {
os.RemoveAll(s.tempDir)
}
}
+44
View File
@@ -0,0 +1,44 @@
package docker
import (
"github.com/mitchellh/multistep"
"os"
"testing"
)
func TestStepTempDir_impl(t *testing.T) {
var _ multistep.Step = new(StepTempDir)
}
func TestStepTempDir(t *testing.T) {
state := testState(t)
step := new(StepTempDir)
defer step.Cleanup(state)
// sanity test
if _, ok := state.GetOk("temp_dir"); ok {
t.Fatalf("temp_dir should not be in state yet")
}
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// Verify that we got the temp dir
dirRaw, ok := state.GetOk("temp_dir")
if !ok {
t.Fatalf("should've made temp_dir")
}
dir := dirRaw.(string)
if _, err := os.Stat(dir); err != nil {
t.Fatalf("err: %s", err)
}
// Cleanup
step.Cleanup(state)
if _, err := os.Stat(dir); err == nil {
t.Fatalf("dir should be gone")
}
}
+20
View File
@@ -0,0 +1,20 @@
package docker
import (
"bytes"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"testing"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("config", testConfigStruct(t))
state.Put("driver", &MockDriver{})
state.Put("hook", &packer.MockHook{})
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
+17
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"net/http"
"net/url"
"os"
)
@@ -14,6 +16,7 @@ type AccessConfig struct {
Project string `mapstructure:"project"`
Provider string `mapstructure:"provider"`
RawRegion string `mapstructure:"region"`
ProxyUrl string `mapstructure:"proxy_url"`
}
// Auth returns a valid Auth object for access to openstack services, or
@@ -23,6 +26,7 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) {
password := c.Password
project := c.Project
provider := c.Provider
proxy := c.ProxyUrl
if username == "" {
username = os.Getenv("SDK_USERNAME")
@@ -47,6 +51,19 @@ func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) {
authoptions.TenantName = project
}
// For corporate networks it may be the case where we want our API calls
// to be sent through a separate HTTP proxy than external traffic.
if proxy != "" {
url, err := url.Parse(proxy)
if err != nil {
return nil, err
}
// The gophercloud.Context has a UseCustomClient method which
// would allow us to override with a new instance of http.Client.
http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(url)}
}
return gophercloud.Authenticate(provider, authoptions)
}
+33
View File
@@ -0,0 +1,33 @@
package qemu
import (
"fmt"
"os"
)
// Artifact is the result of running the Qemu builder, namely a set
// of files associated with the resulting machine.
type Artifact struct {
dir string
f []string
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return a.f
}
func (*Artifact) Id() string {
return "VM"
}
func (a *Artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *Artifact) Destroy() error {
return os.RemoveAll(a.dir)
}
+482
View File
@@ -0,0 +1,482 @@
package qemu
import (
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
const BuilderId = "transcend.qemu"
var netDevice = map[string]bool{
"ne2k_pci": true,
"i82551": true,
"i82557b": true,
"i82559er": true,
"rtl8139": true,
"e1000": true,
"pcnet": true,
"virtio": true,
"virtio-net": true,
"usb-net": true,
"i82559a": true,
"i82559b": true,
"i82559c": true,
"i82550": true,
"i82562": true,
"i82557a": true,
"i82557c": true,
"i82801": true,
"vmxnet3": true,
"i82558a": true,
"i82558b": true,
}
var diskInterface = map[string]bool{
"ide": true,
"scsi": true,
"virtio": true,
}
type Builder struct {
config config
runner multistep.Runner
}
type config struct {
common.PackerConfig `mapstructure:",squash"`
Accelerator string `mapstructure:"accelerator"`
BootCommand []string `mapstructure:"boot_command"`
DiskInterface string `mapstructure:"disk_interface"`
DiskSize uint `mapstructure:"disk_size"`
Format string `mapstructure:"format"`
Headless bool `mapstructure:"headless"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
ISOChecksum string `mapstructure:"iso_checksum"`
ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"`
NetDevice string `mapstructure:"net_device"`
OutputDir string `mapstructure:"output_directory"`
QemuArgs [][]string `mapstructure:"qemuargs"`
ShutdownCommand string `mapstructure:"shutdown_command"`
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"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"`
VMName string `mapstructure:"vm_name"`
RawBootWait string `mapstructure:"boot_wait"`
RawSingleISOUrl string `mapstructure:"iso_url"`
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
bootWait time.Duration ``
shutdownTimeout time.Duration ``
sshWaitTimeout time.Duration ``
tpl *packer.ConfigTemplate
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return nil, err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
if b.config.DiskSize == 0 {
b.config.DiskSize = 40000
}
if b.config.Accelerator == "" {
b.config.Accelerator = "kvm"
}
if b.config.HTTPPortMin == 0 {
b.config.HTTPPortMin = 8000
}
if b.config.HTTPPortMax == 0 {
b.config.HTTPPortMax = 9000
}
if b.config.OutputDir == "" {
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
}
if b.config.RawBootWait == "" {
b.config.RawBootWait = "10s"
}
if b.config.SSHHostPortMin == 0 {
b.config.SSHHostPortMin = 2222
}
if b.config.SSHHostPortMax == 0 {
b.config.SSHHostPortMax = 4444
}
if b.config.SSHPort == 0 {
b.config.SSHPort = 22
}
if b.config.VNCPortMin == 0 {
b.config.VNCPortMin = 5900
}
if b.config.VNCPortMax == 0 {
b.config.VNCPortMax = 6000
}
if b.config.QemuArgs == nil {
b.config.QemuArgs = make([][]string, 0)
}
if b.config.VMName == "" {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
if b.config.Format == "" {
b.config.Format = "qcow2"
}
if b.config.NetDevice == "" {
b.config.NetDevice = "virtio-net"
}
if b.config.DiskInterface == "" {
b.config.DiskInterface = "virtio"
}
// Errors
templates := map[string]*string{
"http_directory": &b.config.HTTPDir,
"iso_checksum": &b.config.ISOChecksum,
"iso_checksum_type": &b.config.ISOChecksumType,
"iso_url": &b.config.RawSingleISOUrl,
"output_directory": &b.config.OutputDir,
"shutdown_command": &b.config.ShutdownCommand,
"ssh_password": &b.config.SSHPassword,
"ssh_username": &b.config.SSHUser,
"vm_name": &b.config.VMName,
"format": &b.config.Format,
"boot_wait": &b.config.RawBootWait,
"shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
"accelerator": &b.config.Accelerator,
"net_device": &b.config.NetDevice,
"disk_interface": &b.config.DiskInterface,
}
for n, ptr := range templates {
var err error
*ptr, err = b.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
for i, url := range b.config.ISOUrls {
var err error
b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
}
}
for i, command := range b.config.BootCommand {
if err := b.config.tpl.Validate(command); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
}
}
if !(b.config.Format == "qcow2" || b.config.Format == "raw") {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
}
if !(b.config.Accelerator == "kvm" || b.config.Accelerator == "xen") {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'kvm' or 'xen' are allowed"))
}
if _, ok := netDevice[b.config.NetDevice]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized network device type"))
}
if _, ok := diskInterface[b.config.DiskInterface]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk interface type"))
}
if b.config.HTTPPortMin > b.config.HTTPPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("http_port_min must be less than http_port_max"))
}
if b.config.ISOChecksum == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
} else {
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
}
if b.config.ISOChecksumType == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("The iso_checksum_type must be specified."))
} else {
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
errs = packer.MultiErrorAppend(
errs,
fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
}
}
if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("One of iso_url or iso_urls must be specified."))
} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
} else if b.config.RawSingleISOUrl != "" {
b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
}
for i, url := range b.config.ISOUrls {
b.config.ISOUrls[i], err = common.DownloadableURL(url)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
}
}
if !b.config.PackerForce {
if _, err := os.Stat(b.config.OutputDir); err == nil {
errs = packer.MultiErrorAppend(
errs,
fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir))
}
}
b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
}
if b.config.RawShutdownTimeout == "" {
b.config.RawShutdownTimeout = "5m"
}
if b.config.RawSSHWaitTimeout == "" {
b.config.RawSSHWaitTimeout = "20m"
}
b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
}
if b.config.SSHKeyPath != "" {
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
}
if b.config.SSHUser == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("An ssh_username must be specified."))
}
b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
}
if b.config.VNCPortMin > b.config.VNCPortMax {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
}
for i, args := range b.config.QemuArgs {
for j, arg := range args {
if err := b.config.tpl.Validate(arg); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing qemu-system_x86-64[%d][%d]: %s", i, j, err))
}
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
return nil, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
// Create the driver that we'll use to communicate with Qemu
driver, err := b.newDriver()
if err != nil {
return nil, fmt.Errorf("Failed creating Qemu driver: %s", err)
}
steps := []multistep.Step{
&common.StepDownload{
Checksum: b.config.ISOChecksum,
ChecksumType: b.config.ISOChecksumType,
Description: "ISO",
ResultKey: "iso_path",
Url: b.config.ISOUrls,
},
new(stepPrepareOutputDir),
new(stepCreateDisk),
new(stepHTTPServer),
new(stepForwardSSH),
new(stepConfigureVNC),
&stepRun{
BootDrive: "d",
Message: "Starting VM, booting from CD-ROM",
},
&stepBootWait{},
&stepTypeBootCommand{},
&stepWaitForShutdown{
Message: "Waiting for initial VM boot to shut down",
},
&stepRun{
BootDrive: "c",
Message: "Starting VM, booting from hard disk",
},
&common.StepConnectSSH{
SSHAddress: sshAddress,
SSHConfig: sshConfig,
SSHWaitTimeout: b.config.sshWaitTimeout,
},
new(common.StepProvision),
new(stepShutdown),
}
// Setup the state bag
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("config", &b.config)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
// Run
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
// Compile the artifact list
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
files = append(files, path)
}
return err
}
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
return nil, err
}
artifact := &Artifact{
dir: b.config.OutputDir,
f: files,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
func (b *Builder) newDriver() (Driver, error) {
qemuPath, err := exec.LookPath("qemu-system-x86_64")
if err != nil {
return nil, err
}
qemuImgPath, err := exec.LookPath("qemu-img")
if err != nil {
return nil, err
}
log.Printf("Qemu path: %s, Qemu Image page: %s", qemuPath, qemuImgPath)
driver := &QemuDriver{
QemuPath: qemuPath,
QemuImgPath: qemuImgPath,
}
if err := driver.Verify(); err != nil {
return nil, err
}
return driver, nil
}
+664
View File
@@ -0,0 +1,664 @@
package qemu
import (
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
"reflect"
"testing"
)
var testPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
-----END RSA PRIVATE KEY-----
`
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "foo",
"iso_checksum_type": "md5",
"iso_url": "http://www.google.com/",
"ssh_username": "foo",
packer.BuildNameConfigKey: "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Error("Builder must implement builder.")
}
}
func TestBuilderPrepare_Defaults(t *testing.T) {
var b Builder
config := testConfig()
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.OutputDir != "output-foo" {
t.Errorf("bad output dir: %s", b.config.OutputDir)
}
if b.config.SSHHostPortMin != 2222 {
t.Errorf("bad min ssh host port: %d", b.config.SSHHostPortMin)
}
if b.config.SSHHostPortMax != 4444 {
t.Errorf("bad max ssh host port: %d", b.config.SSHHostPortMax)
}
if b.config.SSHPort != 22 {
t.Errorf("bad ssh port: %d", b.config.SSHPort)
}
if b.config.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", b.config.VMName)
}
if b.config.Format != "qcow2" {
t.Errorf("bad format: %s", b.config.Format)
}
}
func TestBuilderPrepare_BootWait(t *testing.T) {
var b Builder
config := testConfig()
// Test a default boot_wait
delete(config, "boot_wait")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.RawBootWait != "10s" {
t.Fatalf("bad value: %s", b.config.RawBootWait)
}
// Test with a bad boot_wait
config["boot_wait"] = "this is not good"
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["boot_wait"] = "5s"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_DiskSize(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "disk_size")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad err: %s", err)
}
if b.config.DiskSize != 40000 {
t.Fatalf("bad size: %d", b.config.DiskSize)
}
config["disk_size"] = 60000
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.DiskSize != 60000 {
t.Fatalf("bad size: %s", b.config.DiskSize)
}
}
func TestBuilderPrepare_HTTPPort(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["http_port_min"] = 1000
config["http_port_max"] = 500
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad
config["http_port_min"] = -500
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["http_port_min"] = 500
config["http_port_max"] = 1000
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_Format(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["format"] = "illegal value"
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["format"] = "qcow2"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Good
config["format"] = "raw"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
var b Builder
config := testConfig()
// Test bad
config["iso_checksum"] = ""
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good
config["iso_checksum"] = "FOo"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksum != "foo" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
}
}
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
var b Builder
config := testConfig()
// Test bad
config["iso_checksum_type"] = ""
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good
config["iso_checksum_type"] = "mD5"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksumType != "md5" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
}
// Test unknown
config["iso_checksum_type"] = "fake"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ISOUrl(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "iso_url")
delete(config, "iso_urls")
// Test both epty
config["iso_url"] = ""
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test iso_url set
config["iso_url"] = "http://www.packer.io"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected := []string{"http://www.packer.io"}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
// Test both set
config["iso_url"] = "http://www.packer.io"
config["iso_urls"] = []string{"http://www.packer.io"}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test just iso_urls set
delete(config, "iso_url")
config["iso_urls"] = []string{
"http://www.packer.io",
"http://www.hashicorp.com",
}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected = []string{
"http://www.packer.io",
"http://www.hashicorp.com",
}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
}
func TestBuilderPrepare_OutputDir(t *testing.T) {
var b Builder
config := testConfig()
// Test with existing dir
dir, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir)
config["output_directory"] = dir
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["output_directory"] = "i-hope-i-dont-exist"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
var b Builder
config := testConfig()
// Test with a bad value
config["shutdown_timeout"] = "this is not good"
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["shutdown_timeout"] = "5s"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SSHHostPort(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["ssh_host_port_min"] = 1000
config["ssh_host_port_max"] = 500
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad
config["ssh_host_port_min"] = -500
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["ssh_host_port_min"] = 500
config["ssh_host_port_max"] = 1000
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
var b Builder
config := testConfig()
config["ssh_key_path"] = ""
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
config["ssh_key_path"] = "/i/dont/exist"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["ssh_key_path"] = tf.Name()
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good contents
tf.Seek(0, 0)
tf.Truncate(0)
tf.Write([]byte(testPem))
config["ssh_key_path"] = tf.Name()
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuilderPrepare_SSHUser(t *testing.T) {
var b Builder
config := testConfig()
config["ssh_username"] = ""
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
config["ssh_username"] = "exists"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
var b Builder
config := testConfig()
// Test a default boot_wait
delete(config, "ssh_wait_timeout")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.RawSSHWaitTimeout != "20m" {
t.Fatalf("bad value: %s", b.config.RawSSHWaitTimeout)
}
// Test with a bad value
config["ssh_wait_timeout"] = "this is not good"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["ssh_wait_timeout"] = "5s"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_QemuArgs(t *testing.T) {
var b Builder
config := testConfig()
// Test with empty
delete(config, "qemuargs")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(b.config.QemuArgs, [][]string{}) {
t.Fatalf("bad: %#v", b.config.QemuArgs)
}
// Test with a good one
config["qemuargs"] = [][]interface{}{
[]interface{}{"foo", "bar", "baz"},
}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := [][]string{
[]string{"foo", "bar", "baz"},
}
if !reflect.DeepEqual(b.config.QemuArgs, expected) {
t.Fatalf("bad: %#v", b.config.QemuArgs)
}
}
+214
View File
@@ -0,0 +1,214 @@
package qemu
import (
"bufio"
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"io"
"log"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
"time"
"unicode"
)
type DriverCancelCallback func(state multistep.StateBag) bool
// A driver is able to talk to qemu-system-x86_64 and perform certain
// operations with it.
type Driver interface {
// Stop stops a running machine, forcefully.
Stop() error
// Qemu executes the given command via qemu-system-x86_64
Qemu(qemuArgs ...string) error
// wait on shutdown of the VM with option to cancel
WaitForShutdown(<-chan struct{}) bool
// Qemu executes the given command via qemu-img
QemuImg(...string) error
// Verify checks to make sure that this driver should function
// properly. If there is any indication the driver can't function,
// this will return an error.
Verify() error
// Version reads the version of Qemu that is installed.
Version() (string, error)
}
type QemuDriver struct {
QemuPath string
QemuImgPath string
vmCmd *exec.Cmd
vmEndCh <-chan int
lock sync.Mutex
}
func (d *QemuDriver) Stop() error {
d.lock.Lock()
defer d.lock.Unlock()
if d.vmCmd != nil {
if err := d.vmCmd.Process.Kill(); err != nil {
return err
}
}
return nil
}
func (d *QemuDriver) Qemu(qemuArgs ...string) error {
d.lock.Lock()
defer d.lock.Unlock()
if d.vmCmd != nil {
panic("Existing VM state found")
}
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
log.Printf("Executing %s: %#v", d.QemuPath, qemuArgs)
cmd := exec.Command(d.QemuPath, qemuArgs...)
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
err := cmd.Start()
if err != nil {
err = fmt.Errorf("Error starting VM: %s", err)
return err
}
go logReader("Qemu stdout", stdout_r)
go logReader("Qemu stderr", stderr_r)
log.Printf("Started Qemu. Pid: %d", cmd.Process.Pid)
// Wait for Qemu to complete in the background, and mark when its done
endCh := make(chan int, 1)
go func() {
defer stderr_w.Close()
defer stdout_w.Close()
var exitCode int = 0
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitCode = status.ExitStatus()
} else {
exitCode = 254
}
}
}
endCh <- exitCode
d.lock.Lock()
defer d.lock.Unlock()
d.vmCmd = nil
d.vmEndCh = nil
}()
// Wait at least a couple seconds for an early fail from Qemu so
// we can report that.
select {
case exit := <-endCh:
if exit != 0 {
return fmt.Errorf("Qemu failed to start. Please run with logs to get more info.")
}
case <-time.After(2 * time.Second):
}
// Setup our state so we know we are running
d.vmCmd = cmd
d.vmEndCh = endCh
return nil
}
func (d *QemuDriver) WaitForShutdown(cancelCh <-chan struct{}) bool {
d.lock.Lock()
endCh := d.vmEndCh
d.lock.Unlock()
if endCh == nil {
return true
}
select {
case <-endCh:
return true
case <-cancelCh:
return false
}
}
func (d *QemuDriver) QemuImg(args ...string) error {
var stdout, stderr bytes.Buffer
log.Printf("Executing qemu-img: %#v", args)
cmd := exec.Command(d.QemuImgPath, args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
stdoutString := strings.TrimSpace(stdout.String())
stderrString := strings.TrimSpace(stderr.String())
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("QemuImg error: %s", stderrString)
}
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
return err
}
func (d *QemuDriver) Verify() error {
return nil
}
func (d *QemuDriver) Version() (string, error) {
var stdout bytes.Buffer
cmd := exec.Command(d.QemuPath, "-version")
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return "", err
}
versionOutput := strings.TrimSpace(stdout.String())
log.Printf("Qemu --version output: %s", versionOutput)
versionRe := regexp.MustCompile("qemu-kvm-[0-9]\\.[0-9]")
matches := versionRe.Split(versionOutput, 2)
if len(matches) == 0 {
return "", fmt.Errorf("No version found: %s", versionOutput)
}
log.Printf("Qemu version: %s", matches[0])
return matches[0], nil
}
func logReader(name string, r io.Reader) {
bufR := bufio.NewReader(r)
for {
line, err := bufR.ReadString('\n')
if line != "" {
line = strings.TrimRightFunc(line, unicode.IsSpace)
log.Printf("%s: %s", name, line)
}
if err == io.EOF {
break
}
}
}
+59
View File
@@ -0,0 +1,59 @@
package qemu
import (
gossh "code.google.com/p/go.crypto/ssh"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"io/ioutil"
"os"
)
func sshAddress(state multistep.StateBag) (string, error) {
sshHostPort := state.Get("sshHostPort").(uint)
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
}
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*config)
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
}
if config.SSHKeyPath != "" {
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
if err != nil {
return nil, err
}
auth = append(auth, gossh.ClientAuthKeyring(keyring))
}
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: auth,
}, nil
}
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
keyBytes, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
return nil, err
}
return keyring, nil
}
+25
View File
@@ -0,0 +1,25 @@
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
// stepBootWait waits the configured time period.
type stepBootWait struct{}
func (s *stepBootWait) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
if int64(config.bootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
time.Sleep(config.bootWait)
}
return multistep.ActionContinue
}
func (s *stepBootWait) Cleanup(state multistep.StateBag) {}
+50
View File
@@ -0,0 +1,50 @@
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"math/rand"
"net"
)
// This step configures the VM to enable the VNC server.
//
// Uses:
// config *config
// ui packer.Ui
//
// Produces:
// vnc_port uint - The port that VNC is configured to listen on.
type stepConfigureVNC struct{}
func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
// Find an open VNC port. Note that this can still fail later on
// because we have to release the port at some point. But this does its
// best.
msg := fmt.Sprintf("Looking for available port between %d and %d", config.VNCPortMin, config.VNCPortMax)
ui.Say(msg)
log.Printf(msg)
var vncPort uint
portRange := int(config.VNCPortMax - config.VNCPortMin)
for {
vncPort = uint(rand.Intn(portRange)) + config.VNCPortMin
log.Printf("Trying port: %d", vncPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", vncPort))
if err == nil {
defer l.Close()
break
}
}
ui.Say(fmt.Sprintf("Found available VNC port: %d", vncPort))
state.Put("vnc_port", vncPort)
return multistep.ActionContinue
}
func (stepConfigureVNC) Cleanup(multistep.StateBag) {}
+40
View File
@@ -0,0 +1,40 @@
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"path/filepath"
"strings"
)
// This step creates the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepCreateDisk struct{}
func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName,
strings.ToLower(config.Format)))
command := []string{
"create",
"-f", config.Format,
path,
fmt.Sprintf("%vM", config.DiskSize),
}
ui.Say("Creating hard drive...")
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {}
+44
View File
@@ -0,0 +1,44 @@
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"math/rand"
"net"
)
// This step adds a NAT port forwarding definition so that SSH is available
// on the guest machine.
//
// Uses:
//
// Produces:
type stepForwardSSH struct{}
func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
var sshHostPort uint
portRange := int(config.SSHHostPortMax - config.SSHHostPortMin)
for {
sshHostPort = uint(rand.Intn(portRange)) + config.SSHHostPortMin
log.Printf("Trying port: %d", sshHostPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
if err == nil {
defer l.Close()
break
}
}
ui.Say(fmt.Sprintf("Found port for SSH: %d.", sshHostPort))
// Save the port we're using so that future steps can use it
state.Put("sshHostPort", sshHostPort)
return multistep.ActionContinue
}
func (s *stepForwardSSH) Cleanup(state multistep.StateBag) {}
+75
View File
@@ -0,0 +1,75 @@
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"math/rand"
"net"
"net/http"
)
// This step creates and runs the HTTP server that is serving the files
// specified by the 'http_files` configuration parameter in the template.
//
// Uses:
// config *config
// ui packer.Ui
//
// Produces:
// http_port int - The port the HTTP server started on.
type stepHTTPServer struct {
l net.Listener
}
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
var httpPort uint = 0
if config.HTTPDir == "" {
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
// Find an available TCP port for our HTTP server
var httpAddr string
portRange := int(config.HTTPPortMax - config.HTTPPortMin)
for {
var err error
var offset uint = 0
if portRange > 0 {
// Intn will panic if portRange == 0, so we do a check.
offset = uint(rand.Intn(portRange))
}
httpPort = offset + config.HTTPPortMin
httpAddr = fmt.Sprintf(":%d", httpPort)
log.Printf("Trying port: %d", httpPort)
s.l, err = net.Listen("tcp", httpAddr)
if err == nil {
break
}
}
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
// Start the HTTP server and run it in the background
fileServer := http.FileServer(http.Dir(config.HTTPDir))
server := &http.Server{Addr: httpAddr, Handler: fileServer}
go server.Serve(s.l)
// Save the address into the state so it can be accessed in the future
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
func (s *stepHTTPServer) Cleanup(multistep.StateBag) {
if s.l != nil {
// Close the listener so that the HTTP server stops
s.l.Close()
}
}
+49
View File
@@ -0,0 +1,49 @@
package qemu
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os"
"time"
)
type stepPrepareOutputDir struct{}
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
os.RemoveAll(config.OutputDir)
}
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := os.RemoveAll(config.OutputDir)
if err == nil {
break
}
log.Printf("Error removing output dir: %s", err)
time.Sleep(2 * time.Second)
}
}
}
+116
View File
@@ -0,0 +1,116 @@
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"path/filepath"
"strings"
)
// stepRun runs the virtual machine
type stepRun struct {
BootDrive string
Message string
}
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say(s.Message)
command := getCommandArgs(s.BootDrive, state)
if err := driver.Qemu(command...); err != nil {
err := fmt.Errorf("Error launching VM: %s", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepRun) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if err := driver.Stop(); err != nil {
ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
}
}
func getCommandArgs(bootDrive string, state multistep.StateBag) []string {
config := state.Get("config").(*config)
isoPath := state.Get("iso_path").(string)
vncPort := state.Get("vnc_port").(uint)
sshHostPort := state.Get("sshHostPort").(uint)
ui := state.Get("ui").(packer.Ui)
guiArgument := "sdl"
vnc := fmt.Sprintf("0.0.0.0:%d", vncPort-5900)
vmName := config.VMName
imgPath := filepath.Join(config.OutputDir,
fmt.Sprintf("%s.%s", vmName, strings.ToLower(config.Format)))
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 = "none"
}
defaultArgs := make(map[string]string)
defaultArgs["-name"] = vmName
defaultArgs["-machine"] = fmt.Sprintf("type=pc-1.0,accel=%s", config.Accelerator)
defaultArgs["-display"] = guiArgument
defaultArgs["-netdev"] = "user,id=user.0"
defaultArgs["-device"] = fmt.Sprintf("%s,netdev=user.0", config.NetDevice)
defaultArgs["-drive"] = fmt.Sprintf("file=%s,if=%s", imgPath, config.DiskInterface)
defaultArgs["-cdrom"] = isoPath
defaultArgs["-boot"] = bootDrive
defaultArgs["-m"] = "512m"
defaultArgs["-redir"] = fmt.Sprintf("tcp:%v::22", sshHostPort)
defaultArgs["-vnc"] = vnc
inArgs := make(map[string][]string)
if len(config.QemuArgs) > 0 {
ui.Say("Overriding defaults Qemu arguments with QemuArgs...")
// becuase qemu supports multiple appearances of the same
// switch, just different values, each key in the args hash
// will have an array of string values
for _, qemuArgs := range config.QemuArgs {
key := qemuArgs[0]
val := strings.Join(qemuArgs[1:], "")
if _, ok := inArgs[key]; !ok {
inArgs[key] = make([]string, 0)
}
if len(val) > 0 {
inArgs[key] = append(inArgs[key], val)
}
}
}
// get any remaining missing default args from the default settings
for key := range defaultArgs {
if _, ok := inArgs[key]; !ok {
arg := make([]string, 1)
arg[0] = defaultArgs[key]
inArgs[key] = arg
}
}
// Flatten to array of strings
outArgs := make([]string, 0)
for key, values := range inArgs {
if len(values) > 0 {
for idx := range values {
outArgs = append(outArgs, key, values[idx])
}
} else {
outArgs = append(outArgs, key)
}
}
return outArgs
}
+70
View File
@@ -0,0 +1,70 @@
package qemu
import (
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
// This step shuts down the machine. It first attempts to do so gracefully,
// but ultimately forcefully shuts it down if that fails.
//
// Uses:
// communicator packer.Communicator
// config *config
// driver Driver
// ui packer.Ui
//
// Produces:
// <nothing>
type stepShutdown struct{}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if config.ShutdownCommand != "" {
ui.Say("Gracefully halting virtual machine...")
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Start the goroutine that will time out our graceful attempt
cancelCh := make(chan struct{}, 1)
go func() {
defer close(cancelCh)
<-time.After(config.shutdownTimeout)
}()
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
if ok := driver.WaitForShutdown(cancelCh); !ok {
err := errors.New("Timeout while waiting for machine to shut down.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
ui.Say("Halting the virtual machine...")
if err := driver.Stop(); err != nil {
err := fmt.Errorf("Error stopping VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
log.Println("VM shut down.")
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {}
+175
View File
@@ -0,0 +1,175 @@
package qemu
import (
"fmt"
"github.com/mitchellh/go-vnc"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"net"
"strings"
"time"
"unicode"
"unicode/utf8"
)
const KeyLeftShift uint32 = 0xFFE1
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort uint
Name string
}
// This step "types" the boot command into the VM over VNC.
//
// Uses:
// config *config
// http_port int
// ui packer.Ui
// vnc_port uint
//
// Produces:
// <nothing>
type stepTypeBootCommand struct{}
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
vncPort := state.Get("vnc_port").(uint)
// Connect to VNC
ui.Say("Connecting to VM via VNC")
nc, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", vncPort))
if err != nil {
err := fmt.Errorf("Error connecting to VNC: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
defer nc.Close()
c, err := vnc.Client(nc, &vnc.ClientConfig{Exclusive: true})
if err != nil {
err := fmt.Errorf("Error handshaking with VNC: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
defer c.Close()
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
tplData := &bootCommandTemplateData{
"10.0.2.2",
httpPort,
config.VMName,
}
ui.Say("Typing the boot command over VNC...")
for _, command := range config.BootCommand {
command, err := config.tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Check for interrupts between typing things so we can cancel
// since this isn't the fastest thing.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
vncSendString(c, command)
}
return multistep.ActionContinue
}
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func vncSendString(c *vnc.ClientConn, original string) {
special := make(map[string]uint32)
special["<bs>"] = 0xFF08
special["<del>"] = 0xFFFF
special["<enter>"] = 0xFF0D
special["<esc>"] = 0xFF1B
special["<f1>"] = 0xFFBE
special["<f2>"] = 0xFFBF
special["<f3>"] = 0xFFC0
special["<f4>"] = 0xFFC1
special["<f5>"] = 0xFFC2
special["<f6>"] = 0xFFC3
special["<f7>"] = 0xFFC4
special["<f8>"] = 0xFFC5
special["<f9>"] = 0xFFC6
special["<f10>"] = 0xFFC7
special["<f11>"] = 0xFFC8
special["<f12>"] = 0xFFC9
special["<return>"] = 0xFF0D
special["<tab>"] = 0xFF09
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
// TODO(mitchellh): Ripe for optimizations of some point, perhaps.
for len(original) > 0 {
var keyCode uint32
keyShift := false
if strings.HasPrefix(original, "<wait>") {
log.Printf("Special code '<wait>' found, sleeping one second")
time.Sleep(1 * time.Second)
original = original[len("<wait>"):]
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)
keyCode = specialValue
original = original[len(specialCode):]
break
}
}
if keyCode == 0 {
r, size := utf8.DecodeRuneInString(original)
original = original[size:]
keyCode = uint32(r)
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift)
}
if keyShift {
c.KeyEvent(KeyLeftShift, true)
}
c.KeyEvent(keyCode, true)
c.KeyEvent(keyCode, false)
if keyShift {
c.KeyEvent(KeyLeftShift, false)
}
// qemu is picky, so no matter what, wait a small period
time.Sleep(100 * time.Millisecond)
}
}
+43
View File
@@ -0,0 +1,43 @@
package qemu
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
// stepWaitForShutdown waits for the shutdown of the currently running
// qemu VM.
type stepWaitForShutdown struct {
Message string
}
func (s *stepWaitForShutdown) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
stopCh := make(chan struct{})
defer close(stopCh)
cancelCh := make(chan struct{})
go func() {
for {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
close(cancelCh)
return
}
select {
case <-stopCh:
return
case <-time.After(100 * time.Millisecond):
}
}
}()
ui.Say(s.Message)
driver.WaitForShutdown(cancelCh)
return multistep.ActionContinue
}
func (s *stepWaitForShutdown) Cleanup(state multistep.StateBag) {}
+24 -3
View File
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
@@ -485,9 +486,29 @@ func (b *Builder) Cancel() {
}
func (b *Builder) newDriver() (Driver, error) {
vboxmanagePath, err := exec.LookPath("VBoxManage")
if err != nil {
return nil, err
var vboxmanagePath string
if runtime.GOOS == "windows" {
// On Windows, we check VBOX_INSTALL_PATH env var for the path
if installPath := os.Getenv("VBOX_INSTALL_PATH"); installPath != "" {
log.Printf("[DEBUG] builder/virtualbox: VBOX_INSTALL_PATH: %s",
installPath)
for _, path := range strings.Split(installPath, ";") {
path = filepath.Join(path, "VBoxManage.exe")
if _, err := os.Stat(path); err == nil {
vboxmanagePath = path
break
}
}
}
}
if vboxmanagePath == "" {
var err error
vboxmanagePath, err = exec.LookPath("VBoxManage")
if err != nil {
return nil, err
}
}
log.Printf("VBoxManage path: %s", vboxmanagePath)
+12 -1
View File
@@ -42,7 +42,18 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
if int64(config.bootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
time.Sleep(config.bootWait)
wait := time.After(config.bootWait)
WAITLOOP:
for {
select {
case <-wait:
break WAITLOOP
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
}
}
}
return multistep.ActionContinue
+5 -4
View File
@@ -8,12 +8,13 @@ import (
// Artifact is the result of running the VMware builder, namely a set
// of files associated with the resulting machine.
type Artifact struct {
dir string
f []string
builderId string
dir string
f []string
}
func (*Artifact) BuilderId() string {
return BuilderId
func (a *Artifact) BuilderId() string {
return a.builderId
}
func (a *Artifact) Files() []string {
+57 -19
View File
@@ -10,13 +10,13 @@ import (
"log"
"math/rand"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
const BuilderId = "mitchellh.vmware"
const BuilderIdESX = "mitchellh.vmware-esx"
type Builder struct {
config config
@@ -55,6 +55,13 @@ type config struct {
VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"`
RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"`
RemoteHost string `mapstructure:"remote_host"`
RemotePort uint `mapstructure:"remote_port"`
RemoteUser string `mapstructure:"remote_username"`
RemotePassword string `mapstructure:"remote_password"`
RawBootWait string `mapstructure:"boot_wait"`
RawSingleISOUrl string `mapstructure:"iso_url"`
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
@@ -93,6 +100,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
if b.config.DiskTypeId == "" {
// Default is growable virtual disk split in 2GB files.
b.config.DiskTypeId = "1"
if b.config.RemoteType == "esx5" {
b.config.DiskTypeId = "zeroedthick"
}
}
if b.config.FloppyFiles == nil {
@@ -131,6 +142,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
}
if b.config.RemoteUser == "" {
b.config.RemoteUser = "root"
}
if b.config.RemoteDatastore == "" {
b.config.RemoteDatastore = "datastore1"
}
if b.config.RemotePort == 0 {
b.config.RemotePort = 22
}
if b.config.SSHPort == 0 {
b.config.SSHPort = 22
}
@@ -158,6 +181,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
"vmx_template_path": &b.config.VMXTemplatePath,
"remote_type": &b.config.RemoteType,
"remote_host": &b.config.RemoteHost,
"remote_datastore": &b.config.RemoteDatastore,
"remote_user": &b.config.RemoteUser,
"remote_password": &b.config.RemotePassword,
}
for n, ptr := range templates {
@@ -327,6 +355,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
}
// Remote configuration validation
if b.config.RemoteType != "" {
if b.config.RemoteHost == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("remote_host must be specified"))
}
}
// Warnings
if b.config.ShutdownCommand == "" {
warnings = append(warnings,
@@ -342,8 +378,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
// Initialize the driver that will handle our interaction with VMware
driver, err := NewDriver()
driver, err := NewDriver(&b.config)
if err != nil {
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
}
@@ -364,14 +399,19 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
},
&stepRemoteUpload{
Key: "iso_path",
Message: "Uploading ISO to remote machine...",
},
&stepCreateDisk{},
&stepCreateVMX{},
&stepSuppressMessages{},
&stepHTTPServer{},
&stepConfigureVNC{},
&stepRun{},
&stepTypeBootCommand{},
&common.StepConnectSSH{
SSHAddress: sshAddress,
SSHAddress: driver.SSHAddress,
SSHConfig: sshConfig,
SSHWaitTimeout: b.config.sshWaitTimeout,
NoPty: b.config.SSHSkipRequestPty,
@@ -419,24 +459,22 @@ 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 {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
}
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
files, err := state.Get("dir").(OutputDir).ListFiles()
if err != nil {
return nil, err
}
return &Artifact{b.config.OutputDir, files}, nil
// Set the proper builder ID
builderId := BuilderId
if b.config.RemoteType != "" {
builderId = BuilderIdESX
}
return &Artifact{
builderId: builderId,
dir: b.config.OutputDir,
f: files,
}, nil
}
func (b *Builder) Cancel() {
+36 -15
View File
@@ -3,6 +3,7 @@ package vmware
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"log"
"os/exec"
"runtime"
@@ -20,12 +21,20 @@ type Driver interface {
// Checks if the VMX file at the given path is running.
IsRunning(string) (bool, error)
// SSHAddress returns the SSH address for the VM that is being
// managed by this driver.
SSHAddress(multistep.StateBag) (string, error)
// Start starts a VM specified by the path to the VMX given.
Start(string, bool) error
// Stop stops a VM specified by the path to the VMX given.
Stop(string) error
// SuppressMessages modifies the VMX or surrounding directory so that
// VMware doesn't show any annoying messages.
SuppressMessages(string) error
// Get the path to the VMware ISO for the given flavor.
ToolsIsoPath(string) string
@@ -41,27 +50,39 @@ type Driver interface {
// NewDriver returns a new driver implementation for this operating
// system, or an error if the driver couldn't be initialized.
func NewDriver() (Driver, error) {
func NewDriver(config *config) (Driver, error) {
drivers := []Driver{}
switch runtime.GOOS {
case "darwin":
if config.RemoteType != "" {
drivers = []Driver{
&Fusion5Driver{
AppPath: "/Applications/VMware Fusion.app",
&ESX5Driver{
Host: config.RemoteHost,
Port: config.RemotePort,
Username: config.RemoteUser,
Password: config.RemotePassword,
Datastore: config.RemoteDatastore,
},
}
case "linux":
drivers = []Driver{
new(Workstation9Driver),
new(Player5LinuxDriver),
} else {
switch runtime.GOOS {
case "darwin":
drivers = []Driver{
&Fusion5Driver{
AppPath: "/Applications/VMware Fusion.app",
},
}
case "linux":
drivers = []Driver{
new(Workstation9Driver),
new(Player5LinuxDriver),
}
case "windows":
drivers = []Driver{
new(Workstation9Driver),
}
default:
return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
}
case "windows":
drivers = []Driver{
new(Workstation9Driver),
}
default:
return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
}
errs := ""
+430
View File
@@ -0,0 +1,430 @@
package vmware
import (
"bufio"
"bytes"
gossh "code.google.com/p/go.crypto/ssh"
"encoding/csv"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"github.com/mitchellh/packer/packer"
"io"
"log"
"net"
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
// virtual machines. This driver can only manage one machine at a time.
type ESX5Driver struct {
Host string
Port uint
Username string
Password string
Datastore string
comm packer.Communicator
outputDir string
}
func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
return nil
}
func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, typeId string) error {
diskPath := d.datastorePath(diskPathLocal)
return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", "lsilogic", diskPath)
}
func (d *ESX5Driver) IsRunning(vmxPathLocal string) (bool, error) {
vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
state, err := d.run(nil, "vim-cmd", "vmsvc/power.getstate", vmxPath)
if err != nil {
return false, err
}
return strings.Contains(state, "Powered on"), nil
}
func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error {
vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
return d.sh("vim-cmd", "vmsvc/power.on", vmxPath)
}
func (d *ESX5Driver) Stop(vmxPathLocal string) error {
vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
return d.sh("vim-cmd", "vmsvc/power.off", vmxPath)
}
func (d *ESX5Driver) Register(vmxPathLocal string) error {
vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
if err := d.upload(vmxPath, vmxPathLocal); err != nil {
return err
}
return d.sh("vim-cmd", "solo/registervm", vmxPath)
}
func (d *ESX5Driver) SuppressMessages(vmxPath string) error {
return nil
}
func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
return d.sh("vim-cmd", "vmsvc/unregister", vmxPath)
}
func (d *ESX5Driver) UploadISO(localPath string) (string, error) {
cacheRoot, _ := filepath.Abs(".")
targetFile, err := filepath.Rel(cacheRoot, localPath)
if err != nil {
return "", err
}
finalPath := d.datastorePath(targetFile)
if err := d.mkdir(filepath.Dir(finalPath)); err != nil {
return "", err
}
if err := d.upload(finalPath, localPath); err != nil {
return "", err
}
return finalPath, nil
}
func (d *ESX5Driver) ToolsIsoPath(string) string {
return ""
}
func (d *ESX5Driver) DhcpLeasesPath(string) string {
return ""
}
func (d *ESX5Driver) Verify() error {
checks := []func() error{
d.connect,
d.checkSystemVersion,
d.checkGuestIPHackEnabled,
}
for _, check := range checks {
if err := check(); err != nil {
return err
}
}
return nil
}
func (d *ESX5Driver) HostIP() (string, error) {
ip := net.ParseIP(d.Host)
interfaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, dev := range interfaces {
addrs, err := dev.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok {
if ipnet.Contains(ip) {
return ipnet.IP.String(), nil
}
}
}
}
return "", errors.New("Unable to determine Host IP")
}
func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint) {
var vncPort uint
// TODO(dougm) use esxcli network ip connection list
for port := portMin; port <= portMax; port++ {
address := fmt.Sprintf("%s:%d", d.Host, port)
log.Printf("Trying address: %s...", address)
l, err := net.DialTimeout("tcp", address, 1*time.Second)
if err == nil {
log.Printf("%s in use", address)
l.Close()
} else if e, ok := err.(*net.OpError); ok {
if e.Err == syscall.ECONNREFUSED {
// then port should be available for listening
vncPort = port
break
} else if e.Timeout() {
log.Printf("Timeout connecting to: %s (check firewall rules)", address)
}
}
}
return d.Host, vncPort
}
func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(*config)
if address, ok := state.GetOk("vm_address"); ok {
return address.(string), nil
}
r, err := d.esxcli("network", "vm", "list")
if err != nil {
return "", err
}
record, err := r.find("Name", config.VMName)
if err != nil {
return "", err
}
wid := record["WorldID"]
if wid == "" {
return "", errors.New("VM WorldID not found")
}
r, err = d.esxcli("network", "vm", "port", "list", "-w", wid)
if err != nil {
return "", err
}
record, err = r.read()
if err != nil {
return "", err
}
if record["IPAddress"] == "0.0.0.0" {
return "", errors.New("VM network port found, but no IP address")
}
address := fmt.Sprintf("%s:%d", record["IPAddress"], config.SSHPort)
state.Put("vm_address", address)
return address, nil
}
func (d *ESX5Driver) DirExists() (bool, error) {
err := d.sh("test", "-e", d.outputDir)
return err == nil, nil
}
func (d *ESX5Driver) ListFiles() ([]string, error) {
stdout, err := d.ssh("ls -1p "+d.outputDir, nil)
if err != nil {
return nil, err
}
files := make([]string, 0, 10)
reader := bufio.NewReader(stdout)
for {
line, _, err := reader.ReadLine()
if err == io.EOF {
break
}
if line[len(line)-1] == '/' {
continue
}
files = append(files, filepath.Join(d.outputDir, string(line)))
}
return files, nil
}
func (d *ESX5Driver) MkdirAll() error {
return d.mkdir(d.outputDir)
}
func (d *ESX5Driver) Remove(path string) error {
return d.sh("rm", path)
}
func (d *ESX5Driver) RemoveAll() error {
return d.sh("rm", "-rf", d.outputDir)
}
func (d *ESX5Driver) SetOutputDir(path string) {
d.outputDir = d.datastorePath(path)
}
func (d *ESX5Driver) datastorePath(path string) string {
return filepath.Join("/vmfs/volumes", d.Datastore, path)
}
func (d *ESX5Driver) connect() error {
address := fmt.Sprintf("%s:%d", d.Host, d.Port)
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(d.Password)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(d.Password)),
}
// TODO(dougm) KeyPath support
sshConfig := &ssh.Config{
Connection: ssh.ConnectFunc("tcp", address),
SSHConfig: &gossh.ClientConfig{
User: d.Username,
Auth: auth,
},
NoPty: true,
}
comm, err := ssh.New(sshConfig)
if err != nil {
return err
}
d.comm = comm
return nil
}
func (d *ESX5Driver) checkSystemVersion() error {
r, err := d.esxcli("system", "version", "get")
if err != nil {
return err
}
record, err := r.read()
if err != nil {
return err
}
log.Printf("Connected to %s %s %s", record["Product"],
record["Version"], record["Build"])
return nil
}
func (d *ESX5Driver) checkGuestIPHackEnabled() error {
r, err := d.esxcli("system", "settings", "advanced", "list", "-o", "/Net/GuestIPHack")
if err != nil {
return err
}
record, err := r.read()
if err != nil {
return err
}
if record["IntValue"] != "1" {
return errors.New(
"GuestIPHack is required, enable by running this on the ESX machine:\n" +
"esxcli system settings advanced set -o /Net/GuestIPHack -i 1")
}
return nil
}
func (d *ESX5Driver) mkdir(path string) error {
return d.sh("mkdir", "-p", path)
}
func (d *ESX5Driver) upload(dst, src string) error {
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
return d.comm.Upload(dst, f)
}
func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool {
stdin := bytes.NewBufferString(fmt.Sprintf("%s %s", hash, file))
_, err := d.run(stdin, fmt.Sprintf("%ssum", ctype), "-c")
if err != nil {
return false
}
return true
}
func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error) {
var stdout, stderr bytes.Buffer
cmd := &packer.RemoteCmd{
Command: command,
Stdout: &stdout,
Stderr: &stderr,
Stdin: stdin,
}
err := d.comm.Start(cmd)
if err != nil {
return nil, err
}
cmd.Wait()
if cmd.ExitStatus != 0 {
err = fmt.Errorf("'%s'\n\nStdout: %s\n\nStderr: %s",
cmd.Command, stdout.String(), stderr.String())
return nil, err
}
return &stdout, nil
}
func (d *ESX5Driver) run(stdin io.Reader, args ...string) (string, error) {
stdout, err := d.ssh(strings.Join(args, " "), stdin)
if err != nil {
return "", err
}
return stdout.String(), nil
}
func (d *ESX5Driver) sh(args ...string) error {
_, err := d.run(nil, args...)
return err
}
func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
stdout, err := d.ssh("esxcli --formatter csv "+strings.Join(args, " "), nil)
if err != nil {
return nil, err
}
r := csv.NewReader(bytes.NewReader(stdout.Bytes()))
r.TrailingComma = true
header, err := r.Read()
if err != nil {
return nil, err
}
return &esxcliReader{r, header}, nil
}
type esxcliReader struct {
cr *csv.Reader
header []string
}
func (r *esxcliReader) read() (map[string]string, error) {
fields, err := r.cr.Read()
if err != nil {
return nil, err
}
record := map[string]string{}
for i, v := range fields {
record[r.header[i]] = v
}
return record, nil
}
func (r *esxcliReader) find(key, val string) (map[string]string, error) {
for {
record, err := r.read()
if err != nil {
return nil, err
}
if record[key] == val {
return record, nil
}
}
}
+13
View File
@@ -0,0 +1,13 @@
package vmware
import (
"testing"
)
func TestESX5Driver_implDriver(t *testing.T) {
var _ Driver = new(ESX5Driver)
}
func TestESX5Driver_implRemoteDriver(t *testing.T) {
var _ RemoteDriver = new(ESX5Driver)
}
+24
View File
@@ -2,6 +2,8 @@ package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -58,6 +60,10 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
return false, nil
}
func (d *Fusion5Driver) SSHAddress(state multistep.StateBag) (string, error) {
return sshAddress(state)
}
func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
guiArgument := "gui"
if headless == true {
@@ -81,6 +87,15 @@ func (d *Fusion5Driver) Stop(vmxPath string) error {
return nil
}
func (d *Fusion5Driver) SuppressMessages(vmxPath string) error {
dir := filepath.Dir(vmxPath)
base := filepath.Base(vmxPath)
base = strings.Replace(base, ".vmx", "", -1)
plistPath := filepath.Join(dir, base+".plist")
return ioutil.WriteFile(plistPath, []byte(fusionSuppressPlist), 0644)
}
func (d *Fusion5Driver) Verify() error {
if _, err := os.Stat(d.AppPath); err != nil {
if os.IsNotExist(err) {
@@ -124,3 +139,12 @@ func (d *Fusion5Driver) ToolsIsoPath(k string) string {
func (d *Fusion5Driver) DhcpLeasesPath(device string) string {
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
}
const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>disallowUpgrade</key>
<true/>
</dict>
</plist>`
+9
View File
@@ -2,6 +2,7 @@ package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"os"
"os/exec"
"path/filepath"
@@ -86,6 +87,10 @@ func (d *Player5LinuxDriver) IsRunning(vmxPath string) (bool, error) {
return false, nil
}
func (d *Player5LinuxDriver) SSHAddress(state multistep.StateBag) (string, error) {
return sshAddress(state)
}
func (d *Player5LinuxDriver) Start(vmxPath string, headless bool) error {
guiArgument := "gui"
if headless {
@@ -109,6 +114,10 @@ func (d *Player5LinuxDriver) Stop(vmxPath string) error {
return nil
}
func (d *Player5LinuxDriver) SuppressMessages(vmxPath string) error {
return nil
}
func (d *Player5LinuxDriver) Verify() error {
if err := d.findApp(); err != nil {
return fmt.Errorf("VMware Player application ('vmplayer') not found in path.")
+9
View File
@@ -2,6 +2,7 @@ package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"log"
"os"
"os/exec"
@@ -61,6 +62,10 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) {
return false, nil
}
func (d *Workstation9Driver) SSHAddress(state multistep.StateBag) (string, error) {
return sshAddress(state)
}
func (d *Workstation9Driver) Start(vmxPath string, headless bool) error {
guiArgument := "gui"
if headless {
@@ -84,6 +89,10 @@ func (d *Workstation9Driver) Stop(vmxPath string) error {
return nil
}
func (d *Workstation9Driver) SuppressMessages(vmxPath string) error {
return nil
}
func (d *Workstation9Driver) Verify() error {
var err error
if d.AppPath == "" {
+62
View File
@@ -0,0 +1,62 @@
package vmware
import (
"os"
"path/filepath"
)
// OutputDir is an interface type that abstracts the creation and handling
// of the output directory for VMware-based products. The abstraction is made
// so that the output directory can be properly made on remote (ESXi) based
// VMware products as well as local.
type OutputDir interface {
DirExists() (bool, error)
ListFiles() ([]string, error)
MkdirAll() error
Remove(string) error
RemoveAll() error
SetOutputDir(string)
}
// localOutputDir is an OutputDir implementation where the directory
// is on the local machine.
type localOutputDir struct {
dir string
}
func (d *localOutputDir) DirExists() (bool, error) {
_, err := os.Stat(d.dir)
return err == nil, nil
}
func (d *localOutputDir) ListFiles() ([]string, error) {
files := make([]string, 0, 10)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
}
return files, filepath.Walk(d.dir, visit)
}
func (d *localOutputDir) MkdirAll() error {
return os.MkdirAll(d.dir, 0755)
}
func (d *localOutputDir) Remove(path string) error {
return os.Remove(path)
}
func (d *localOutputDir) RemoveAll() error {
return os.RemoveAll(d.dir)
}
func (d *localOutputDir) SetOutputDir(path string) {
d.dir = path
}
+16
View File
@@ -0,0 +1,16 @@
package vmware
type RemoteDriver interface {
Driver
// UploadISO uploads a local ISO to the remote side and returns the
// new path that should be used in the VMX along with an error if it
// exists.
UploadISO(string) (string, error)
// Adds a VM to inventory specified by the path to the VMX given.
Register(string) error
// Removes a VM from inventory specified by the path to the VMX given.
Unregister(string) error
}
+1 -1
View File
@@ -33,7 +33,7 @@ func sshAddress(state multistep.StateBag) (string, error) {
var ok bool
macAddress := ""
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
if macAddress, ok = vmxData["ethernet0.generatedAddress"]; !ok || macAddress == "" {
if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" {
return "", errors.New("couldn't find MAC address in VMX")
}
}
+28 -29
View File
@@ -16,7 +16,7 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
// This step removes unnecessary files from the final result.
//
// Uses:
// config *config
// dir OutputDir
// ui packer.Ui
//
// Produces:
@@ -24,41 +24,40 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
type stepCleanFiles struct{}
func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
dir := state.Get("dir").(OutputDir)
ui := state.Get("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 {
files, err := dir.ListFiles()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
for _, path := range files {
// 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))
if err = dir.Remove(path); err != nil {
// Only report the error if the file still exists
if _, serr := os.Stat(path); serr == nil || !os.IsNotExist(serr) {
state.Put("error", err)
return multistep.ActionHalt
}
}
}
}
return multistep.ActionContinue
}
+2 -2
View File
@@ -55,12 +55,12 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
continue
}
filenameKey := match + ".filename"
filenameKey := match + "filename"
if filename, ok := vmxData[filenameKey]; ok {
if filename == isoPath {
// Change the CD-ROM device back to auto-detect to eject
vmxData[filenameKey] = "auto detect"
vmxData[match+".deviceType"] = "cdrom-raw"
vmxData[match+"devicetype"] = "cdrom-raw"
}
}
}
+40 -16
View File
@@ -22,8 +22,31 @@ import (
// vnc_port uint - The port that VNC is configured to listen on.
type stepConfigureVNC struct{}
func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
type VNCAddressFinder interface {
VNCAddress(uint, uint) (string, uint)
}
func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint) {
// Find an open VNC port. Note that this can still fail later on
// because we have to release the port at some point. But this does its
// best.
var vncPort uint
portRange := int(portMax - portMin)
for {
vncPort = uint(rand.Intn(portRange)) + portMin
log.Printf("Trying port: %d", vncPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", vncPort))
if err == nil {
defer l.Close()
break
}
}
return "127.0.0.1", vncPort
}
func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
@@ -43,27 +66,27 @@ func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
// Find an open VNC port. Note that this can still fail later on
// because we have to release the port at some point. But this does its
// best.
var vncFinder VNCAddressFinder
if finder, ok := driver.(VNCAddressFinder); ok {
vncFinder = finder
} else {
vncFinder = s
}
log.Printf("Looking for available port between %d and %d", config.VNCPortMin, config.VNCPortMax)
var vncPort uint
portRange := int(config.VNCPortMax - config.VNCPortMin)
for {
vncPort = uint(rand.Intn(portRange)) + config.VNCPortMin
log.Printf("Trying port: %d", vncPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", vncPort))
if err == nil {
defer l.Close()
break
}
vncIp, vncPort := vncFinder.VNCAddress(config.VNCPortMin, config.VNCPortMax)
if vncPort == 0 {
err := fmt.Errorf("Unable to find available VNC port between %d and %d",
config.VNCPortMin, config.VNCPortMax)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Found available VNC port: %d", vncPort)
vmxData := ParseVMX(string(vmxBytes))
vmxData["RemoteDisplay.vnc.enabled"] = "TRUE"
vmxData["RemoteDisplay.vnc.port"] = fmt.Sprintf("%d", vncPort)
vmxData["remotedisplay.vnc.enabled"] = "TRUE"
vmxData["remotedisplay.vnc.port"] = fmt.Sprintf("%d", vncPort)
if err := WriteVMX(vmxPath, vmxData); err != nil {
err := fmt.Errorf("Error writing VMX data: %s", err)
@@ -73,6 +96,7 @@ func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
}
state.Put("vnc_port", vncPort)
state.Put("vnc_ip", vncIp)
return multistep.ActionContinue
}
+30 -7
View File
@@ -8,6 +8,7 @@ import (
"log"
"os"
"path/filepath"
"strings"
)
type vmxTemplateData struct {
@@ -26,9 +27,11 @@ type vmxTemplateData struct {
//
// Produces:
// vmx_path string - The path to the VMX file.
type stepCreateVMX struct{}
type stepCreateVMX struct {
tempDir string
}
func (stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
@@ -77,6 +80,7 @@ func (stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
log.Println("Setting custom VMX data...")
for k, v := range config.VMXData {
log.Printf("Setting VMX: '%s' = '%s'", k, v)
k = strings.ToLower(k)
vmxData[k] = v
}
}
@@ -84,14 +88,30 @@ func (stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
log.Println("Floppy path present, setting in VMX")
vmxData["floppy0.present"] = "TRUE"
vmxData["floppy0.fileType"] = "file"
vmxData["floppy0.fileName"] = floppyPathRaw.(string)
vmxData["floppy0.filetype"] = "file"
vmxData["floppy0.filename"] = floppyPathRaw.(string)
}
// Set this so that no dialogs ever appear from Packer.
vmxData["msg.autoAnswer"] = "true"
vmxData["msg.autoanswer"] = "true"
vmxPath := filepath.Join(config.OutputDir, config.VMName+".vmx")
vmxDir := config.OutputDir
if config.RemoteType != "" {
// For remote builds, we just put the VMX in a temporary
// directory since it just gets uploaded anyways.
vmxDir, err = ioutil.TempDir("", "packer-vmx")
if err != nil {
err := fmt.Errorf("Error preparing VMX template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the tempDir so we clean it up
s.tempDir = vmxDir
}
vmxPath := filepath.Join(vmxDir, config.VMName+".vmx")
if err := WriteVMX(vmxPath, vmxData); err != nil {
err := fmt.Errorf("Error creating VMX file: %s", err)
state.Put("error", err)
@@ -104,7 +124,10 @@ func (stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (stepCreateVMX) Cleanup(multistep.StateBag) {
func (s *stepCreateVMX) Cleanup(multistep.StateBag) {
if s.tempDir != "" {
os.RemoveAll(s.tempDir)
}
}
// This is the default VMX template used if no other template is given.
+54 -18
View File
@@ -1,49 +1,85 @@
package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os"
"time"
)
type stepPrepareOutputDir struct{}
type stepPrepareOutputDir struct {
dir OutputDir
}
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
func (s *stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
os.RemoveAll(config.OutputDir)
}
dir := s.outputDir(state)
dir.SetOutputDir(config.OutputDir)
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
exists, err := dir.DirExists()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if exists {
if config.PackerForce {
ui.Say("Deleting previous output directory...")
dir.RemoveAll()
} else {
state.Put("error", fmt.Errorf("Output directory '%s' already exists.", config.OutputDir))
return multistep.ActionHalt
}
}
if err := dir.MkdirAll(); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
s.dir = dir
state.Put("dir", dir)
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
func (s *stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := os.RemoveAll(config.OutputDir)
if err == nil {
break
}
if s.dir != nil {
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := s.dir.RemoveAll()
if err == nil {
break
}
log.Printf("Error removing output dir: %s", err)
time.Sleep(2 * time.Second)
log.Printf("Error removing output dir: %s", err)
time.Sleep(2 * time.Second)
}
}
}
}
func (s *stepPrepareOutputDir) outputDir(state multistep.StateBag) (dir OutputDir) {
driver := state.Get("driver").(Driver)
switch d := driver.(type) {
case OutputDir:
log.Printf("Using driver as the OutputDir implementation")
dir = d
default:
log.Printf("Using localOutputDir implementation")
dir = new(localOutputDir)
}
return
}
+42
View File
@@ -0,0 +1,42 @@
package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
// stepRemoteUpload uploads some thing from the state bag to a remote driver
// (if it can) and stores that new remote path into the state bag.
type stepRemoteUpload struct {
Key string
Message string
}
func (s *stepRemoteUpload) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
remote, ok := driver.(RemoteDriver)
if !ok {
return multistep.ActionContinue
}
ui.Say(s.Message)
path := state.Get(s.Key).(string)
log.Printf("Remote uploading: %s", path)
newPath, err := remote.UploadISO(path)
if err != nil {
err := fmt.Errorf("Error uploading file: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put(s.Key, newPath)
return multistep.ActionContinue
}
func (s *stepRemoteUpload) Cleanup(state multistep.StateBag) {
}
+37 -2
View File
@@ -20,6 +20,8 @@ import (
type stepRun struct {
bootTime time.Time
vmxPath string
registered bool
}
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
@@ -27,6 +29,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
vncIp := state.Get("vnc_ip").(string)
vncPort := state.Get("vnc_port").(uint)
// Set the VMX path so that we know we started the machine
@@ -38,7 +41,18 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
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))
"%s:%d", vncIp, vncPort))
}
if remoteDriver, ok := driver.(RemoteDriver); ok {
if err := remoteDriver.Register(vmxPath); err != nil {
err := fmt.Errorf("Error registering VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.registered = true
}
if err := driver.Start(vmxPath, config.Headless); err != nil {
@@ -51,7 +65,19 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
// Wait the wait amount
if int64(config.bootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait.String()))
time.Sleep(config.bootWait)
wait := time.After(config.bootWait)
WAITLOOP:
for {
select {
case <-wait:
break WAITLOOP
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
}
}
}
return multistep.ActionContinue
@@ -80,5 +106,14 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
ui.Error(fmt.Sprintf("Error stopping VM: %s", err))
}
}
if remoteDriver, ok := driver.(RemoteDriver); ok && s.registered {
ui.Say("Unregistering virtual machine...")
if err := remoteDriver.Unregister(s.vmxPath); err != nil {
ui.Error(fmt.Sprintf("Error unregistering VM: %s", err))
}
s.registered = false
}
}
}
+29
View File
@@ -0,0 +1,29 @@
package vmware
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
// This step suppresses any messages that VMware product might show.
type stepSuppressMessages struct{}
func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
log.Println("Suppressing messages in VMX")
if err := driver.SuppressMessages(vmxPath); err != nil {
err := fmt.Errorf("Error suppressing messages: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepSuppressMessages) Cleanup(state multistep.StateBag) {}
+6 -2
View File
@@ -36,13 +36,15 @@ type stepTypeBootCommand struct{}
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
vncIp := state.Get("vnc_ip").(string)
vncPort := state.Get("vnc_port").(uint)
// Connect to VNC
ui.Say("Connecting to VM via VNC")
nc, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", vncPort))
nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIp, vncPort))
if err != nil {
err := fmt.Errorf("Error connecting to VNC: %s", err)
state.Put("error", err)
@@ -64,7 +66,9 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
// Determine the host IP
var ipFinder HostIPFinder
if runtime.GOOS == "windows" {
if finder, ok := driver.(HostIPFinder); ok {
ipFinder = finder
} else if runtime.GOOS == "windows" {
ipFinder = new(VMnetNatConfIPFinder)
} else {
ipFinder = &IfconfigIPFinder{Device: "vmnet8"}
+4 -1
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"log"
"os"
"regexp"
"sort"
@@ -23,7 +24,8 @@ func ParseVMX(contents string) map[string]string {
continue
}
results[matches[1]] = matches[2]
key := strings.ToLower(matches[1])
results[key] = matches[2]
}
return results
@@ -51,6 +53,7 @@ func EncodeVMX(contents map[string]string) string {
// WriteVMX takes a path to a VMX file and contents in the form of a
// map and writes it out.
func WriteVMX(path string, data map[string]string) (err error) {
log.Printf("Writing VMX to: %s", path)
f, err := os.Create(path)
if err != nil {
return
+12 -12
View File
@@ -105,21 +105,21 @@ func DownloadableURL(original string) (string, error) {
url.Path = strings.Replace(url.Path, `\`, `/`, -1)
}
if _, err := os.Stat(url.Path); err != nil {
return "", err
}
// Only do the filepath transformations if the file appears
// to actually exist.
if _, err := os.Stat(url.Path); err == nil {
url.Path, err = filepath.Abs(url.Path)
if err != nil {
return "", err
}
url.Path, err = filepath.Abs(url.Path)
if err != nil {
return "", err
}
url.Path, err = filepath.EvalSymlinks(url.Path)
if err != nil {
return "", err
}
url.Path, err = filepath.EvalSymlinks(url.Path)
if err != nil {
return "", err
url.Path = filepath.Clean(url.Path)
}
url.Path = filepath.Clean(url.Path)
}
// Make sure it is lowercased
+2 -2
View File
@@ -144,8 +144,8 @@ func TestDownloadableURL_FilePaths(t *testing.T) {
for _, prefix := range []string{"", "file://"} {
// Nonexistent file
_, err = DownloadableURL(prefix + "i/dont/exist")
if err == nil {
t.Fatal("expected err")
if err != nil {
t.Fatalf("err: %s", err)
}
// Good file
+9 -3
View File
@@ -16,10 +16,16 @@ import (
//
// Produces:
// <nothing>
type StepProvision struct{}
type StepProvision struct {
Comm packer.Communicator
}
func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
comm := s.Comm
if comm == nil {
comm = state.Get("communicator").(packer.Communicator)
}
func (*StepProvision) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
hook := state.Get("hook").(packer.Hook)
ui := state.Get("ui").(packer.Ui)
+4 -1
View File
@@ -23,7 +23,9 @@ const defaultConfig = `
"amazon-chroot": "packer-builder-amazon-chroot",
"amazon-instance": "packer-builder-amazon-instance",
"digitalocean": "packer-builder-digitalocean",
"docker": "packer-builder-docker",
"openstack": "packer-builder-openstack",
"qemu": "packer-builder-qemu",
"virtualbox": "packer-builder-virtualbox",
"vmware": "packer-builder-vmware"
},
@@ -36,7 +38,8 @@ const defaultConfig = `
},
"post-processors": {
"vagrant": "packer-post-processor-vagrant"
"vagrant": "packer-post-processor-vagrant",
"vsphere": "packer-post-processor-vsphere"
},
"provisioners": {
+1
View File
@@ -82,6 +82,7 @@ func wrappedMain() int {
"Packer Version: %s %s %s",
packer.Version, packer.VersionPrerelease, packer.GitCommit)
log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
log.Printf("Built with Go Version: %s", runtime.Version())
// Prepare stdin for plugin usage by switching it to a pipe
setupStdin()
+1 -1
View File
@@ -106,7 +106,7 @@ func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
attr = 1
}
return fmt.Sprintf("\033[%d;%d;40m%s\033[0m", attr, color, message)
return fmt.Sprintf("\033[%d;%dm%s\033[0m", attr, color, message)
}
func (u *ColoredUi) supportsColors() bool {
+3 -3
View File
@@ -19,19 +19,19 @@ func TestColoredUi(t *testing.T) {
ui.Say("foo")
result := readWriter(bufferUi)
if result != "\033[1;33;40mfoo\033[0m\n" {
if result != "\033[1;33mfoo\033[0m\n" {
t.Fatalf("invalid output: %s", result)
}
ui.Message("foo")
result = readWriter(bufferUi)
if result != "\033[0;33;40mfoo\033[0m\n" {
if result != "\033[0;33mfoo\033[0m\n" {
t.Fatalf("invalid output: %s", result)
}
ui.Error("foo")
result = readWriter(bufferUi)
if result != "\033[1;31;40mfoo\033[0m\n" {
if result != "\033[1;31mfoo\033[0m\n" {
t.Fatalf("invalid output: %s", result)
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var GitCommit string
// The version of packer.
const Version = "0.3.11"
const Version = "0.4.1"
// 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/builder/docker"
"github.com/mitchellh/packer/packer/plugin"
)
func main() {
plugin.ServeBuilder(new(docker.Builder))
}
+1
View File
@@ -0,0 +1 @@
package main
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/mitchellh/packer/builder/qemu"
"github.com/mitchellh/packer/packer/plugin"
)
func main() {
plugin.ServeBuilder(new(qemu.Builder))
}
+1
View File
@@ -0,0 +1 @@
package main
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/packer/post-processor/vsphere"
)
func main() {
plugin.ServePostProcessor(new(vsphere.PostProcessor))
}
@@ -0,0 +1 @@
package main
+163
View File
@@ -0,0 +1,163 @@
package vagrant
import (
"compress/flate"
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
)
type DigitalOceanBoxConfig struct {
common.PackerConfig `mapstructure:",squash"`
OutputPath string `mapstructure:"output"`
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
CompressionLevel string `mapstructure:"compression_level"`
tpl *packer.ConfigTemplate
}
type DigitalOceanVagrantfileTemplate struct {
Image string ""
Region string ""
}
type DigitalOceanBoxPostProcessor struct {
config DigitalOceanBoxConfig
}
func (p *DigitalOceanBoxPostProcessor) Configure(rDigitalOcean ...interface{}) error {
md, err := common.DecodeConfig(&p.config, rDigitalOcean...)
if err != nil {
return err
}
p.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return err
}
p.config.tpl.UserVars = p.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
validates := map[string]*string{
"output": &p.config.OutputPath,
"vagrantfile_template": &p.config.VagrantfileTemplate,
"compression_level": &p.config.CompressionLevel,
}
for n, ptr := range validates {
if err := p.config.tpl.Validate(*ptr); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing %s: %s", n, err))
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *DigitalOceanBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
// Determine the image and region...
tplData := &DigitalOceanVagrantfileTemplate{}
parts := strings.Split(artifact.Id(), ":")
if len(parts) != 2 {
return nil, false, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
}
tplData.Region = parts[0]
tplData.Image = parts[1]
// Compile the output path
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
ArtifactId: artifact.Id(),
BuildName: p.config.PackerBuildName,
Provider: "digitalocean",
})
if err != nil {
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, 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, false, err
}
defer vf.Close()
vagrantfileContents := defaultDigitalOceanVagrantfile
if p.config.VagrantfileTemplate != "" {
log.Printf("Using vagrantfile template: %s", p.config.VagrantfileTemplate)
f, err := os.Open(p.config.VagrantfileTemplate)
if err != nil {
err = fmt.Errorf("error opening vagrantfile template: %s", err)
return nil, false, err
}
defer f.Close()
contents, err := ioutil.ReadAll(f)
if err != nil {
err = fmt.Errorf("error reading vagrantfile template: %s", err)
return nil, false, err
}
vagrantfileContents = string(contents)
}
vagrantfileContents, err = p.config.tpl.Process(vagrantfileContents, tplData)
if err != nil {
return nil, false, fmt.Errorf("Error writing Vagrantfile: %s", err)
}
vf.Write([]byte(vagrantfileContents))
vf.Close()
// Create the metadata
metadata := map[string]string{"provider": "digital_ocean"}
if err := WriteMetadata(dir, metadata); err != nil {
return nil, false, err
}
// Compress the directory to the given output path
var level int = flate.DefaultCompression
if p.config.CompressionLevel != "" {
level, err = strconv.Atoi(p.config.CompressionLevel)
if err != nil {
return nil, false, err
}
}
if err := DirToBox(outputPath, dir, ui, level); err != nil {
err = fmt.Errorf("error creating box: %s", err)
return nil, false, err
}
return NewArtifact("DigitalOcean", outputPath), true, nil
}
var defaultDigitalOceanVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :digital_ocean do |digital_ocean|
digital_ocean.image = "{{ .Image }}"
digital_ocean.region = "{{ .Region }}"
end
end
`

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