Compare commits

..

181 Commits

Author SHA1 Message Date
Megan Marsh 542cb1d872 Cut version 1.3.5 2019-02-28 14:58:54 -08:00
Megan Marsh 58f0b8c22e remove vet which seems to have been broken in golang v1.12 2019-02-28 14:51:29 -08:00
Megan Marsh 09df6a733d cut release 1.3.5 2019-02-28 14:49:36 -08:00
Megan Marsh 64d5ef8709 update changelog date 2019-02-28 14:39:05 -08:00
Megan Marsh 49a6436c18 fix wording in changelog 2019-02-28 13:02:19 -08:00
Megan Marsh 7dbd42c7f3 update changelog 2019-02-28 11:50:39 -08:00
Megan Marsh 1e47421ba1 Merge pull request #7351 from hashicorp/move_pause_before_connection
move pause to after connection can be established.
2019-02-28 10:56:11 -08:00
Adrien Delorme 0e8afa37d5 Merge pull request #7347 from amphied/master
Fix support for aws-us-gov
2019-02-27 10:57:53 +01:00
Michael F 01a0b0c766 kmsArnStartPattern - fix support for aws-us-gov
While the `kmsArnStartPattern` regexp supports `us-gov` as a region, it does not take the AWS partition into consideration.
For more info see https://docs.aws.amazon.com/govcloud-us/latest/ug-east/using-govcloud-arns.html

Added test case for AWS partition us-gov as well as a negative test case for a non-existing AWS partition.
2019-02-26 16:54:54 -08:00
Megan Marsh 9db844a807 move pause to after connection can be established. 2019-02-26 12:24:45 -08:00
Megan Marsh b8510f35fc Merge pull request #7330 from hashicorp/except_post_processor_tests
name a post-processor to it's type when it is not named
2019-02-26 09:26:17 -08:00
Adrien Delorme cc2e30826b Merge pull request #7344 from ad-m/patch-1
Add hyperone builder to CHANGELOG.md
2019-02-26 16:16:59 +01:00
Adrien Delorme b32449cdcc Merge pull request #7336 from dkuntz2/make-private-ip-fixer-error-more-clearly
Make the amazon-private-ip fixer errors more visible
2019-02-26 09:47:31 +01:00
Megan Marsh 674ff489a9 Merge pull request #7343 from hashicorp/fix_6369
move snapshot deletion to cleanup; this allows people with tag-specif…
2019-02-25 17:50:14 -08:00
Megan Marsh 026e543b24 move snapshot deletion to cleanup; this allows people with tag-specific permissions to perform snapshot deletion operation 2019-02-25 17:01:19 -08:00
Megan Marsh 134cf3a141 update gemfile to fix vulnerability in bootstrap-saas dependency 2019-02-25 12:58:35 -08:00
Don Kuntz 279fb7a632 gofix 2019-02-25 14:41:26 -06:00
Adam Dobrawy 419a8c3324 Add hyperone builder to CHANGELOG.md 2019-02-25 21:31:22 +01:00
Don Kuntz b0589c9643 Append underlying error to output when FixerAmazonPrivateIP cannot parse the value of ssh_private_ip 2019-02-25 14:20:14 -06:00
Don Kuntz 04de86d211 Add test for non-boolean values in ssh_private_ip for FixerAmazonPrivateIP 2019-02-25 14:19:13 -06:00
Megan Marsh 1c3bc70b12 Merge pull request #7338 from chhaj5236/feature/update_aliyun_sdk
alicloud: update aliyun sdk to support eu-west-1 region
2019-02-25 09:32:02 -08:00
Megan Marsh 3f7c4f7406 Merge pull request #7341 from hashicorp/fix_7325
openstack WaitForImage: pass context for cancellation
2019-02-25 09:30:36 -08:00
Megan Marsh 05897c8653 Merge pull request #7294 from hyperonecom/hyperone
Add HyperOne builder
2019-02-25 08:30:04 -08:00
Megan Marsh f7ba933809 Merge pull request #7332 from joellopes03/fix_7301
Use IMDS to get subscription for Azure MSI
2019-02-25 08:25:12 -08:00
Adrien Delorme b9411712eb builder/openstack/step_create_image.go: pass context for cancellation
fix #7325
2019-02-25 15:03:11 +01:00
Adrien Delorme 3cda840504 Merge pull request #7329 from lrxcy/fix_190220
add Endpoints Description
2019-02-25 09:02:32 +01:00
Miłosz Smółka 94a7a07c87 Merge branch 'master' into hyperone 2019-02-24 15:16:21 +01:00
Miłosz Smółka 0673bb2c57 Add cleanup for stepCreateImage 2019-02-23 14:57:19 +01:00
gregorybrzeski ad9349a6c5 add reviewers for builder to CODEOWNERS file 2019-02-23 12:13:41 +01:00
lrxcy ab939989ea fix some typos 2019-02-23 14:03:48 +08:00
bozhi.ch f751e20b7e update aliyun sdk to support eu-west-1 region 2019-02-23 11:43:35 +08:00
Don Kuntz e4faa98b6f cleanup: remove continue statement after a return statement, because it's unreachable 2019-02-22 10:38:27 -06:00
Don Kuntz e251adb37e Make the amazon-private-ip fixer errors more visible
At present, when using the packer fix command on a template that has
"ssh_private_ip" set to anything but a boolean value, the fixer will
fail, and appear to fail silently, simply returning a non-zero status
code without any message.

To determine what happened, users have to know to set PACKER_LOG=1 to
make the log message visible.

So far as I can tell, this is the only instance of log.Fatalf being
called, and based on the surrounding code the better solution would be
to return an error, which will then be visible to users of packer fix
without having to look in the logs.
2019-02-22 10:26:55 -06:00
Adrien Delorme 4da79a8837 Merge pull request #7333 from pop/patch-2
Update vsphere+vsphere-template array pairing docs
2019-02-22 10:59:32 +01:00
Joel Lopes c8a60c77d1 Add comments 2019-02-21 18:12:57 -08:00
Megan Marsh 440f1f5f3c Merge pull request #7317 from hashicorp/pause_before_connecting
add a configurable pause before step_connect to work around bootstrap…
2019-02-21 11:59:47 -08:00
Elijah Caine M. Voigt 472b5ba710 Update vsphere+vsphere-template array pairing docs
The example and description of pairing `vsphere` and `vsphere-template` in a JSON array is accurate but ought to be more spelled out.
This introduces a change which makes that part of the example and docs more verbose, hopefully mitigating potential confusion.
2019-02-21 10:51:48 -08:00
Joel Lopes 1c030cb54b Use IMDS to get subscription for Azure MSI 2019-02-21 09:35:14 -08:00
Adrien Delorme 944c00900e parse_test.go: still display file name in case of error 2019-02-20 12:54:57 +01:00
Adrien Delorme c12e9eea3a template/parse_test.go: name the post-processors to their type as it's now the default 2019-02-20 12:49:21 +01:00
Adrien Delorme b6b3cc901c template/parse_test.go: use diff for easier eye debug 2019-02-20 12:48:52 +01:00
Adrien Delorme b37dee1a67 Update post-processor docs 2019-02-20 12:39:37 +01:00
Adrien Delorme 5eccbc702d name a post-processor to it's type if it is not named 2019-02-20 12:38:30 +01:00
Adrien Delorme 3167d5d52c document post-processor execpt more 2019-02-20 12:38:21 +01:00
Adrien Delorme 0f7065f6b1 post-processor except: don't match empty names 2019-02-20 12:38:03 +01:00
Adrien Delorme 7e2d86731e tests: add a post-processor that creates an unnamed.txt file 2019-02-20 11:03:17 +01:00
lrxcy 3f04b8193f fix endpoints description 2019-02-20 14:38:54 +08:00
lrxcy 4fae17767d add Endpoints Description 2019-02-20 12:18:16 +08:00
Megan Marsh 5cc442872e Merge pull request #7314 from hashicorp/fix_7234
add noSSLVerify to ovftool args
2019-02-19 16:54:48 -08:00
Adrien Delorme de336ef15e TestBuildOnlyFileMultipleFlags: check that tomato.txt is not created 2019-02-19 16:40:42 +01:00
Adrien Delorme b04b8b3857 test building with -except="" 2019-02-19 15:25:41 +01:00
Adrien Delorme 560b15de00 Merge pull request #7326 from hashicorp/allow_to_set_build_settings
scripts/build.sh: allow to set build settings when building
2019-02-19 15:11:51 +01:00
Adrien Delorme f6be550f1a scripts/build.sh: allow to set build settings when building
this will for example allow me to have the following alias:
alias buildmain='export T=$(mktemp -d) && ALL_XC_OS="linux darwin windows" ALL_XC_ARCH="amd64" GOLDFLAGS="-s -w" ./scripts/build.sh && cd pkg/ && for dir in *; do zip -r "$dir.zip" $dir & ; done ;  wait && mv *.zip $T/. && open $T'

that build only on 'main' platforms, so that I can share binaries easily.
2019-02-19 15:10:30 +01:00
Adrien Delorme e93eda2c88 Merge pull request #7311 from hashicorp/fix_7228
make sure we don't try to load an iso into memory because of a user m…
2019-02-18 18:35:01 +01:00
Adrien Delorme 4f46bd74ab Merge pull request #7313 from kristi/patch-1
add DescribeInstanceStatus to minimum permissions
2019-02-18 18:19:56 +01:00
Megan Marsh 31c8acc5bc add a configurable pause before step_connect to work around bootstrap race conditions 2019-02-15 15:05:29 -08:00
Megan Marsh 9b69790514 update changelog 2019-02-15 13:12:45 -08:00
Megan Marsh 75f574bd4b Merge pull request #7221 from hashicorp/vagrant_builder
Vagrant builder
2019-02-15 10:05:44 -08:00
Megan Marsh cb5c896c01 Merge pull request #7310 from hashicorp/fix_7286
also strip \r to work around windows file ending strangeness
2019-02-15 09:19:33 -08:00
Megan Marsh 9f702af6d9 we lost the skip_add step somewhere 2019-02-14 14:46:14 -08:00
Megan Marsh bbc52f9a7d add noSSLVerify to ovftool args 2019-02-14 12:10:58 -08:00
Megan Marsh be21850e32 Merge pull request #7060 from andrewsomething/do-post-processor
Add digitalocean-import post-processor.
2019-02-14 11:58:00 -08:00
Kristi 7e7e486901 add DescribeInstanceStatus to minimum permissions
Packer needs DescribeInstanceStatus or else it ends up waiting forever for the instance to become ready.
2019-02-14 11:53:53 -08:00
Megan Marsh 2a9f49a5c6 make sure we don't try to load an iso into memory because of a user mistake 2019-02-13 16:21:55 -08:00
Andrew Starr-Bochicchio 37b3305301 Better varibable naming. 2019-02-13 18:55:27 -05:00
Megan Marsh bd3339f1f9 also strip \r to work around windows file ending strangeness 2019-02-13 15:41:37 -08:00
Megan Marsh 9fe1366eeb Merge pull request #7180 from xinau/packer-provisioner-inspec
Added inspec.io provisioner
2019-02-13 13:14:32 -08:00
xinau b449af84ee moved concurrency-safe ui code to packer/ui.go 2019-02-13 21:01:50 +00:00
Andrew Starr-Bochicchio ee430d9a40 Only filter on suffix if more than one file. 2019-02-12 20:20:39 -05:00
xinau ae59b81f44 added check for docker backend 2019-02-12 20:07:13 +00:00
Megan Marsh 9bada2e35d Merge pull request #7305 from boumenot/pr-azure-new-maintainer
azure: new maintainer
2019-02-12 11:11:22 -08:00
Christopher Boumenot bc69f3e1c3 azure: new maintainer 2019-02-12 10:49:05 -08:00
xinau 36c1e8d838 moved adapter to common package 2019-02-12 07:10:57 +00:00
xinau 8526244285 changed local_port to uint representation 2019-02-12 06:32:19 +00:00
xinau c207451f7e Merge branch 'master' of https://github.com/hashicorp/packer into packer-provisioner-inspec 2019-02-12 05:32:05 +00:00
Megan Marsh 956bc35769 Merge pull request #7281 from pauloj95/patch-1
add force to powershell move command
2019-02-11 16:16:18 -08:00
Andrew Starr-Bochicchio 62865cf37b Mention environmental variables in docs. 2019-02-11 18:24:16 -05:00
Andrew Starr-Bochicchio 203b6ca8b4 Prefix env vars w/ 'DIGITALOCEAN_' 2019-02-11 18:20:35 -05:00
Megan Marsh c7278e871c Merge pull request #7038 from kmbulebu/openstack_deprecated_api
Replaced compute/v2/images with imageservice/v2/images.
2019-02-11 15:05:45 -08:00
Megan Marsh e0ed7b6742 Merge pull request #7293 from hashicorp/amazon_import_docs
add perms
2019-02-11 14:24:28 -08:00
Megan Marsh f2439625bf Merge pull request #7298 from hashicorp/fix_7256
Fix 7256
2019-02-11 14:24:01 -08:00
Megan Marsh 22e3f09db3 Merge pull request #7282 from hashicorp/do_6994
Allow amazon builders to read credentials from Vault.
2019-02-11 12:49:23 -08:00
Megan Marsh 2a613dd6e9 fix up the github suggestion 2019-02-11 11:49:17 -08:00
Adrien Delorme 3ae5a912d4 Update builder/amazon/common/step_pre_validate.go
Co-Authored-By: SwampDragons <megan@hashicorp.com>
2019-02-11 11:41:58 -08:00
Megan Marsh 51b46b851a formatted amazon docs 2019-02-11 11:40:56 -08:00
Megan Marsh 37f474ceca add perms 2019-02-11 09:21:45 -08:00
Megan Marsh d318f3942b Merge pull request #7302 from a-yiorgos/patch-1
allow the basic example to pass packer validate
2019-02-11 09:13:06 -08:00
Yiorgos Adamopoulos 591533cee0 allow the basic example to pass packer validate
tested with Packer v1.3.4.  Without the builders section is complains for " Unknown root level key in template:  " for every key in the JSON.
2019-02-11 17:05:55 +02:00
Miłosz Smółka 49889802b9 Update go.sum 2019-02-10 13:22:10 +01:00
Miłosz Smółka 7501b1434f Add missing dependency to the vendor tree 2019-02-10 13:20:30 +01:00
Adam Dobrawy 61772d715d Add chroot in bats tests 2019-02-10 05:15:22 +01:00
Megan Marsh 3fd589fa72 typos 2019-02-08 09:31:30 -08:00
Megan Marsh e2d8c0bfd7 docs 2019-02-08 09:28:10 -08:00
Megan Marsh 07c0c599e0 attach guest additions via SATA when necessasry 2019-02-08 09:15:15 -08:00
Miłosz Smółka c2b39b8b3b Add h1-client-go to vendor directory 2019-02-08 11:15:45 +01:00
Megan Marsh 5057220ad2 use VAGRANT_CWD rather than changing packer run directories 2019-02-07 14:35:01 -08:00
Megan Marsh dc848ea5d7 just make vagrantfile instead of calling init 2019-02-07 12:39:56 -08:00
Miłosz Smółka 228829139b Merge branch 'master' into hyperone 2019-02-07 14:12:13 +01:00
Miłosz Smółka 34beaaabe4 Move checking aws cli to separate function 2019-02-07 09:30:47 +01:00
Miłosz Smółka 1ee4517370 Update source_image names in tests 2019-02-07 09:22:30 +01:00
Miłosz Smółka fdf028c463 Search SCSI device in /sys/bus due to issues with by-path 2019-02-06 12:30:14 +01:00
pauloj95 b1fffee614 Update guest_commands_test.go 2019-02-06 10:50:29 +00:00
pauloj95 b4d1c77cc0 Update guest_commands_test.go 2019-02-06 10:30:37 +00:00
pauloj95 d7b93aa343 add force to guestcommands test too 2019-02-06 10:27:45 +00:00
Megan Marsh e56d7f7234 fix tests 2019-02-05 15:34:59 -08:00
Megan Marsh 608b7cb7a3 review comments 2019-02-05 15:16:47 -08:00
Megan Marsh 90baa006da add docs on all builders. 2019-02-05 14:19:24 -08:00
Megan Marsh 3704a053d0 move region validation and credential wait into step pre validate 2019-02-05 14:07:04 -08:00
pauloj95 8d8b880ac4 add force to powershell move command
this will allow powershell move in provisioners and replace file if existing found...

issue example: https://github.com/hashicorp/packer/issues/7280
2019-02-05 17:47:00 +00:00
Adrien Delorme e8c655bf15 Update website/source/docs/builders/vagrant.html.md
Co-Authored-By: SwampDragons <megan@hashicorp.com>
2019-02-05 09:31:38 -08:00
Miłosz Smółka 0d8e9f50bf Add proper SCSI search 2019-02-05 16:20:42 +01:00
Adrien Delorme 9787e787d4 Merge pull request #7276 from paulmey/fix-7267
Fix Azure interactive authentication
2019-02-05 10:34:50 +01:00
Paul Meyer 68516fc05c Fix Azure interactive authentication
Builder looks up tenant ID before asking for token. Client config did
not allow that. Also found that token provider was not properly
initialized.

Fixes 7267
2019-02-05 02:35:12 +00:00
Megan Marsh a7d9d62996 fix tests on windows 2019-02-04 16:11:25 -08:00
Megan Marsh 8add176ab7 finish first pass at vault code; needs testing and cleanup of error messages 2019-02-04 15:54:14 -08:00
Megan Marsh 7a78b47e83 make vault engine stuff into a little struct for easier management 2019-02-04 15:29:45 -08:00
Megan Marsh 6b41a1663a updated the amazon ebs docs to include vault integration 2019-02-04 15:13:20 -08:00
Megan Marsh af7131b169 tests, logic cleanup, docs for vagrant builder 2019-02-04 14:08:43 -08:00
Megan Marsh 52892699ca make it work with a local vagrant box 2019-02-04 14:08:43 -08:00
Megan Marsh ad21367b21 vagrant builder 2019-02-04 14:08:43 -08:00
Megan Marsh 9f93fb2748 Merge pull request #7264 from rhtyd/go-cloudstack-vendor-update
vendor: update go-cloudstack v2.4.1
2019-02-04 14:02:01 -08:00
Megan Marsh ee2ed063e9 Merge pull request #7265 from danowar2k/master
FIX for PR #7254
2019-02-04 13:58:06 -08:00
Megan Marsh 2d3f84abf0 Merge pull request #7271 from hashicorp/make_generate
make generate
2019-02-04 13:11:24 -08:00
Rickard von Essen 36546c5dfe Merge pull request #7274 from rickard-von-essen/ansible-docs
Ansible docs
2019-02-04 20:19:39 +01:00
Megan Marsh 6c2e60ffa9 Merge pull request #7266 from hashicorp/fix_7259
quote variables in powershell script to account for spaces
2019-02-04 10:34:55 -08:00
Rickard von Essen 26aa0144e6 ansible: Change the doc navigator to Ansible (Remote) 2019-02-04 18:56:52 +01:00
Rickard von Essen 52b55fb899 ansible: change local_port to uint instead of string 2019-02-04 18:47:42 +01:00
Rickard von Essen 6d700d34df ansible: some doc style fixes 2019-02-04 18:21:44 +01:00
Adrien Delorme 8621949e01 make generate 2019-02-04 15:16:38 +01:00
Adrien Delorme 9f1ec211a0 Merge pull request #7268 from mayn/docs-updates-v144
docs - minor fixes
2019-02-04 10:45:20 +01:00
Matthew Aynalem b404cfc365 add builder/tencentcloud-cvm to sidebar
post-processors/docker-import - move tags under optional
add missing data types to various attributes
2019-02-03 16:24:38 -08:00
Miłosz Smółka f7de056f23 Add proper cleanup of chroot disk
Closes #3
2019-02-03 00:30:56 +01:00
Rohit Yadav 5af1c89e6e go.sum: tidy and remove old go-cloudstack dependency checksums
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
2019-02-03 01:55:25 +05:30
Miłosz Smółka 5e8eade205 Add support for custom API url of compatible service
Closes #4
2019-02-02 14:05:06 +01:00
Daniel Poggenpohl 6857e937e2 - FIX: That's what I get for wanting to use fancy or/and piping, back to basics (Now using explicit rc test to decide whether to quit) 2019-02-01 23:11:22 +01:00
Megan Marsh 482ba644a8 quote variables in powershell script to account for spaces 2019-02-01 13:23:17 -08:00
Daniel Poggenpohl b0abd1fbf3 - FIX: I should call my defined functions, else they are useless 2019-02-01 20:48:42 +01:00
Megan Marsh 6cac03044f Merge pull request #7248 from oceyral/oceyral/feat/add_user_data
Add "custom_data" key to packer manifest post-processor
2019-02-01 11:37:44 -08:00
Rohit Yadav 47e03a815f vendor: update go-cloudstack v2.4.1
This fixes #7263 and updates the dependency go-cloudstack v2.4.1

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
2019-02-02 00:56:40 +05:30
Megan Marsh f0b1d3cbf2 Merge pull request #7262 from hashicorp/fix_7257
make sure 'only' completely ignores post-processor
2019-02-01 10:01:55 -08:00
Miłosz Smółka 6921cc93a9 Add acceptance tests for chroot 2019-02-01 18:58:16 +01:00
Miłosz Smółka 4704565869 Rename vm_flavour to vm_type 2019-02-01 18:57:43 +01:00
Adrien Delorme 25f51b9f99 Merge pull request #7261 from davividal/fixes-checksum-1.11.4
Fixes checksum for dependencies for Go 1.11.4+
2019-02-01 18:06:19 +01:00
Adrien Delorme ca180d27fb update/fix some more modules 2019-02-01 17:53:56 +01:00
Miłosz Smółka e7e84f04e7 Fix timestamp interpolation in mount path
Closes #1
2019-02-01 17:46:35 +01:00
Adrien Delorme dd3e2c255c respect a 'only' defined in a post-processor 2019-02-01 15:50:06 +01:00
Adrien Delorme 074a74ec38 make sure 'only' completely ignores post-processor
before this commit, if one would put a 'only' inside the post-processor definition, the post process could be skipped
2019-02-01 15:21:41 +01:00
Adrien Delorme 55bfee509a Merge pull request #7260 from Murcurio/patch-1
Change GCP metadata url to FQDN
2019-02-01 14:44:33 +01:00
Davi Vidal 2bc4e6aec6 Fixes checksum for dependencies for Go 1.11.4+ 2019-02-01 12:38:28 +01:00
Olivier Ceyral 4d10d668ee Add "custom_data" key to packer manifest 2019-02-01 10:41:34 +01:00
Murcurio 60c7d22ffb Change metadata url to FQDN
If instance has a different DNS/search domain then "metadata" alone won't work. The FQDN of "metadata.google.internal" is included in hosts file, and will work regardless of DNS configuration. Alternatively the IP 169.254.169.254 can be used.
2019-02-01 15:36:40 +11:00
Megan Marsh eef4fc7a01 Merge pull request #7222 from Adezandee/gce-post-processors
googlecompute-postprocessors: service account and export configs
2019-01-31 16:58:42 -08:00
Megan Marsh 6ea64bc378 Merge pull request #7191 from arizvisa/GH-7190
Adds support to the vmware builders for specifying the number of cores per socket via the `cores` option.
2019-01-31 15:27:52 -08:00
Megan Marsh 888f4d440c Merge pull request #7251 from yaakov-h/7250-fix-overflow
Hyper-V: Fix integer overflows in 32-bit builds
2019-01-31 15:22:33 -08:00
Megan Marsh 63a3bb0105 Merge pull request #7254 from danowar2k/master
Fix for #7253
2019-01-31 11:07:09 -08:00
Daniel Poggenpohl 864ea7084d Merge branch '#7253-build-script-capitalization' 2019-01-31 15:53:53 +01:00
Daniel Poggenpohl ce760eec09 - More comments
- encapsulated code in functions to make it more readable
- validate presence of used tools/binaries (check if they're in PATH)
- more output
- FIX: make all uname/OSTYPE output to lowercase and check only for that
- refactored method name convert_path -> convertPathOnCygwin
- gave convert_path two arguments to make it more readable (Readability over tight code? I don't know what is required)
- some variable expansion now uses braces
2019-01-31 15:50:10 +01:00
Yaakov 0a08920816 Hyper-V: Fix integer overflows in 32-bit builds 2019-01-31 13:53:01 +11:00
Adrien Delorme 20c2524881 prepare for 1.3.5-dev 2019-01-30 16:45:38 +01:00
Adrien Delorme 3626fbd0f9 Cut version 1.3.4 2019-01-30 16:33:57 +01:00
Miłosz Smółka 3d3e933f48 Add host key hash verify 2019-01-29 10:44:23 +01:00
Miłosz Smółka 4a863379fa Update docs 2019-01-28 13:00:30 +01:00
Miłosz Smółka bd4ce90728 Add chroot disk build 2019-01-28 12:33:25 +01:00
Miłosz Smółka aaee600be0 Update docs 2019-01-25 11:39:30 +01:00
Arnaud Dezandee 8975f67901 googlecompute-export: add exporter instance configs 2019-01-25 11:36:18 +01:00
Arnaud Dezandee 91d2cb8c83 googlecompute-import: add service accounts 2019-01-25 11:36:18 +01:00
Miłosz Smółka c1892b60af Add basic hyperone example 2019-01-25 11:15:36 +01:00
Ali Rizvi-Santiago 3f559f8782 Updated the documentation with the vmx option that it uses. 2019-01-25 02:40:34 -06:00
Miłosz Smółka 3ce05c7c26 Add acceptance tests 2019-01-23 14:59:28 +01:00
Miłosz Smółka be30415395 Add HyperOne builder 2019-01-23 14:04:05 +01:00
Miłosz Smółka 9cbeae2c5a Merge branch 'master' into hyperone 2019-01-22 11:26:50 +01:00
Miłosz Smółka 7947e52089 Add basic HyperOne end-to-end tests 2019-01-21 19:20:59 +01:00
xinau f5b13e3cb5 added inspec.io provisioner 2019-01-20 15:43:47 +00:00
Ali Rizvi-Santiago 9d2f98394e Revert "Renamed the terse option, cores, to to the less-terse version cores_per_socket."
This reverts commit 0c37a3c68f and avoids bikeshedding from other committers.
2019-01-17 14:49:07 -06:00
Ali Rizvi-Santiago 0c37a3c68f Renamed the terse option, cores, to to the less-terse version cores_per_socket. 2019-01-17 10:39:19 -06:00
Ali Rizvi-Santiago 4d3f9cc1af Updated the documentation for the vmware-iso builder to describe the cores option. 2019-01-17 10:39:19 -06:00
Ali Rizvi-Santiago 4894873971 Added logic to the vmware-iso builder that appends the number of cores per socket to the vmx in stepCreateVmx. 2019-01-17 10:39:19 -06:00
Ali Rizvi-Santiago 062c62eed8 Added support for specifying the number of cores as cores to vmware-common. 2019-01-17 10:39:19 -06:00
Andrew Starr-Bochicchio c0c5c6afac Add DigitalOcean post-processor. 2018-11-29 21:00:57 -05:00
Kevin Bulebush 76d1bf567e Replaced compute/v2/images with imageservice/v2/images.
Removed usage of a deprecated API.
Standardized clientService naming.
2018-11-25 14:30:31 -05:00
Andrew Starr-Bochicchio 444fcd12c9 Update vendored godo. 2018-11-22 21:31:12 -05:00
511 changed files with 97520 additions and 39382 deletions
+59 -1
View File
@@ -1,4 +1,62 @@
## 1.3.4 (upcoming)
## 1.3.5 (February 28, 2019)
### IMPROVEMENTS:
* builder/alicloud: Update aliyun sdk to support eu-west-1 region [GH-7338]
* builder/amazon: AWS users can now use the Vault AWS engine to generate
temporary credentials. [GH-7282]
* builder/azure: IMDS to get subscription for Azure MSI [GH-7332]
* builder/openstack: Replaced deprecated compute/ api with imageservice/
[GH-7038]
* builder/virtualbox: New "guest_additions_interface" option to enable
attaching via a SATA interface. [GH-7298]
* builder/vmware: Add `cores` option for specifying the number of cores per
socket. [GH-7191]
* bulder/openstac: Deprecated compute/v2/images API [GH-7268]
* core: Add validation check to help folks who swap their iso_path and
checksum_path [GH-7311]
* fixer/amazon: Make the amazon-private-ip fixer errors more visible [GH-7336]
* post-processor/googlecompute-export: Extend auth for the GCE-post-processors
to act like the GCE builder. [GH-7222]
* post-processor/googlecompute-import: Extend auth for the GCE-post-processors
to act like the GCE builder. [GH-7222]
* post-processor/manifest: Add "custom_data" key to packer manifest post-
processor [GH-7248]
### BUG FIXES:
* builder/amazon: Fix support for aws-us-gov [GH-7347]
* builder/amazon: Move snapshot deletion to cleanup phase. [GH-7343]
* builder/azure: Fixed Azure interactive authentication [GH-7276]
* builder/cloudstack: Updated sdk version; can now use ostype name in
template_os option. [GH-7264]
* builder/google: Change metadata url to use a FQDN fixing bug stemming from
differing DNS/search domains. [GH-7260]
* builder/hyper-v: Fix integer overflows in 32-bit builds [GH-7251]
* builder/hyper-v: Fix regression where we improperly handled spaces in switch
names [GH-7266]
* builder/openstack: Pass context So we know to cancel during WaitForImage
[GH-7341]
* builder/vmware-esxi: Strip \r\n whitespace from end of names of
files stored on esxi. [GH-7310]
* builder/vmware: Add "--noSSLVerify" to args in ovftool Validation [GH-7314]
* core: clean up Makefile [GH-7254][GH-7265]
* core: Fixes mismatches in checksums for dependencies for Go 1.11.4+ [GH-7261]
* core: make sure 'only' option is completely ignored by post-processors
[GH-7262]
* core: name a post-processor to its type when it is not named [GH-7330]
* provisioner/salt: Force powershell to overwrite duplicate files [GH-7281]
### Features:
* **new builder** `vagrant` allows users to call vagrant to provision starting
from vagrant boxes and save them as new vagrant boxes. [GH-7221]
* **new builder:** `hyperone` for building new images on HyperOne Platform on
top of existing image or from the scratch with the use of chroot. [GH-7294]
* **new post-processor** `digitalocean-import`Add digitalocean-import post-
processor. [GH-7060]
* **new provisioner**`inspec` Added inspec.io provisioner [GH-7180]
* communicator: Add configurable pause after communicator can connect but
before it performs provisioning tasks [GH-7317] [GH-7351]
## 1.3.4 (January 30, 2019)
### IMPROVEMENTS:
* builder/alicloud: delete copied image and snapshots if corresponding options
are specified [GH-7050]
+2 -1
View File
@@ -5,7 +5,7 @@
/builder/alicloud/ @chhaj5236
/builder/amazon/ebssurrogate/ @jen20
/builder/amazon/ebsvolume/ @jen20
/builder/azure/ @boumenot
/builder/azure/ @paulmey
/builder/hyperv/ @taliesins
/builder/lxc/ @ChrisLundquist
/builder/lxd/ @ChrisLundquist
@@ -16,6 +16,7 @@
/builder/ncloud/ @YuSungDuk
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/hcloud/ @LKaemmerling
/builder/hyperone @m110 @gregorybrzeski @ad-m
# provisioners
+2 -2
View File
@@ -11,7 +11,7 @@ GOPATH=$(shell go env GOPATH)
# gofmt
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/(ansible|inspec)/test-fixtures/exit1')
# Get the git commit
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
@@ -101,7 +101,7 @@ generate: deps ## Generate dynamically generated code
goimports -w common/bootcommand/boot_command.go
gofmt -w command/plugin.go
test: fmt-check mode-check vet ## Run unit tests
test: fmt-check mode-check ## Run unit tests
@go test $(TEST) $(TESTARGS) -timeout=2m
# testacc runs acceptance tests
+2
View File
@@ -205,6 +205,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn)
state.Put("awsSession", session)
state.Put("hook", hook)
+65 -2
View File
@@ -16,8 +16,21 @@ import (
cleanhttp "github.com/hashicorp/go-cleanhttp"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/template/interpolate"
vaultapi "github.com/hashicorp/vault/api"
)
type VaultAWSEngineOptions struct {
Name string `mapstructure:"name"`
RoleARN string `mapstructure:"role_arn"`
TTL string `mapstructure:"ttl"`
EngineName string `mapstructure:"engine_name"`
}
func (v *VaultAWSEngineOptions) Empty() bool {
return len(v.Name) == 0 && len(v.RoleARN) == 0 &&
len(v.EngineName) == 0 && len(v.TTL) == 0
}
// AccessConfig is for common configuration related to AWS access
type AccessConfig struct {
AccessKey string `mapstructure:"access_key"`
@@ -32,6 +45,7 @@ type AccessConfig struct {
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
Token string `mapstructure:"token"`
session *session.Session
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine"`
getEC2Connection func() ec2iface.EC2API
}
@@ -44,6 +58,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
}
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
config.WithCredentials(staticCreds)
@@ -122,14 +137,62 @@ func (c *AccessConfig) IsChinaCloud() bool {
return strings.HasPrefix(c.SessionRegion(), "cn-")
}
func (c *AccessConfig) GetCredsFromVault() error {
// const EnvVaultAddress = "VAULT_ADDR"
// const EnvVaultToken = "VAULT_TOKEN"
vaultConfig := vaultapi.DefaultConfig()
cli, err := vaultapi.NewClient(vaultConfig)
if err != nil {
return fmt.Errorf("Error getting Vault client: %s", err)
}
if c.VaultAWSEngine.EngineName == "" {
c.VaultAWSEngine.EngineName = "aws"
}
path := fmt.Sprintf("/%s/creds/%s", c.VaultAWSEngine.EngineName,
c.VaultAWSEngine.Name)
secret, err := cli.Logical().Read(path)
if err != nil {
return fmt.Errorf("Error reading vault secret: %s", err)
}
if secret == nil {
return fmt.Errorf("Vault Secret does not exist at the given path.")
}
c.AccessKey = secret.Data["access_key"].(string)
c.SecretKey = secret.Data["secret_key"].(string)
token := secret.Data["security_token"]
if token != nil {
c.Token = token.(string)
} else {
c.Token = ""
}
return nil
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.SkipMetadataApiCheck {
log.Println("(WARN) skip_metadata_api_check ignored.")
}
// Either both access and secret key must be set or neither of them should
// be.
// Make sure it's obvious from the config how we're getting credentials:
// Vault, Packer config, or environemnt.
if !c.VaultAWSEngine.Empty() {
if len(c.AccessKey) > 0 {
errs = append(errs,
fmt.Errorf("If you have set vault_aws_engine, you must not set"+
" the access_key or secret_key."))
}
// Go ahead and grab those credentials from Vault now, so we can set
// the keys and token now.
err := c.GetCredsFromVault()
if err != nil {
errs = append(errs, err)
}
}
if (len(c.AccessKey) > 0) != (len(c.SecretKey) > 0) {
errs = append(errs,
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
+1 -1
View File
@@ -151,7 +151,7 @@ func (c *AMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
func validateKmsKey(kmsKey string) (valid bool) {
kmsKeyIdPattern := `[a-f0-9-]+$`
aliasPattern := `alias/[a-zA-Z0-9:/_-]+$`
kmsArnStartPattern := `^arn:aws:kms:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12}):`
kmsArnStartPattern := `^arn:aws(-us-gov)?:kms:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12}):`
if regexp.MustCompile(fmt.Sprintf("^%s", kmsKeyIdPattern)).MatchString(kmsKey) {
return true
}
+2
View File
@@ -187,6 +187,7 @@ func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
"alias/foo/bar",
"arn:aws:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
"arn:aws:kms:us-east-1:012345678910:alias/foo/bar",
"arn:aws-us-gov:kms:us-gov-east-1:123456789012:key/12345678-1234-abcd-0000-123456789012",
}
for _, validCase := range validCases {
c.AMIKmsKeyId = validCase
@@ -201,6 +202,7 @@ func TestAMIConfigPrepare_ValidateKmsKey(t *testing.T) {
"ghij1234+e567_890f-a12b-a123b4cd56ef",
"foo/bar",
"arn:aws:kms:us-east-1:012345678910:foo/bar",
"arn:foo:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef",
}
for _, invalidCase := range invalidCases {
c.AMIKmsKeyId = invalidCase
+15 -11
View File
@@ -17,6 +17,7 @@ type StepCreateEncryptedAMICopy struct {
EncryptBootVolume bool
Name string
AMIMappings []BlockDevice
ToDelete []*string
}
func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@@ -107,8 +108,7 @@ func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.St
return multistep.ActionHalt
}
// Remove associated unencrypted snapshot(s)
ui.Say("Deleting unencrypted snapshots")
// Figure out which unencrypted snapshot(s) to delete
snapshots := state.Get("snapshots").(map[string][]string)
OuterLoop:
@@ -121,15 +121,7 @@ OuterLoop:
continue OuterLoop
}
}
ui.Message(fmt.Sprintf("Deleting Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
deleteSnapOpts := &ec2.DeleteSnapshotInput{
SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
}
if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
return multistep.ActionHalt
}
s.ToDelete = append(s.ToDelete, aws.String(*blockDevice.Ebs.SnapshotId))
}
}
@@ -171,4 +163,16 @@ func (s *StepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) {
ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
return
}
ui.Say("Deleting unencrypted snapshots")
for _, snap := range s.ToDelete {
ui.Message(fmt.Sprintf("Deleting Snapshot ID: %s", *snap))
deleteSnapOpts := &ec2.DeleteSnapshotInput{
SnapshotId: snap,
}
if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
return
}
}
}
@@ -3,9 +3,12 @@ package common
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@@ -20,6 +23,51 @@ type StepPreValidate struct {
func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if accessConfig, ok := state.GetOk("access_config"); ok {
accessconf := accessConfig.(*AccessConfig)
if !accessconf.VaultAWSEngine.Empty() {
// loop over the authentication a few times to give vault-created creds
// time to become eventually-consistent
ui.Say("You're using Vault-generated AWS credentials. It may take a " +
"few moments for them to become available on AWS. Waiting...")
err := retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
ec2conn, err := accessconf.NewEC2Connection()
if err != nil {
return true, err
}
_, err = listEC2Regions(ec2conn)
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "AuthFailure" {
log.Printf("Waiting for Vault-generated AWS credentials" +
" to pass authentication... trying again.")
return false, nil
}
return true, err
}
return true, nil
})
if err != nil {
state.Put("error", fmt.Errorf("Was unable to Authenticate to AWS using Vault-"+
"Generated Credentials within the retry timeout."))
return multistep.ActionHalt
}
}
if amiConfig, ok := state.GetOk("ami_config"); ok {
amiconf := amiConfig.(*AMIConfig)
if !amiconf.AMISkipRegionValidation {
regionsToValidate := append(amiconf.AMIRegions, accessconf.RawRegion)
err := accessconf.ValidateRegion(regionsToValidate...)
if err != nil {
state.Put("error", fmt.Errorf("error validating regions: %v", err))
return multistep.ActionHalt
}
}
}
}
if s.ForceDeregister {
ui.Say("Force Deregister flag found, skipping prevalidating AMI Name")
return multistep.ActionContinue
+3 -7
View File
@@ -92,13 +92,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if err != nil {
return nil, err
}
if !b.config.AMISkipRegionValidation {
regionsToValidate := append(b.config.AMIRegions, b.config.RawRegion)
err := b.config.AccessConfig.ValidateRegion(regionsToValidate...)
if err != nil {
return nil, fmt.Errorf("error validating regions: %v", err)
}
}
ec2conn := ec2.New(session, &aws.Config{
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
})
@@ -106,6 +100,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn)
state.Put("awsSession", session)
state.Put("hook", hook)
+2
View File
@@ -114,6 +114,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn)
state.Put("awsSession", session)
state.Put("hook", hook)
+1
View File
@@ -103,6 +103,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
+2
View File
@@ -184,6 +184,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn)
state.Put("awsSession", session)
state.Put("hook", hook)
@@ -10,7 +10,11 @@ import (
)
func NewDeviceFlowOAuthTokenProvider(env azure.Environment, say func(string), tenantID string) oAuthTokenProvider {
return &deviceflowOauthTokenProvider{}
return &deviceflowOauthTokenProvider{
env: env,
say: say,
tenantID: tenantID,
}
}
type deviceflowOauthTokenProvider struct {
+14 -2
View File
@@ -59,8 +59,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.ctxCancel = cancel
defer cancel()
if err := newConfigRetriever().FillParameters(b.config); err != nil {
return nil, err
// User's intent to use MSI is indicated with empty subscription id, tenant, client id, client cert, client secret and jwt.
// FillParameters function will set subscription and tenant id here. Therefore getServicePrincipalTokens won't select right auth type.
// If we run this after getServicePrincipalTokens call then getServicePrincipalTokens won't have tenant id.
if !b.config.useMSI() {
if err := newConfigRetriever().FillParameters(b.config); err != nil {
return nil, err
}
}
log.Print(":: Configuration")
@@ -74,6 +79,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, err
}
// We need subscription id and tenant id for arm operations. Users hasn't specified one so we try to detect them here.
if b.config.useMSI() {
if err := newConfigRetriever().FillParameters(b.config); err != nil {
return nil, err
}
}
ui.Message("Creating Azure Resource Manager (ARM) client ...")
azureClient, err := NewAzureClient(
b.config.SubscriptionID,
+1 -2
View File
@@ -155,8 +155,7 @@ func (c ClientConfig) useDeviceLogin() bool {
c.ClientID == "" &&
c.ClientSecret == "" &&
c.ClientJWT == "" &&
c.ClientCertPath == "" &&
c.TenantID == ""
c.ClientCertPath == ""
}
func (c ClientConfig) useMSI() bool {
+12 -5
View File
@@ -268,11 +268,18 @@ func getCloud() *azure.Environment {
// tests for assertRequiredParametersSet
func Test_ClientConfig_CanUseDeviceCode(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
// TenantID is optional
assertValid(t, cfg)
// TenantID is optional, but Builder will look up tenant ID before requesting
t.Run("without TenantID", func(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
assertValid(t, cfg)
})
t.Run("with TenantID", func(t *testing.T) {
cfg := emptyClientConfig()
cfg.SubscriptionID = "12345"
cfg.TenantID = "12345"
assertValid(t, cfg)
})
}
func assertValid(t *testing.T, cfg ClientConfig) {
+39
View File
@@ -8,6 +8,10 @@ package arm
// 1. TenantID
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/packer/builder/azure/common"
)
@@ -24,6 +28,14 @@ func newConfigRetriever() configRetriever {
}
func (cr configRetriever) FillParameters(c *Config) error {
if c.SubscriptionID == "" {
subscriptionID, err := cr.getSubscriptionFromIMDS()
if err != nil {
return err
}
c.SubscriptionID = subscriptionID
}
if c.TenantID == "" {
tenantID, err := cr.findTenantID(*c.cloudEnvironment, c.SubscriptionID)
if err != nil {
@@ -34,3 +46,30 @@ func (cr configRetriever) FillParameters(c *Config) error {
return nil
}
func (cr configRetriever) getSubscriptionFromIMDS() (string, error) {
client := &http.Client{}
req, _ := http.NewRequest("GET", "http://169.254.169.254/metadata/instance/compute", nil)
req.Header.Add("Metadata", "True")
q := req.URL.Query()
q.Add("format", "json")
q.Add("api-version", "2017-08-01")
req.URL.RawQuery = q.Encode()
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
resp_body, _ := ioutil.ReadAll(resp.Body)
result := map[string]string{}
err = json.Unmarshal(resp_body, &result)
if err != nil {
return "", err
}
return result["subscriptionId"], nil
}
+8 -8
View File
@@ -12,16 +12,16 @@ import (
type Artifact struct {
// The name of the snapshot
snapshotName string
SnapshotName string
// The ID of the image
snapshotId int
SnapshotId int
// The name of the region
regionNames []string
RegionNames []string
// The client for making API calls
client *godo.Client
Client *godo.Client
}
func (*Artifact) BuilderId() string {
@@ -34,11 +34,11 @@ func (*Artifact) Files() []string {
}
func (a *Artifact) Id() string {
return fmt.Sprintf("%s:%s", strings.Join(a.regionNames[:], ","), strconv.FormatUint(uint64(a.snapshotId), 10))
return fmt.Sprintf("%s:%s", strings.Join(a.RegionNames[:], ","), strconv.FormatUint(uint64(a.SnapshotId), 10))
}
func (a *Artifact) String() string {
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.snapshotName, a.snapshotId, strings.Join(a.regionNames[:], ","))
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.SnapshotName, a.SnapshotId, strings.Join(a.RegionNames[:], ","))
}
func (a *Artifact) State(name string) interface{} {
@@ -46,7 +46,7 @@ func (a *Artifact) State(name string) interface{} {
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
_, err := a.client.Images.Delete(context.TODO(), a.snapshotId)
log.Printf("Destroying image: %d (%s)", a.SnapshotId, a.SnapshotName)
_, err := a.Client.Images.Delete(context.TODO(), a.SnapshotId)
return err
}
+4 -4
View File
@@ -113,10 +113,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
artifact := &Artifact{
snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_image_id").(int),
regionNames: state.Get("regions").([]string),
client: client,
SnapshotName: state.Get("snapshot_name").(string),
SnapshotId: state.Get("snapshot_image_id").(int),
RegionNames: state.Get("regions").([]string),
Client: client,
}
return artifact, nil
+1 -1
View File
@@ -91,7 +91,7 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("transferring Snapshot ID: %d", imageTransfer.ID))
if err := waitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
if err := WaitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
client, 20*time.Minute); err != nil {
// If we get an error the first time, actually report it
err := fmt.Errorf("Error waiting for snapshot transfer: %s", err)
+2 -2
View File
@@ -158,9 +158,9 @@ func waitForActionState(
}
}
// waitForImageState simply blocks until the image action is in
// WaitForImageState simply blocks until the image action is in
// a state we expect, while eventually timing out.
func waitForImageState(
func WaitForImageState(
desiredState string, imageId, actionId int,
client *godo.Client, timeout time.Duration) error {
done := make(chan struct{})
+10 -1
View File
@@ -34,7 +34,7 @@ type driverGCE struct {
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
func NewClientGCE(a *AccountFile) (*http.Client, error) {
var err error
var client *http.Client
@@ -78,6 +78,15 @@ func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
return nil, err
}
return client, nil
}
func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
client, err := NewClientGCE(a)
if err != nil {
return nil, err
}
log.Printf("[INFO] Instantiating GCE client...")
service, err := compute.New(client)
if err != nil {
+1 -1
View File
@@ -15,7 +15,7 @@ const StartupScriptStatusNotDone string = "notdone"
var StartupScriptLinux string = fmt.Sprintf(`#!/usr/bin/env bash
echo "Packer startup script starting."
RETVAL=0
BASEMETADATAURL=http://metadata/computeMetadata/v1/instance/
BASEMETADATAURL=http://metadata.google.internal/computeMetadata/v1/instance/
GetMetadata () {
echo "$(curl -f -H "Metadata-Flavor: Google" ${BASEMETADATAURL}/${1} 2> /dev/null)"
+48
View File
@@ -0,0 +1,48 @@
package hyperone
import (
"context"
"fmt"
"github.com/hyperonecom/h1-client-go"
)
type Artifact struct {
imageName string
imageID string
client *openapi.APIClient
}
func (a *Artifact) BuilderId() string {
return BuilderID
}
func (a *Artifact) Files() []string {
return nil
}
func (a *Artifact) Id() string {
return a.imageID
}
func (a *Artifact) String() string {
return fmt.Sprintf("Image '%s' created, ID: %s", a.imageName, a.imageID)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
if a.imageID == "" {
// No image to destroy
return nil
}
_, err := a.client.ImageApi.ImageDelete(context.TODO(), a.imageID)
if err != nil {
return err
}
return nil
}
+120
View File
@@ -0,0 +1,120 @@
package hyperone
import (
"fmt"
"log"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hyperonecom/h1-client-go"
)
const BuilderID = "hyperone.builder"
type Builder struct {
config Config
runner multistep.Runner
client *openapi.APIClient
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
config, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = *config
cfg := openapi.NewConfiguration()
cfg.AddDefaultHeader("x-auth-token", b.config.Token)
if b.config.Project != "" {
cfg.AddDefaultHeader("x-project", b.config.Project)
}
if b.config.APIURL != "" {
cfg.BasePath = b.config.APIURL
}
prefer := fmt.Sprintf("respond-async,wait=%d", int(b.config.StateTimeout.Seconds()))
cfg.AddDefaultHeader("Prefer", prefer)
b.client = openapi.NewAPIClient(cfg)
return nil, nil
}
type wrappedCommandTemplate struct {
Command string
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
wrappedCommand := func(command string) (string, error) {
ctx := b.config.ctx
ctx.Data = &wrappedCommandTemplate{Command: command}
return interpolate.Render(b.config.ChrootCommandWrapper, &ctx)
}
state := &multistep.BasicStateBag{}
state.Put("config", &b.config)
state.Put("client", b.client)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
steps := []multistep.Step{
&stepCreateSSHKey{},
&stepCreateVM{},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: getPublicIP,
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
}
if b.config.ChrootDisk {
steps = append(steps,
&stepPrepareDevice{},
&stepPreMountCommands{},
&stepMountChroot{},
&stepPostMountCommands{},
&stepMountExtra{},
&stepCopyFiles{},
&stepChrootProvision{},
&stepStopVM{},
&stepDetachDisk{},
&stepCreateVMFromDisk{},
&stepCreateImage{},
)
} else {
steps = append(steps,
&common.StepProvision{},
&stepStopVM{},
&stepCreateImage{},
)
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
artifact := &Artifact{
imageID: state.Get("image_id").(string),
imageName: state.Get("image_name").(string),
client: b.client,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the runner...")
b.runner.Cancel()
}
}
+63
View File
@@ -0,0 +1,63 @@
package hyperone
import (
"os"
"testing"
builderT "github.com/hashicorp/packer/helper/builder/testing"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccBasic,
})
}
func TestBuilderAcc_chroot(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccChroot,
})
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("HYPERONE_TOKEN"); v == "" {
t.Fatal("HYPERONE_TOKEN must be set for acceptance tests")
}
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"vm_type": "a1.nano",
"source_image": "ubuntu",
"disk_size": 10
}]
}
`
const testBuilderAccChroot = `
{
"builders": [{
"type": "test",
"source_image": "ubuntu",
"disk_size": 10,
"vm_type": "a1.nano",
"chroot_disk": true,
"chroot_command_wrapper": "sudo {{.Command}}",
"pre_mount_commands": [
"parted {{.Device}} mklabel msdos mkpart primary 1M 100% set 1 boot on print",
"mkfs.ext4 {{.Device}}1"
],
"post_mount_commands": [
"apt-get update",
"apt-get install debootstrap",
"debootstrap --arch amd64 bionic {{.MountPath}}"
]
}]
}
`
+54
View File
@@ -0,0 +1,54 @@
package hyperone
import (
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"github.com/hashicorp/packer/packer"
)
type CommandWrapper func(string) (string, error)
// ChrootCommunicator works as a wrapper on SSHCommunicator, modyfing paths in
// flight to be run in a chroot.
type ChrootCommunicator struct {
Chroot string
CmdWrapper CommandWrapper
Wrapped packer.Communicator
}
func (c *ChrootCommunicator) Start(cmd *packer.RemoteCmd) error {
command := strconv.Quote(cmd.Command)
chrootCommand, err := c.CmdWrapper(
fmt.Sprintf("sudo chroot %s /bin/sh -c %s", c.Chroot, command))
if err != nil {
return err
}
cmd.Command = chrootCommand
return c.Wrapped.Start(cmd)
}
func (c *ChrootCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
dst = filepath.Join(c.Chroot, dst)
return c.Wrapped.Upload(dst, r, fi)
}
func (c *ChrootCommunicator) UploadDir(dst string, src string, exclude []string) error {
dst = filepath.Join(c.Chroot, dst)
return c.Wrapped.UploadDir(dst, src, exclude)
}
func (c *ChrootCommunicator) Download(src string, w io.Writer) error {
src = filepath.Join(c.Chroot, src)
return c.Wrapped.Download(src, w)
}
func (c *ChrootCommunicator) DownloadDir(src string, dst string, exclude []string) error {
src = filepath.Join(c.Chroot, src)
return c.Wrapped.DownloadDir(src, dst, exclude)
}
+293
View File
@@ -0,0 +1,293 @@
package hyperone
import (
"errors"
"fmt"
"io/ioutil"
"os"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/json"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/go-homedir"
"github.com/mitchellh/mapstructure"
)
const (
configPath = "~/.h1-cli/conf.json"
tokenEnv = "HYPERONE_TOKEN"
defaultDiskType = "ssd"
defaultImageService = "564639bc052c084e2f2e3266"
defaultStateTimeout = 5 * time.Minute
defaultUserName = "guru"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
APIURL string `mapstructure:"api_url"`
Token string `mapstructure:"token"`
Project string `mapstructure:"project"`
TokenLogin string `mapstructure:"token_login"`
StateTimeout time.Duration `mapstructure:"state_timeout"`
SourceImage string `mapstructure:"source_image"`
ImageName string `mapstructure:"image_name"`
ImageDescription string `mapstructure:"image_description"`
ImageTags map[string]interface{} `mapstructure:"image_tags"`
ImageService string `mapstructure:"image_service"`
VmType string `mapstructure:"vm_type"`
VmName string `mapstructure:"vm_name"`
VmTags map[string]interface{} `mapstructure:"vm_tags"`
DiskName string `mapstructure:"disk_name"`
DiskType string `mapstructure:"disk_type"`
DiskSize float32 `mapstructure:"disk_size"`
Network string `mapstructure:"network"`
PrivateIP string `mapstructure:"private_ip"`
PublicIP string `mapstructure:"public_ip"`
PublicNetAdpService string `mapstructure:"public_netadp_service"`
ChrootDisk bool `mapstructure:"chroot_disk"`
ChrootDiskSize float32 `mapstructure:"chroot_disk_size"`
ChrootDiskType string `mapstructure:"chroot_disk_type"`
ChrootMountPath string `mapstructure:"chroot_mount_path"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
ChrootCopyFiles []string `mapstructure:"chroot_copy_files"`
ChrootCommandWrapper string `mapstructure:"chroot_command_wrapper"`
MountOptions []string `mapstructure:"mount_options"`
MountPartition string `mapstructure:"mount_partition"`
PreMountCommands []string `mapstructure:"pre_mount_commands"`
PostMountCommands []string `mapstructure:"post_mount_commands"`
SSHKeys []string `mapstructure:"ssh_keys"`
UserData string `mapstructure:"user_data"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := &Config{}
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_command",
"chroot_command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
},
},
}, raws...)
if err != nil {
return nil, nil, err
}
cliConfig, err := loadCLIConfig()
if err != nil {
return nil, nil, err
}
// Defaults
if c.Comm.SSHUsername == "" {
c.Comm.SSHUsername = defaultUserName
}
if c.Comm.SSHTimeout == 0 {
c.Comm.SSHTimeout = 10 * time.Minute
}
if c.APIURL == "" {
c.APIURL = os.Getenv("HYPERONE_API_URL")
}
if c.Token == "" {
c.Token = os.Getenv(tokenEnv)
if c.Token == "" {
c.Token = cliConfig.Profile.APIKey
}
// Fetching token by SSH is available only for the default API endpoint
if c.TokenLogin != "" && c.APIURL == "" {
c.Token, err = fetchTokenBySSH(c.TokenLogin)
if err != nil {
return nil, nil, err
}
}
}
if c.Project == "" {
c.Project = cliConfig.Profile.Project.ID
}
if c.StateTimeout == 0 {
c.StateTimeout = defaultStateTimeout
}
if c.ImageName == "" {
name, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
return nil, nil, err
}
c.ImageName = name
}
if c.ImageService == "" {
c.ImageService = defaultImageService
}
if c.VmName == "" {
c.VmName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
}
if c.DiskType == "" {
c.DiskType = defaultDiskType
}
if c.PublicNetAdpService == "" {
c.PublicNetAdpService = "public"
}
if c.ChrootCommandWrapper == "" {
c.ChrootCommandWrapper = "{{.Command}}"
}
if c.ChrootDiskSize == 0 {
c.ChrootDiskSize = c.DiskSize
}
if c.ChrootDiskType == "" {
c.ChrootDiskType = c.DiskType
}
if c.ChrootMountPath == "" {
path, err := interpolate.Render("/mnt/packer-hyperone-volumes/{{timestamp}}", nil)
if err != nil {
return nil, nil, err
}
c.ChrootMountPath = path
}
if c.ChrootMounts == nil {
c.ChrootMounts = make([][]string, 0)
}
if len(c.ChrootMounts) == 0 {
c.ChrootMounts = [][]string{
{"proc", "proc", "/proc"},
{"sysfs", "sysfs", "/sys"},
{"bind", "/dev", "/dev"},
{"devpts", "devpts", "/dev/pts"},
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
}
}
if c.ChrootCopyFiles == nil {
c.ChrootCopyFiles = []string{"/etc/resolv.conf"}
}
if c.MountPartition == "" {
c.MountPartition = "1"
}
// Validation
var errs *packer.MultiError
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if c.Token == "" {
errs = packer.MultiErrorAppend(errs, errors.New("token is required"))
}
if c.VmType == "" {
errs = packer.MultiErrorAppend(errs, errors.New("vm type is required"))
}
if c.DiskSize == 0 {
errs = packer.MultiErrorAppend(errs, errors.New("disk size is required"))
}
if c.SourceImage == "" {
errs = packer.MultiErrorAppend(errs, errors.New("source image is required"))
}
if c.ChrootDisk {
if len(c.PreMountCommands) == 0 {
errs = packer.MultiErrorAppend(errs, errors.New("pre-mount commands are required for chroot disk"))
}
}
for _, mounts := range c.ChrootMounts {
if len(mounts) != 3 {
errs = packer.MultiErrorAppend(
errs, errors.New("each chroot_mounts entry should have three elements"))
break
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
packer.LogSecretFilter.Set(c.Token)
return c, nil, nil
}
type cliConfig struct {
Profile struct {
APIKey string `json:"apiKey"`
Project struct {
ID string `json:"_id"`
} `json:"project"`
} `json:"profile"`
}
func loadCLIConfig() (cliConfig, error) {
path, err := homedir.Expand(configPath)
if err != nil {
return cliConfig{}, err
}
_, err = os.Stat(path)
if err != nil {
// Config not found
return cliConfig{}, nil
}
content, err := ioutil.ReadFile(path)
if err != nil {
return cliConfig{}, err
}
var c cliConfig
err = json.Unmarshal(content, &c)
if err != nil {
return cliConfig{}, err
}
return c, nil
}
func getPublicIP(state multistep.StateBag) (string, error) {
return state.Get("public_ip").(string), nil
}
+28
View File
@@ -0,0 +1,28 @@
package hyperone
import (
"context"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepChrootProvision struct{}
func (s *stepChrootProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
sshCommunicator := state.Get("communicator").(packer.Communicator)
comm := &ChrootCommunicator{
Chroot: config.ChrootMountPath,
CmdWrapper: wrappedCommand,
Wrapped: sshCommunicator,
}
stepProvision := common.StepProvision{Comm: comm}
return stepProvision.Run(ctx, state)
}
func (s *stepChrootProvision) Cleanup(multistep.StateBag) {}
+40
View File
@@ -0,0 +1,40 @@
package hyperone
import (
"context"
"fmt"
"log"
"path/filepath"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCopyFiles struct{}
func (s *stepCopyFiles) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if len(config.ChrootCopyFiles) == 0 {
return multistep.ActionContinue
}
ui.Say("Copying files from host to chroot...")
for _, path := range config.ChrootCopyFiles {
chrootPath := filepath.Join(config.ChrootMountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath)
command := fmt.Sprintf("cp --remove-destination %s %s", path, chrootPath)
err := runCommands([]string{command}, config.ctx, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepCopyFiles) Cleanup(state multistep.StateBag) {}
+65
View File
@@ -0,0 +1,65 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hyperonecom/h1-client-go"
)
type stepCreateImage struct {
imageID string
}
func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
vmID := state.Get("vm_id").(string)
ui.Say("Creating image...")
image, _, err := client.ImageApi.ImageCreate(ctx, openapi.ImageCreate{
Name: config.ImageName,
Vm: vmID,
Service: config.ImageService,
Description: config.ImageDescription,
Tag: config.ImageTags,
})
if err != nil {
err := fmt.Errorf("error creating image: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.imageID = image.Id
state.Put("image_id", image.Id)
state.Put("image_name", image.Name)
return multistep.ActionContinue
}
func (s *stepCreateImage) Cleanup(state multistep.StateBag) {
if s.imageID == "" {
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
_, err := client.ImageApi.ImageDelete(context.TODO(), s.imageID)
if err != nil {
ui.Error(fmt.Sprintf("error deleting image '%s' - consider deleting it manually: %s",
s.imageID, formatOpenAPIError(err)))
}
}
+82
View File
@@ -0,0 +1,82 @@
package hyperone
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"runtime"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"golang.org/x/crypto/ssh"
)
type stepCreateSSHKey struct {
Debug bool
DebugKeyPath string
}
func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
ui.Say("Creating a temporary ssh key for the VM...")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
state.Put("error", fmt.Errorf("error generating ssh key: %s", err))
return multistep.ActionHalt
}
privDER := x509.MarshalPKCS1PrivateKey(priv)
privBLK := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: privDER,
}
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&privBLK)
pub, err := ssh.NewPublicKey(&priv.PublicKey)
if err != nil {
state.Put("error", fmt.Errorf("error getting public key: %s", err))
return multistep.ActionHalt
}
pubSSHFormat := string(ssh.MarshalAuthorizedKey(pub))
// Remember public SSH key for future connections
state.Put("ssh_public_key", pubSSHFormat)
// If we're in debug mode, output the private key to the working directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write(pem.EncodeToMemory(&privBLK)); err != nil {
state.Put("error", fmt.Errorf("error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {}
+199
View File
@@ -0,0 +1,199 @@
package hyperone
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hyperonecom/h1-client-go"
)
type stepCreateVM struct {
vmID string
}
const (
chrootDiskName = "packer-chroot-disk"
)
func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
sshKey := state.Get("ssh_public_key").(string)
ui.Say("Creating VM...")
netAdapter := pickNetAdapter(config)
var sshKeys = []string{sshKey}
sshKeys = append(sshKeys, config.SSHKeys...)
disks := []openapi.VmCreateDisk{
{
Service: config.DiskType,
Size: config.DiskSize,
},
}
if config.ChrootDisk {
disks = append(disks, openapi.VmCreateDisk{
Service: config.ChrootDiskType,
Size: config.ChrootDiskSize,
Name: chrootDiskName,
})
}
options := openapi.VmCreate{
Name: config.VmName,
Image: config.SourceImage,
Service: config.VmType,
SshKeys: sshKeys,
Disk: disks,
Netadp: []openapi.VmCreateNetadp{netAdapter},
UserMetadata: config.UserData,
Tag: config.VmTags,
}
vm, _, err := client.VmApi.VmCreate(ctx, options)
if err != nil {
err := fmt.Errorf("error creating VM: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.vmID = vm.Id
state.Put("vm_id", vm.Id)
hdds, _, err := client.VmApi.VmListHdd(ctx, vm.Id)
if err != nil {
err := fmt.Errorf("error listing hdd: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, hdd := range hdds {
if hdd.Disk.Name == chrootDiskName {
state.Put("chroot_disk_id", hdd.Disk.Id)
controllerNumber := strings.ToLower(strings.Trim(hdd.ControllerNumber, "{}"))
state.Put("chroot_controller_number", controllerNumber)
state.Put("chroot_controller_location", int(hdd.ControllerLocation))
break
}
}
netadp, _, err := client.VmApi.VmListNetadp(ctx, vm.Id)
if err != nil {
err := fmt.Errorf("error listing netadp: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(netadp) < 1 {
err := fmt.Errorf("no network adapters found")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
publicIP, err := associatePublicIP(ctx, config, client, netadp[0])
if err != nil {
err := fmt.Errorf("error associating IP: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("public_ip", publicIP)
return multistep.ActionContinue
}
func pickNetAdapter(config *Config) openapi.VmCreateNetadp {
if config.Network == "" {
if config.PublicIP != "" {
return openapi.VmCreateNetadp{
Service: config.PublicNetAdpService,
Ip: []string{config.PublicIP},
}
}
} else {
var privateIPs []string
if config.PrivateIP == "" {
privateIPs = nil
} else {
privateIPs = []string{config.PrivateIP}
}
return openapi.VmCreateNetadp{
Service: "private",
Network: config.Network,
Ip: privateIPs,
}
}
return openapi.VmCreateNetadp{
Service: config.PublicNetAdpService,
}
}
func associatePublicIP(ctx context.Context, config *Config, client *openapi.APIClient, netadp openapi.Netadp) (string, error) {
if config.Network == "" || config.PublicIP == "" {
// Public IP belongs to attached net adapter
return netadp.Ip[0].Address, nil
}
var privateIP string
if config.PrivateIP == "" {
privateIP = netadp.Ip[0].Id
} else {
privateIP = config.PrivateIP
}
ip, _, err := client.IpApi.IpActionAssociate(ctx, config.PublicIP, openapi.IpActionAssociate{Ip: privateIP})
if err != nil {
return "", err
}
return ip.Address, nil
}
func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
if s.vmID == "" {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Deleting VM %s...", s.vmID))
err := deleteVMWithDisks(s.vmID, state)
if err != nil {
ui.Error(err.Error())
}
}
func deleteVMWithDisks(vmID string, state multistep.StateBag) error {
client := state.Get("client").(*openapi.APIClient)
hdds, _, err := client.VmApi.VmListHdd(context.TODO(), vmID)
if err != nil {
return fmt.Errorf("error listing hdd: %s", formatOpenAPIError(err))
}
deleteOptions := openapi.VmDelete{}
for _, hdd := range hdds {
deleteOptions.RemoveDisks = append(deleteOptions.RemoveDisks, hdd.Disk.Id)
}
_, err = client.VmApi.VmDelete(context.TODO(), vmID, deleteOptions)
if err != nil {
return fmt.Errorf("Error deleting server '%s' - please delete it manually: %s", vmID, formatOpenAPIError(err))
}
return nil
}
@@ -0,0 +1,63 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hyperonecom/h1-client-go"
)
type stepCreateVMFromDisk struct {
vmID string
}
func (s *stepCreateVMFromDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
sshKey := state.Get("ssh_public_key").(string)
chrootDiskID := state.Get("chroot_disk_id").(string)
ui.Say("Creating VM from disk...")
options := openapi.VmCreate{
Name: config.VmName,
Service: config.VmType,
Disk: []openapi.VmCreateDisk{
{
Id: chrootDiskID,
},
},
SshKeys: []string{sshKey},
Boot: false,
}
vm, _, err := client.VmApi.VmCreate(ctx, options)
if err != nil {
err := fmt.Errorf("error creating VM from disk: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.vmID = vm.Id
state.Put("vm_id", vm.Id)
return multistep.ActionContinue
}
func (s *stepCreateVMFromDisk) Cleanup(state multistep.StateBag) {
if s.vmID == "" {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Deleting VM %s (from chroot disk)...", s.vmID))
err := deleteVMWithDisks(s.vmID, state)
if err != nil {
ui.Error(err.Error())
}
}
+116
View File
@@ -0,0 +1,116 @@
package hyperone
import (
"testing"
"github.com/hyperonecom/h1-client-go"
"github.com/stretchr/testify/assert"
)
func TestPickNetAdapter(t *testing.T) {
cases := []struct {
Name string
Config Config
Expected openapi.VmCreateNetadp
}{
{
Name: "no_network",
Config: Config{
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "public",
},
},
{
Name: "no_network_public_ip",
Config: Config{
PublicIP: "some-public-ip",
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "public",
Ip: []string{"some-public-ip"},
},
},
{
Name: "no_network_private_ip",
Config: Config{
PrivateIP: "some-private-ip",
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "public",
},
},
{
Name: "no_network_both_ip",
Config: Config{
PublicIP: "some-public-ip",
PrivateIP: "some-private-ip",
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "public",
Ip: []string{"some-public-ip"},
},
},
{
Name: "network_no_ip",
Config: Config{
Network: "some-network",
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "private",
Network: "some-network",
},
},
{
Name: "network_public_ip",
Config: Config{
Network: "some-network",
PublicIP: "some-public-ip",
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "private",
Network: "some-network",
},
},
{
Name: "network_private_ip",
Config: Config{
Network: "some-network",
PrivateIP: "some-private-ip",
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "private",
Network: "some-network",
Ip: []string{"some-private-ip"},
},
},
{
Name: "network_both_ip",
Config: Config{
Network: "some-network",
PublicIP: "some-public-ip",
PrivateIP: "some-private-ip",
PublicNetAdpService: "public",
},
Expected: openapi.VmCreateNetadp{
Service: "private",
Network: "some-network",
Ip: []string{"some-private-ip"},
},
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
result := pickNetAdapter(&c.Config)
assert.Equal(t, c.Expected, result)
})
}
}
+34
View File
@@ -0,0 +1,34 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hyperonecom/h1-client-go"
)
type stepDetachDisk struct {
vmID string
}
func (s *stepDetachDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
vmID := state.Get("vm_id").(string)
chrootDiskID := state.Get("chroot_disk_id").(string)
ui.Say("Detaching chroot disk...")
_, _, err := client.VmApi.VmDeleteHddDiskId(ctx, vmID, chrootDiskID)
if err != nil {
err := fmt.Errorf("error detaching disk: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepDetachDisk) Cleanup(state multistep.StateBag) {}
+51
View File
@@ -0,0 +1,51 @@
package hyperone
import (
"context"
"fmt"
"log"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepMountChroot struct{}
func (s *stepMountChroot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
log.Printf("Mount path: %s", config.ChrootMountPath)
ui.Say(fmt.Sprintf("Creating mount directory: %s", config.ChrootMountPath))
opts := ""
if len(config.MountOptions) > 0 {
opts = "-o " + strings.Join(config.MountOptions, " -o ")
}
deviceMount := device
if config.MountPartition != "" {
deviceMount = fmt.Sprintf("%s%s", device, config.MountPartition)
}
commands := []string{
fmt.Sprintf("mkdir -m 755 -p %s", config.ChrootMountPath),
fmt.Sprintf("mount %s %s %s", opts, deviceMount, config.ChrootMountPath),
}
err := runCommands(commands, config.ctx, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("mount_path", config.ChrootMountPath)
return multistep.ActionContinue
}
func (s *stepMountChroot) Cleanup(state multistep.StateBag) {}
+45
View File
@@ -0,0 +1,45 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepMountExtra struct{}
func (s *stepMountExtra) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Mounting additional paths within the chroot...")
for _, mountInfo := range config.ChrootMounts {
innerPath := mountPath + mountInfo[2]
flags := "-t " + mountInfo[0]
if mountInfo[0] == "bind" {
flags = "--bind"
}
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
commands := []string{
fmt.Sprintf("mkdir -m 755 -p %s", innerPath),
fmt.Sprintf("mount %s %s %s", flags, mountInfo[1], innerPath),
}
err := runCommands(commands, config.ctx, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepMountExtra) Cleanup(state multistep.StateBag) {}
@@ -0,0 +1,37 @@
package hyperone
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type postMountCommandsData struct {
Device string
MountPath string
}
type stepPostMountCommands struct{}
func (s *stepPostMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
ctx := config.ctx
ctx.Data = &postMountCommandsData{
Device: device,
MountPath: config.ChrootMountPath,
}
ui.Say("Running post-mount commands...")
if err := runCommands(config.PostMountCommands, ctx, state); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepPostMountCommands) Cleanup(state multistep.StateBag) {}
@@ -0,0 +1,37 @@
package hyperone
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type preMountCommandsData struct {
Device string
MountPath string
}
type stepPreMountCommands struct{}
func (s *stepPreMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
ctx := config.ctx
ctx.Data = &preMountCommandsData{
Device: device,
MountPath: config.ChrootMountPath,
}
ui.Say("Running pre-mount commands...")
if err := runCommands(config.PreMountCommands, ctx, state); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepPreMountCommands) Cleanup(state multistep.StateBag) {}
+50
View File
@@ -0,0 +1,50 @@
package hyperone
import (
"context"
"fmt"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
const (
vmBusPath = "/sys/bus/vmbus/devices"
)
type stepPrepareDevice struct{}
func (s *stepPrepareDevice) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
controllerNumber := state.Get("chroot_controller_number").(string)
controllerLocation := state.Get("chroot_controller_location").(int)
log.Println("Searching for available device...")
cmd := fmt.Sprintf("find %s/%s/ -path *:%d/block -exec ls {} \\;",
vmBusPath, controllerNumber, controllerLocation)
block, err := captureOutput(cmd, state)
if err != nil {
err := fmt.Errorf("error finding available device: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if block == "" {
err := fmt.Errorf("device not found")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
device := fmt.Sprintf("/dev/%s", block)
ui.Say(fmt.Sprintf("Found device: %s", device))
state.Put("device", device)
return multistep.ActionContinue
}
func (s *stepPrepareDevice) Cleanup(state multistep.StateBag) {}
+32
View File
@@ -0,0 +1,32 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hyperonecom/h1-client-go"
)
type stepStopVM struct{}
func (s *stepStopVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
vmID := state.Get("vm_id").(string)
ui.Say("Stopping VM...")
_, _, err := client.VmApi.VmActionStop(ctx, vmID)
if err != nil {
err := fmt.Errorf("error stopping VM: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepStopVM) Cleanup(multistep.StateBag) {}
+89
View File
@@ -0,0 +1,89 @@
package hyperone
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io/ioutil"
"net"
"os"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/hashicorp/packer/common/json"
)
const (
sshAddress = "api.hyperone.com:22"
sshSubsystem = "rbx-auth"
hostKeyHash = "3e2aa423d42d7e8b14d50625512c8ac19db767ed"
)
type sshData struct {
ID string `json:"_id"`
}
func sshAgent() ssh.AuthMethod {
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
}
return nil
}
func fetchTokenBySSH(user string) (string, error) {
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
sshAgent(),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
hash := sha1Sum(key)
if hash != hostKeyHash {
return fmt.Errorf("invalid host key hash: %s", hash)
}
return nil
},
}
client, err := ssh.Dial("tcp", sshAddress, sshConfig)
if err != nil {
return "", err
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
stdout, err := session.StdoutPipe()
if err != nil {
return "", err
}
err = session.RequestSubsystem(sshSubsystem)
if err != nil {
return "", err
}
out, err := ioutil.ReadAll(stdout)
if err != nil {
return "", err
}
var data sshData
err = json.Unmarshal(out, &data)
if err != nil {
return "", err
}
return data.ID, nil
}
func sha1Sum(pubKey ssh.PublicKey) string {
sum := sha1.Sum(pubKey.Marshal())
return hex.EncodeToString(sum[:])
}
+86
View File
@@ -0,0 +1,86 @@
package hyperone
import (
"bytes"
"fmt"
"log"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hyperonecom/h1-client-go"
)
func formatOpenAPIError(err error) string {
openAPIError, ok := err.(openapi.GenericOpenAPIError)
if !ok {
return err.Error()
}
return fmt.Sprintf("%s (body: %s)", openAPIError.Error(), openAPIError.Body())
}
func runCommands(commands []string, ctx interpolate.Context, state multistep.StateBag) error {
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
comm := state.Get("communicator").(packer.Communicator)
for _, rawCmd := range commands {
intCmd, err := interpolate.Render(rawCmd, &ctx)
if err != nil {
return fmt.Errorf("error interpolating: %s", err)
}
command, err := wrappedCommand(intCmd)
if err != nil {
return fmt.Errorf("error wrapping command: %s", err)
}
remoteCmd := &packer.RemoteCmd{
Command: command,
}
ui.Say(fmt.Sprintf("Executing command: %s", command))
err = remoteCmd.StartWithUi(comm, ui)
if err != nil {
return fmt.Errorf("error running remote cmd: %s", err)
}
if remoteCmd.ExitStatus != 0 {
return fmt.Errorf(
"received non-zero exit code %d from command: %s",
remoteCmd.ExitStatus,
command)
}
}
return nil
}
func captureOutput(command string, state multistep.StateBag) (string, error) {
comm := state.Get("communicator").(packer.Communicator)
var stdout bytes.Buffer
remoteCmd := &packer.RemoteCmd{
Command: command,
Stdout: &stdout,
}
log.Println(fmt.Sprintf("Executing command: %s", command))
err := comm.Start(remoteCmd)
if err != nil {
return "", fmt.Errorf("error running remote cmd: %s", err)
}
remoteCmd.Wait()
if remoteCmd.ExitStatus != 0 {
return "", fmt.Errorf(
"received non-zero exit code %d from command: %s",
remoteCmd.ExitStatus,
command)
}
return strings.TrimSpace(stdout.String()), nil
}
+3 -3
View File
@@ -61,9 +61,9 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
}
// convert the MB to bytes
ramSize := int64(s.RamSize * 1024 * 1024)
diskSize := int64(s.DiskSize * 1024 * 1024)
diskBlockSize := int64(s.DiskBlockSize * 1024 * 1024)
ramSize := int64(s.RamSize) * 1024 * 1024
diskSize := int64(s.DiskSize) * 1024 * 1024
diskBlockSize := int64(s.DiskBlockSize) * 1024 * 1024
err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, ramSize, diskSize, diskBlockSize,
s.SwitchName, s.Generation, s.DifferencingDisk, s.FixedVHD, s.Version)
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"log"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
)
// Artifact is an artifact implementation that contains built images.
+6 -1
View File
@@ -71,6 +71,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, fmt.Errorf("Error initializing compute client: %s", err)
}
imageClient, err := b.config.imageV2Client()
if err != nil {
return nil, fmt.Errorf("Error initializing image client: %s", err)
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
@@ -165,7 +170,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
artifact := &Artifact{
ImageId: state.Get("image").(string),
BuilderIdValue: BuilderId,
Client: computeClient,
Client: imageClient,
}
return artifact, nil
+19 -8
View File
@@ -9,8 +9,8 @@ import (
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@@ -19,19 +19,27 @@ type stepCreateImage struct {
UseBlockStorageVolume bool
}
func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
server := state.Get("server").(*servers.Server)
ui := state.Get("ui").(packer.Ui)
// We need the v2 compute client
client, err := config.computeV2Client()
computeClient, err := config.computeV2Client()
if err != nil {
err = fmt.Errorf("Error initializing compute client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
// We need the v2 image client
imageClient, err := config.imageV2Client()
if err != nil {
err = fmt.Errorf("Error initializing image service client: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
// Create the image.
// Image source depends on the type of the Compute instance. It can be
// Block Storage service volume or regular Compute service local volume.
@@ -58,7 +66,7 @@ func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
}
imageId = image.ImageID
} else {
imageId, err = servers.CreateImage(client, server.ID, servers.CreateImageOpts{
imageId, err = servers.CreateImage(computeClient, server.ID, servers.CreateImageOpts{
Name: config.ImageName,
Metadata: config.ImageMetadata,
}).ExtractImageID()
@@ -76,7 +84,7 @@ func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
// Wait for the image to become ready
ui.Say(fmt.Sprintf("Waiting for image %s (image id: %s) to become ready...", config.ImageName, imageId))
if err := WaitForImage(client, imageId); err != nil {
if err := WaitForImage(ctx, imageClient, imageId); err != nil {
err := fmt.Errorf("Error waiting for image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -91,11 +99,14 @@ func (s *stepCreateImage) Cleanup(multistep.StateBag) {
}
// WaitForImage waits for the given Image ID to become ready.
func WaitForImage(client *gophercloud.ServiceClient, imageId string) error {
func WaitForImage(ctx context.Context, client *gophercloud.ServiceClient, imageId string) error {
maxNumErrors := 10
numErrors := 0
for {
if err := ctx.Err(); err != nil {
return err
}
image, err := images.Get(client, imageId).Extract()
if err != nil {
errCode, ok := err.(*gophercloud.ErrUnexpectedResponseCode)
@@ -113,11 +124,11 @@ func WaitForImage(client *gophercloud.ServiceClient, imageId string) error {
return err
}
if image.Status == "ACTIVE" {
if image.Status == "active" {
return nil
}
log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress)
log.Printf("Waiting for image creation status: %s", image.Status)
time.Sleep(2 * time.Second)
}
}
+50
View File
@@ -0,0 +1,50 @@
package vagrant
import (
"fmt"
"path/filepath"
"github.com/hashicorp/packer/packer"
)
// This is the common builder ID to all of these artifacts.
const BuilderId = "vagrant"
// Artifact is the result of running the vagrant builder, namely a set
// of files associated with the resulting machine.
type artifact struct {
OutputDir string
BoxName string
}
// NewArtifact returns a vagrant artifact containing the .box file
func NewArtifact(dir string) (packer.Artifact, error) {
return &artifact{
OutputDir: dir,
BoxName: "package.box",
}, nil
}
func (*artifact) BuilderId() string {
return BuilderId
}
func (a *artifact) Files() []string {
return []string{a.BoxName}
}
func (a *artifact) Id() string {
return filepath.Join(a.OutputDir, a.BoxName)
}
func (a *artifact) String() string {
return fmt.Sprintf("Vagrant box is %s", a.Id())
}
func (a *artifact) State(name string) interface{} {
return nil
}
func (a *artifact) Destroy() error {
return nil
}
+47
View File
@@ -0,0 +1,47 @@
package vagrant
import (
"runtime"
"strings"
"testing"
"github.com/hashicorp/packer/packer"
)
func TestArtifact_Impl(t *testing.T) {
var raw interface{} = &artifact{}
if _, ok := raw.(packer.Artifact); !ok {
t.Fatalf("Artifact does not implement packer.Artifact")
}
}
func TestArtifactId(t *testing.T) {
a := &artifact{
OutputDir: "/my/dir",
BoxName: "package.box",
}
expected := "/my/dir/package.box"
if runtime.GOOS == "windows" {
expected = strings.Replace(expected, "/", "\\", -1)
}
if strings.Compare(a.Id(), expected) != 0 {
t.Fatalf("artifact ID should match: expected: %s received: %s", expected, a.Id())
}
}
func TestArtifactString(t *testing.T) {
a := &artifact{
OutputDir: "/my/dir",
BoxName: "package.box",
}
expected := "Vagrant box is /my/dir/package.box"
if runtime.GOOS == "windows" {
expected = strings.Replace(expected, "/", "\\", -1)
}
if strings.Compare(a.String(), expected) != 0 {
t.Fatalf("artifact string should match: expected: %s received: %s", expected, a.String())
}
}
+276
View File
@@ -0,0 +1,276 @@
package vagrant
import (
"errors"
"fmt"
"log"
"path/filepath"
"strings"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
// Builder implements packer.Builder and builds the actual VirtualBox
// images.
type Builder struct {
config *Config
runner multistep.Runner
}
type SSHConfig struct {
Comm communicator.Config `mapstructure:",squash"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
bootcommand.BootConfig `mapstructure:",squash"`
SSHConfig `mapstructure:",squash"`
// This is the name of the new virtual machine.
// By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build.
OutputDir string `mapstructure:"output_dir"`
SourceBox string `mapstructure:"source_path"`
GlobalID string `mapstructure:"global_id"`
Checksum string `mapstructure:"checksum"`
ChecksumType string `mapstructure:"checksum_type"`
BoxName string `mapstructure:"box_name"`
Provider string `mapstructure:"provider"`
Communicator string `mapstructure:"communicator"`
// What vagrantfile to use
VagrantfileTpl string `mapstructure:"vagrantfile_template"`
// Whether to Halt, Suspend, or Destroy the box
TeardownMethod string `mapstructure:"teardown_method"`
// Options for the "vagrant init" command
BoxVersion string `mapstructure:"box_version"`
Template string `mapstructure:"template"`
SyncedFolder string `mapstructure:"synced_folder"`
// Options for the "vagrant box add" command
SkipAdd bool `mapstructure:"skip_add"`
AddCACert string `mapstructure:"add_cacert"`
AddCAPath string `mapstructure:"add_capath"`
AddCert string `mapstructure:"add_cert"`
AddClean bool `mapstructure:"add_clean"`
AddForce bool `mapstructure:"add_force"`
AddInsecure bool `mapstructure:"add_insecure"`
// Don't package the Vagrant box after build.
SkipPackage bool `mapstructure:"skip_package"`
OutputVagrantfile string `mapstructure:"output_vagrantfile"`
PackageInclude []string `mapstructure:"package_include"`
ctx interpolate.Context
}
// Prepare processes the build configuration parameters.
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config = new(Config)
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
},
},
}, raws...)
if err != nil {
return nil, err
}
// Accumulate any errors and warnings
var errs *packer.MultiError
warnings := make([]string, 0)
if b.config.OutputDir == "" {
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
}
if b.config.Comm.SSHTimeout == 0 {
b.config.Comm.SSHTimeout = 10 * time.Minute
}
if b.config.Comm.Type != "ssh" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf(`The Vagrant builder currently only supports the ssh communicator"`))
}
// The box isn't a namespace like you'd pull from vagrant cloud
if b.config.BoxName == "" {
b.config.BoxName = fmt.Sprintf("packer_%s", b.config.PackerBuildName)
}
if b.config.SourceBox == "" {
if b.config.GlobalID == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required unless you have set global_id"))
}
} else {
if b.config.GlobalID != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You may either set global_id or source_path but not both"))
}
if strings.HasSuffix(b.config.SourceBox, ".box") {
b.config.SourceBox, err = common.ValidatedURL(b.config.SourceBox)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
}
fileOK := common.FileExistsLocally(b.config.SourceBox)
if !fileOK {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Source file '%s' needs to exist at time of config validation!", b.config.SourceBox))
}
}
}
if b.config.TeardownMethod == "" {
// If we're using a box that's already opened on the system, don't
// automatically destroy it. If we open the box ourselves, then go ahead
// and kill it by default.
if b.config.GlobalID != "" {
b.config.TeardownMethod = "halt"
} else {
b.config.TeardownMethod = "destroy"
}
} else {
matches := false
for _, name := range []string{"halt", "suspend", "destroy"} {
if strings.ToLower(b.config.TeardownMethod) == name {
matches = true
}
}
if !matches {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf(`TeardownMethod must be "halt", "suspend", or "destroy"`))
}
}
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}
return warnings, nil
}
// Run executes a Packer build and returns a packer.Artifact representing
// a VirtualBox appliance.
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 VirtualBox
VagrantCWD, err := filepath.Abs(b.config.OutputDir)
if err != nil {
return nil, err
}
driver, err := NewDriver(VagrantCWD)
if err != nil {
return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("debug", b.config.PackerDebug)
state.Put("driver", driver)
state.Put("cache", cache)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps.
steps := []multistep.Step{}
// Download if source box isn't from vagrant cloud.
if strings.HasSuffix(b.config.SourceBox, ".box") {
steps = append(steps, &common.StepDownload{
Checksum: b.config.Checksum,
ChecksumType: b.config.ChecksumType,
Description: "Box",
Extension: "box",
ResultKey: "box_path",
Url: []string{b.config.SourceBox},
})
}
steps = append(steps,
&common.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
},
&StepCreateVagrantfile{
Template: b.config.Template,
SyncedFolder: b.config.SyncedFolder,
SourceBox: b.config.SourceBox,
OutputDir: b.config.OutputDir,
GlobalID: b.config.GlobalID,
},
&StepAddBox{
BoxVersion: b.config.BoxVersion,
CACert: b.config.AddCACert,
CAPath: b.config.AddCAPath,
DownloadCert: b.config.AddCert,
Clean: b.config.AddClean,
Force: b.config.AddForce,
Insecure: b.config.AddInsecure,
Provider: b.config.Provider,
SourceBox: b.config.SourceBox,
BoxName: b.config.BoxName,
GlobalID: b.config.GlobalID,
SkipAdd: b.config.SkipAdd,
},
&StepUp{
TeardownMethod: b.config.TeardownMethod,
Provider: b.config.Provider,
GlobalID: b.config.GlobalID,
},
&StepSSHConfig{
b.config.GlobalID,
},
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: CommHost(),
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
},
new(common.StepProvision),
&StepPackage{
SkipPackage: b.config.SkipPackage,
Include: b.config.PackageInclude,
Vagrantfile: b.config.OutputVagrantfile,
GlobalID: b.config.GlobalID,
})
// Run the steps.
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
// Report any errors.
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.")
}
return NewArtifact(b.config.OutputDir)
}
// Cancel.
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
+91
View File
@@ -0,0 +1,91 @@
package vagrant
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_ValidateSource(t *testing.T) {
b := &Builder{}
type testCase struct {
config map[string]interface{}
errExpected bool
reason string
}
cases := []testCase{
{
config: map[string]interface{}{
"global_id": "a3559ec",
},
errExpected: true,
reason: "Need to set SSH communicator.",
},
{
config: map[string]interface{}{
"global_id": "a3559ec",
"communicator": "ssh",
},
errExpected: false,
reason: "Shouldn't fail because we've set global_id",
},
{
config: map[string]interface{}{
"communicator": "ssh",
},
errExpected: true,
reason: "Should fail because we must set source_path or global_id",
},
{
config: map[string]interface{}{
"source_path": "./mybox",
"communicator": "ssh",
},
errExpected: false,
reason: "Source path is set; we should be fine",
},
{
config: map[string]interface{}{
"source_path": "./mybox",
"communicator": "ssh",
"global_id": "a3559ec",
},
errExpected: true,
reason: "Both source path and global are set: we should error.",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"global_id": "a3559ec",
"teardown_method": "suspend",
},
errExpected: false,
reason: "Valid argument for teardown method",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"global_id": "a3559ec",
"teardown_method": "surspernd",
},
errExpected: true,
reason: "Inalid argument for teardown method",
},
}
for _, tc := range cases {
_, err := b.Prepare(tc.config)
if (err != nil) != tc.errExpected {
t.Fatalf("Unexpected behavior from test case %#v; %s.", tc.config, tc.reason)
}
}
}
+67
View File
@@ -0,0 +1,67 @@
package vagrant
import (
"fmt"
"os/exec"
"runtime"
)
// A driver is able to talk to Vagrant and perform certain
// operations with it.
type VagrantDriver interface {
// Calls "vagrant init"
Init([]string) error
// Calls "vagrant add"
Add([]string) error
// Calls "vagrant up"
Up([]string) (string, string, error)
// Calls "vagrant halt"
Halt(string) error
// Calls "vagrant suspend"
Suspend(string) error
SSHConfig(string) (*VagrantSSHConfig, error)
// Calls "vagrant destroy"
Destroy(string) error
// Calls "vagrant package"[
Package([]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 VirtualBox that is installed.
Version() (string, error)
}
func NewDriver(outputDir string) (VagrantDriver, error) {
// Hardcode path for now while I'm developing. Obviously this path needs
// to be discovered based on OS.
vagrantBinary := "vagrant"
if runtime.GOOS == "windows" {
vagrantBinary = "vagrant.exe"
}
if _, err := exec.LookPath(vagrantBinary); err != nil {
return nil, fmt.Errorf("Error: Packer cannot find Vagrant in the path: %s", err.Error())
}
driver := &Vagrant_2_2_Driver{
vagrantBinary: vagrantBinary,
VagrantCWD: outputDir,
}
if err := driver.Verify(); err != nil {
return nil, err
}
return driver, nil
}
+199
View File
@@ -0,0 +1,199 @@
package vagrant
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/hashicorp/go-version"
)
const VAGRANT_MIN_VERSION = ">= 2.0.2"
type Vagrant_2_2_Driver struct {
vagrantBinary string
VagrantCWD string
}
// Calls "vagrant init"
func (d *Vagrant_2_2_Driver) Init(args []string) error {
_, _, err := d.vagrantCmd(append([]string{"init"}, args...)...)
return err
}
// Calls "vagrant add"
func (d *Vagrant_2_2_Driver) Add(args []string) error {
// vagrant box add partyvm ubuntu-14.04.vmware.box
_, _, err := d.vagrantCmd(append([]string{"box", "add"}, args...)...)
return err
}
// Calls "vagrant up"
func (d *Vagrant_2_2_Driver) Up(args []string) (string, string, error) {
stdout, stderr, err := d.vagrantCmd(append([]string{"up"}, args...)...)
return stdout, stderr, err
}
// Calls "vagrant halt"
func (d *Vagrant_2_2_Driver) Halt(id string) error {
args := []string{"halt"}
if id != "" {
args = append(args, id)
}
_, _, err := d.vagrantCmd(args...)
return err
}
// Calls "vagrant suspend"
func (d *Vagrant_2_2_Driver) Suspend(id string) error {
args := []string{"suspend"}
if id != "" {
args = append(args, id)
}
_, _, err := d.vagrantCmd(args...)
return err
}
// Calls "vagrant destroy"
func (d *Vagrant_2_2_Driver) Destroy(id string) error {
args := []string{"destroy", "-f"}
if id != "" {
args = append(args, id)
}
_, _, err := d.vagrantCmd(args...)
return err
}
// Calls "vagrant package"
func (d *Vagrant_2_2_Driver) Package(args []string) error {
args = append(args, "--output", filepath.Join(d.VagrantCWD, "package.box"))
_, _, err := d.vagrantCmd(append([]string{"package"}, args...)...)
return err
}
// Verify makes sure that Vagrant exists at the given path
func (d *Vagrant_2_2_Driver) Verify() error {
vagrantPath, err := exec.LookPath(d.vagrantBinary)
if err != nil {
return fmt.Errorf("Can't find Vagrant binary!")
}
_, err = os.Stat(vagrantPath)
if err != nil {
return fmt.Errorf("Can't find Vagrant binary.")
}
constraints, err := version.NewConstraint(VAGRANT_MIN_VERSION)
vers, err := d.Version()
v, err := version.NewVersion(vers)
if err != nil {
return fmt.Errorf("Error figuring out Vagrant version.")
}
if !constraints.Check(v) {
return fmt.Errorf("installed Vagrant version must be >=2.0.2")
}
return nil
}
type VagrantSSHConfig struct {
Hostname string
User string
Port string
UserKnownHostsFile string
StrictHostKeyChecking bool
PasswordAuthentication bool
IdentityFile string
IdentitiesOnly bool
LogLevel string
}
func parseSSHConfig(lines []string, value string) string {
out := ""
for _, line := range lines {
if index := strings.Index(line, value); index != -1 {
out = line[index+len(value):]
}
}
return out
}
func yesno(yn string) bool {
if yn == "no" {
return false
}
return true
}
func (d *Vagrant_2_2_Driver) SSHConfig(id string) (*VagrantSSHConfig, error) {
// vagrant ssh-config --host 8df7860
args := []string{"ssh-config"}
if id != "" {
args = append(args, id)
}
stdout, _, err := d.vagrantCmd(args...)
sshConf := &VagrantSSHConfig{}
lines := strings.Split(stdout, "\n")
sshConf.Hostname = parseSSHConfig(lines, "HostName ")
sshConf.User = parseSSHConfig(lines, "User ")
sshConf.Port = parseSSHConfig(lines, "Port ")
sshConf.UserKnownHostsFile = parseSSHConfig(lines, "UserKnownHostsFile ")
sshConf.IdentityFile = parseSSHConfig(lines, "IdentityFile ")
sshConf.LogLevel = parseSSHConfig(lines, "LogLevel ")
// handle the booleans
sshConf.StrictHostKeyChecking = yesno(parseSSHConfig(lines, "StrictHostKeyChecking "))
sshConf.PasswordAuthentication = yesno(parseSSHConfig(lines, "PasswordAuthentication "))
sshConf.IdentitiesOnly = yesno((parseSSHConfig(lines, "IdentitiesOnly ")))
return sshConf, err
}
// Version reads the version of VirtualBox that is installed.
func (d *Vagrant_2_2_Driver) Version() (string, error) {
stdoutString, _, err := d.vagrantCmd([]string{"--version"}...)
// Example stdout:
// Installed Version: 2.2.3
//
// Vagrant was unable to check for the latest version of Vagrant.
// Please check manually at https://www.vagrantup.com
// Use regex to find version
reg := regexp.MustCompile(`(\d+\.)?(\d+\.)?(\*|\d+)`)
version := reg.FindString(stdoutString)
if version == "" {
return "", err
}
return version, nil
}
func (d *Vagrant_2_2_Driver) vagrantCmd(args ...string) (string, string, error) {
var stdout, stderr bytes.Buffer
log.Printf("Calling Vagrant CLI: %#v", args)
cmd := exec.Command(d.vagrantBinary, args...)
cmd.Env = append(os.Environ(), fmt.Sprintf("VAGRANT_CWD=%s", d.VagrantCWD))
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("Vagrant error: %s", stderrString)
}
log.Printf("[vagrant driver] stdout: %s", stdoutString)
log.Printf("[vagrant driver] stderr: %s", stderrString)
return stdoutString, stderrString, err
}
+19
View File
@@ -0,0 +1,19 @@
package vagrant
import (
"github.com/hashicorp/packer/helper/multistep"
)
func CommHost() func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
config := state.Get("config").(*Config)
return config.Comm.SSHHost, nil
}
}
func SSHPort() func(multistep.StateBag) (int, error) {
return func(state multistep.StateBag) (int, error) {
config := state.Get("config").(*Config)
return config.Comm.SSHPort, nil
}
}
+100
View File
@@ -0,0 +1,100 @@
package vagrant
import (
"context"
"log"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepAddBox struct {
BoxVersion string
CACert string
CAPath string
DownloadCert string
Clean bool
Force bool
Insecure bool
Provider string
SourceBox string
BoxName string
GlobalID string
SkipAdd bool
}
func (s *StepAddBox) generateAddArgs() []string {
addArgs := []string{}
if strings.HasSuffix(s.SourceBox, ".box") {
addArgs = append(addArgs, s.BoxName)
}
addArgs = append(addArgs, s.SourceBox)
if s.BoxVersion != "" {
addArgs = append(addArgs, "--box-version", s.BoxVersion)
}
if s.CACert != "" {
addArgs = append(addArgs, "--cacert", s.CACert)
}
if s.CAPath != "" {
addArgs = append(addArgs, "--capath", s.CAPath)
}
if s.DownloadCert != "" {
addArgs = append(addArgs, "--cert", s.DownloadCert)
}
if s.Clean {
addArgs = append(addArgs, "--clean")
}
if s.Force {
addArgs = append(addArgs, "--force")
}
if s.Insecure {
addArgs = append(addArgs, "--insecure")
}
if s.Provider != "" {
addArgs = append(addArgs, "--provider", s.Provider)
}
return addArgs
}
func (s *StepAddBox) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packer.Ui)
if s.SkipAdd {
ui.Say("skip_add was set so we assume the box is already in Vagrant...")
return multistep.ActionContinue
}
if s.GlobalID != "" {
ui.Say("Using a global-id; skipping Vagrant add command...")
return multistep.ActionContinue
}
ui.Say("Adding box using vagrant box add..")
addArgs := s.generateAddArgs()
log.Printf("[vagrant] Calling box add with following args %s", strings.Join(addArgs, " "))
// Call vagrant using prepared arguments
err := driver.Add(addArgs)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepAddBox) Cleanup(state multistep.StateBag) {
}
+62
View File
@@ -0,0 +1,62 @@
package vagrant
import (
"strings"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepAdd_Impl(t *testing.T) {
var raw interface{}
raw = new(StepAddBox)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("initialize should be a step")
}
}
func TestPrepAddArgs(t *testing.T) {
type testArgs struct {
Step StepAddBox
Expected []string
}
addTests := []testArgs{
{
Step: StepAddBox{
SourceBox: "my_source_box.box",
BoxName: "AWESOME BOX",
},
Expected: []string{"AWESOME BOX", "my_source_box.box"},
},
{
Step: StepAddBox{
SourceBox: "my_source_box",
BoxName: "AWESOME BOX",
},
Expected: []string{"my_source_box"},
},
{
Step: StepAddBox{
BoxVersion: "eleventyone",
CACert: "adfasdf",
CAPath: "adfasdf",
DownloadCert: "adfasdf",
Clean: true,
Force: true,
Insecure: true,
Provider: "virtualbox",
SourceBox: "bananabox.box",
BoxName: "bananas",
},
Expected: []string{"bananas", "bananabox.box", "--box-version", "eleventyone", "--cacert", "adfasdf", "--capath", "adfasdf", "--cert", "adfasdf", "--clean", "--force", "--insecure", "--provider", "virtualbox"},
},
}
for _, addTest := range addTests {
addArgs := addTest.Step.generateAddArgs()
for i, val := range addTest.Expected {
if strings.Compare(addArgs[i], val) != 0 {
t.Fatalf("expected %#v but received %#v", addTest.Expected, addArgs)
}
}
}
}
@@ -0,0 +1,96 @@
package vagrant
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"text/template"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepCreateVagrantfile struct {
Template string
SourceBox string
OutputDir string
SyncedFolder string
GlobalID string
}
var DEFAULT_TEMPLATE = `Vagrant.configure("2") do |config|
config.vm.box = "{{.BoxName}}"
{{ if ne .SyncedFolder "" -}}
config.vm.synced_folder "{{.SyncedFolder}}", "/vagrant"
{{- else -}}
config.vm.synced_folder ".", "/vagrant", disabled: true
{{- end}}
end`
type VagrantfileOptions struct {
SyncedFolder string
BoxName string
}
func (s *StepCreateVagrantfile) createVagrantfile() (string, error) {
tplPath := filepath.Join(s.OutputDir, "Vagrantfile")
templateFile, err := os.Create(tplPath)
if err != nil {
retErr := fmt.Errorf("Error creating vagrantfile %s", err.Error())
return "", retErr
}
var tpl *template.Template
if s.Template == "" {
// Generate vagrantfile template based on our default
tpl = template.Must(template.New("VagrantTpl").Parse(DEFAULT_TEMPLATE))
} else {
// Read in the template from provided file.
tpl, err = template.ParseFiles(s.Template)
if err != nil {
return "", err
}
}
opts := &VagrantfileOptions{
SyncedFolder: s.SyncedFolder,
BoxName: s.SourceBox,
}
err = tpl.Execute(templateFile, opts)
if err != nil {
return "", err
}
abspath, err := filepath.Abs(tplPath)
if err != nil {
return "", err
}
return abspath, nil
}
func (s *StepCreateVagrantfile) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
// Skip the initialize step if we're trying to launch from a global ID.
if s.GlobalID != "" {
ui.Say("Using a global-id; skipping Vagrant init in this directory...")
return multistep.ActionContinue
}
ui.Say("Creating a Vagrantfile in the build directory...")
vagrantfilePath, err := s.createVagrantfile()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
log.Printf("Created vagrantfile at %s", vagrantfilePath)
return multistep.ActionContinue
}
func (s *StepCreateVagrantfile) Cleanup(state multistep.StateBag) {
}
@@ -0,0 +1,60 @@
package vagrant
import (
"io/ioutil"
"os"
"strings"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepCreateVagrantfile_Impl(t *testing.T) {
var raw interface{}
raw = new(StepCreateVagrantfile)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("initialize should be a step")
}
}
func TestCreateFile(t *testing.T) {
testy := StepCreateVagrantfile{
OutputDir: "./",
SourceBox: "bananas",
}
templatePath, err := testy.createVagrantfile()
if err != nil {
t.Fatalf(err.Error())
}
defer os.Remove(templatePath)
contents, err := ioutil.ReadFile(templatePath)
actual := string(contents)
expected := `Vagrant.configure("2") do |config|
config.vm.box = "bananas"
config.vm.synced_folder ".", "/vagrant", disabled: true
end`
if ok := strings.Compare(actual, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
}
}
func TestCreateFile_customSync(t *testing.T) {
testy := StepCreateVagrantfile{
OutputDir: "./",
SyncedFolder: "myfolder/foldertimes",
}
templatePath, err := testy.createVagrantfile()
if err != nil {
t.Fatalf(err.Error())
}
defer os.Remove(templatePath)
contents, err := ioutil.ReadFile(templatePath)
actual := string(contents)
expected := `Vagrant.configure("2") do |config|
config.vm.box = ""
config.vm.synced_folder "myfolder/foldertimes", "/vagrant"
end`
if ok := strings.Compare(actual, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
}
}
+49
View File
@@ -0,0 +1,49 @@
package vagrant
import (
"context"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepPackage struct {
SkipPackage bool
Include []string
Vagrantfile string
GlobalID string
}
func (s *StepPackage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packer.Ui)
if s.SkipPackage {
ui.Say("skip_package flag set; not going to call Vagrant package on this box.")
return multistep.ActionContinue
}
ui.Say("Packaging box...")
packageArgs := []string{}
if s.GlobalID != "" {
packageArgs = append(packageArgs, s.GlobalID)
}
if len(s.Include) > 0 {
packageArgs = append(packageArgs, "--include", strings.Join(s.Include, ","))
}
if s.Vagrantfile != "" {
packageArgs = append(packageArgs, "--vagrantfile", s.Vagrantfile)
}
err := driver.Package(packageArgs)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPackage) Cleanup(state multistep.StateBag) {
}
+53
View File
@@ -0,0 +1,53 @@
package vagrant
import (
"context"
"strconv"
"github.com/hashicorp/packer/helper/multistep"
)
// Vagrant already sets up ssh on the guests; our job is to find out what
// it did. We can do that with the ssh-config command. Example output:
// $ vagrant ssh-config
// Host default
// HostName 172.16.41.194
// User vagrant
// Port 22
// UserKnownHostsFile /dev/null
// StrictHostKeyChecking no
// PasswordAuthentication no
// IdentityFile /Users/mmarsh/Projects/vagrant-boxes/ubuntu/.vagrant/machines/default/vmware_fusion/private_key
// IdentitiesOnly yes
// LogLevel FATAL
type StepSSHConfig struct {
GlobalID string
}
func (s *StepSSHConfig) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
config := state.Get("config").(*Config)
sshConfig, err := driver.SSHConfig(s.GlobalID)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
config.Comm.SSHPrivateKeyFile = sshConfig.IdentityFile
config.Comm.SSHUsername = sshConfig.User
config.Comm.SSHHost = sshConfig.Hostname
port, err := strconv.Atoi(sshConfig.Port)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
config.Comm.SSHPort = port
return multistep.ActionContinue
}
func (s *StepSSHConfig) Cleanup(state multistep.StateBag) {
}
+62
View File
@@ -0,0 +1,62 @@
package vagrant
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepUp struct {
TeardownMethod string
Provider string
GlobalID string
}
func (s *StepUp) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Calling Vagrant Up...")
var args []string
if s.GlobalID != "" {
args = append(args, s.GlobalID)
}
if s.Provider != "" {
args = append(args, fmt.Sprintf("--provider=%s", s.Provider))
}
_, _, err := driver.Up(args)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepUp) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("%sing Vagrant box...", s.TeardownMethod))
var err error
if s.TeardownMethod == "halt" {
err = driver.Halt(s.GlobalID)
} else if s.TeardownMethod == "suspend" {
err = driver.Suspend(s.GlobalID)
} else if s.TeardownMethod == "destroy" {
err = driver.Destroy(s.GlobalID)
} else {
// Should never get here because of template validation
state.Put("error", fmt.Errorf("Invalid teardown method selected; must be either halt, suspend, or destory."))
}
if err != nil {
state.Put("error", fmt.Errorf("Error halting Vagrant machine; please try to do this manually"))
}
}
@@ -21,8 +21,9 @@ import (
//
// Produces:
type StepAttachGuestAdditions struct {
attachedPath string
GuestAdditionsMode string
attachedPath string
GuestAdditionsMode string
GuestAdditionsInterface string
}
func (s *StepAttachGuestAdditions) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
@@ -40,12 +41,22 @@ func (s *StepAttachGuestAdditions) Run(_ context.Context, state multistep.StateB
guestAdditionsPath := state.Get("guest_additions_path").(string)
// Attach the guest additions to the computer
controllerName := "IDE Controller"
port := "1"
device := "0"
if s.GuestAdditionsInterface == "sata" {
controllerName = "SATA Controller"
port = "2"
device = "0"
}
log.Println("Attaching guest additions ISO onto IDE controller...")
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "1",
"--device", "0",
"--storagectl", controllerName,
"--port", port,
"--device", device,
"--type", "dvddrive",
"--medium", guestAdditionsPath,
}
@@ -71,11 +82,20 @@ func (s *StepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(Driver)
vmName := state.Get("vmName").(string)
controllerName := "IDE Controller"
port := "1"
device := "0"
if s.GuestAdditionsInterface == "sata" {
controllerName = "SATA Controller"
port = "2"
device = "0"
}
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "1",
"--device", "0",
"--storagectl", controllerName,
"--port", port,
"--device", device,
"--medium", "none",
}
@@ -20,7 +20,8 @@ import (
//
// Produces:
type StepRemoveDevices struct {
Bundling VBoxBundleConfig
Bundling VBoxBundleConfig
GuestAdditionsInterface string
}
func (s *StepRemoveDevices) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
@@ -100,11 +101,19 @@ func (s *StepRemoveDevices) Run(_ context.Context, state multistep.StateBag) mul
if _, ok := state.GetOk("guest_additions_attached"); ok {
ui.Message("Removing guest additions drive...")
controllerName := "IDE Controller"
port := "1"
device := "0"
if s.GuestAdditionsInterface == "sata" {
controllerName = "SATA Controller"
port = "2"
device = "0"
}
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "1",
"--device", "0",
"--storagectl", controllerName,
"--port", port,
"--device", device,
"--medium", "none",
}
if err := driver.VBoxManage(command...); err != nil {
+23 -16
View File
@@ -41,20 +41,21 @@ type Config struct {
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
vboxcommon.VBoxBundleConfig `mapstructure:",squash"`
DiskSize uint `mapstructure:"disk_size"`
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
GuestOSType string `mapstructure:"guest_os_type"`
HardDriveDiscard bool `mapstructure:"hard_drive_discard"`
HardDriveInterface string `mapstructure:"hard_drive_interface"`
SATAPortCount int `mapstructure:"sata_port_count"`
HardDriveNonrotational bool `mapstructure:"hard_drive_nonrotational"`
ISOInterface string `mapstructure:"iso_interface"`
KeepRegistered bool `mapstructure:"keep_registered"`
SkipExport bool `mapstructure:"skip_export"`
VMName string `mapstructure:"vm_name"`
DiskSize uint `mapstructure:"disk_size"`
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
GuestAdditionsInterface string `mapstructure:"guest_additions_interface"`
GuestOSType string `mapstructure:"guest_os_type"`
HardDriveDiscard bool `mapstructure:"hard_drive_discard"`
HardDriveInterface string `mapstructure:"hard_drive_interface"`
SATAPortCount int `mapstructure:"sata_port_count"`
HardDriveNonrotational bool `mapstructure:"hard_drive_nonrotational"`
ISOInterface string `mapstructure:"iso_interface"`
KeepRegistered bool `mapstructure:"keep_registered"`
SkipExport bool `mapstructure:"skip_export"`
VMName string `mapstructure:"vm_name"`
ctx interpolate.Context
}
@@ -125,6 +126,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ISOInterface = "ide"
}
if b.config.GuestAdditionsInterface == "" {
b.config.GuestAdditionsInterface = b.config.ISOInterface
}
if b.config.VMName == "" {
b.config.VMName = fmt.Sprintf(
"packer-%s-%d", b.config.PackerBuildName, interpolate.InitTime.Unix())
@@ -227,7 +232,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new(stepCreateDisk),
new(stepAttachISO),
&vboxcommon.StepAttachGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
},
&vboxcommon.StepConfigureVRDP{
VRDPBindAddress: b.config.VRDPBindAddress,
@@ -280,7 +286,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Delay: b.config.PostShutdownDelay,
},
&vboxcommon.StepRemoveDevices{
Bundling: b.config.VBoxBundleConfig,
Bundling: b.config.VBoxBundleConfig,
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
},
&vboxcommon.StepVBoxManage{
Commands: b.config.VBoxManagePost,
+5 -2
View File
@@ -84,7 +84,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ImportFlags: b.config.ImportFlags,
},
&vboxcommon.StepAttachGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
},
&vboxcommon.StepConfigureVRDP{
VRDPBindAddress: b.config.VRDPBindAddress,
@@ -136,7 +137,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Timeout: b.config.ShutdownTimeout,
Delay: b.config.PostShutdownDelay,
},
new(vboxcommon.StepRemoveDevices),
&vboxcommon.StepRemoveDevices{
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
},
&vboxcommon.StepVBoxManage{
Commands: b.config.VBoxManagePost,
Ctx: b.config.ctx,
+17 -13
View File
@@ -28,19 +28,20 @@ type Config struct {
vboxcommon.VBoxManagePostConfig `mapstructure:",squash"`
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
Checksum string `mapstructure:"checksum"`
ChecksumType string `mapstructure:"checksum_type"`
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
ImportFlags []string `mapstructure:"import_flags"`
ImportOpts string `mapstructure:"import_opts"`
SourcePath string `mapstructure:"source_path"`
TargetPath string `mapstructure:"target_path"`
VMName string `mapstructure:"vm_name"`
KeepRegistered bool `mapstructure:"keep_registered"`
SkipExport bool `mapstructure:"skip_export"`
Checksum string `mapstructure:"checksum"`
ChecksumType string `mapstructure:"checksum_type"`
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsInterface string `mapstructure:"guest_additions_interface"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
ImportFlags []string `mapstructure:"import_flags"`
ImportOpts string `mapstructure:"import_opts"`
SourcePath string `mapstructure:"source_path"`
TargetPath string `mapstructure:"target_path"`
VMName string `mapstructure:"vm_name"`
KeepRegistered bool `mapstructure:"keep_registered"`
SkipExport bool `mapstructure:"skip_export"`
ctx interpolate.Context
}
@@ -72,6 +73,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
if c.GuestAdditionsPath == "" {
c.GuestAdditionsPath = "VBoxGuestAdditions.iso"
}
if c.GuestAdditionsInterface == "" {
c.GuestAdditionsInterface = "ide"
}
if c.VMName == "" {
c.VMName = fmt.Sprintf(
+1 -1
View File
@@ -69,7 +69,7 @@ func (c *DriverConfig) Validate(SkipExport bool) error {
// now, so that we don't fail for a simple mistake after a long
// build
ovftool := GetOVFTool()
ovfToolArgs := []string{"--verifyOnly", fmt.Sprintf("vi://%s:%s@%s",
ovfToolArgs := []string{"--noSSLVerify", "--verifyOnly", fmt.Sprintf("vi://%s:%s@%s",
url.QueryEscape(c.RemoteUser),
url.QueryEscape(c.RemotePassword),
c.RemoteHost)}
+1 -1
View File
@@ -47,7 +47,7 @@ type ESX5Driver struct {
func (d *ESX5Driver) Clone(dst, src string, linked bool) error {
linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\n"), "\n") }
linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\r\n"), "\n") }
d.SetOutputDir(path.Dir(filepath.ToSlash(dst)))
srcVmx := d.datastorePath(src)
+6
View File
@@ -14,6 +14,7 @@ type HWConfig struct {
// cpu information
CpuCount int `mapstructure:"cpus"`
MemorySize int `mapstructure:"memory"`
CoreCount int `mapstructure:"cores"`
// network type and adapter
Network string `mapstructure:"network"`
@@ -40,6 +41,11 @@ func (c *HWConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, fmt.Errorf("An invalid amount of memory was specified (memory < 0): %d", c.MemorySize))
}
// Hardware and cpu options
if c.CoreCount < 0 {
errs = append(errs, fmt.Errorf("An invalid number of cores was specified (cores < 0): %d", c.CoreCount))
}
// Peripherals
if !c.Sound {
c.Sound = false
+5
View File
@@ -8,6 +8,7 @@ import (
func testHWConfig() *HWConfig {
return &HWConfig{
CpuCount: 1,
CoreCount: 1,
MemorySize: 512,
Sound: true,
@@ -26,6 +27,10 @@ func TestHWConfigPrepare(t *testing.T) {
t.Errorf("bad cpu count: %d", c.CpuCount)
}
if c.CoreCount < 0 {
t.Errorf("bad core count: %d", c.CoreCount)
}
if c.MemorySize < 0 {
t.Errorf("bad memory size: %d", c.MemorySize)
}
+8 -1
View File
@@ -423,12 +423,19 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist
s.tempDir = vmxDir
}
/// Now to handle options that will modify the template
/// Now to handle options that will modify the template without using "vmxTemplateData"
vmxData := vmwcommon.ParseVMX(vmxContents)
// If no cpus were specified, then remove the entry to use the default
if vmxData["numvcpus"] == "" {
delete(vmxData, "numvcpus")
}
// If some number of cores were specified, then update "cpuid.coresPerSocket" with the requested value
if config.HWConfig.CoreCount > 0 {
vmxData["cpuid.corespersocket"] = strconv.Itoa(config.HWConfig.CoreCount)
}
/// Write the vmxData to the vmxPath
vmxPath := filepath.Join(vmxDir, config.VMName+".vmx")
if err := vmwcommon.WriteVMX(vmxPath, vmxData); err != nil {
+39 -7
View File
@@ -28,7 +28,7 @@ func TestBuildOnlyFileCommaFlags(t *testing.T) {
}
for _, f := range []string{"chocolate.txt", "vanilla.txt",
"apple.txt", "peach.txt", "pear.txt"} {
"apple.txt", "peach.txt", "pear.txt", "unnamed.txt"} {
if !fileExists(f) {
t.Errorf("Expected to find %s", f)
}
@@ -37,6 +37,10 @@ func TestBuildOnlyFileCommaFlags(t *testing.T) {
if fileExists("cherry.txt") {
t.Error("Expected NOT to find cherry.txt")
}
if !fileExists("tomato.txt") {
t.Error("Expected to find tomato.txt")
}
}
func TestBuildStdin(t *testing.T) {
@@ -58,7 +62,8 @@ func TestBuildStdin(t *testing.T) {
fatalCommand(t, c.Meta)
}
for _, f := range []string{"vanilla.txt", "cherry.txt", "chocolate.txt"} {
for _, f := range []string{"vanilla.txt", "cherry.txt", "chocolate.txt",
"unnamed.txt"} {
if !fileExists(f) {
t.Errorf("Expected to find %s", f)
}
@@ -85,13 +90,37 @@ func TestBuildOnlyFileMultipleFlags(t *testing.T) {
fatalCommand(t, c.Meta)
}
for _, f := range []string{"vanilla.txt"} {
for _, f := range []string{"vanilla.txt", "tomato.txt"} {
if fileExists(f) {
t.Errorf("Expected NOT to find %s", f)
}
}
for _, f := range []string{"chocolate.txt", "cherry.txt",
"apple.txt", "peach.txt", "pear.txt"} {
"apple.txt", "peach.txt", "pear.txt", "unnamed.txt"} {
if !fileExists(f) {
t.Errorf("Expected to find %s", f)
}
}
}
func TestBuildEverything(t *testing.T) {
c := &BuildCommand{
Meta: testMetaFile(t),
}
args := []string{
`-except=`,
filepath.Join(testFixture("build-only"), "template.json"),
}
defer cleanup()
if code := c.Run(args); code != 0 {
fatalCommand(t, c.Meta)
}
for _, f := range []string{"chocolate.txt", "vanilla.txt", "tomato.txt",
"apple.txt", "cherry.txt", "pear.txt", "peach.txt", "unnamed.txt"} {
if !fileExists(f) {
t.Errorf("Expected to find %s", f)
}
@@ -104,7 +133,7 @@ func TestBuildExceptFileCommaFlags(t *testing.T) {
}
args := []string{
"-except=chocolate,apple",
"-except=chocolate,vanilla",
filepath.Join(testFixture("build-only"), "template.json"),
}
@@ -114,12 +143,13 @@ func TestBuildExceptFileCommaFlags(t *testing.T) {
fatalCommand(t, c.Meta)
}
for _, f := range []string{"chocolate.txt", "apple.txt", "peach.txt"} {
for _, f := range []string{"chocolate.txt", "vanilla.txt", "tomato.txt",
"unnamed.txt"} {
if fileExists(f) {
t.Errorf("Expected NOT to find %s", f)
}
}
for _, f := range []string{"vanilla.txt", "cherry.txt", "pear.txt"} {
for _, f := range []string{"apple.txt", "cherry.txt", "pear.txt", "peach.txt"} {
if !fileExists(f) {
t.Errorf("Expected to find %s", f)
}
@@ -169,4 +199,6 @@ func cleanup() {
os.RemoveAll("apple.txt")
os.RemoveAll("peach.txt")
os.RemoveAll("pear.txt")
os.RemoveAll("tomato.txt")
os.RemoveAll("unnamed.txt")
}
+10 -2
View File
@@ -26,6 +26,7 @@ import (
filebuilder "github.com/hashicorp/packer/builder/file"
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
hcloudbuilder "github.com/hashicorp/packer/builder/hcloud"
hyperonebuilder "github.com/hashicorp/packer/builder/hyperone"
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
@@ -41,8 +42,9 @@ import (
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
qemubuilder "github.com/hashicorp/packer/builder/qemu"
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
tencentcloudbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
tritonbuilder "github.com/hashicorp/packer/builder/triton"
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
virtualboxisobuilder "github.com/hashicorp/packer/builder/virtualbox/iso"
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
vmwareisobuilder "github.com/hashicorp/packer/builder/vmware/iso"
@@ -52,6 +54,7 @@ import (
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
compresspostprocessor "github.com/hashicorp/packer/post-processor/compress"
digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import"
dockerimportpostprocessor "github.com/hashicorp/packer/post-processor/docker-import"
dockerpushpostprocessor "github.com/hashicorp/packer/post-processor/docker-push"
dockersavepostprocessor "github.com/hashicorp/packer/post-processor/docker-save"
@@ -71,6 +74,7 @@ import (
chefsoloprovisioner "github.com/hashicorp/packer/provisioner/chef-solo"
convergeprovisioner "github.com/hashicorp/packer/provisioner/converge"
fileprovisioner "github.com/hashicorp/packer/provisioner/file"
inspecprovisioner "github.com/hashicorp/packer/provisioner/inspec"
powershellprovisioner "github.com/hashicorp/packer/provisioner/powershell"
puppetmasterlessprovisioner "github.com/hashicorp/packer/provisioner/puppet-masterless"
puppetserverprovisioner "github.com/hashicorp/packer/provisioner/puppet-server"
@@ -99,6 +103,7 @@ var Builders = map[string]packer.Builder{
"file": new(filebuilder.Builder),
"googlecompute": new(googlecomputebuilder.Builder),
"hcloud": new(hcloudbuilder.Builder),
"hyperone": new(hyperonebuilder.Builder),
"hyperv-iso": new(hypervisobuilder.Builder),
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
"lxc": new(lxcbuilder.Builder),
@@ -114,8 +119,9 @@ var Builders = map[string]packer.Builder{
"profitbricks": new(profitbricksbuilder.Builder),
"qemu": new(qemubuilder.Builder),
"scaleway": new(scalewaybuilder.Builder),
"tencentcloud-cvm": new(tencentcloudbuilder.Builder),
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
"triton": new(tritonbuilder.Builder),
"vagrant": new(vagrantbuilder.Builder),
"virtualbox-iso": new(virtualboxisobuilder.Builder),
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
"vmware-iso": new(vmwareisobuilder.Builder),
@@ -130,6 +136,7 @@ var Provisioners = map[string]packer.Provisioner{
"chef-solo": new(chefsoloprovisioner.Provisioner),
"converge": new(convergeprovisioner.Provisioner),
"file": new(fileprovisioner.Provisioner),
"inspec": new(inspecprovisioner.Provisioner),
"powershell": new(powershellprovisioner.Provisioner),
"puppet-masterless": new(puppetmasterlessprovisioner.Provisioner),
"puppet-server": new(puppetserverprovisioner.Provisioner),
@@ -146,6 +153,7 @@ var PostProcessors = map[string]packer.PostProcessor{
"artifice": new(artificepostprocessor.PostProcessor),
"checksum": new(checksumpostprocessor.PostProcessor),
"compress": new(compresspostprocessor.PostProcessor),
"digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor),
"docker-import": new(dockerimportpostprocessor.PostProcessor),
"docker-push": new(dockerpushpostprocessor.PostProcessor),
"docker-save": new(dockersavepostprocessor.PostProcessor),
@@ -38,6 +38,25 @@
"type": "shell-local",
"inline": [ "touch pear.txt" ]
}
],
[
{
"only": [
"vanilla"
],
"name": "tomato",
"type": "shell-local",
"inline": [ "touch tomato.txt" ]
}
],
[
{
"only": [
"chocolate"
],
"type": "shell-local",
"inline": [ "touch unnamed.txt" ]
}
]
]
}
@@ -1,4 +1,4 @@
package ansible
package adapter
import (
"bytes"
@@ -17,7 +17,7 @@ import (
// An adapter satisfies SSH requests (from an Ansible client) by delegating SSH
// exec and subsystem commands to a packer.Communicator.
type adapter struct {
type Adapter struct {
done <-chan struct{}
l net.Listener
config *ssh.ServerConfig
@@ -26,8 +26,8 @@ type adapter struct {
comm packer.Communicator
}
func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *adapter {
return &adapter{
func NewAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *Adapter {
return &Adapter{
done: done,
l: l,
config: config,
@@ -37,7 +37,7 @@ func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig,
}
}
func (c *adapter) Serve() {
func (c *Adapter) Serve() {
log.Printf("SSH proxy: serving on %s", c.l.Addr())
for {
@@ -62,7 +62,7 @@ func (c *adapter) Serve() {
}
}
func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error {
func (c *Adapter) Handle(conn net.Conn, ui packer.Ui) error {
log.Print("SSH proxy: accepted connection")
_, chans, reqs, err := ssh.NewServerConn(conn, c.config)
if err != nil {
@@ -89,7 +89,7 @@ func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error {
return nil
}
func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
func (c *Adapter) handleSession(newChannel ssh.NewChannel) error {
channel, requests, err := newChannel.Accept()
if err != nil {
return err
@@ -182,11 +182,11 @@ func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
return nil
}
func (c *adapter) Shutdown() {
func (c *Adapter) Shutdown() {
c.l.Close()
}
func (c *adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int {
func (c *Adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int {
var exitStatus int
switch {
case strings.HasPrefix(command, "scp ") && serveSCP(command[4:]):
@@ -206,7 +206,7 @@ func serveSCP(args string) bool {
return bytes.IndexAny(opts, "tf") >= 0
}
func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error {
func (c *Adapter) scpExec(args string, in io.Reader, out io.Writer) error {
opts, rest := scpOptions(args)
// remove the quoting that ansible added to rest for shell safety.
@@ -226,7 +226,7 @@ func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error {
return errors.New("no scp mode specified")
}
func (c *adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int {
func (c *Adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int {
cmd := &packer.RemoteCmd{
Stdin: in,
Stdout: out,
@@ -1,4 +1,4 @@
package ansible
package adapter
import (
"errors"
@@ -26,7 +26,7 @@ func TestAdapter_Serve(t *testing.T) {
ui := new(packer.NoopUi)
sut := newAdapter(done, &l, config, "", newUi(ui), communicator{})
sut := NewAdapter(done, &l, config, "", ui, communicator{})
go func() {
i := 0
for range acceptC {
@@ -1,4 +1,4 @@
package ansible
package adapter
import (
"bufio"
-11
View File
@@ -1190,7 +1190,6 @@ func newParser(filename string, b []byte, opts ...Option) *parser {
Stats: &stats,
// start rule is rule [0] unless an alternate entrypoint is specified
entrypoint: g.rules[0].name,
emptyState: make(storeDict),
}
p.setOptions(opts)
@@ -1279,9 +1278,6 @@ type parser struct {
choiceNoMatch string
// recovery expression stack, keeps track of the currently available recovery expression, these are traversed in reverse
recoveryStack []map[string]interface{}
// emptyState contains an empty storeDict, which is used to optimize cloneState if global "state" store is not used.
emptyState storeDict
}
// push a variable set on the vstack.
@@ -1455,13 +1451,6 @@ func (p *parser) cloneState() storeDict {
defer p.out(p.in("cloneState"))
}
if len(p.cur.state) == 0 {
if len(p.emptyState) > 0 {
p.emptyState = make(storeDict)
}
return p.emptyState
}
state := make(storeDict, len(p.cur.state))
for k, v := range p.cur.state {
if c, ok := v.(Cloner); ok {
+5
View File
@@ -58,6 +58,11 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs [
// If iso_checksum has no value use iso_checksum_url instead.
if c.ISOChecksum == "" {
if strings.HasSuffix(strings.ToLower(c.ISOChecksumURL), ".iso") {
errs = append(errs, fmt.Errorf("Error parsing checksum:"+
" .iso is not a valid checksum extension"))
return warnings, errs
}
u, err := url.Parse(c.ISOChecksumURL)
if err != nil {
errs = append(errs,
+10
View File
@@ -239,6 +239,16 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) {
if i.ISOChecksum != "bar0" {
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
}
// Test that we won't try to read an iso into memory because of a user
// error
i = testISOConfig()
i.ISOChecksumURL = "file:///not_read.iso"
i.ISOChecksum = ""
warns, err = i.Prepare(nil)
if err == nil {
t.Fatalf("should have error because iso is bad filetype: %s", err)
}
}
func TestISOConfigPrepare_ISOChecksumType(t *testing.T) {
+4 -4
View File
@@ -242,13 +242,13 @@ func getCreateVMScript(opts *scriptOptions) (string, error) {
}
var tpl = template.Must(template.New("createVM").Parse(`
$vhdPath = Join-Path -Path {{ .Path }} -ChildPath {{ .VHDX }}
$vhdPath = Join-Path -Path "{{ .Path }}" -ChildPath "{{ .VHDX }}"
{{ if ne .HardDrivePath "" -}}
{{- if .DiffDisks -}}
Hyper-V\New-VHD -Path $vhdPath -ParentPath {{ .HardDrivePath }} -Differencing -BlockSizeBytes {{ .VHDBlockSizeBytes }}
Hyper-V\New-VHD -Path $vhdPath -ParentPath "{{ .HardDrivePath }}" -Differencing -BlockSizeBytes {{ .VHDBlockSizeBytes }}
{{- else -}}
Copy-Item -Path {{ .HardDrivePath }} -Destination $vhdPath
Copy-Item -Path "{{ .HardDrivePath }}" -Destination $vhdPath
{{- end -}}
{{- else -}}
{{- if .FixedVHD -}}
@@ -258,7 +258,7 @@ $vhdPath = Join-Path -Path {{ .Path }} -ChildPath {{ .VHDX }}
{{- end -}}
{{- end }}
Hyper-V\New-VM -Name {{ .VMName }} -Path {{ .Path }} -MemoryStartupBytes {{ .MemoryStartupBytes }} -VHDPath $vhdPath -SwitchName {{ .SwitchName }}
Hyper-V\New-VM -Name "{{ .VMName }}" -Path "{{ .Path }}" -MemoryStartupBytes {{ .MemoryStartupBytes }} -VHDPath $vhdPath -SwitchName "{{ .SwitchName }}"
{{- if eq .Generation 2}} -Generation {{ .Generation }} {{- end -}}
{{- if ne .Version ""}} -Version {{ .Version }} {{- end -}}
`))
+19 -19
View File
@@ -26,9 +26,9 @@ func Test_getCreateVMScript(t *testing.T) {
t.Fatalf("Error: %s", err.Error())
}
expected := `$vhdPath = Join-Path -Path C://mypath -ChildPath myvm.vhd
Hyper-V\New-VHD -Path $vhdPath -ParentPath C://harddrivepath -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName hyperv-vmx-switch -Version 5.0`
expected := `$vhdPath = Join-Path -Path "C://mypath" -ChildPath "myvm.vhd"
Hyper-V\New-VHD -Path $vhdPath -ParentPath "C://harddrivepath" -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name "myvm" -Path "C://mypath" -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName "hyperv-vmx-switch" -Version 5.0`
if ok := strings.Compare(scriptString, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, scriptString)
}
@@ -48,9 +48,9 @@ Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vh
t.Fatalf("Error: %s", err.Error())
}
expected = `$vhdPath = Join-Path -Path C://mypath -ChildPath myvm.vhdx
Hyper-V\New-VHD -Path $vhdPath -ParentPath C://harddrivepath -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName hyperv-vmx-switch -Generation 2 -Version 5.0`
expected = `$vhdPath = Join-Path -Path "C://mypath" -ChildPath "myvm.vhdx"
Hyper-V\New-VHD -Path $vhdPath -ParentPath "C://harddrivepath" -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name "myvm" -Path "C://mypath" -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName "hyperv-vmx-switch" -Generation 2 -Version 5.0`
if ok := strings.Compare(scriptString, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, scriptString)
}
@@ -63,9 +63,9 @@ Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vh
t.Fatalf("Error: %s", err.Error())
}
expected = `$vhdPath = Join-Path -Path C://mypath -ChildPath myvm.vhdx
Hyper-V\New-VHD -Path $vhdPath -ParentPath C://harddrivepath -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName hyperv-vmx-switch -Version 5.0`
expected = `$vhdPath = Join-Path -Path "C://mypath" -ChildPath "myvm.vhdx"
Hyper-V\New-VHD -Path $vhdPath -ParentPath "C://harddrivepath" -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name "myvm" -Path "C://mypath" -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName "hyperv-vmx-switch" -Version 5.0`
if ok := strings.Compare(scriptString, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, scriptString)
}
@@ -86,9 +86,9 @@ Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vh
if err != nil {
t.Fatalf("Error: %s", err.Error())
}
expected = `$vhdPath = Join-Path -Path C://mypath -ChildPath myvm.vhdx
Hyper-V\New-VHD -Path $vhdPath -ParentPath C://harddrivepath -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName hyperv-vmx-switch`
expected = `$vhdPath = Join-Path -Path "C://mypath" -ChildPath "myvm.vhdx"
Hyper-V\New-VHD -Path $vhdPath -ParentPath "C://harddrivepath" -Differencing -BlockSizeBytes 10
Hyper-V\New-VM -Name "myvm" -Path "C://mypath" -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName "hyperv-vmx-switch"`
if ok := strings.Compare(scriptString, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, scriptString)
}
@@ -98,9 +98,9 @@ Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vh
if err != nil {
t.Fatalf("Error: %s", err.Error())
}
expected = `$vhdPath = Join-Path -Path C://mypath -ChildPath myvm.vhdx
Copy-Item -Path C://harddrivepath -Destination $vhdPath
Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName hyperv-vmx-switch`
expected = `$vhdPath = Join-Path -Path "C://mypath" -ChildPath "myvm.vhdx"
Copy-Item -Path "C://harddrivepath" -Destination $vhdPath
Hyper-V\New-VM -Name "myvm" -Path "C://mypath" -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName "hyperv-vmx-switch"`
if ok := strings.Compare(scriptString, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, scriptString)
}
@@ -110,9 +110,9 @@ Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vh
if err != nil {
t.Fatalf("Error: %s", err.Error())
}
expected = `$vhdPath = Join-Path -Path C://mypath -ChildPath myvm.vhdx
expected = `$vhdPath = Join-Path -Path "C://mypath" -ChildPath "myvm.vhdx"
Hyper-V\New-VHD -Path $vhdPath -SizeBytes 8192 -BlockSizeBytes 10
Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName hyperv-vmx-switch`
Hyper-V\New-VM -Name "myvm" -Path "C://mypath" -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName "hyperv-vmx-switch"`
if ok := strings.Compare(scriptString, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, scriptString)
}
@@ -122,9 +122,9 @@ Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vh
if err != nil {
t.Fatalf("Error: %s", err.Error())
}
expected = `$vhdPath = Join-Path -Path C://mypath -ChildPath myvm.vhd
expected = `$vhdPath = Join-Path -Path "C://mypath" -ChildPath "myvm.vhd"
Hyper-V\New-VHD -Path $vhdPath -Fixed -SizeBytes 8192
Hyper-V\New-VM -Name myvm -Path C://mypath -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName hyperv-vmx-switch`
Hyper-V\New-VM -Name "myvm" -Path "C://mypath" -MemoryStartupBytes 1024 -VHDPath $vhdPath -SwitchName "hyperv-vmx-switch"`
if ok := strings.Compare(scriptString, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, scriptString)
}
+25
View File
@@ -0,0 +1,25 @@
{
"variables": {
"token": "{{ env `HYPERONE_TOKEN` }}",
"project": "{{ env `HYPERONE_PROJECT` }}"
},
"builders": [
{
"token": "{{ user `token` }}",
"project": "{{ user `project` }}",
"type": "hyperone",
"source_image": "ubuntu",
"disk_size": 10,
"vm_type": "a1.nano"
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"apt-get update",
"apt-get upgrade -y"
]
}
]
}
+29
View File
@@ -0,0 +1,29 @@
{
"variables": {
"token": "{{ env `HYPERONE_TOKEN` }}",
"project": "{{ env `HYPERONE_PROJECT` }}"
},
"builders": [
{
"token": "{{ user `token` }}",
"project": "{{ user `project` }}",
"type": "hyperone",
"source_image": "ubuntu",
"disk_size": 10,
"vm_type": "a1.nano",
"chroot_disk": true,
"chroot_command_wrapper": "sudo {{.Command}}",
"pre_mount_commands": [
"parted {{.Device}} mklabel msdos mkpart primary 1M 100% set 1 boot on print",
"mkfs.ext4 {{.Device}}1"
],
"post_mount_commands": [
"apt-get update",
"apt-get install debootstrap",
"debootstrap --arch amd64 bionic {{.MountPath}}"
]
}
],
"provisioners": []
}
+2 -3
View File
@@ -1,7 +1,7 @@
package fix
import (
"log"
"fmt"
"strconv"
"strings"
@@ -53,8 +53,7 @@ func (FixerAmazonPrivateIP) Fix(input map[string]interface{}) (map[string]interf
var err error
privateIP, err = strconv.ParseBool(privateIPi.(string))
if err != nil {
log.Fatalf("Wrong type for ssh_private_ip")
continue
return nil, fmt.Errorf("ssh_private_ip is not a boolean, %s", err)
}
}
+16
View File
@@ -75,3 +75,19 @@ func TestFixerAmazonPrivateIP(t *testing.T) {
}
}
}
func TestFixerAmazonPrivateIPNonBoolean(t *testing.T) {
var f FixerAmazonPrivateIP
input := map[string]interface{}{
"builders": []map[string]interface{}{{
"type": "amazon-ebs",
"ssh_private_ip": "not-a-boolean-value",
}},
}
_, err := f.Fix(input)
if err == nil {
t.Fatal("should have errored")
}
}
+12 -5
View File
@@ -6,6 +6,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Azure/go-autorest v10.12.0+incompatible
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 // indirect
github.com/Bowery/prompt v0.0.0-20180817134258-8a1d5376df1c // indirect
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290
github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 // indirect
github.com/Jeffail/gabs v1.1.1 // indirect
@@ -19,6 +20,7 @@ require (
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd // indirect
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 // indirect
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6 // indirect
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43
github.com/armon/go-metrics v0.0.0-20180713145231-3c58d8115a78 // indirect
github.com/armon/go-radix v0.0.0-20160115234725-4239b77079c7 // indirect
@@ -34,10 +36,11 @@ require (
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
github.com/creack/goselect v0.0.0-20180210034346-528c74964609 // indirect
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f // indirect
github.com/denverdino/aliyungo v0.0.0-20180417075537-ebad04655e03
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/digitalocean/godo v0.0.0-20170407151542-4c04abe183f4
github.com/digitalocean/godo v1.7.3
github.com/dnaeon/go-vcr v1.0.0 // indirect
github.com/docker/docker v0.0.0-20170406124027-fa3e2d5ab9b5 // indirect
github.com/docker/go-connections v0.4.0 // indirect
@@ -90,9 +93,11 @@ require (
github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20181106190520-2236f141171e // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb
github.com/hetznercloud/hcloud-go v1.12.0
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee // indirect
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kardianos/govendor v1.0.9 // indirect
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
github.com/keybase/go-crypto v0.0.0-20181127160227-255a5089e85a // indirect
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77 // indirect
@@ -114,7 +119,7 @@ require (
github.com/mitchellh/cli v0.0.0-20170908181043-65fcae5817c8
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7
github.com/mitchellh/go-homedir v0.0.0-20151025052427-d682a8f0cf13 // indirect
github.com/mitchellh/go-homedir v0.0.0-20151025052427-d682a8f0cf13
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/mitchellh/iochan v0.0.0-20150529224432-87b45ffd0e95
@@ -122,6 +127,7 @@ require (
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784
github.com/mitchellh/reflectwalk v1.0.0
github.com/mna/pigeon v1.0.0 // indirect
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef // indirect
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 // indirect
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
@@ -160,13 +166,14 @@ require (
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1
github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311
github.com/xanzy/go-cloudstack v2.1.4+incompatible
github.com/xanzy/go-cloudstack v2.4.1+incompatible
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5 // indirect
google.golang.org/api v0.0.0-20180818000503-e21acd801f91
google.golang.org/grpc v1.17.0 // indirect
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
+34 -6
View File
@@ -1,3 +1,4 @@
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1 h1:RMTyvS5bjvSWiUcfqfr/E2pxHEMrALvU+E12n6biymg=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE=
@@ -9,6 +10,8 @@ github.com/Azure/go-autorest v10.12.0+incompatible h1:6YphwUK+oXbzvCc1fd5VrnxCek
github.com/Azure/go-autorest v10.12.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Bowery/prompt v0.0.0-20180817134258-8a1d5376df1c h1:fAMg70P5ydy1uiIj6CdA69h6nmQKbv18VlVOXhKNrcM=
github.com/Bowery/prompt v0.0.0-20180817134258-8a1d5376df1c/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 h1:K9I21XUHNbYD3GNMmJBN0UKJCpdP+glftwNZ7Bo8kqY=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 h1:dmc/C8bpE5VkQn65PNbbyACDC8xw8Hpp/NEurdPmQDQ=
@@ -35,6 +38,8 @@ github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd h1:S3Fr6QnkpW9VRjiEY
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2ptWsJn8p8RDufBjBDKIxLU7BTYGOM=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6 h1:uZuxRZCz65cG1o6K/xUqImNcYKtmk9ylqaH0itMSvzA=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43 h1:ePCAQPf5tUc5IMcUvu6euhSGna7jzs7eiXtJXHig6Zc=
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43/go.mod h1:S6puKjZ9ZeqUPBv2hEBnMZGcM2J6mOsDRQcmxkMAND0=
github.com/armon/go-metrics v0.0.0-20180713145231-3c58d8115a78 h1:mdRSArcFLfW0VoL34LZAKSz6LkkK4jFxVx2xYavACMg=
@@ -70,17 +75,23 @@ github.com/creack/goselect v0.0.0-20180210034346-528c74964609 h1:FSxXMd2wCHj6Gqg
github.com/creack/goselect v0.0.0-20180210034346-528c74964609/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA=
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/denverdino/aliyungo v0.0.0-20180417075537-ebad04655e03 h1:NYqwUkb/ypX8OZmBDpPPbZ4npAPjlGFXZ4aSOyRzRL8=
github.com/denverdino/aliyungo v0.0.0-20180417075537-ebad04655e03/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978 h1:oyfbRmu7YnytN4bXhvJPY1HPgUL52j5AxtgGDU0bMVs=
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/digitalocean/godo v0.0.0-20170407151542-4c04abe183f4 h1:34XBbvedApvUMZY6E6Q95ksnpl+FtX+KF3ywZzZY6X0=
github.com/digitalocean/godo v0.0.0-20170407151542-4c04abe183f4/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/digitalocean/godo v1.7.3 h1:0tFPilFBDsVSiAKF8hyzj/MAmRYxNc5MGhAiYZYVrpo=
github.com/digitalocean/godo v1.7.3/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/dnaeon/go-vcr v1.0.0 h1:1QZ+ahihvRvppcJnFvuoHAdnZTf1PqKjO4Ftr1cfQTo=
github.com/dnaeon/go-vcr v1.0.0/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/docker v0.0.0-20170406124027-fa3e2d5ab9b5 h1:V/WghsgVPZl+92drOvPEwuZdHskJmrnD83V1msaV/Jc=
github.com/docker/docker v0.0.0-20170406124027-fa3e2d5ab9b5 h1:DwY2bFs8p+xf2WaQewx2hnGdjYR5K4UAaxcNPyKkTek=
github.com/docker/docker v0.0.0-20170406124027-fa3e2d5ab9b5/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
@@ -140,7 +151,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:1yOKgt0XYKUg1HOKunGOSt2ocU4bxLCjmIHt0vRtVHM=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
@@ -169,7 +180,7 @@ github.com/hashicorp/go-plugin v0.0.0-20181030172320-54b6ff97d818 h1:wA1XRGBHMdp
github.com/hashicorp/go-plugin v0.0.0-20181030172320-54b6ff97d818/go.mod h1:Ft7ju2vWzhO0ETMKUVo12XmXmII6eSUS4rsPTkY/siA=
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6 h1:qCv4319q2q7XKn0MQbi8p37hsJ+9Xo8e6yojA73JVxk=
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6/go.mod h1:fXcdFsQoipQa7mwORhKad5jmDCeSy/RCGzWA08PO0lM=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:9HVkPxOpo+yO93Ah4yrO67d/qh0fbLLWbKqhYjyHq9A=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
@@ -195,6 +206,8 @@ github.com/hetznercloud/hcloud-go v1.12.0 h1:ugZO8a8ADekqSWi7xWlcs6pxr4QE0tw5Vny
github.com/hetznercloud/hcloud-go v1.12.0/go.mod h1:g5pff0YNAZywQaivY/CmhUYFVp7oP0nu3MiODC2W4Hw=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775 h1:MIteIoIQ5nFoOmwEHPDsqng8d0dtKj3lCnQCwGvtxXc=
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs=
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee h1:AQ/QmCk6x8ECPpf2pkPtA4lyncEEBbs8VFnVXPYKhIs=
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee/go.mod h1:N0t2vlmpe8nyZB5ouIbJQPDSR+mH6oe7xHB9VZHSUzM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
@@ -203,6 +216,8 @@ github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564 h1:+HMa2xWQOm+9eb
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc=
github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/keybase/go-crypto v0.0.0-20181127160227-255a5089e85a h1:X/UFlwD2/UV0RCy+8ITi4DmxJwk83YUH7bXwkJIHHMo=
@@ -268,6 +283,8 @@ github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784 h1:+DAetXqxv/
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mna/pigeon v1.0.0 h1:n46IoStjdzjaXuyBH53j9HZ8CVqGWpC7P5/v8dP4qEY=
github.com/mna/pigeon v1.0.0/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04=
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef h1:E/seV1Rtsnr2juBw1Dfz4iDPT3/5s1H/BATx+ePmSyo=
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U=
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 h1:y6FZWUBBt1iPmJyGbGza3ncvVBMKzgd32oFChRZR7Do=
@@ -355,27 +372,38 @@ github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670 h1:HQWT4ta3wW5GZ790Ga
github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311 h1:s5pyxd5S6wRs2WpEE0xRfWUF46Wbz44h203KnbX0ecI=
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/go-cloudstack v2.1.4+incompatible h1:5c72sRFakVv8wH/HsQFg+xr37CmNQU2UbJfaBjW5f0c=
github.com/xanzy/go-cloudstack v2.1.4+incompatible/go.mod h1:s3eL3z5pNXF5FVybcT+LIVdId8pYn709yv6v5mrkrQE=
github.com/xanzy/go-cloudstack v2.4.1+incompatible h1:Oc4xa2+I94h1g/QJ+nHoq597nJz2KXzxuQx/weOx0AU=
github.com/xanzy/go-cloudstack v2.4.1+incompatible/go.mod h1:s3eL3z5pNXF5FVybcT+LIVdId8pYn709yv6v5mrkrQE=
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4 h1:AJCW0rhPjFKEAoValWpqnRKxX8YV0Xvqfw+dOexCTPc=
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5 h1:ev5exjGDsOo0NPTB0qdCcE53BfWl1IICJlhgXgfT9fM=
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
google.golang.org/api v0.0.0-20180818000503-e21acd801f91 h1:MgYYgjaWMS2qQiDwCznfbqNmEOdSULlvjCvSCvIe/Wo=
google.golang.org/api v0.0.0-20180818000503-e21acd801f91/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -407,7 +435,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 h1:kb0VV7NuIojvRfzwslQeP3yArBqJHW9tOl4t38VS1jM=
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:y0IMTfclpMdsdIbr6uwmJn5/WZ7vFuObxDMdrylFM3A=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+3
View File
@@ -66,6 +66,9 @@ type Config struct {
WinRMInsecure bool `mapstructure:"winrm_insecure"`
WinRMUseNTLM bool `mapstructure:"winrm_use_ntlm"`
WinRMTransportDecorator func() winrm.Transporter
// Delay
PauseBeforeConnect time.Duration `mapstructure:"pause_before_connecting"`
}
// ReadSSHPrivateKeyFile returns the SSH private key bytes
+26 -1
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"time"
"github.com/hashicorp/packer/communicator/none"
"github.com/hashicorp/packer/helper/multistep"
@@ -44,6 +45,18 @@ type StepConnect struct {
substep multistep.Step
}
func (s *StepConnect) pause(pauseLen time.Duration, ctx context.Context) bool {
// Use a select to determine if we get cancelled during the wait
log.Printf("Pausing before connecting...")
select {
case <-ctx.Done():
return true
case <-time.After(pauseLen):
}
log.Printf("Pause over; connecting...")
return false
}
func (s *StepConnect) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
@@ -94,7 +107,19 @@ func (s *StepConnect) Run(ctx context.Context, state multistep.StateBag) multist
}
s.substep = step
return s.substep.Run(ctx, state)
action := s.substep.Run(ctx, state)
if action == multistep.ActionHalt {
return action
}
if s.Config.PauseBeforeConnect > 0 {
cancelled := s.pause(s.Config.PauseBeforeConnect, ctx)
if cancelled {
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConnect) Cleanup(state multistep.StateBag) {
+10 -5
View File
@@ -180,13 +180,18 @@ func (c *Core) Build(n string) (Build, error) {
for _, rawPs := range c.Template.PostProcessors {
current := make([]coreBuildPostProcessor, 0, len(rawPs))
for _, rawP := range rawPs {
// If we skip, ignore
rawP.OnlyExcept.Except = append(rawP.OnlyExcept.Except, c.except...)
if rawP.OnlyExcept.Skip(rawName) {
if rawP.Skip(rawName) {
continue
}
if rawP.OnlyExcept.Skip(rawP.Name) {
break
// -except skips post-processor & build
foundExcept := false
for _, except := range c.except {
if except != "" && except == rawP.Name {
foundExcept = true
}
}
if foundExcept {
continue
}
// Get the post-processor
+46 -1
View File
@@ -343,7 +343,7 @@ func (u *MachineReadableUi) ProgressBar() ProgressBar {
return new(NoopProgressBar)
}
// TimestampedUi is a UI that wraps another UI implementation and prefixes
// TimestampedUi is a UI that wraps another UI implementation and
// prefixes each message with an RFC3339 timestamp
type TimestampedUi struct {
Ui Ui
@@ -376,3 +376,48 @@ func (u *TimestampedUi) ProgressBar() ProgressBar { return u.Ui.ProgressBar() }
func (u *TimestampedUi) timestampLine(string string) string {
return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
}
// Safe is a UI that wraps another UI implementation and
// provides concurrency-safe access
type SafeUi struct {
Sem chan int
Ui Ui
}
var _ Ui = new(SafeUi)
func (u *SafeUi) Ask(s string) (string, error) {
u.Sem <- 1
ret, err := u.Ui.Ask(s)
<-u.Sem
return ret, err
}
func (u *SafeUi) Say(s string) {
u.Sem <- 1
u.Ui.Say(s)
<-u.Sem
}
func (u *SafeUi) Message(s string) {
u.Sem <- 1
u.Ui.Message(s)
<-u.Sem
}
func (u *SafeUi) Error(s string) {
u.Sem <- 1
u.Ui.Error(s)
<-u.Sem
}
func (u *SafeUi) Machine(t string, args ...string) {
u.Sem <- 1
u.Ui.Machine(t, args...)
<-u.Sem
}
func (u *SafeUi) ProgressBar() ProgressBar {
return new(NoopProgressBar)
}
@@ -0,0 +1,365 @@
package digitaloceanimport
import (
"context"
"fmt"
"golang.org/x/oauth2"
"log"
"os"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/digitalocean/godo"
"github.com/hashicorp/packer/builder/digitalocean"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
const BuilderId = "packer.post-processor.digitalocean-import"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
APIToken string `mapstructure:"api_token"`
SpacesKey string `mapstructure:"spaces_key"`
SpacesSecret string `mapstructure:"spaces_secret"`
SpacesRegion string `mapstructure:"spaces_region"`
SpaceName string `mapstructure:"space_name"`
ObjectName string `mapstructure:"space_object_name"`
SkipClean bool `mapstructure:"skip_clean"`
Tags []string `mapstructure:"image_tags"`
Name string `mapstructure:"image_name"`
Description string `mapstructure:"image_description"`
Distribution string `mapstructure:"image_distribution"`
ImageRegions []string `mapstructure:"image_regions"`
Timeout time.Duration `mapstructure:"timeout"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
}
type apiTokenSource struct {
AccessToken string
}
type logger struct {
logger *log.Logger
}
func (t *apiTokenSource) Token() (*oauth2.Token, error) {
return &oauth2.Token{
AccessToken: t.AccessToken,
}, nil
}
func (l logger) Log(args ...interface{}) {
l.logger.Println(args...)
}
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &p.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{"space_object_name"},
},
}, raws...)
if err != nil {
return err
}
if p.config.SpacesKey == "" {
p.config.SpacesKey = os.Getenv("DIGITALOCEAN_SPACES_ACCESS_KEY")
}
if p.config.SpacesSecret == "" {
p.config.SpacesSecret = os.Getenv("DIGITALOCEAN_SPACES_SECRET_KEY")
}
if p.config.APIToken == "" {
p.config.APIToken = os.Getenv("DIGITALOCEAN_API_TOKEN")
}
if p.config.ObjectName == "" {
p.config.ObjectName = "packer-import-{{timestamp}}"
}
if p.config.Distribution == "" {
p.config.Distribution = "Unkown"
}
if p.config.Timeout == 0 {
p.config.Timeout = 20 * time.Minute
}
errs := new(packer.MultiError)
if err = interpolate.Validate(p.config.ObjectName, &p.config.ctx); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing space_object_name template: %s", err))
}
requiredArgs := map[string]*string{
"api_token": &p.config.APIToken,
"spaces_key": &p.config.SpacesKey,
"spaces_secret": &p.config.SpacesSecret,
"spaces_region": &p.config.SpacesRegion,
"space_name": &p.config.SpaceName,
"image_name": &p.config.Name,
"image_regions": &p.config.ImageRegions[0],
}
for key, ptr := range requiredArgs {
if *ptr == "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
}
if len(errs.Errors) > 0 {
return errs
}
packer.LogSecretFilter.Set(p.config.SpacesKey, p.config.SpacesSecret, p.config.APIToken)
log.Println(p.config)
return nil
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
var err error
p.config.ObjectName, err = interpolate.Render(p.config.ObjectName, &p.config.ctx)
if err != nil {
return nil, false, fmt.Errorf("Error rendering space_object_name template: %s", err)
}
log.Printf("Rendered space_object_name as %s", p.config.ObjectName)
source := ""
artifacts := artifact.Files()
log.Println("Looking for image in artifact")
if len(artifacts) > 1 {
validSuffix := []string{"raw", "img", "qcow2", "vhdx", "vdi", "vmdk", "tar.bz2", "tar.xz", "tar.gz"}
for _, path := range artifact.Files() {
for _, suffix := range validSuffix {
if strings.HasSuffix(path, suffix) {
source = path
break
}
}
if source != "" {
break
}
}
} else {
source = artifact.Files()[0]
}
if source == "" {
return nil, false, fmt.Errorf("Image file not found")
}
spacesCreds := credentials.NewStaticCredentials(p.config.SpacesKey, p.config.SpacesSecret, "")
spacesEndpoint := fmt.Sprintf("https://%s.digitaloceanspaces.com", p.config.SpacesRegion)
spacesConfig := &aws.Config{
Credentials: spacesCreds,
Endpoint: aws.String(spacesEndpoint),
Region: aws.String(p.config.SpacesRegion),
LogLevel: aws.LogLevel(aws.LogDebugWithSigning),
Logger: &logger{
logger: log.New(os.Stderr, "", log.LstdFlags),
},
}
sess := session.New(spacesConfig)
ui.Message(fmt.Sprintf("Uploading %s to spaces://%s/%s", source, p.config.SpaceName, p.config.ObjectName))
err = uploadImageToSpaces(source, p, sess)
if err != nil {
return nil, false, err
}
ui.Message(fmt.Sprintf("Completed upload of %s to spaces://%s/%s", source, p.config.SpaceName, p.config.ObjectName))
client := godo.NewClient(oauth2.NewClient(oauth2.NoContext, &apiTokenSource{
AccessToken: p.config.APIToken,
}))
ui.Message(fmt.Sprintf("Started import of spaces://%s/%s", p.config.SpaceName, p.config.ObjectName))
image, err := importImageFromSpaces(p, client)
if err != nil {
return nil, false, err
}
ui.Message(fmt.Sprintf("Waiting for import of image %s to complete (may take a while)", p.config.Name))
err = waitUntilImageAvailable(client, image.ID, p.config.Timeout)
if err != nil {
return nil, false, fmt.Errorf("Import of image %s failed with error: %s", p.config.Name, err)
}
ui.Message(fmt.Sprintf("Import of image %s complete", p.config.Name))
if len(p.config.ImageRegions) > 1 {
// Remove the first region from the slice as the image is already there.
regions := p.config.ImageRegions
regions[0] = regions[len(regions)-1]
regions[len(regions)-1] = ""
regions = regions[:len(regions)-1]
ui.Message(fmt.Sprintf("Distributing image %s to additional regions: %v", p.config.Name, regions))
err = distributeImageToRegions(client, image.ID, regions, p.config.Timeout)
if err != nil {
return nil, false, err
}
}
log.Printf("Adding created image ID %v to output artifacts", image.ID)
artifact = &digitalocean.Artifact{
SnapshotName: image.Name,
SnapshotId: image.ID,
RegionNames: p.config.ImageRegions,
Client: client,
}
if !p.config.SkipClean {
ui.Message(fmt.Sprintf("Deleting import source spaces://%s/%s", p.config.SpaceName, p.config.ObjectName))
err = deleteImageFromSpaces(p, sess)
if err != nil {
return nil, false, err
}
}
return artifact, false, nil
}
func uploadImageToSpaces(source string, p *PostProcessor, s *session.Session) (err error) {
file, err := os.Open(source)
if err != nil {
return fmt.Errorf("Failed to open %s: %s", source, err)
}
uploader := s3manager.NewUploader(s)
_, err = uploader.Upload(&s3manager.UploadInput{
Body: file,
Bucket: &p.config.SpaceName,
Key: &p.config.ObjectName,
ACL: aws.String("public-read"),
})
if err != nil {
return fmt.Errorf("Failed to upload %s: %s", source, err)
}
file.Close()
return nil
}
func importImageFromSpaces(p *PostProcessor, client *godo.Client) (image *godo.Image, err error) {
log.Printf("Importing custom image from spaces://%s/%s", p.config.SpaceName, p.config.ObjectName)
url := fmt.Sprintf("https://%s.%s.digitaloceanspaces.com/%s", p.config.SpaceName, p.config.SpacesRegion, p.config.ObjectName)
createRequest := &godo.CustomImageCreateRequest{
Name: p.config.Name,
Url: url,
Region: p.config.ImageRegions[0],
Distribution: p.config.Distribution,
Description: p.config.Description,
Tags: p.config.Tags,
}
image, _, err = client.Images.Create(context.TODO(), createRequest)
if err != nil {
return image, fmt.Errorf("Failed to import from spaces://%s/%s: %s", p.config.SpaceName, p.config.ObjectName, err)
}
return image, nil
}
func waitUntilImageAvailable(client *godo.Client, imageId int, timeout time.Duration) (err error) {
done := make(chan struct{})
defer close(done)
result := make(chan error, 1)
go func() {
attempts := 0
for {
attempts += 1
log.Printf("Waiting for image to become available... (attempt: %d)", attempts)
image, _, err := client.Images.GetByID(context.TODO(), imageId)
if err != nil {
result <- err
return
}
if image.Status == "available" {
result <- nil
return
}
if image.ErrorMessage != "" {
result <- fmt.Errorf("%v", image.ErrorMessage)
return
}
time.Sleep(3 * time.Second)
select {
case <-done:
return
default:
}
}
}()
log.Printf("Waiting for up to %d seconds for image to become available", timeout/time.Second)
select {
case err := <-result:
return err
case <-time.After(timeout):
err := fmt.Errorf("Timeout while waiting to for action to become available")
return err
}
}
func distributeImageToRegions(client *godo.Client, imageId int, regions []string, timeout time.Duration) (err error) {
for _, region := range regions {
transferRequest := &godo.ActionRequest{
"type": "transfer",
"region": region,
}
log.Printf("Transferring image to %s", region)
action, _, err := client.ImageActions.Transfer(context.TODO(), imageId, transferRequest)
if err != nil {
return fmt.Errorf("Error transferring image: %s", err)
}
if err := digitalocean.WaitForImageState(godo.ActionCompleted, imageId, action.ID, client, timeout); err != nil {
if err != nil {
return fmt.Errorf("Error transferring image: %s", err)
}
}
}
return nil
}
func deleteImageFromSpaces(p *PostProcessor, s *session.Session) (err error) {
s3conn := s3.New(s)
_, err = s3conn.DeleteObject(&s3.DeleteObjectInput{
Bucket: &p.config.SpaceName,
Key: &p.config.ObjectName,
})
if err != nil {
return fmt.Errorf("Failed to delete spaces://%s/%s: %s", p.config.SpaceName, p.config.ObjectName, err)
}
return nil
}
@@ -0,0 +1,32 @@
package digitaloceanimport
import (
"bytes"
"testing"
"github.com/hashicorp/packer/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{}
}
func testPP(t *testing.T) *PostProcessor {
var p PostProcessor
if err := p.Configure(testConfig()); err != nil {
t.Fatalf("err: %s", err)
}
return &p
}
func testUi() *packer.BasicUi {
return &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
}
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packer.PostProcessor = new(PostProcessor)
}
@@ -15,10 +15,19 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Paths []string `mapstructure:"paths"`
KeepOriginalImage bool `mapstructure:"keep_input_artifact"`
AccountFile string `mapstructure:"account_file"`
ctx interpolate.Context
DiskSizeGb int64 `mapstructure:"disk_size"`
DiskType string `mapstructure:"disk_type"`
KeepOriginalImage bool `mapstructure:"keep_input_artifact"`
MachineType string `mapstructure:"machine_type"`
Network string `mapstructure:"network"`
Paths []string `mapstructure:"paths"`
Subnetwork string `mapstructure:"subnetwork"`
Zone string `mapstructure:"zone"`
Account googlecompute.AccountFile
ctx interpolate.Context
}
type PostProcessor struct {
@@ -35,12 +44,38 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
return err
}
errs := new(packer.MultiError)
if len(p.config.Paths) == 0 {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("paths must be specified"))
}
// Set defaults.
if p.config.DiskSizeGb == 0 {
p.config.DiskSizeGb = 200
}
if p.config.DiskType == "" {
p.config.DiskType = "pd-ssd"
}
if p.config.MachineType == "" {
p.config.MachineType = "n1-highcpu-4"
}
if p.config.Network == "" && p.config.Subnetwork == "" {
p.config.Network = "default"
}
if len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
ui.Say("Starting googlecompute-export...")
ui.Say(fmt.Sprintf("Exporting image to destinations: %v", p.config.Paths))
if artifact.BuilderId() != googlecompute.BuilderId {
err := fmt.Errorf(
"Unknown artifact type: %s\nCan only export from Google Compute Engine builder artifacts.",
@@ -48,79 +83,90 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return nil, p.config.KeepOriginalImage, err
}
result := &Artifact{paths: p.config.Paths}
builderAccountFile := artifact.State("AccountFilePath").(string)
builderImageName := artifact.State("ImageName").(string)
builderProjectId := artifact.State("ProjectId").(string)
builderZone := artifact.State("BuildZone").(string)
if len(p.config.Paths) > 0 {
accountKeyFilePath := artifact.State("AccountFilePath").(string)
imageName := artifact.State("ImageName").(string)
imageSizeGb := artifact.State("ImageSizeGb").(int64)
projectId := artifact.State("ProjectId").(string)
zone := artifact.State("BuildZone").(string)
ui.Say(fmt.Sprintf("Exporting image %v to destination: %v", builderImageName, p.config.Paths))
// Set up instance configuration.
instanceName := fmt.Sprintf("%s-exporter", artifact.Id())
metadata := map[string]string{
"image_name": imageName,
"name": instanceName,
"paths": strings.Join(p.config.Paths, " "),
"startup-script": StartupScript,
"zone": zone,
}
exporterConfig := googlecompute.Config{
InstanceName: instanceName,
SourceImageProjectId: "debian-cloud",
SourceImage: "debian-8-jessie-v20160629",
DiskName: instanceName,
DiskSizeGb: imageSizeGb + 10,
DiskType: "pd-standard",
Metadata: metadata,
MachineType: "n1-standard-4",
Zone: zone,
Network: "default",
RawStateTimeout: "5m",
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/devstorage.full_control",
},
}
exporterConfig.CalcTimeout()
if p.config.Zone == "" {
p.config.Zone = builderZone
}
// Set up credentials and GCE driver.
if accountKeyFilePath != "" {
err := googlecompute.ProcessAccountFile(&exporterConfig.Account, accountKeyFilePath)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
}
driver, err := googlecompute.NewDriverGCE(ui, projectId, &exporterConfig.Account)
// Set up credentials for GCE driver.
if builderAccountFile != "" {
err := googlecompute.ProcessAccountFile(&p.config.Account, builderAccountFile)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", &exporterConfig)
state.Put("driver", driver)
state.Put("ui", ui)
// Build the steps.
steps := []multistep.Step{
&googlecompute.StepCreateSSHKey{
Debug: p.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("gce_%s.pem", p.config.PackerBuildName),
},
&googlecompute.StepCreateInstance{
Debug: p.config.PackerDebug,
},
new(googlecompute.StepWaitStartupScript),
new(googlecompute.StepTeardownInstance),
}
// Run the steps.
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(state)
}
if p.config.AccountFile != "" {
err := googlecompute.ProcessAccountFile(&p.config.Account, p.config.AccountFile)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
}
// Set up exporter instance configuration.
exporterName := fmt.Sprintf("%s-exporter", artifact.Id())
exporterMetadata := map[string]string{
"image_name": builderImageName,
"name": exporterName,
"paths": strings.Join(p.config.Paths, " "),
"startup-script": StartupScript,
"zone": p.config.Zone,
}
exporterConfig := googlecompute.Config{
DiskName: exporterName,
DiskSizeGb: p.config.DiskSizeGb,
DiskType: p.config.DiskType,
InstanceName: exporterName,
MachineType: p.config.MachineType,
Metadata: exporterMetadata,
Network: p.config.Network,
RawStateTimeout: "5m",
SourceImageFamily: "debian-9-worker",
SourceImageProjectId: "compute-image-tools",
Subnetwork: p.config.Subnetwork,
Zone: p.config.Zone,
Scopes: []string{
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/devstorage.full_control",
"https://www.googleapis.com/auth/userinfo.email",
},
}
exporterConfig.CalcTimeout()
driver, err := googlecompute.NewDriverGCE(ui, builderProjectId, &p.config.Account)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", &exporterConfig)
state.Put("driver", driver)
state.Put("ui", ui)
// Build the steps.
steps := []multistep.Step{
&googlecompute.StepCreateSSHKey{
Debug: p.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("gce_%s.pem", p.config.PackerBuildName),
},
&googlecompute.StepCreateInstance{
Debug: p.config.PackerDebug,
},
new(googlecompute.StepWaitStartupScript),
new(googlecompute.StepTeardownInstance),
}
// Run the steps.
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(state)
result := &Artifact{paths: p.config.Paths}
return result, p.config.KeepOriginalImage, nil
}
+13 -15
View File
@@ -1,6 +1,6 @@
package googlecomputeexport
var StartupScript string = `#!/bin/sh
var StartupScript string = `#!/bin/bash
GetMetadata () {
echo "$(curl -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/$1 2> /dev/null)"
@@ -8,11 +8,11 @@ GetMetadata () {
IMAGENAME=$(GetMetadata image_name)
NAME=$(GetMetadata name)
DISKNAME=${NAME}-toexport
PATHS=$(GetMetadata paths)
PATHS=($(GetMetadata paths))
ZONE=$(GetMetadata zone)
Exit () {
for i in ${PATHS}; do
for i in ${PATHS[@]}; do
LOGDEST="${i}.exporter.log"
echo "Uploading exporter log to ${LOGDEST}..."
gsutil -h "Content-Type:text/plain" cp /var/log/daemon.log ${LOGDEST}
@@ -40,17 +40,15 @@ if ! gcloud compute instances attach-disk ${NAME} --disk ${DISKNAME} --device-na
Exit 1
fi
echo "Dumping disk..."
if ! dd if=/dev/disk/by-id/google-toexport of=disk.raw bs=4096 conv=sparse; then
echo "Failed to dump disk to image."
echo "GCEExport: Running export tool."
gce_export -gcs_path "${PATHS[0]}" -disk /dev/disk/by-id/google-toexport -y
if [ $? -ne 0 ]; then
echo "ExportFailed: Failed to export disk source to ${PATHS[0]}."
Exit 1
fi
echo "Compressing and tar'ing disk image..."
if ! tar -czf root.tar.gz disk.raw; then
echo "Failed to tar disk image."
Exit 1
fi
echo "ExportSuccess"
sync
echo "Detaching disk..."
if ! gcloud compute instances detach-disk ${NAME} --disk ${DISKNAME} --zone ${ZONE}; then
@@ -64,10 +62,10 @@ if ! gcloud compute disks delete ${DISKNAME} --zone ${ZONE}; then
FAIL=1
fi
for i in ${PATHS}; do
echo "Uploading tar'ed disk image to ${i}..."
if ! gsutil -o GSUtil:parallel_composite_upload_threshold=100M cp root.tar.gz ${i}; then
echo "Failed to upload image to ${i}."
for i in ${PATHS[@]:1}; do
echo "Copying archive image to ${i}..."
if ! gsutil -o GSUtil:parallel_composite_upload_threshold=100M cp ${PATHS[0]} ${i}; then
echo "Failed to copy image to ${i}."
FAIL=1
fi
done
@@ -13,13 +13,9 @@ import (
"github.com/hashicorp/packer/builder/googlecompute"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/post-processor/compress"
"github.com/hashicorp/packer/template/interpolate"
"golang.org/x/oauth2"
"golang.org/x/oauth2/jwt"
)
type Config struct {
@@ -38,12 +34,12 @@ type Config struct {
KeepOriginalImage bool `mapstructure:"keep_input_artifact"`
SkipClean bool `mapstructure:"skip_clean"`
ctx interpolate.Context
Account googlecompute.AccountFile
ctx interpolate.Context
}
type PostProcessor struct {
config Config
runner multistep.Runner
}
func (p *PostProcessor) Configure(raws ...interface{}) error {
@@ -60,24 +56,29 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
return err
}
errs := new(packer.MultiError)
// Set defaults
if p.config.GCSObjectName == "" {
p.config.GCSObjectName = "packer-import-{{timestamp}}.tar.gz"
}
errs := new(packer.MultiError)
// Check and render gcs_object_name
if err = interpolate.Validate(p.config.GCSObjectName, &p.config.ctx); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing gcs_object_name template: %s", err))
}
if p.config.AccountFile != "" {
if err := googlecompute.ProcessAccountFile(&p.config.Account, p.config.AccountFile); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
templates := map[string]*string{
"bucket": &p.config.Bucket,
"image_name": &p.config.ImageName,
"project_id": &p.config.ProjectId,
"account_file": &p.config.AccountFile,
"bucket": &p.config.Bucket,
"image_name": &p.config.ImageName,
"project_id": &p.config.ProjectId,
}
for key, ptr := range templates {
if *ptr == "" {
@@ -94,7 +95,10 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
var err error
client, err := googlecompute.NewClientGCE(&p.config.Account)
if err != nil {
return nil, false, err
}
if artifact.BuilderId() != compress.BuilderId {
err = fmt.Errorf(
@@ -108,18 +112,18 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return nil, false, fmt.Errorf("Error rendering gcs_object_name template: %s", err)
}
rawImageGcsPath, err := UploadToBucket(p.config.AccountFile, ui, artifact, p.config.Bucket, p.config.GCSObjectName)
rawImageGcsPath, err := UploadToBucket(client, ui, artifact, p.config.Bucket, p.config.GCSObjectName)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
gceImageArtifact, err := CreateGceImage(p.config.AccountFile, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures)
gceImageArtifact, err := CreateGceImage(client, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
if !p.config.SkipClean {
err = DeleteFromBucket(p.config.AccountFile, ui, p.config.Bucket, p.config.GCSObjectName)
err = DeleteFromBucket(client, ui, p.config.Bucket, p.config.GCSObjectName)
if err != nil {
return nil, p.config.KeepOriginalImage, err
}
@@ -128,24 +132,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return gceImageArtifact, p.config.KeepOriginalImage, nil
}
func UploadToBucket(accountFile string, ui packer.Ui, artifact packer.Artifact, bucket string, gcsObjectName string) (string, error) {
var client *http.Client
var account googlecompute.AccountFile
err := googlecompute.ProcessAccountFile(&account, accountFile)
if err != nil {
return "", err
}
var DriverScopes = []string{"https://www.googleapis.com/auth/devstorage.full_control"}
conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: DriverScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
client = conf.Client(oauth2.NoContext)
func UploadToBucket(client *http.Client, ui packer.Ui, artifact packer.Artifact, bucket string, gcsObjectName string) (string, error) {
service, err := storage.New(client)
if err != nil {
return "", err
@@ -162,7 +149,7 @@ func UploadToBucket(accountFile string, ui packer.Ui, artifact packer.Artifact,
}
if source == "" {
return "", fmt.Errorf("No tar.gz file found in list of articats")
return "", fmt.Errorf("No tar.gz file found in list of artifacts")
}
artifactFile, err := os.Open(source)
@@ -178,28 +165,10 @@ func UploadToBucket(accountFile string, ui packer.Ui, artifact packer.Artifact,
return "", err
}
return "https://storage.googleapis.com/" + bucket + "/" + gcsObjectName, nil
return storageObject.SelfLink, nil
}
func CreateGceImage(accountFile string, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string) (packer.Artifact, error) {
var client *http.Client
var account googlecompute.AccountFile
err := googlecompute.ProcessAccountFile(&account, accountFile)
if err != nil {
return nil, err
}
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: DriverScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
client = conf.Client(oauth2.NoContext)
func CreateGceImage(client *http.Client, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string) (packer.Artifact, error) {
service, err := compute.New(client)
if err != nil {
return nil, err
@@ -253,24 +222,7 @@ func CreateGceImage(accountFile string, ui packer.Ui, project string, rawImageUR
return &Artifact{paths: []string{op.TargetLink}}, nil
}
func DeleteFromBucket(accountFile string, ui packer.Ui, bucket string, gcsObjectName string) error {
var client *http.Client
var account googlecompute.AccountFile
err := googlecompute.ProcessAccountFile(&account, accountFile)
if err != nil {
return err
}
var DriverScopes = []string{"https://www.googleapis.com/auth/devstorage.full_control"}
conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: DriverScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
client = conf.Client(oauth2.NoContext)
func DeleteFromBucket(client *http.Client, ui packer.Ui, bucket string, gcsObjectName string) error {
service, err := storage.New(client)
if err != nil {
return err
+7 -6
View File
@@ -10,12 +10,13 @@ type ArtifactFile struct {
}
type Artifact struct {
BuildName string `json:"name"`
BuilderType string `json:"builder_type"`
BuildTime int64 `json:"build_time"`
ArtifactFiles []ArtifactFile `json:"files"`
ArtifactId string `json:"artifact_id"`
PackerRunUUID string `json:"packer_run_uuid"`
BuildName string `json:"name"`
BuilderType string `json:"builder_type"`
BuildTime int64 `json:"build_time"`
ArtifactFiles []ArtifactFile `json:"files"`
ArtifactId string `json:"artifact_id"`
PackerRunUUID string `json:"packer_run_uuid"`
CustomData map[string]string `json:"custom_data"`
}
func (a *Artifact) BuilderId() string {

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