Compare commits

..

171 Commits

Author SHA1 Message Date
Wilken Rivera a2ba700ac0 Remove deprecated object ids from configs 2020-10-21 13:56:22 -04:00
Wilken Rivera 1621ab59d8 tests/azure/arm: Update test image name (#10133)
I've run into a few cases where running the full test suite for the ARM
builder would fail because of conflicting image names. This is a
workaround for now the bigger fix is to have the acceptance clean up any
created images.

Results before change
```
the managed image named testBuilderAccManagedDiskWindows-1603151855 already exists in the resource group packer-acceptance-test, use the -force option to automatically delete it.
--- FAIL: TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group (0.66s)
```

Results after change
```
PASS
ok      github.com/hashicorp/packer/builder/azure/arm   2431.115s

```
2020-10-21 11:12:50 +02:00
Megan Marsh 799c548c40 Merge pull request #10116 from OblateSpheroid/GH8915
Feat (builder/oracle-oci): Allow filtering on base image
2020-10-20 14:41:09 -07:00
Adrien Delorme 6d4fae0f2d Add HCL2 aws_secretsmanager function (#10124)
* refactor aws get secrets function out to reuse it else where
* add aws_secretsmanager func and docs for HCL2
* fix GetSecret: allow to pick secret version
2020-10-20 16:21:40 +02:00
js-g 2f26e6d823 tests (builder/oracle-oci): test base image filter in config 2020-10-19 23:30:46 -04:00
Megan Marsh 584fea678b update changelog 2020-10-19 15:34:55 -07:00
Megan Marsh b8e6e2c081 Merge pull request #10085 from 10000coins/f-aws-tenancy
builder/amazon: Support for source instance tenancy
2020-10-19 12:49:07 -07:00
Megan Marsh cc7dbf6092 Add consul_key function to integrate concul with hcl2 tempaltes. (#10119)
* Add consul_key function to integrate concul with hcl2 tempaltes.
* sidebar nav
2020-10-19 14:07:13 +02:00
Jeroen Ketelaar 4f5e878a17 Fixed documentation to have valid JSON for image_filter (#10122) 2020-10-19 11:05:58 +02:00
Adrien Delorme b24911661f add sshkey package and ssh-keygen comand (#10101)
* add sshkey.Generate function that returns an sshkey.Pair to be used with openssh.
* add cmd/ssh-keygen/main.go for testing purposes
* add a test calling ssh.ParsePrivateKey & ssh.ParseAuthorizedKey (which is very
   similar to what openssh would do to read a keypair)

The wrapping of the keys should be handled by crypto/x509.MarshalPKCS8PrivateKey 
& x/crypto/ssh.NewPublicKey which does not work for ed25519 and dsa. 
x509.MarshalPKCS8PrivateKey marshals ed25519 keys but the keys did not work with openssh. 
x509.MarshalPKCS8PrivateKey does not handle dsa keys.
So I had to 'wrap' those manually by reading the code of the openssh package.
Note that ssh.NewPublicKey works with any keytype. I should probably do a PR to ssh to have a NewPrivateKey & Marshalling funcs
2020-10-19 10:24:34 +02:00
Evan Pipho dd068acfd1 Re-generate docs 2020-10-16 23:44:58 +00:00
Megan Marsh ef1b401a22 Merge pull request #10102 from mattr-/update-amazon-builder-for-io2
builder/amazon: Add io2 as a supported volume type
2020-10-16 16:32:57 -07:00
Megan Marsh cee3a14392 Merge pull request #10115 from bhundven/googlecompute_add-create-image-option
Add 'create_image' option to googlecompute
2020-10-16 16:01:44 -07:00
Bryan Hundven ecc8c6a44a Replace create_image with skip_create_image, as requested
Closes #9965
2020-10-16 15:42:30 -07:00
Bryan Hundven 476eefa26b Add 'create_image' option to googlecompute
When building a CI/CD deployment, during the PR process it's nice to be
able to run the builder, but not create the image the build produces.

Closes #9965
2020-10-16 15:42:17 -07:00
Megan Marsh 456214eb51 Merge pull request #10056 from marinsalinas/new_client
New official Outscale SDK migration.
2020-10-16 15:28:54 -07:00
Evan Pipho 608307cd1e Re-allow spot + tenancy. Validate tenancy is set to a usable value 2020-10-16 21:43:22 +00:00
Evan Pipho d5d1a8708e Add tests for Tenancy vs Spot Price 2020-10-16 21:43:22 +00:00
Evan Pipho 6967e02103 Add support for source instance tenancy to amazon builders 2020-10-16 21:43:22 +00:00
Megan Marsh a8ee23a9f9 Merge pull request #10117 from hashicorp/packer_version_func_hcl
implement packer_version func in hcl
2020-10-16 14:38:39 -07:00
Megan Marsh f696e8286e contextual functions menu section for vault func 2020-10-16 14:27:16 -07:00
Megan Marsh 09c6d2880f implement packer_version variable in hcl 2020-10-16 14:07:05 -07:00
Megan Marsh 66b81d9bee add build.name to hcl code (#10114) 2020-10-16 10:57:42 +02:00
Wilken Rivera a17b1a5a89 Fix references to source blocks (#10113) 2020-10-16 10:26:29 +02:00
js-g b0cc71d35e docs (builder/oracle-oci): add documentaion for base_image_filter 2020-10-15 17:26:01 -04:00
js-g 0c34d6ca12 feat (builder/oracle-oci): add regex search on base image name 2020-10-15 15:28:38 -04:00
Wilken Rivera 7ba2ffe171 Merge pull request #10106 from hashicorp/more_hcl_docs
More hcl docs
2020-10-15 13:50:46 -04:00
Wilken Rivera 5d2f2b20e7 Merge pull request #10112 from jmfury/jm.bump-hsm-version-1.0.7
[Website] Update HSM version w/ waypoint
2020-10-15 13:45:48 -04:00
Jimmy Merritello f79b9dc735 Update HSM version 2020-10-15 11:35:19 -05:00
js-g 27128dc62f feat (builder/oracle-oci): allow filtering on base image 2020-10-15 11:02:16 -04:00
Wilken Rivera 343ac2d48d syntax-json: Fix nested provisioner block example (#10109) 2020-10-15 16:04:01 +02:00
Megan Marsh 06efa61c13 a tiny bit more cleanup 2020-10-14 15:56:58 -07:00
Megan Marsh da15b85451 more updates 2020-10-14 13:35:21 -07:00
Megan Marsh 2cdc052d7c Merge pull request #10097 from hashicorp/more_hcl_docs
Update HCL docs with a local resource-free example to empower users t…
2020-10-14 13:34:54 -07:00
Megan Marsh 31df482b61 remove old docs for previous behavior 2020-10-14 13:11:52 -07:00
Megan Marsh 00cc425b84 docs tweaks 2020-10-14 12:58:04 -07:00
Megan Marsh 5522980e4d Merge pull request #10103 from jmfury/jm.website-bump-hsm-menu-v1.0.5
[Website] Bump HSM version
2020-10-14 12:46:16 -07:00
Megan Marsh 84d5de369f Merge pull request #10104 from pwillis-els/pwillis-els-patch-1
Fix typos in hcl2_upgrade
2020-10-14 12:38:07 -07:00
Peter Willis f1b57b1e66 Fix typos 2020-10-14 15:04:18 -04:00
Peter Willis a81811663a Fix typos 2020-10-14 14:52:17 -04:00
Megan Marsh c16354d594 Update HCL docs with a local resource-free example to empower users to tinker. 2020-10-14 11:24:15 -07:00
Jimmy Merritello e98b2d39db Updated pkg 2020-10-14 12:55:53 -05:00
Jimmy Merritello 8121d6d8dc Bump HSM version 2020-10-14 09:39:50 -05:00
Matt Rogers 15f97421e2 builder/amazon: Add io2 as a supported volume type
io2 volumes are new as of 2020-08-24. This adds support for specifying
them in a packer template and having the iops value specified take
effect.
2020-10-14 09:01:17 -05:00
Wilken Rivera c7ba5c9a14 Fix line ending issues for test files (#10096) 2020-10-14 11:03:43 +02:00
Megan Marsh 22622b234b Merge pull request #10095 from hashicorp/hcl_docs
add docs for hcl2_upgrade command
2020-10-13 15:36:49 -07:00
Marin Salinas 22a49a2f2e chore: fix test 2020-10-13 16:19:15 -05:00
Megan Marsh 33019a0e01 add docs for hcl2_upgrade command 2020-10-13 13:56:54 -07:00
Marin Salinas 472b008a94 chore: fix test 2020-10-13 15:16:01 -05:00
Megan Marsh 4810a3d86f Merge pull request #10077 from hashicorp/document_3348
builder/qemu: docs: users must re-apply packer defaults for drive and device arguments
2020-10-13 11:41:28 -07:00
Marin Salinas 2578bbbb50 chore: fix test 2020-10-13 12:54:25 -05:00
Marin Salinas 09f57537bf chore: fix lint issues on osc builder 2020-10-13 11:02:13 -05:00
Marin Salinas 56807553fc chore: fix lint issues on osc builder 2020-10-13 10:45:32 -05:00
Marin Salinas c5dffc1bff chore: add go mod vendor for new Outscale SDK 2020-10-13 10:17:37 -05:00
Wilken Rivera 3abae29752 Update generated docs 2020-10-13 10:33:59 -04:00
Megan Marsh ad129e7df9 update changelog 2020-10-12 16:29:41 -07:00
Megan Marsh 75cfc97da4 Merge pull request #10081 from hashicorp/f-ansible-remote-ssm-connectivity
provisioner/Ansible: Add Amazon SSM setup documentation
2020-10-12 10:49:41 -07:00
Megan Marsh 01ed398756 fix up alicloud docs to make it clearer that disk_size and other disk device options are not top level fields. (#10080) 2020-10-12 10:28:25 +02:00
Wilken Rivera d378a4477a Merge pull request #10079 from hashicorp/vsphere_cluster_host_investigation
fix docs linking issue
2020-10-09 22:11:31 -04:00
Wilken Rivera 8154e5772f provisioner/Ansible: Add Amazon SSM setup documentation 2020-10-09 17:05:14 -04:00
Megan Marsh 709a173060 fix docs linking issue 2020-10-09 14:01:21 -07:00
Marin Salinas dcaf56596d Merge pull request #10 from marinsalinas/bsuvolume
BSU Volume sdk migration
2020-10-09 14:37:53 -05:00
Megan Marsh b05fd9303e document that users must re-apply packer defualts for drive and device arguments 2020-10-09 10:39:27 -07:00
Megan Marsh b460d687ea Merge pull request #10068 from jmfury/jm.website-bump-hsm-menu
Bump HSM version and rm meganav styles
2020-10-09 10:15:23 -07:00
Megan Marsh 774a168957 Merge pull request #10064 from hashicorp/refactor_step_run
builder/qemu: (tech-debt) Major refactor of step_run.
2020-10-08 11:54:34 -07:00
Jimmy Merritello 7ed2c2920f Bump HSM version and rm meganav styles 2020-10-08 13:53:36 -05:00
Megan Marsh 3e35630d8d remove slashes to prevent windows test fails 2020-10-08 11:36:36 -07:00
James Cradock 153d4111fa Fix typo in build argument description (#10066) 2020-10-08 14:58:22 +02:00
Megan Marsh 62a401ef28 Fix 10060 (#10065)
* fix overeager validation
* fix validation of openstack source images
2020-10-08 10:54:41 +02:00
Megan Marsh d6aa172edc remove typo'd option from docs 2020-10-07 16:14:13 -07:00
Megan Marsh d7ba10c23e update changelog 2020-10-07 15:52:06 -07:00
Megan Marsh f076c97e5c Merge pull request #10062 from jmfury/jm.add-hashi-stack-menu
[Website] Add new HashiStackMenu
2020-10-07 15:36:31 -07:00
Megan Marsh 8e632866b1 typo 2020-10-07 15:22:08 -07:00
Megan Marsh b54b778572 major refactor of the step_run. Splits step into two major parts:
- generating defaults
- overriding defaults with user args

The default generation has been shuffled around some, in order to
make sure that any changes to a specific arg happen in one place
to make it easier to reason about those args. Related args have
been moved close to one another.

The deviceArgs and driveArgs were overly complex after several
layers of copy/paste modifications. Careful pruning reduced the
layers of logic and repeated code, to help make it easier to reason
about.
2020-10-07 15:09:45 -07:00
Jimmy Merritello ea3b804af1 Bump version 2020-10-07 15:30:18 -05:00
Adrien Delorme 14e253f316 upgrade panicwrap to v1 (#10059)
* and go mod tidy this allows us to get rid of a dep
2020-10-07 11:22:32 -04:00
Jimmy Merritello 7f0a043a63 Add new HashiStackMenu 2020-10-07 09:57:47 -05:00
Patrik d4d78feed2 feat(scaleway): get image by label if not an UUID (#10061)
Signed-off-by: Patrik Cyvoct <patrik@ptrk.io>
2020-10-07 16:04:00 +02:00
abarbare a478bf6f37 fix: update scaleway website documentation (#10058) 2020-10-07 12:02:08 +02:00
Ricardo Rosales 809f38be3a [azure-chroot] Updating parameter exlude_from_latest to exclude_from_latest (#10034) 2020-10-07 11:08:33 +02:00
Megan Marsh 61c6085651 final fix to make service account impersonation work with iap tunnels (#10054) 2020-10-06 15:34:06 -04:00
Megan Marsh d05eb3401b Merge pull request #10030 from angdraug/no-panicwrap-in-plugins
no panicwrap in plugins
2020-10-06 12:21:15 -07:00
Megan Marsh 7ee9a4b638 Merge pull request #9968 from upodroid/google-impersonation
GCP: add service account impersonation
2020-10-06 11:32:03 -07:00
Romain Lecat 10e356abe6 Fix typo in Outscale name (#10051) 2020-10-06 16:43:49 +02:00
zhsj ef9713b348 Replace kardianos/osext.Executable with os.Executable (#10048)
os.Executable is available since go1.8
https://github.com/kardianos/osext/blob/master/osext_go18.go
2020-10-06 10:59:32 +02:00
Megan Marsh b34e61c961 update changelog 2020-10-05 15:11:18 -07:00
Megan Marsh deb08409f1 Merge pull request #10040 from OblateSpheroid/GH6844
Feat (oracle/oci): allow specifying image compartment
2020-10-05 15:02:23 -07:00
Megan Marsh aeb63f9bef Merge pull request #10031 from hashicorp/hcl_sensitive
HCL: hide sensitive variables from output
2020-10-05 14:58:55 -07:00
Megan Marsh 721cbac645 Merge pull request #9996 from hashicorp/fix_9995
Make shell-local post-processor return copy of previous artifact
2020-10-05 13:00:51 -07:00
Adrien Delorme 4cb218749b remove unecessary check 2020-10-05 15:34:35 +02:00
Adrien Delorme fcaf766569 add docs for sensitive variables 2020-10-05 15:34:35 +02:00
Adrien Delorme 9f97173371 PostProcessorServer.PostProcess: don't close the artifact we are serving
Sometimes, the artifact returned by PostProcess is the artifact from client.Artifact() and in that case we don't want to close client; otherwise the outcome is sort of undetermined. See #9995 for a good test file.

fix #9995
2020-10-05 14:27:55 +02:00
js-g 2222d112bd docs: update OCI page to include image_compartment_id 2020-10-04 23:05:31 -04:00
js-g c892f0582b GH#6844: allow image compartment to be specified 2020-10-02 22:30:52 -04:00
Megan Marsh 157b5cdc28 Merge pull request #10017 from kostasns/oci_boot_volume
Allow to specify boot volume size in OCI builder
2020-10-02 11:04:05 -07:00
Megan Marsh 37b4533cba Merge pull request #10022 from hashicorp/fix_10020
fix pathing in cd_files copy to make sure directories make it into th…
2020-10-02 09:43:57 -07:00
Megan Marsh b105e5e416 Update common/step_create_cdrom.go
Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2020-10-02 09:17:29 -07:00
Adrien Delorme 8f9001b531 uncopy 2020-10-02 11:35:20 +02:00
Adrien Delorme 10b648b282 Update .gitattributes 2020-10-02 11:06:02 +02:00
Adrien Delorme ebb1cb406c ignore walk error 2020-10-02 10:53:17 +02:00
Adrien Delorme fcf16315a3 add tests 2020-10-02 10:49:21 +02:00
Adrien Delorme bb22cfcf34 HCL2: pass sensitive variables to packer.LogSecretFilter
Co-Authored-By: Megan Marsh <1008838+SwampDragons@users.noreply.github.com>
2020-10-02 10:17:07 +02:00
upodroid 1c4b8c3d31 fmt fix 2020-10-01 23:24:46 +01:00
upodroid b1c74f9df0 Merge branch 'master' of github.com:hashicorp/packer into google-impersonation 2020-10-01 23:19:15 +01:00
Megan Marsh c0be097afc Merge pull request #10010 from OblateSpheroid/GH10008
Bug fix(10008): fix ansible-galaxy for v1 files
2020-10-01 14:25:22 -07:00
upodroid 2228b18fcb revendor the libraries 2020-10-01 22:06:15 +01:00
Dmitry Borodaenko d18b7839b9 no panicwrap in plugins
As of mitchellh/panicwrap#25, a call to panicwrap.Wrapped() unsets the
cookie in the env, that makes packer plugin child process inherit an env
without the panicwrap cookie and panicwrap itself.

This trips up CleanupClients() in Packer's plugin client: instead of the
real plugin server it now kills its panicwrap parent -- which doesn't
forward SIGKILL to its child because it's not a signal that can be
caught -- and ends up indefinitely waiting in client.Kill() for an EOF
that will never come.

This workaround is to not even try to panicwrap in a plugin server.
2020-10-01 13:32:00 -07:00
upodroid f13099edfb rebase the branch 2020-10-01 20:39:06 +01:00
Kostas 8c07e21be8 update hcl2spec 2020-10-01 09:06:22 +03:00
Kostas 18c4f271ac rename variable; add docs 2020-10-01 09:01:51 +03:00
Megan Marsh 984f21d409 Merge pull request #10016 from hashicorp/azr-fix-coalesce-empty-string-
use "github.com/hashicorp/go-cty-funcs/collection".CoalesceFunc
2020-09-30 15:49:02 -07:00
Megan Marsh 465ce3596f Merge pull request #10019 from hashicorp/circleci-build-tools-update
Update build tools to latest supported versions
2020-09-30 15:31:38 -07:00
Megan Marsh 69fd5a1527 fix pathing in cd_files copy to make sure directories make it into the cd root. 2020-09-30 15:15:55 -07:00
packer-ci e08e7734f5 Putting source back into Dev Mode 2020-09-30 18:45:29 +00:00
Marin Salinas 49e4d83cd7 refactor: remove oapi references in bsusurrogate builder 2020-09-30 10:39:24 -05:00
Marin Salinas aa3ec3fd0f refactor: change chroot builder to new SDK 2020-09-30 10:38:31 -05:00
Marin Salinas c0c0f003f9 refactor: fix bsu builder to accept proxy 2020-09-30 10:37:41 -05:00
Marin Salinas d813c3647d refactor: change bsuvolume builder to new SDK 2020-09-30 10:37:09 -05:00
Marin Salinas 79e25bf4ac Merge pull request #9 from marinsalinas/bsusurrogate
Bsusurrogate sdk migration
2020-09-30 10:35:26 -05:00
Wilken Rivera 2a879aff14 Update build tools to latest supported versions 2020-09-30 11:22:13 -04:00
Kostas 72166febee allow to specify boot volume size 2020-09-30 13:31:37 +03:00
Adrien Delorme 180dc4a54e add test 2020-09-30 12:02:52 +02:00
Adrien Delorme f3e65b5eb1 use "github.com/hashicorp/go-cty-funcs/collection".CoalesceFunc
fix #9419
after https://github.com/hashicorp/go-cty-funcs/pull/5 was merged
2020-09-30 11:59:36 +02:00
js-g d7507ae67c fix (provisioner/ansible): add checks for both roles and collections 2020-09-29 13:46:59 -04:00
sylviamoss c8874c9382 improve docker_tag cast to avoid failures 2020-09-29 14:41:43 +02:00
js-g 08d485b1f9 GH 10008: fix ansible-galaxy for v1 files 2020-09-28 18:32:27 -04:00
sylviamoss 49bc7665c1 fix docker push tags cast 2020-09-28 11:18:24 +02:00
sylviamoss aa9c162c60 improve docker_tags artifact state read 2020-09-28 10:52:28 +02:00
sylviamoss e758891878 make shell-local post-processor return copy of previous artifact 2020-09-25 16:53:59 +02:00
upodroid be56632f7e fix vendoring 2020-09-20 16:50:03 +01:00
upodroid 9b121e85f9 fix dodgy pointers 2020-09-20 15:31:45 +01:00
Upo 3f6230470b Merge branch 'master' into google-impersonation 2020-09-20 15:23:04 +01:00
upodroid 2faacfd5d7 add service account impersonation 2020-09-20 15:18:37 +01:00
Marin Salinas abdb4bcfbf refactor: change ssh host to osc ssh host 2020-09-11 12:11:52 -05:00
Marin Salinas 645e5afa36 refactor: change bsuvolume builder to new SDK 2020-09-11 12:11:24 -05:00
Marin Salinas b844b7f1c7 refactor: change bsusurrogate builder to new SDK 2020-09-11 10:38:13 -05:00
Marin Salinas 007f6cce4c feat: add wait funtions for snapshot and omi in osc builders 2020-09-11 09:47:08 -05:00
Marin Salinas eed047b83b feat: add proxy support to osc builder 2020-09-11 09:46:04 -05:00
Marin Salinas ddc09ded89 Merge pull request #8 from marinsalinas/step_create_omi
refactor: update step_create_omi to new OSC SDK
2020-09-11 09:39:58 -05:00
Marin Salinas e0badb3fb7 refactor: update step_update_omi_attributes and create_tags to new OSC SDK 2020-08-26 12:25:46 -05:00
Marin Salinas 8885a5ef31 refactor: update step_create_omi to new OSC SDK 2020-08-25 18:02:11 -05:00
Marin Salinas f0dc26613a Merge pull request #7 from marinsalinas/step_deregister_omi
Step Deregister OMI
2020-08-25 17:22:58 -05:00
Marin Salinas 8166dad533 Merge pull request #5 from marinsalinas/step_cleanup_volumes
refactor: change step_cleanup_volumes to new OSC SDK
2020-08-21 15:03:47 -05:00
Marin Salinas aba6e104ae Merge pull request #6 from marinsalinas/step_security_group
Step Security Group
2020-08-21 13:33:30 -05:00
PacoDw 8274d4848c chore: added forece_deregister in the test case to verify the step_deregister_omi 2020-08-21 13:30:21 -05:00
PacoDw 071854ea4a chore: implemented OSC API in step_deregister_omi 2020-08-21 13:29:41 -05:00
PacoDw a13dfe1f42 chore: added GetRegion() and NewOSCClientByRegion functions 2020-08-21 13:29:27 -05:00
PacoDw 60bd61dc18 chore: removed security group filter function, it was moved to step_security_group file 2020-08-21 11:31:00 -05:00
PacoDw c3a4b60ea5 chore: implemented OSC API in step_security_group 2020-08-21 11:30:10 -05:00
Marin Salinas 7a45e4c8b0 refactor: change step tag bsu volumes to new OSC SDK 2020-08-21 10:40:14 -05:00
Marin Salinas c58d6f9b33 refactor: migrate run vm step to new SDK 2020-08-21 10:10:25 -05:00
Marin Salinas 6f0bb33c9a refactor: change tags ssh file to new OSC SDK 2020-08-20 20:35:51 -05:00
Marin Salinas e5c14044fb refactor: change tags file to new OSC SDK 2020-08-20 19:12:38 -05:00
Marin Salinas addc3dd6e7 refactor: change step_cleanup_volumes to new OSC SDK 2020-08-20 18:33:02 -05:00
Marin Salinas d9a7626249 Merge pull request #4 from marinsalinas/step_public_ip
Step public ip
2020-08-20 18:04:03 -05:00
PacoDw 7745369dce chore: set true associate_public_ip_address attribute to test the step_public_ip 2020-08-20 14:00:13 -05:00
PacoDw 86966ecfee :chore: implemented OSC API in pre_public_ip step 2020-08-20 13:59:25 -05:00
Marin Salinas 6f6a656486 Merge pull request #3 from marinsalinas/step_key_pair
Step Key Pair
2020-08-18 16:20:35 -05:00
PacoDw 232d5a3ce6 chore: changed oapi to osc 2020-08-18 15:06:00 -05:00
PacoDw 43e9d43ebd chore: removed API attribute 2020-08-18 13:52:49 -05:00
Marin Salinas 7d8f28e1a3 Merge pull request #2 from marinsalinas/step_source_info
Step source info
2020-08-18 13:34:16 -05:00
Marin Salinas 8b2cdc5821 refactor: change step_network_info to new OSC SDK 2020-08-18 13:33:45 -05:00
Marin Salinas 8649496c6c refactor: change new sdk on step_source_omi_info 2020-08-18 13:33:45 -05:00
Marin Salinas daefa4d086 chore: update vendor dependencies 2020-08-18 13:33:45 -05:00
PacoDw 4431bb87b2 :chore: added api key to specify the API connection 2020-08-18 13:33:45 -05:00
PacoDw 823b957a1f :chore: implemented OSC connection in pre_validate file 2020-08-18 13:33:45 -05:00
PacoDw 22481d1ac2 :chore: added OSC Connection in builder file 2020-08-18 13:32:38 -05:00
PacoDw 1fbe715c13 :chore: added OSC Connection in builder file 2020-08-18 12:23:39 -05:00
PacoDw 1bfb2d9170 :chore: implemented transport file to make the OSC API Connection 2020-08-17 10:04:28 -05:00
PacoDw 1055007cba :chore: added OSC API Client Connection 2020-08-17 10:02:00 -05:00
PacoDw 74f868da37 :chore: changed the AMI with an existing AMI to work fine with the test case 2020-08-13 17:03:32 -05:00
1239 changed files with 81857 additions and 90975 deletions
+4 -4
View File
@@ -7,11 +7,11 @@ version: 2.1
executors:
golang:
docker:
- image: circleci/golang:1.13
- image: circleci/golang:1.15
resource_class: medium+
darwin:
macos:
xcode: "9.0"
xcode: "12.0.0"
commands:
install-go-run-tests-unix:
@@ -67,7 +67,7 @@ jobs:
steps:
- install-go-run-tests-unix:
GOOS: darwin
GOVERSION: "1.13"
GOVERSION: "1.15"
- codecov/upload:
file: coverage.txt
test-windows:
@@ -76,7 +76,7 @@ jobs:
shell: bash.exe
steps:
- install-go-run-tests-windows:
GOVERSION: "1.13"
GOVERSION: "1.15"
- codecov/upload:
file: coverage.txt
check-lint:
+1
View File
@@ -6,6 +6,7 @@
*.mdx text eol=lf
*.ps1 text eol=lf
*.hcl text eol=lf
*.txt text eol=lf
go.mod text eol=lf
go.sum text eol=lf
common/test-fixtures/root/* eol=lf
+36
View File
@@ -1,3 +1,39 @@
## 1.6.5 (Upcoming)
### BUG FIXES:
* builder/azure-chroot: Fix typo in option `exlude_from_latest` to
`exclude_from_latest`. Old name will still be respected. [GH-10034]
* builder/openstack: Fix source image validation regression when using filters.
[GH-10065]
* core/hcl2: Packer HCL's "Coalesce" function now behaves same way as
Terraform's. [GH-10016]
* core/HCL: Hide sensitive variables from output. [GH-10031]
* core: Fix artifact handling so that input artifacts are properly preserved in
postprocessors that don't modify artifacts. [GH-9996]
* core: Fix pathing in cd_files to copy proper directory tree when user
provided absolute paths. [GH-10022]
* provisioner/ansible: Ansible galaxy no longer forces use of collections in v1
files. [GH-10010]
### IMPROVEMENTS:
* builder/amazon: Add `io2` as a supported volume type. [GH-10102]
* builder/amazon: Add support for source instance tenancy [GH-10085]
* builder/google: Add service account impersonation. [GH-9968] [GH-10054]
* builder/googlecompute: Add `skip_create_image` option. [GH-10115]
* builder/oracle-oci: New option to specify image compartment separate from
build compartment. [GH-10040]
* builder/oracle-oci: New option to specify boot volume size. [GH-10017]
* builder/outscale: Migrate to new Outscale SDK. [GH-10056]
* builder/scaleway: Allow the user to use an image label (eg ubuntu_focal)
instead of a hardcoded UUID on the Scaleway builder. [GH-10061]
* hcl: Add build.name variable so users can access build name in addition to
source name. [GH-10114]
* hcl: Add consul_key function to HCL templates. [GH-10119]
* hcl: Add packer.version variable to hcl configs so users can access the
Packer release version. [GH-10117]
## 1.6.4 (September 30, 2020)
### BUG FIXES:
+41 -81
View File
@@ -12,6 +12,8 @@ import (
"github.com/hashicorp/packer/template/interpolate"
)
// The "AlicloudDiskDevice" object us used for the `ECSSystemDiskMapping` and
// `ECSImagesDiskMappings` options, and contains the following fields:
type AlicloudDiskDevice struct {
// The value of disk name is blank by default. [2,
// 128] English or Chinese characters, must begin with an
@@ -19,8 +21,7 @@ type AlicloudDiskDevice struct {
// ., _ and -. The disk name will appear on the console. It cannot
// begin with `http://` or `https://`.
DiskName string `mapstructure:"disk_name" required:"false"`
// Category of the system disk. Optional values
// are:
// Category of the system disk. Optional values are:
// - cloud - general cloud disk
// - cloud_efficiency - efficiency cloud disk
// - cloud_ssd - cloud SSD
@@ -32,6 +33,8 @@ type AlicloudDiskDevice struct {
// Snapshots are used to create the data
// disk After this parameter is specified, Size is ignored. The actual
// size of the created disk is the size of the specified snapshot.
// This field is only used in the ECSImagesDiskMappings option, not
// the ECSSystemDiskMapping option.
SnapshotId string `mapstructure:"disk_snapshot_id" required:"false"`
// The value of disk description is blank by
// default. [2, 256] characters. The disk description will appear on the
@@ -44,94 +47,51 @@ type AlicloudDiskDevice struct {
// such as /dev/xvdb It is null unless the Status is In_use.
Device string `mapstructure:"disk_device" required:"false"`
// Whether or not to encrypt the data disk.
// If this option is set to true, the data disk will be encryped and corresponding snapshot in the target image will also be encrypted. By
// If this option is set to true, the data disk will be encryped and
// corresponding snapshot in the target image will also be encrypted. By
// default, if this is an extra data disk, Packer will not encrypt the
// data disk. Otherwise, Packer will keep the encryption setting to what
// it was in the source image. Please refer to Introduction of ECS disk encryption
// for more details.
// it was in the source image. Please refer to Introduction of ECS disk
// encryption for more details.
Encrypted config.Trilean `mapstructure:"disk_encrypted" required:"false"`
}
// The "AlicloudDiskDevices" object is used to define disk mappings for your
// instance.
type AlicloudDiskDevices struct {
// Image disk mapping for system
// disk.
// - `disk_category` (string) - Category of the system disk. Optional values
// are:
// - `cloud` - general cloud disk
// - `cloud_efficiency` - efficiency cloud disk
// - `cloud_ssd` - cloud SSD
//
// For phased-out instance types and non-I/O optimized instances, the
// default value is cloud. Otherwise, the default value is
// cloud\_efficiency.
//
// - `disk_description` (string) - The value of disk description is blank by
// default. \[2, 256\] characters. The disk description will appear on the
// console. It cannot begin with `http://` or `https://`.
//
// - `disk_name` (string) - The value of disk name is blank by default. \[2,
// 128\] English or Chinese characters, must begin with an
// uppercase/lowercase letter or Chinese character. Can contain numbers,
// `.`, `_` and `-`. The disk name will appear on the console. It cannot
// begin with `http://` or `https://`.
//
// - `disk_size` (number) - Size of the system disk, measured in GiB. Value
// range: \[20, 500\]. The specified value must be equal to or greater
// than max{20, ImageSize}. Default value: max{40, ImageSize}.
// Image disk mapping for the system disk.
// See the [disk device configuration](#disk-devices-configuration) section
// for more information on options.
// Usage example:
//
// ```json
// "builders": [{
// "type":"alicloud-ecs",
// "system_disk_mapping": {
// "disk_size": 50,
// "disk_name": "mydisk"
// },
// ...
// }
// ```
ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false"`
// Add one or more data
// disks to the image.
//
// - `disk_category` (string) - Category of the data disk. Optional values
// are:
// - `cloud` - general cloud disk
// - `cloud_efficiency` - efficiency cloud disk
// - `cloud_ssd` - cloud SSD
//
// Default value: cloud.
//
// - `disk_delete_with_instance` (boolean) - Whether or not the disk is
// released along with the instance:
// - True indicates that when the instance is released, this disk will
// be released with it
// - False indicates that when the instance is released, this disk will
// be retained.
// - `disk_description` (string) - The value of disk description is blank by
// default. \[2, 256\] characters. The disk description will appear on the
// console. It cannot begin with `http://` or `https://`.
//
// - `disk_device` (string) - Device information of the related instance:
// such as `/dev/xvdb` It is null unless the Status is In\_use.
//
// - `disk_name` (string) - The value of disk name is blank by default. \[2,
// 128\] English or Chinese characters, must begin with an
// uppercase/lowercase letter or Chinese character. Can contain numbers,
// `.`, `_` and `-`. The disk name will appear on the console. It cannot
// begin with `http://` or `https://`.
//
// - `disk_size` (number) - Size of the data disk, in GB, values range:
// - `cloud` - 5 \~ 2000
// - `cloud_efficiency` - 20 \~ 2048
// - `cloud_ssd` - 20 \~ 2048
//
// The value should be equal to or greater than the size of the specific
// SnapshotId.
//
// - `disk_snapshot_id` (string) - Snapshots are used to create the data
// disk After this parameter is specified, Size is ignored. The actual
// size of the created disk is the size of the specified snapshot.
//
// Snapshots from on or before July 15, 2013 cannot be used to create a
// disk.
//
// - `disk_encrypted` (boolean) - Whether or not to encrypt the data disk.
// If this option is set to true, the data disk will be encryped and corresponding snapshot in the target image will also be encrypted. By
// default, if this is an extra data disk, Packer will not encrypt the
// data disk. Otherwise, Packer will keep the encryption setting to what
// it was in the source image. Please refer to Introduction of [ECS disk encryption](https://www.alibabacloud.com/help/doc-detail/59643.htm)
// for more details.
// Add one or more data disks to the image.
// See the [disk device configuration](#disk-devices-configuration) section
// for more information on options.
// Usage example:
//
// ```json
// "builders": [{
// "type":"alicloud-ecs",
// "image_disk_mappings": [
// {
// "disk_snapshot_id": "someid",
// "disk_device": "dev/xvdb"
// }
// ],
// ...
// }
// ```
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false"`
}
+2 -2
View File
@@ -134,8 +134,8 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp
ebsBlockDevice.VolumeSize = aws.Int64(blockDevice.VolumeSize)
}
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
// IOPS is only valid for io1 and io2 types
if blockDevice.VolumeType == "io1" || blockDevice.VolumeType == "io2" {
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
}
@@ -66,6 +66,25 @@ func TestBlockDevice(t *testing.T) {
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: 1000,
},
Result: &ec2.BlockDeviceMapping{
DeviceName: aws.String("/dev/sdb"),
Ebs: &ec2.EbsBlockDevice{
VolumeType: aws.String("io2"),
VolumeSize: aws.Int64(8),
DeleteOnTermination: aws.Bool(true),
Iops: aws.Int64(1000),
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
+13
View File
@@ -376,6 +376,12 @@ type RunConfig struct {
// subnet-12345def, where Packer will launch the EC2 instance. This field is
// required if you are using an non-default VPC.
SubnetId string `mapstructure:"subnet_id" required:"false"`
// [Tenancy](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html) used
// when Packer launches the EC2 instance, allowing it to be launched on dedicated hardware.
//
// The default is "default", meaning shared tenancy. Allowed values are "default",
// "dedicated" and "host".
Tenancy string `mapstructure:"tenancy" required:"false"`
// The name of the temporary key pair to
// generate. By default, Packer generates a name that looks like
// `packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
@@ -631,6 +637,13 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if c.Tenancy != "" &&
c.Tenancy != "default" &&
c.Tenancy != "dedicated" &&
c.Tenancy != "host" {
errs = append(errs, fmt.Errorf("Error: Unknown tenancy type %s", c.Tenancy))
}
return errs
}
+20
View File
@@ -232,3 +232,23 @@ func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
t.Fatal("keypair name does not match")
}
}
func TestRunConfigPrepare_TenancyBad(t *testing.T) {
c := testConfig()
c.Tenancy = "not_real"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatal("Should error if tenancy is set to an invalid type")
}
}
func TestRunConfigPrepare_TenancyGood(t *testing.T) {
validTenancy := []string{"", "default", "dedicated", "host"}
for _, vt := range validTenancy {
c := testConfig()
c.Tenancy = vt
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("Should not error if tenancy is set to %s", vt)
}
}
}
@@ -33,6 +33,7 @@ type StepRunSourceInstance struct {
IsRestricted bool
SourceAMI string
Tags map[string]string
Tenancy string
UserData string
UserDataFile string
VolumeTags map[string]string
@@ -195,6 +196,10 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
}
if s.Tenancy != "" {
runOpts.Placement.Tenancy = aws.String(s.Tenancy)
}
var runResp *ec2.Reservation
err = retry.Config{
Tries: 11,
+1
View File
@@ -206,6 +206,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
+2
View File
@@ -81,6 +81,7 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -226,6 +227,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
+1
View File
@@ -228,6 +228,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
@@ -103,6 +103,7 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -249,6 +250,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
+1
View File
@@ -209,6 +209,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
@@ -105,6 +105,7 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -229,6 +230,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
+1
View File
@@ -286,6 +286,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
Tags: b.config.RunTags,
Tenancy: b.config.Tenancy,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
}
@@ -81,6 +81,7 @@ type FlatConfig struct {
SpotTag []hcl2template.FlatKeyValue `mapstructure:"spot_tag" required:"false" cty:"spot_tag" hcl:"spot_tag"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter" hcl:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
Tenancy *string `mapstructure:"tenancy" required:"false" cty:"tenancy" hcl:"tenancy"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs" hcl:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data" hcl:"user_data"`
@@ -232,6 +233,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"spot_tag": &hcldec.BlockListSpec{TypeName: "spot_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"tenancy": &hcldec.AttrSpec{Name: "tenancy", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
@@ -1,4 +1,4 @@
ERROR: -> DeploymentFailed : At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.
ERROR: -> BadRequest
ERROR: -> InvalidRequestFormat : Cannot parse the request.
ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.
ERROR: -> DeploymentFailed : At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.
ERROR: -> BadRequest
ERROR: -> InvalidRequestFormat : Cannot parse the request.
ERROR: -> InvalidJson : Error converting value "playground" to type 'Microsoft.WindowsAzure.Networking.Nrp.Frontend.Contract.Csm.Public.IpAllocationMethod'. Path 'properties.publicIPAllocationMethod', line 1, position 130.
@@ -1 +1 @@
ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found.
ERROR: -> ResourceNotFound : The Resource 'Microsoft.Compute/images/PackerUbuntuImage' under resource group 'packer-test00' was not found.
+1 -4
View File
@@ -6,7 +6,6 @@ package arm
// * ARM_CLIENT_ID
// * ARM_CLIENT_SECRET
// * ARM_SUBSCRIPTION_ID
// * ARM_OBJECT_ID
// * ARM_STORAGE_ACCOUNT
//
// The subscription in question should have a resource group
@@ -151,7 +150,7 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
"build_resource_group_name" : "packer-acceptance-test",
"managed_image_resource_group_name": "packer-acceptance-test",
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroup-{{timestamp}}",
"os_type": "Windows",
"image_publisher": "MicrosoftWindowsServer",
@@ -262,7 +261,6 @@ const testBuilderAccBlobWindows = `
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
"object_id": "{{env ` + "`ARM_OBJECT_ID`" + `}}",
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
},
"builders": [{
@@ -271,7 +269,6 @@ const testBuilderAccBlobWindows = `
"client_id": "{{user ` + "`client_id`" + `}}",
"client_secret": "{{user ` + "`client_secret`" + `}}",
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
"object_id": "{{user ` + "`object_id`" + `}}",
"storage_account": "{{user ` + "`storage_account`" + `}}",
"resource_group_name": "packer-acceptance-test",
@@ -16,8 +16,9 @@ type SharedImageGalleryDestination struct {
ImageName string `mapstructure:"image_name" required:"true"`
ImageVersion string `mapstructure:"image_version" required:"true"`
TargetRegions []TargetRegion `mapstructure:"target_regions"`
ExcludeFromLatest bool `mapstructure:"exlude_from_latest"`
TargetRegions []TargetRegion `mapstructure:"target_regions"`
ExcludeFromLatest bool `mapstructure:"exclude_from_latest"`
ExcludeFromLatestTypo bool `mapstructure:"exlude_from_latest" undocumented:"true"`
}
// TargetRegion describes a region where the shared image should be replicated
@@ -61,5 +62,10 @@ func (sigd *SharedImageGalleryDestination) Validate(prefix string) (errs []error
warns = append(warns,
fmt.Sprintf("%s.target_regions is empty; image will only be available in the region of the gallery", prefix))
}
if sigd.ExcludeFromLatestTypo == true && sigd.ExcludeFromLatest == false {
warns = append(warns,
fmt.Sprintf("%s.exlude_from_latest is being deprecated, please use exclude_from_latest", prefix))
sigd.ExcludeFromLatest = sigd.ExcludeFromLatestTypo
}
return
}
@@ -9,12 +9,13 @@ import (
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatSharedImageGalleryDestination struct {
ResourceGroup *string `mapstructure:"resource_group" required:"true" cty:"resource_group" hcl:"resource_group"`
GalleryName *string `mapstructure:"gallery_name" required:"true" cty:"gallery_name" hcl:"gallery_name"`
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
ImageVersion *string `mapstructure:"image_version" required:"true" cty:"image_version" hcl:"image_version"`
TargetRegions []FlatTargetRegion `mapstructure:"target_regions" cty:"target_regions" hcl:"target_regions"`
ExcludeFromLatest *bool `mapstructure:"exlude_from_latest" cty:"exlude_from_latest" hcl:"exlude_from_latest"`
ResourceGroup *string `mapstructure:"resource_group" required:"true" cty:"resource_group" hcl:"resource_group"`
GalleryName *string `mapstructure:"gallery_name" required:"true" cty:"gallery_name" hcl:"gallery_name"`
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
ImageVersion *string `mapstructure:"image_version" required:"true" cty:"image_version" hcl:"image_version"`
TargetRegions []FlatTargetRegion `mapstructure:"target_regions" cty:"target_regions" hcl:"target_regions"`
ExcludeFromLatest *bool `mapstructure:"exclude_from_latest" cty:"exclude_from_latest" hcl:"exclude_from_latest"`
ExcludeFromLatestTypo *bool `mapstructure:"exlude_from_latest" undocumented:"true" cty:"exlude_from_latest" hcl:"exlude_from_latest"`
}
// FlatMapstructure returns a new FlatSharedImageGalleryDestination.
@@ -29,12 +30,13 @@ func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() m
// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination.
func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
"target_regions": &hcldec.BlockListSpec{TypeName: "target_regions", Nested: hcldec.ObjectSpec((*FlatTargetRegion)(nil).HCL2Spec())},
"exlude_from_latest": &hcldec.AttrSpec{Name: "exlude_from_latest", Type: cty.Bool, Required: false},
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
"target_regions": &hcldec.BlockListSpec{TypeName: "target_regions", Nested: hcldec.ObjectSpec((*FlatTargetRegion)(nil).HCL2Spec())},
"exclude_from_latest": &hcldec.AttrSpec{Name: "exclude_from_latest", Type: cty.Bool, Required: false},
"exlude_from_latest": &hcldec.AttrSpec{Name: "exlude_from_latest", Type: cty.Bool, Required: false},
}
return s
}
@@ -21,12 +21,13 @@ func TestSharedImageGalleryDestination_ResourceID(t *testing.T) {
func TestSharedImageGalleryDestination_Validate(t *testing.T) {
type fields struct {
ResourceGroup string
GalleryName string
ImageName string
ImageVersion string
TargetRegions []TargetRegion
ExcludeFromLatest bool
ResourceGroup string
GalleryName string
ImageName string
ImageVersion string
TargetRegions []TargetRegion
ExcludeFromLatest bool
ExcludeFromLatestTypo bool
}
tests := []struct {
name string
@@ -66,6 +67,29 @@ func TestSharedImageGalleryDestination_Validate(t *testing.T) {
},
wantWarns: []string{"sigdest.target_regions is empty; image will only be available in the region of the gallery"},
},
{
name: "warn if using exlude_from_latest",
fields: fields{
ResourceGroup: "ResourceGroup",
GalleryName: "GalleryName",
ImageName: "ImageName",
ImageVersion: "0.1.2",
TargetRegions: []TargetRegion{
TargetRegion{
Name: "region1",
ReplicaCount: 5,
StorageAccountType: "Standard_ZRS",
},
TargetRegion{
Name: "region2",
ReplicaCount: 3,
StorageAccountType: "Standard_LRS",
},
},
ExcludeFromLatestTypo: true,
},
wantWarns: []string{"sigdest.exlude_from_latest is being deprecated, please use exclude_from_latest"},
},
{
name: "version format",
wantErrs: []string{
@@ -105,12 +129,13 @@ func TestSharedImageGalleryDestination_Validate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sigd := &SharedImageGalleryDestination{
ResourceGroup: tt.fields.ResourceGroup,
GalleryName: tt.fields.GalleryName,
ImageName: tt.fields.ImageName,
ImageVersion: tt.fields.ImageVersion,
TargetRegions: tt.fields.TargetRegions,
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
ResourceGroup: tt.fields.ResourceGroup,
GalleryName: tt.fields.GalleryName,
ImageName: tt.fields.ImageName,
ImageVersion: tt.fields.ImageVersion,
TargetRegions: tt.fields.TargetRegions,
ExcludeFromLatest: tt.fields.ExcludeFromLatest,
ExcludeFromLatestTypo: tt.fields.ExcludeFromLatestTypo,
}
gotErrs, gotWarns := sigd.Validate("sigdest")
+14 -11
View File
@@ -30,19 +30,22 @@ func (a *ImportArtifact) Id() string {
}
func (a *ImportArtifact) String() string {
tags := a.StateData["docker_tags"]
if tags == nil {
return fmt.Sprintf("Imported Docker image: %s", a.Id())
}
cast := tags.([]interface{})
names := []string{}
for _, name := range cast {
if n, ok := name.(string); ok {
names = append(names, n)
var tags []string
switch t := a.StateData["docker_tags"].(type) {
case []string:
tags = t
case []interface{}:
for _, name := range t {
if n, ok := name.(string); ok {
tags = append(tags, n)
}
}
}
return fmt.Sprintf("Imported Docker image: %s with tags %s",
a.Id(), strings.Join(names, " "))
if len(tags) > 0 {
return fmt.Sprintf("Imported Docker image: %s with tags %s",
a.Id(), strings.Join(tags, " "))
}
return fmt.Sprintf("Imported Docker image: %s", a.Id())
}
func (a *ImportArtifact) State(name string) interface{} {
+35 -22
View File
@@ -9,26 +9,39 @@ import (
"golang.org/x/oauth2/jwt"
)
func ProcessAccountFile(text string) (*jwt.Config, error) {
// Assume text is a JSON string
conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...)
if err != nil {
// If text was not JSON, assume it is a file path instead
if _, err := os.Stat(text); os.IsNotExist(err) {
return nil, fmt.Errorf(
"account_file path does not exist: %s",
text)
}
data, err := ioutil.ReadFile(text)
if err != nil {
return nil, fmt.Errorf(
"Error reading account_file from path '%s': %s",
text, err)
}
conf, err = google.JWTConfigFromJSON(data, DriverScopes...)
if err != nil {
return nil, fmt.Errorf("Error parsing account_file: %s", err)
}
}
return conf, nil
type ServiceAccount struct {
jsonKey []byte
jwt *jwt.Config
}
// ProcessAccountFile will return a ServiceAccount for the JSON account file stored in text.
// Otherwise it will return an error if text does not look or reference a valid account file.
func ProcessAccountFile(text string) (*ServiceAccount, error) {
// Assume text is a JSON string
if conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...); err == nil {
return &ServiceAccount{
jsonKey: []byte(text),
jwt: conf,
}, nil
}
// If text was not JSON, assume it is a file path instead
if _, err := os.Stat(text); os.IsNotExist(err) {
return nil, fmt.Errorf("account_file path does not exist: %s", text)
}
data, err := ioutil.ReadFile(text)
if err != nil {
return nil, fmt.Errorf("Error reading account_file from path '%s': %s", text, err)
}
conf, err := google.JWTConfigFromJSON(data, DriverScopes...)
if err != nil {
return nil, fmt.Errorf("Error parsing account_file: %s", err)
}
return &ServiceAccount{
jsonKey: data,
jwt: conf,
}, nil
}
+14 -6
View File
@@ -36,8 +36,15 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
// Run executes a googlecompute Packer build and returns a packer.Artifact
// representing a GCE machine image.
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
driver, err := NewDriverGCE(
ui, b.config.ProjectId, b.config.account, b.config.VaultGCPOauthEngine)
cfg := GCEDriverConfig{
Ui: ui,
ProjectId: b.config.ProjectId,
Account: b.config.account,
ImpersonateServiceAccountName: b.config.ImpersonateServiceAccount,
VaultOauthEngineName: b.config.VaultGCPOauthEngine,
}
driver, err := NewDriverGCE(cfg)
if err != nil {
return nil, err
}
@@ -70,10 +77,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug,
},
&StepStartTunnel{
IAPConf: &b.config.IAPConfig,
CommConf: &b.config.Comm,
AccountFile: b.config.AccountFile,
ProjectId: b.config.ProjectId,
IAPConf: &b.config.IAPConfig,
CommConf: &b.config.Comm,
AccountFile: b.config.AccountFile,
ImpersonateAccount: b.config.ImpersonateServiceAccount,
ProjectId: b.config.ProjectId,
},
&communicator.StepConnect{
Config: &b.config.Comm,
+8 -5
View File
@@ -17,7 +17,6 @@ import (
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"golang.org/x/oauth2/jwt"
compute "google.golang.org/api/compute/v1"
)
@@ -35,6 +34,8 @@ type Config struct {
// run Packer on a GCE instance with a service account. Instructions for
// creating the file or using service accounts are above.
AccountFile string `mapstructure:"account_file" required:"false"`
// This allows service account impersonation as per the [docs](https://cloud.google.com/iam/docs/impersonating-service-accounts).
ImpersonateServiceAccount string `mapstructure:"impersonate_service_account" required:"false"`
// The project ID that will be used to launch instances and store images.
ProjectId string `mapstructure:"project_id" required:"true"`
// Full or partial URL of the guest accelerator type. GPU accelerators can
@@ -75,6 +76,8 @@ type Config struct {
EnableIntegrityMonitoring bool `mapstructure:"enable_integrity_monitoring" required:"false"`
// Whether to use an IAP proxy.
IAPConfig `mapstructure:",squash"`
// Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"`
// The unique name of the resulting image. Defaults to
// `packer-{{timestamp}}`.
ImageName string `mapstructure:"image_name" required:"false"`
@@ -266,7 +269,7 @@ type Config struct {
UseOSLogin bool `mapstructure:"use_os_login" required:"false"`
// Can be set instead of account_file. If set, this builder will use
// HashiCorp Vault to generate an Oauth token for authenticating against
// Google's cloud. The value should be the path of the token generator
// Google Cloud. The value should be the path of the token generator
// within vault.
// For information on how to configure your Vault + GCP engine to produce
// Oauth tokens, see https://www.vaultproject.io/docs/auth/gcp
@@ -280,7 +283,7 @@ type Config struct {
// Example: "us-central1-a"
Zone string `mapstructure:"zone" required:"true"`
account *jwt.Config
account *ServiceAccount
imageAlreadyExists bool
ctx interpolate.Context
}
@@ -481,9 +484,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
// Authenticating via an account file
if c.AccountFile != "" {
if c.VaultGCPOauthEngine != "" {
if c.VaultGCPOauthEngine != "" && c.ImpersonateServiceAccount != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You cannot "+
"specify both account_file and vault_gcp_oauth_engine."))
"specify impersonate_service_account, account_file and vault_gcp_oauth_engine at the same time"))
}
cfg, err := ProcessAccountFile(c.AccountFile)
if err != nil {
+4
View File
@@ -64,6 +64,7 @@ type FlatConfig struct {
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
AccountFile *string `mapstructure:"account_file" required:"false" cty:"account_file" hcl:"account_file"`
ImpersonateServiceAccount *string `mapstructure:"impersonate_service_account" required:"false" cty:"impersonate_service_account" hcl:"impersonate_service_account"`
ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
AcceleratorType *string `mapstructure:"accelerator_type" required:"false" cty:"accelerator_type" hcl:"accelerator_type"`
AcceleratorCount *int64 `mapstructure:"accelerator_count" required:"false" cty:"accelerator_count" hcl:"accelerator_count"`
@@ -80,6 +81,7 @@ type FlatConfig struct {
IAPHashBang *string `mapstructure:"iap_hashbang" required:"false" cty:"iap_hashbang" hcl:"iap_hashbang"`
IAPExt *string `mapstructure:"iap_ext" required:"false" cty:"iap_ext" hcl:"iap_ext"`
IAPTunnelLaunchWait *int `mapstructure:"iap_tunnel_launch_wait" required:"false" cty:"iap_tunnel_launch_wait" hcl:"iap_tunnel_launch_wait"`
SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"`
ImageName *string `mapstructure:"image_name" required:"false" cty:"image_name" hcl:"image_name"`
ImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description" hcl:"image_description"`
ImageEncryptionKey *FlatCustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false" cty:"image_encryption_key" hcl:"image_encryption_key"`
@@ -182,6 +184,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false},
"impersonate_service_account": &hcldec.AttrSpec{Name: "impersonate_service_account", Type: cty.String, Required: false},
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"accelerator_type": &hcldec.AttrSpec{Name: "accelerator_type", Type: cty.String, Required: false},
"accelerator_count": &hcldec.AttrSpec{Name: "accelerator_count", Type: cty.Number, Required: false},
@@ -198,6 +201,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"iap_hashbang": &hcldec.AttrSpec{Name: "iap_hashbang", Type: cty.String, Required: false},
"iap_ext": &hcldec.AttrSpec{Name: "iap_ext", Type: cty.String, Required: false},
"iap_tunnel_launch_wait": &hcldec.AttrSpec{Name: "iap_tunnel_launch_wait", Type: cty.Number, Required: false},
"skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
"image_encryption_key": &hcldec.BlockSpec{TypeName: "image_encryption_key", Nested: hcldec.ObjectSpec((*FlatCustomerEncryptionKey)(nil).HCL2Spec())},
+30 -20
View File
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"
@@ -25,7 +24,6 @@ import (
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
)
// driverGCE is a Driver implementation that actually talks to GCE.
@@ -37,6 +35,14 @@ type driverGCE struct {
ui packer.Ui
}
type GCEDriverConfig struct {
Ui packer.Ui
ProjectId string
Account *ServiceAccount
ImpersonateServiceAccountName string
VaultOauthEngineName string
}
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
// Define a TokenSource that gets tokens from Vault
@@ -72,31 +78,34 @@ func (ots OauthTokenSource) Token() (*oauth2.Token, error) {
}
func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersonatesa string) (option.ClientOption, error) {
var err error
var client *http.Client
var opts option.ClientOption
if vaultOauth != "" {
// Auth with Vault Oauth
log.Printf("Using Vault to generate Oauth token.")
ts := OauthTokenSource{vaultOauth}
return oauth2.NewClient(context.TODO(), ts), nil
opts = option.WithTokenSource(ts)
} else if conf != nil && len(conf.PrivateKey) > 0 {
} else if impersonatesa != "" {
opts = option.ImpersonateCredentials(impersonatesa)
} else if account != nil && account.jwt != nil && len(account.jwt.PrivateKey) > 0 {
// Auth with AccountFile if provided
log.Printf("[INFO] Requesting Google token via account_file...")
log.Printf("[INFO] -- Email: %s", conf.Email)
log.Printf("[INFO] -- Email: %s", account.jwt.Email)
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(conf.PrivateKey))
log.Printf("[INFO] -- Private Key Length: %d", len(account.jwt.PrivateKey))
// Initiate an http.Client. The following GET request will be
// authorized and authenticated on the behalf of
// your service account.
client = conf.Client(context.TODO())
opts = option.WithCredentialsJSON(account.jsonKey)
} else {
log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...")
client, err = google.DefaultClient(context.TODO(), DriverScopes...)
ts, err := google.DefaultTokenSource(context.TODO(), "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
return nil, err
}
opts = option.WithTokenSource(ts)
// The DefaultClient uses the DefaultTokenSource of the google lib.
// The DefaultTokenSource uses the "Application Default Credentials"
// It looks for credentials in the following places, preferring the first location found:
@@ -115,22 +124,23 @@ func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
return nil, err
}
return client, nil
return opts, nil
}
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (Driver, error) {
client, err := NewClientGCE(conf, vaultOauth)
func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName)
if err != nil {
return nil, err
}
log.Printf("[INFO] Instantiating GCE client...")
service, err := compute.NewService(context.TODO(), option.WithHTTPClient(client))
service, err := compute.NewService(context.TODO(), opts)
if err != nil {
return nil, err
}
osLoginService, err := oslogin.NewService(context.TODO(), option.WithHTTPClient(client))
log.Printf("[INFO] Instantiating OS Login client...")
osLoginService, err := oslogin.NewService(context.TODO(), opts)
if err != nil {
return nil, err
}
@@ -139,10 +149,10 @@ func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (
service.UserAgent = useragent.String()
return &driverGCE{
projectId: p,
projectId: config.ProjectId,
service: service,
osLoginService: osLoginService,
ui: ui,
ui: config.Ui,
}, nil
}
@@ -23,6 +23,11 @@ func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if config.SkipCreateImage {
ui.Say("Skipping image creation...")
return multistep.ActionContinue
}
if config.PackerForce && config.imageAlreadyExists {
ui.Say("Deleting previous image...")
@@ -56,7 +56,7 @@ func (s *StepCreateWindowsPassword) Run(ctx context.Context, state multistep.Sta
email := ""
if c.account != nil {
email = c.account.Email
email = c.account.jwt.Email
}
data := WindowsPasswordConfig{
@@ -47,7 +47,7 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
state.Put("ssh_key_public_sha256", hex.EncodeToString(sha256sum[:]))
if config.account != nil {
s.accountEmail = config.account.Email
s.accountEmail = config.account.jwt.Email
}
if s.accountEmail == "" {
+9 -4
View File
@@ -131,10 +131,11 @@ func (e RetryableTunnelError) Error() string {
}
type StepStartTunnel struct {
IAPConf *IAPConfig
CommConf *communicator.Config
AccountFile string
ProjectId string
IAPConf *IAPConfig
CommConf *communicator.Config
AccountFile string
ImpersonateAccount string
ProjectId string
tunnelDriver TunnelDriver
}
@@ -276,6 +277,10 @@ func (s *StepStartTunnel) Run(ctx context.Context, state multistep.StateBag) mul
"--zone", c.Zone, "--project", s.ProjectId,
}
if s.ImpersonateAccount != "" {
args = append(args, fmt.Sprintf("--impersonate-service-account='%s'", s.ImpersonateAccount))
}
// This is the port the IAP tunnel listens on, on localhost.
// TODO make setting LocalHostPort optional
err = ApplyIAPTunnel(s.CommConf, s.IAPConf.IAPLocalhostPort)
+13 -6
View File
@@ -252,14 +252,21 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
hasOnlySourceImage := len(c.SourceImage) > 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0
hasOnlySourceImageName := len(c.SourceImageName) > 0 && len(c.SourceImage) == 0 && len(c.ExternalSourceImageURL) == 0
hasOnlyExternalSourceImageURL := len(c.ExternalSourceImageURL) > 0 && len(c.SourceImage) == 0 && len(c.SourceImageName) == 0
if c.SourceImage == "" && c.SourceImageName == "" && c.ExternalSourceImageURL == "" && c.SourceImageFilters.Filters.Empty() {
errs = append(errs, errors.New("Either a source_image, a source_image_name, an external_source_image_url or source_image_filter must be specified"))
} else if !(hasOnlySourceImage || hasOnlySourceImageName || hasOnlyExternalSourceImageURL) {
errs = append(errs, errors.New("Only a source_image, a source_image_name or an external_source_image_url can be specified, not multiple."))
} else {
// Make sure we've only set one image source option
thereCanBeOnlyOne := []bool{len(c.SourceImageName) > 0, len(c.SourceImage) > 0, len(c.ExternalSourceImageURL) > 0, !c.SourceImageFilters.Filters.Empty()}
numSet := 0
for _, val := range thereCanBeOnlyOne {
if val {
numSet += 1
}
}
if numSet > 1 {
errs = append(errs, errors.New("Only one of the options source_image, source_image_name, external_source_image_url, or source_image_filter can be specified, not multiple."))
}
}
// if external_source_image_format is not set use qcow2 as default
+24
View File
@@ -141,6 +141,7 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
t.Fatalf("err: %s", err)
}
c = testRunConfig()
// test setting both ExternalSourceImageURL and SourceImageName causes an error
c.SourceImage = ""
c.SourceImageName = "abcd"
@@ -149,6 +150,7 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
t.Fatalf("err: %s", err)
}
c = testRunConfig()
// test neither setting SourceImage, SourceImageName or ExternalSourceImageURL causes an error
c.SourceImage = ""
c.SourceImageName = ""
@@ -157,6 +159,7 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
t.Fatalf("err: %s", err)
}
c = testRunConfig()
// test setting only ExternalSourceImageURL passes
c.SourceImage = ""
c.SourceImageName = ""
@@ -174,6 +177,27 @@ func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
if matches, _ := regexp.MatchString(p, c.SourceImageName); !matches {
t.Fatalf("invalid format for SourceImageName: %s", c.SourceImageName)
}
c = testRunConfig()
// test setting a filter passes
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = ""
c.SourceImageFilters = ImageFilter{
Filters: ImageFilterOptions{
Name: "Ubuntu 16.04",
Visibility: "public",
Owner: "1234567890",
Tags: []string{"prod", "ready"},
Properties: map[string]string{"os_distro": "ubuntu", "os_version": "16.04"},
},
MostRecent: true,
}
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("Should not error if everything but filter is empty: %s", err)
}
}
// This test case confirms that only allowed fields will be set to values
+40 -6
View File
@@ -1,4 +1,4 @@
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
package oci
@@ -35,6 +35,16 @@ type CreateVNICDetails struct {
SubnetId *string `mapstructure:"subnet_id" required:"false"`
}
type ListImagesRequest struct {
// fields that can be specified under "base_image_filter"
CompartmentId *string `mapstructure:"compartment_id"`
DisplayName *string `mapstructure:"display_name"`
DisplayNameSearch *string `mapstructure:"display_name_search"`
OperatingSystem *string `mapstructure:"operating_system"`
OperatingSystemVersion *string `mapstructure:"operating_system_version"`
Shape *string `mapstructure:"shape"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
@@ -69,14 +79,17 @@ type Config struct {
CompartmentID string `mapstructure:"compartment_ocid"`
// Image
BaseImageID string `mapstructure:"base_image_ocid"`
ImageName string `mapstructure:"image_name"`
BaseImageID string `mapstructure:"base_image_ocid"`
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
ImageName string `mapstructure:"image_name"`
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
// Instance
InstanceName string `mapstructure:"instance_name"`
InstanceName *string `mapstructure:"instance_name"`
InstanceTags map[string]string `mapstructure:"instance_tags"`
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
Shape string `mapstructure:"shape"`
BootVolumeSizeInGBs int64 `mapstructure:"disk_size"`
// Metadata optionally contains custom metadata key/value pairs provided in the
// configuration. While this can be used to set metadata["user_data"] the explicit
@@ -253,6 +266,10 @@ func (c *Config) Prepare(raws ...interface{}) error {
c.CompartmentID = tenancyOCID
}
if c.ImageCompartmentID == "" {
c.ImageCompartmentID = c.CompartmentID
}
if c.Shape == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("'shape' must be specified"))
@@ -263,9 +280,17 @@ func (c *Config) Prepare(raws ...interface{}) error {
errs, errors.New("'subnet_ocid' must be specified"))
}
if c.BaseImageID == "" {
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
errs = packer.MultiErrorAppend(
errs, errors.New("'base_image_ocid' must be specified"))
errs, errors.New("'base_image_ocid' or 'base_image_filter' must be specified"))
}
if c.BaseImageFilter.CompartmentId == nil {
c.BaseImageFilter.CompartmentId = &c.CompartmentID
}
if c.BaseImageFilter.Shape == nil {
c.BaseImageFilter.Shape = &c.Shape
}
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
@@ -326,6 +351,15 @@ func (c *Config) Prepare(raws ...interface{}) error {
}
}
// Set default boot volume size to 50 if not set
// Check if size set is allowed by OCI
if c.BootVolumeSizeInGBs == 0 {
c.BootVolumeSizeInGBs = 50
} else if c.BootVolumeSizeInGBs < 50 || c.BootVolumeSizeInGBs > 16384 {
errs = packer.MultiErrorAppend(
errs, errors.New("'disk_size' must be between 50 and 16384 GBs"))
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
+40 -1
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
package oci
import (
@@ -76,11 +76,14 @@ type FlatConfig struct {
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
BaseImageFilter *FlatListImagesRequest `mapstructure:"base_image_filter" cty:"base_image_filter" hcl:"base_image_filter"`
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
ImageCompartmentID *string `mapstructure:"image_compartment_ocid" cty:"image_compartment_ocid" hcl:"image_compartment_ocid"`
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
InstanceTags map[string]string `mapstructure:"instance_tags" cty:"instance_tags" hcl:"instance_tags"`
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags" cty:"instance_defined_tags" hcl:"instance_defined_tags"`
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
BootVolumeSizeInGBs *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
@@ -169,11 +172,14 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
"base_image_filter": &hcldec.BlockSpec{TypeName: "base_image_filter", Nested: hcldec.ObjectSpec((*FlatListImagesRequest)(nil).HCL2Spec())},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_compartment_ocid": &hcldec.AttrSpec{Name: "image_compartment_ocid", Type: cty.String, Required: false},
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
"instance_tags": &hcldec.AttrSpec{Name: "instance_tags", Type: cty.Map(cty.String), Required: false},
"instance_defined_tags": &hcldec.AttrSpec{Name: "instance_defined_tags", Type: cty.Map(cty.String), Required: false},
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
@@ -223,3 +229,36 @@ func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
}
return s
}
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatListImagesRequest struct {
CompartmentId *string `mapstructure:"compartment_id" cty:"compartment_id" hcl:"compartment_id"`
DisplayName *string `mapstructure:"display_name" cty:"display_name" hcl:"display_name"`
DisplayNameSearch *string `mapstructure:"display_name_search" cty:"display_name_search" hcl:"display_name_search"`
OperatingSystem *string `mapstructure:"operating_system" cty:"operating_system" hcl:"operating_system"`
OperatingSystemVersion *string `mapstructure:"operating_system_version" cty:"operating_system_version" hcl:"operating_system_version"`
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
}
// FlatMapstructure returns a new FlatListImagesRequest.
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ListImagesRequest) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatListImagesRequest)
}
// HCL2Spec returns the hcl spec of a ListImagesRequest.
// This spec is used by HCL to read the fields of ListImagesRequest.
// The decoded values from this spec will then be applied to a FlatListImagesRequest.
func (*FlatListImagesRequest) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"compartment_id": &hcldec.AttrSpec{Name: "compartment_id", Type: cty.String, Required: false},
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
"display_name_search": &hcldec.AttrSpec{Name: "display_name_search", Type: cty.String, Required: false},
"operating_system": &hcldec.AttrSpec{Name: "operating_system", Type: cty.String, Required: false},
"operating_system_version": &hcldec.AttrSpec{Name: "operating_system_version", Type: cty.String, Required: false},
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
}
return s
}
+32 -1
View File
@@ -44,7 +44,8 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
"create_vnic_details": map[string]interface{}{
"nsg_ids": []string{"ocd1..."},
},
"shape": "VM.Standard1.1",
"shape": "VM.Standard1.1",
"disk_size": 60,
}
}
@@ -87,6 +88,36 @@ func TestConfig(t *testing.T) {
}
})
t.Run("BaseImageFilterWithoutOCID", func(t *testing.T) {
raw := testConfig(cfgFile)
raw["base_image_ocid"] = ""
raw["base_image_filter"] = map[string]interface{}{
"display_name": "hello_world",
}
var c Config
errs := c.Prepare(raw)
if errs != nil {
t.Fatalf("Unexpected error in configuration %+v", errs)
}
})
t.Run("BaseImageFilterDefault", func(t *testing.T) {
raw := testConfig(cfgFile)
var c Config
errs := c.Prepare(raw)
if errs != nil {
t.Fatalf("Unexpected error in configuration %+v", errs)
}
if *c.BaseImageFilter.Shape != raw["shape"] {
t.Fatalf("Default base_image_filter shape %v does not equal config shape %v",
*c.BaseImageFilter.Shape, raw["shape"])
}
})
t.Run("NoAccessConfig", func(t *testing.T) {
raw := testConfig(cfgFile)
delete(raw, "access_cfg_file")
+65 -19
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"regexp"
"time"
core "github.com/oracle/oci-go-sdk/core"
@@ -51,23 +52,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
metadata["user_data"] = d.cfg.UserData
}
instanceDetails := core.LaunchInstanceDetails{
AvailabilityDomain: &d.cfg.AvailabilityDomain,
CompartmentId: &d.cfg.CompartmentID,
DefinedTags: d.cfg.InstanceDefinedTags,
FreeformTags: d.cfg.InstanceTags,
ImageId: &d.cfg.BaseImageID,
Shape: &d.cfg.Shape,
SubnetId: &d.cfg.SubnetID,
Metadata: metadata,
}
// When empty, the default display name is used.
if d.cfg.InstanceName != "" {
instanceDetails.DisplayName = &d.cfg.InstanceName
}
// Pass VNIC details, if specified, to the instance
// Create VNIC details for instance
CreateVnicDetails := core.CreateVnicDetails{
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
@@ -80,7 +65,68 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
}
instanceDetails.CreateVnicDetails = &CreateVnicDetails
// Determine base image ID
var imageId *string
if d.cfg.BaseImageID != "" {
imageId = &d.cfg.BaseImageID
} else {
// Pull images and determine which image ID to use, if BaseImageId not specified
response, err := d.computeClient.ListImages(ctx, core.ListImagesRequest{
CompartmentId: d.cfg.BaseImageFilter.CompartmentId,
DisplayName: d.cfg.BaseImageFilter.DisplayName,
OperatingSystem: d.cfg.BaseImageFilter.OperatingSystem,
OperatingSystemVersion: d.cfg.BaseImageFilter.OperatingSystemVersion,
Shape: d.cfg.BaseImageFilter.Shape,
LifecycleState: "AVAILABLE",
SortBy: "TIMECREATED",
SortOrder: "DESC",
})
if err != nil {
return "", err
}
if len(response.Items) == 0 {
return "", errors.New("base_image_filter returned no images")
}
if d.cfg.BaseImageFilter.DisplayNameSearch != nil {
// Return most recent image that matches regex
imageNameRegex, err := regexp.Compile(*d.cfg.BaseImageFilter.DisplayNameSearch)
if err != nil {
return "", err
}
for _, image := range response.Items {
if imageNameRegex.MatchString(*image.DisplayName) {
imageId = image.Id
break
}
}
if imageId == nil {
return "", errors.New("No image matched display_name_search criteria")
}
} else {
// If no regex provided, simply return most recent image pulled
imageId = response.Items[0].Id
}
}
// Create Source details which will be used to Launch Instance
InstanceSourceDetails := core.InstanceSourceViaImageDetails{
ImageId: imageId,
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
}
// Build instance details
instanceDetails := core.LaunchInstanceDetails{
AvailabilityDomain: &d.cfg.AvailabilityDomain,
CompartmentId: &d.cfg.CompartmentID,
CreateVnicDetails: &CreateVnicDetails,
DefinedTags: d.cfg.InstanceDefinedTags,
DisplayName: d.cfg.InstanceName,
FreeformTags: d.cfg.InstanceTags,
Shape: &d.cfg.Shape,
SourceDetails: InstanceSourceDetails,
SubnetId: &d.cfg.SubnetID,
Metadata: metadata,
}
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
@@ -94,7 +140,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
// CreateImage creates a new custom image.
func (d *driverOCI) CreateImage(ctx context.Context, id string) (core.Image, error) {
res, err := d.computeClient.CreateImage(ctx, core.CreateImageRequest{CreateImageDetails: core.CreateImageDetails{
CompartmentId: &d.cfg.CompartmentID,
CompartmentId: &d.cfg.ImageCompartmentID,
InstanceId: &id,
DisplayName: &d.cfg.ImageName,
FreeformTags: d.cfg.Tags,
+11 -22
View File
@@ -9,9 +9,7 @@ package bsu
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/hashicorp/hcl/v2/hcldec"
osccommon "github.com/hashicorp/packer/builder/osc/common"
@@ -21,7 +19,6 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
// The unique ID for this builder
@@ -86,24 +83,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
oapiconn := oapi.NewClient(clientConfig, skipClient)
oscConn := b.config.NewOSCClient()
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("osc", oscConn)
state.Put("accessConfig", &b.config.AccessConfig)
state.Put("hook", hook)
state.Put("ui", ui)
@@ -129,7 +115,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&osccommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
DebugKeyPath: fmt.Sprintf("oapi_%s", b.config.PackerBuildName),
DebugKeyPath: fmt.Sprintf("osc_%s", b.config.PackerBuildName),
},
&osccommon.StepPublicIp{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
@@ -161,6 +147,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
RawRegion: b.config.RawRegion,
},
&osccommon.StepGetPassword{
Debug: b.config.PackerDebug,
@@ -170,8 +157,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.SSHHost(
oapiconn,
Host: osccommon.OscSSHHost(
oscConn.VmApi,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
@@ -190,10 +177,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
OMIName: b.config.OMIName,
Regions: b.config.OMIRegions,
},
&stepCreateOMI{},
&stepCreateOMI{
RawRegion: b.config.RawRegion,
},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
SnapshotAccountIds: b.config.SnapshotAccountIDs,
RawRegion: b.config.RawRegion,
Ctx: b.config.ctx,
},
&osccommon.StepCreateTags{
@@ -217,7 +207,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact := &osccommon.Artifact{
Omis: omis.(map[string]string),
BuilderIdValue: BuilderId,
Config: clientConfig,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
+4 -22
View File
@@ -2,13 +2,9 @@
package bsu
import (
"crypto/tls"
"net/http"
"testing"
"github.com/hashicorp/packer/builder/osc/common"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/outscale/osc-go/oapi"
)
func TestBuilderAcc_basic(t *testing.T) {
@@ -23,31 +19,17 @@ func TestBuilderAcc_basic(t *testing.T) {
func testAccPreCheck(t *testing.T) {
}
func testOAPIConn() (*oapi.Client, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
clientConfig, err := access.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
return oapi.NewClient(clientConfig, skipClient), nil
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-65efcc11",
"source_omi": "ami-abe953fa",
"ssh_username": "outscale",
"omi_name": "packer-test {{timestamp}}"
"omi_name": "packer-test",
"associate_public_ip_address": true,
"force_deregister": true
}]
}
`
+37 -27
View File
@@ -5,62 +5,68 @@ import (
"fmt"
"log"
"github.com/antihax/optional"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type stepCreateOMI struct {
image *oapi.Image
image *osc.Image
RawRegion string
}
func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
ui := state.Get("ui").(packer.Ui)
// Create the image
omiName := config.OMIName
ui.Say(fmt.Sprintf("Creating OMI %s from vm %s", omiName, vm.VmId))
createOpts := oapi.CreateImageRequest{
createOpts := osc.CreateImageRequest{
VmId: vm.VmId,
ImageName: omiName,
BlockDeviceMappings: config.BlockDevices.BuildOMIDevices(),
BlockDeviceMappings: config.BlockDevices.BuildOscOMIDevices(),
}
resp, err := oapiconn.POST_CreateImage(createOpts)
if err != nil || resp.OK == nil {
resp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(createOpts),
})
if err != nil || resp.Image.ImageId == "" {
err := fmt.Errorf("Error creating OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := resp.OK.Image
image := resp.Image
// Set the OMI ID in the state
ui.Message(fmt.Sprintf("OMI: %s", image.ImageId))
omis := make(map[string]string)
omis[oapiconn.GetConfig().Region] = image.ImageId
omis[s.RawRegion] = image.ImageId
state.Put("omis", omis)
// Wait for the image to become ready
ui.Say("Waiting for OMI to become ready...")
if err := osccommon.WaitUntilImageAvailable(oapiconn, image.ImageId); err != nil {
if err := osccommon.WaitUntilOscImageAvailable(oscconn, image.ImageId); err != nil {
log.Printf("Error waiting for OMI: %s", err)
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{image.ImageId},
},
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{image.ImageId},
},
}),
})
if err != nil {
log.Printf("Unable to determine reason waiting for OMI failed: %s", err)
err = fmt.Errorf("Unknown error waiting for OMI.")
err = fmt.Errorf("Unknown error waiting for OMI")
} else {
stateReason := imagesResp.OK.Images[0].StateComment
stateReason := imagesResp.Images[0].StateComment
err = fmt.Errorf("Error waiting for OMI. Reason: %s", stateReason)
}
@@ -69,10 +75,12 @@ func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{image.ImageId},
},
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{image.ImageId},
},
}),
})
if err != nil {
err := fmt.Errorf("Error searching for OMI: %s", err)
@@ -80,12 +88,12 @@ func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
ui.Error(err.Error())
return multistep.ActionHalt
}
s.image = &imagesResp.OK.Images[0]
s.image = &imagesResp.Images[0]
snapshots := make(map[string][]string)
for _, blockDeviceMapping := range imagesResp.OK.Images[0].BlockDeviceMappings {
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
if blockDeviceMapping.Bsu.SnapshotId != "" {
snapshots[oapiconn.GetConfig().Region] = append(snapshots[oapiconn.GetConfig().Region], blockDeviceMapping.Bsu.SnapshotId)
snapshots[s.RawRegion] = append(snapshots[s.RawRegion], blockDeviceMapping.Bsu.SnapshotId)
}
}
state.Put("snapshots", snapshots)
@@ -104,12 +112,14 @@ func (s *stepCreateOMI) Cleanup(state multistep.StateBag) {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deregistering the OMI because cancellation or error...")
DeleteOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId}
if _, err := oapiconn.POST_DeleteImage(DeleteOpts); err != nil {
DeleteOpts := osc.DeleteImageRequest{ImageId: s.image.ImageId}
if _, _, err := oscconn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(DeleteOpts),
}); err != nil {
ui.Error(fmt.Sprintf("Error Deleting OMI, may still be around: %s", err))
return
}
+10 -24
View File
@@ -6,10 +6,8 @@ package bsusurrogate
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"github.com/hashicorp/hcl/v2/hcldec"
osccommon "github.com/hashicorp/packer/builder/osc/common"
@@ -19,7 +17,6 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
const BuilderId = "oapi.outscale.bsusurrogate"
@@ -104,31 +101,20 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
oapiconn := oapi.NewClient(clientConfig, skipClient)
oscConn := b.config.NewOSCClient()
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("osc", oscConn)
state.Put("accessConfig", &b.config.AccessConfig)
state.Put("hook", hook)
state.Put("ui", ui)
//VMStep
omiDevices := b.config.BuildOMIDevices()
launchDevices := b.config.BuildLaunchDevices()
omiDevices := b.config.BuildOscOMIDevices()
launchOSCDevices := b.config.BuildOSCLaunchDevices()
steps := []multistep.Step{
&osccommon.StepPreValidate{
@@ -193,8 +179,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.SSHHost(
oapiconn,
Host: osccommon.OscSSHHost(
oscConn.VmApi,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
@@ -207,7 +193,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DisableStopVm: b.config.DisableStopVm,
},
&StepSnapshotVolumes{
LaunchDevices: launchDevices,
LaunchDevices: launchOSCDevices,
},
&osccommon.StepDeregisterOMI{
AccessConfig: &b.config.AccessConfig,
@@ -219,7 +205,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepRegisterOMI{
RootDevice: b.config.RootDevice,
OMIDevices: omiDevices,
LaunchDevices: launchDevices,
LaunchDevices: launchOSCDevices,
RawRegion: b.config.RawRegion,
},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
@@ -247,7 +234,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact := &osccommon.Artifact{
Omis: omis.(map[string]string),
BuilderIdValue: BuilderId,
Config: clientConfig,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
+1 -1
View File
@@ -24,7 +24,7 @@ const testBuilderAccBasic = `
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-65efcc11",
"source_omi": "ami-abe953fa",
"ssh_username": "outscale",
"omi_name": "packer-test {{timestamp}}",
"omi_virtualization_type": "hvm",
+36 -26
View File
@@ -4,23 +4,25 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepRegisterOMI creates the OMI.
type StepRegisterOMI struct {
RootDevice RootBlockDevice
OMIDevices []oapi.BlockDeviceMappingImage
LaunchDevices []oapi.BlockDeviceMappingVmCreation
image *oapi.Image
OMIDevices []osc.BlockDeviceMappingImage
LaunchDevices []osc.BlockDeviceMappingVmCreation
image *osc.Image
RawRegion string
}
func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
snapshotIds := state.Get("snapshot_ids").(map[string]string)
ui := state.Get("ui").(packer.Ui)
@@ -28,14 +30,16 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
blockDevices := s.combineDevices(snapshotIds)
registerOpts := oapi.CreateImageRequest{
registerOpts := osc.CreateImageRequest{
ImageName: config.OMIName,
Architecture: "x86_64",
RootDeviceName: s.RootDevice.DeviceName,
BlockDeviceMappings: blockDevices,
}
registerResp, err := oapiconn.POST_CreateImage(registerOpts)
registerResp, _, err := oscconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(registerOpts),
})
if err != nil {
state.Put("error", fmt.Errorf("Error registering OMI: %s", err))
ui.Error(state.Get("error").(error).Error())
@@ -43,24 +47,26 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
}
// Set the OMI ID in the state
ui.Say(fmt.Sprintf("OMI: %s", registerResp.OK.Image.ImageId))
ui.Say(fmt.Sprintf("OMI: %s", registerResp.Image.ImageId))
omis := make(map[string]string)
omis[oapiconn.GetConfig().Region] = registerResp.OK.Image.ImageId
omis[s.RawRegion] = registerResp.Image.ImageId
state.Put("omis", omis)
// Wait for the image to become ready
ui.Say("Waiting for OMI to become ready...")
if err := osccommon.WaitUntilImageAvailable(oapiconn, registerResp.OK.Image.ImageId); err != nil {
if err := osccommon.WaitUntilOscImageAvailable(oscconn, registerResp.Image.ImageId); err != nil {
err := fmt.Errorf("Error waiting for OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{registerResp.OK.Image.ImageId},
},
imagesResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{registerResp.Image.ImageId},
},
}),
})
if err != nil {
@@ -69,12 +75,12 @@ func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) mul
ui.Error(err.Error())
return multistep.ActionHalt
}
s.image = &imagesResp.OK.Images[0]
s.image = &imagesResp.Images[0]
snapshots := make(map[string][]string)
for _, blockDeviceMapping := range imagesResp.OK.Images[0].BlockDeviceMappings {
for _, blockDeviceMapping := range imagesResp.Images[0].BlockDeviceMappings {
if blockDeviceMapping.Bsu.SnapshotId != "" {
snapshots[oapiconn.GetConfig().Region] = append(snapshots[oapiconn.GetConfig().Region], blockDeviceMapping.Bsu.SnapshotId)
snapshots[s.RawRegion] = append(snapshots[s.RawRegion], blockDeviceMapping.Bsu.SnapshotId)
}
}
state.Put("snapshots", snapshots)
@@ -93,19 +99,23 @@ func (s *StepRegisterOMI) Cleanup(state multistep.StateBag) {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deregistering the OMI because cancellation or error...")
deregisterOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId}
if _, err := oapiconn.POST_DeleteImage(deregisterOpts); err != nil {
deregisterOpts := osc.DeleteImageRequest{ImageId: s.image.ImageId}
_, _, err := oscconn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(deregisterOpts),
})
if err != nil {
ui.Error(fmt.Sprintf("Error deregistering OMI, may still be around: %s", err))
return
}
}
func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []oapi.BlockDeviceMappingImage {
devices := map[string]oapi.BlockDeviceMappingImage{}
func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []osc.BlockDeviceMappingImage {
devices := map[string]osc.BlockDeviceMappingImage{}
for _, device := range s.OMIDevices {
devices[device.DeviceName] = device
@@ -125,18 +135,18 @@ func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []oapi.B
devices[device.DeviceName] = copyToDeviceMappingImage(device)
}
blockDevices := []oapi.BlockDeviceMappingImage{}
blockDevices := []osc.BlockDeviceMappingImage{}
for _, device := range devices {
blockDevices = append(blockDevices, device)
}
return blockDevices
}
func copyToDeviceMappingImage(device oapi.BlockDeviceMappingVmCreation) oapi.BlockDeviceMappingImage {
deviceImage := oapi.BlockDeviceMappingImage{
func copyToDeviceMappingImage(device osc.BlockDeviceMappingVmCreation) osc.BlockDeviceMappingImage {
deviceImage := osc.BlockDeviceMappingImage{
DeviceName: device.DeviceName,
VirtualDeviceName: device.VirtualDeviceName,
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
DeleteOnVmDeletion: device.Bsu.DeleteOnVmDeletion,
Iops: device.Bsu.Iops,
SnapshotId: device.Bsu.SnapshotId,
@@ -6,11 +6,12 @@ import (
"sync"
"time"
"github.com/antihax/optional"
multierror "github.com/hashicorp/go-multierror"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepSnapshotVolumes creates snapshots of the created volumes.
@@ -18,14 +19,14 @@ import (
// Produces:
// snapshot_ids map[string]string - IDs of the created snapshots
type StepSnapshotVolumes struct {
LaunchDevices []oapi.BlockDeviceMappingVmCreation
LaunchDevices []osc.BlockDeviceMappingVmCreation
snapshotIds map[string]string
}
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(oapi.Vm)
vm := state.Get("vm").(osc.Vm)
var volumeId string
for _, volume := range vm.BlockDeviceMappings {
@@ -40,19 +41,21 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
description := fmt.Sprintf("Packer: %s", time.Now().String())
createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
createSnapResp, _, err := oscconn.SnapshotApi.CreateSnapshot(context.Background(), &osc.CreateSnapshotOpts{
CreateSnapshotRequest: optional.NewInterface(osc.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
}),
})
if err != nil {
return err
}
// Set the snapshot ID so we can delete it later
s.snapshotIds[deviceName] = createSnapResp.OK.Snapshot.SnapshotId
s.snapshotIds[deviceName] = createSnapResp.Snapshot.SnapshotId
// Wait for snapshot to be created
err = osccommon.WaitUntilSnapshotCompleted(oapiconn, createSnapResp.OK.Snapshot.SnapshotId)
err = osccommon.WaitUntilOscSnapshotCompleted(oscconn, createSnapResp.Snapshot.SnapshotId)
return err
}
@@ -65,7 +68,7 @@ func (s *StepSnapshotVolumes) Run(ctx context.Context, state multistep.StateBag)
var errs *multierror.Error
for _, device := range s.LaunchDevices {
wg.Add(1)
go func(device oapi.BlockDeviceMappingVmCreation) {
go func(device osc.BlockDeviceMappingVmCreation) {
defer wg.Done()
if err := s.snapshotVolume(ctx, device.DeviceName, state); err != nil {
errs = multierror.Append(errs, err)
@@ -94,11 +97,13 @@ func (s *StepSnapshotVolumes) Cleanup(state multistep.StateBag) {
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Removing snapshots since we cancelled or halted...")
for _, snapshotId := range s.snapshotIds {
_, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: snapshotId})
for _, snapshotID := range s.snapshotIds {
_, _, err := oscconn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{SnapshotId: snapshotID}),
})
if err != nil {
ui.Error(fmt.Sprintf("Error: %s", err))
}
+8 -4
View File
@@ -1,13 +1,15 @@
package bsuvolume
import (
"context"
"fmt"
"log"
"sort"
"strings"
"github.com/antihax/optional"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// map of region to list of volume IDs
@@ -22,7 +24,7 @@ type Artifact struct {
BuilderIdValue string
// Client connection for performing API stuff.
Conn *oapi.Client
Conn *osc.APIClient
// StateData should store data such as GeneratedData
// to be shared with post-processors
@@ -70,10 +72,12 @@ func (a *Artifact) Destroy() error {
for _, volumeID := range volumeIDs {
log.Printf("Deregistering Volume ID (%s) from region (%s)", volumeID, region)
input := oapi.DeleteVolumeRequest{
input := osc.DeleteVolumeRequest{
VolumeId: volumeID,
}
if _, err := a.Conn.POST_DeleteVolume(input); err != nil {
if _, _, err := a.Conn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
DeleteVolumeRequest: optional.NewInterface(optional.NewInterface(input)),
}); err != nil {
errors = append(errors, err)
}
}
+15 -18
View File
@@ -6,10 +6,8 @@ package bsuvolume
import (
"context"
"crypto/tls"
"fmt"
"log"
"net/http"
"github.com/hashicorp/hcl/v2/hcldec"
osccommon "github.com/hashicorp/packer/builder/osc/common"
@@ -19,7 +17,6 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
const BuilderId = "oapi.outscale.bsuvolume"
@@ -89,22 +86,22 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
// clientConfig, err := b.config.Config()
// if err != nil {
// return nil, err
// }
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// skipClient := &http.Client{
// Transport: &http.Transport{
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
// },
// }
oapiconn := oapi.NewClient(clientConfig, skipClient)
oscConn := b.config.NewOSCClient()
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("oapi", oapiconn)
state.Put("osc", oscConn)
state.Put("hook", hook)
state.Put("ui", ui)
@@ -117,7 +114,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug,
BsuOptimized: b.config.BsuOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs",
ExpectedRootDevice: "bsu",
IamVmProfile: b.config.IamVmProfile,
VmInitiatedShutdownBehavior: b.config.VmInitiatedShutdownBehavior,
VmType: b.config.VmType,
@@ -170,8 +167,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.SSHHost(
oapiconn,
Host: osccommon.OscSSHHost(
oscConn.VmApi,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
@@ -198,7 +195,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact := &Artifact{
Volumes: state.Get("bsuvolumes").(BsuVolumes),
BuilderIdValue: BuilderId,
Conn: oapiconn,
Conn: oscConn,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
ui.Say(fmt.Sprintf("Created Volumes: %s", artifact))
+1 -21
View File
@@ -2,13 +2,9 @@
package bsuvolume
import (
"crypto/tls"
"net/http"
"testing"
"github.com/hashicorp/packer/builder/osc/common"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/outscale/osc-go/oapi"
)
func TestBuilderAcc_basic(t *testing.T) {
@@ -23,22 +19,6 @@ func TestBuilderAcc_basic(t *testing.T) {
func testAccPreCheck(t *testing.T) {
}
func testOAPIConn() (*oapi.Client, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
clientConfig, err := access.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
return oapi.NewClient(clientConfig, skipClient), nil
}
const testBuilderAccBasic = `
{
"builders": [
@@ -46,7 +26,7 @@ const testBuilderAccBasic = `
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-65efcc11",
"source_omi": "ami-abe953fa",
"ssh_username": "outscale",
"bsu_volumes": [
{
+14 -10
View File
@@ -4,28 +4,30 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type stepTagBSUVolumes struct {
VolumeMapping []BlockDevice
RawRegion string
Ctx interpolate.Context
}
func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
ui := state.Get("ui").(packer.Ui)
volumes := make(BsuVolumes)
for _, instanceBlockDevices := range vm.BlockDeviceMappings {
for _, configVolumeMapping := range s.VolumeMapping {
if configVolumeMapping.DeviceName == instanceBlockDevices.DeviceName {
volumes[oapiconn.GetConfig().Region] = append(
volumes[oapiconn.GetConfig().Region],
volumes[s.RawRegion] = append(
volumes[s.RawRegion],
instanceBlockDevices.Bsu.VolumeId)
}
}
@@ -35,14 +37,14 @@ func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) mul
if len(s.VolumeMapping) > 0 {
ui.Say("Tagging BSU volumes...")
toTag := map[string][]oapi.ResourceTag{}
toTag := map[string][]osc.ResourceTag{}
for _, mapping := range s.VolumeMapping {
if len(mapping.Tags) == 0 {
ui.Say(fmt.Sprintf("No tags specified for volume on %s...", mapping.DeviceName))
continue
}
tags, err := mapping.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
tags, err := mapping.Tags.OSCTags(s.Ctx, s.RawRegion, state)
if err != nil {
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
state.Put("error", err)
@@ -59,9 +61,11 @@ func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) mul
}
for volumeId, tags := range toTag {
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: []string{volumeId},
Tags: tags,
_, _, err := oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: []string{volumeId},
Tags: tags,
}),
})
if err != nil {
err := fmt.Errorf("Error tagging BSU Volume %s on %s: %s", volumeId, vm.VmId, err)
+6 -20
View File
@@ -8,9 +8,7 @@ package chroot
import (
"context"
"crypto/tls"
"errors"
"net/http"
"runtime"
"github.com/hashicorp/hcl/v2/hcldec"
@@ -20,7 +18,6 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
// The unique ID for this builder
@@ -193,19 +190,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, errors.New("The outscale-chroot builder only works on Linux environments.")
}
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
oapiconn := oapi.NewClient(clientConfig, skipClient)
oscConn := b.config.NewOSCClient()
wrappedCommand := func(command string) (string, error) {
ctx := b.config.ctx
ctx.Data = &wrappedCommandTemplate{Command: command}
@@ -215,8 +200,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("osc", oscConn)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
@@ -266,7 +250,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepCopyFiles{},
&StepChrootProvision{},
&StepEarlyCleanup{},
&StepSnapshot{},
&StepSnapshot{
RawRegion: b.config.RawRegion,
},
&osccommon.StepDeregisterOMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.OMIForceDeregister,
@@ -276,6 +262,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&StepCreateOMI{
RootVolumeSize: b.config.RootVolumeSize,
RawRegion: b.config.RawRegion,
},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
@@ -307,7 +294,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact := &osccommon.Artifact{
Omis: state.Get("omis").(map[string]string),
BuilderIdValue: BuilderId,
Config: clientConfig,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
+2 -2
View File
@@ -6,14 +6,14 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepCheckRootDevice makes sure the root device on the OMI is BSU-backed.
type StepCheckRootDevice struct{}
func (s *StepCheckRootDevice) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
image := state.Get("source_image").(oapi.Image)
image := state.Get("source_image").(osc.Image)
ui := state.Get("ui").(packer.Ui)
ui.Say("Checking the root device on source OMI...")
+23 -19
View File
@@ -4,42 +4,44 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepCreateOMI creates the OMI.
type StepCreateOMI struct {
RootVolumeSize int64
RawRegion string
}
func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oapiconn := state.Get("oapi").(*oapi.Client)
osconn := state.Get("osc").(*osc.APIClient)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating the OMI...")
var (
registerOpts oapi.CreateImageRequest
mappings []oapi.BlockDeviceMappingImage
image oapi.Image
registerOpts osc.CreateImageRequest
mappings []osc.BlockDeviceMappingImage
image osc.Image
rootDeviceName string
)
if config.FromScratch {
mappings = config.OMIBlockDevices.BuildOMIDevices()
mappings = config.OMIBlockDevices.BuildOscOMIDevices()
rootDeviceName = config.RootDeviceName
} else {
image = state.Get("source_image").(oapi.Image)
image = state.Get("source_image").(osc.Image)
mappings = image.BlockDeviceMappings
rootDeviceName = image.RootDeviceName
}
newMappings := make([]oapi.BlockDeviceMappingImage, len(mappings))
newMappings := make([]osc.BlockDeviceMappingImage, len(mappings))
for i, device := range mappings {
newDevice := device
@@ -48,14 +50,14 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
newDevice.Bsu.VolumeSize = gibSize
if newDevice.DeviceName == rootDeviceName {
if newDevice.Bsu != (oapi.BsuToCreate{}) {
if newDevice.Bsu != (osc.BsuToCreate{}) {
newDevice.Bsu.SnapshotId = snapshotId
} else {
newDevice.Bsu = oapi.BsuToCreate{SnapshotId: snapshotId}
newDevice.Bsu = osc.BsuToCreate{SnapshotId: snapshotId}
}
if config.FromScratch || s.RootVolumeSize > newDevice.Bsu.VolumeSize {
newDevice.Bsu.VolumeSize = s.RootVolumeSize
if config.FromScratch || int32(s.RootVolumeSize) > newDevice.Bsu.VolumeSize {
newDevice.Bsu.VolumeSize = int32(s.RootVolumeSize)
}
}
@@ -63,7 +65,7 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
}
if config.FromScratch {
registerOpts = oapi.CreateImageRequest{
registerOpts = osc.CreateImageRequest{
ImageName: config.OMIName,
Architecture: "x86_64",
RootDeviceName: rootDeviceName,
@@ -73,23 +75,25 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
registerOpts = buildRegisterOpts(config, image, newMappings)
}
registerResp, err := oapiconn.POST_CreateImage(registerOpts)
registerResp, _, err := osconn.ImageApi.CreateImage(context.Background(), &osc.CreateImageOpts{
CreateImageRequest: optional.NewInterface(registerOpts),
})
if err != nil {
state.Put("error", fmt.Errorf("Error registering OMI: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
imageID := registerResp.OK.Image.ImageId
imageID := registerResp.Image.ImageId
// Set the OMI ID in the state
ui.Say(fmt.Sprintf("OMI: %s", imageID))
omis := make(map[string]string)
omis[oapiconn.GetConfig().Region] = imageID
omis[s.RawRegion] = imageID
state.Put("omis", omis)
ui.Say("Waiting for OMI to become ready...")
if err := osccommon.WaitUntilImageAvailable(oapiconn, imageID); err != nil {
if err := osccommon.WaitUntilOscImageAvailable(osconn, imageID); err != nil {
err := fmt.Errorf("Error waiting for OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -101,8 +105,8 @@ func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multi
func (s *StepCreateOMI) Cleanup(state multistep.StateBag) {}
func buildRegisterOpts(config *Config, image oapi.Image, mappings []oapi.BlockDeviceMappingImage) oapi.CreateImageRequest {
registerOpts := oapi.CreateImageRequest{
func buildRegisterOpts(config *Config, image osc.Image, mappings []osc.BlockDeviceMappingImage) osc.CreateImageRequest {
registerOpts := osc.CreateImageRequest{
ImageName: config.OMIName,
Architecture: image.Architecture,
RootDeviceName: image.RootDeviceName,
+26 -20
View File
@@ -6,11 +6,12 @@ import (
"fmt"
"log"
"github.com/antihax/optional"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepCreateVolume creates a new volume from the snapshot of the root
@@ -23,18 +24,19 @@ type StepCreateVolume struct {
RootVolumeSize int64
RootVolumeType string
RootVolumeTags osccommon.TagMap
RawRegion string
Ctx interpolate.Context
}
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
ui := state.Get("ui").(packer.Ui)
var err error
volTags, err := s.RootVolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
volTags, err := s.RootVolumeTags.OSCTags(s.Ctx, s.RawRegion, state)
if err != nil {
state.Put("error", err)
@@ -42,7 +44,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
var createVolume *oapi.CreateVolumeRequest
var createVolume *osc.CreateVolumeRequest
if config.FromScratch {
rootVolumeType := osccommon.VolumeTypeGp2
if s.RootVolumeType == "io1" {
@@ -53,17 +55,17 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
} else if s.RootVolumeType != "" {
rootVolumeType = s.RootVolumeType
}
createVolume = &oapi.CreateVolumeRequest{
createVolume = &osc.CreateVolumeRequest{
SubregionName: vm.Placement.SubregionName,
Size: s.RootVolumeSize,
Size: int32(s.RootVolumeSize),
VolumeType: rootVolumeType,
}
} else {
// Determine the root device snapshot
image := state.Get("source_image").(oapi.Image)
image := state.Get("source_image").(osc.Image)
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
var rootDevice *oapi.BlockDeviceMappingImage
var rootDevice *osc.BlockDeviceMappingImage
for _, device := range image.BlockDeviceMappings {
if device.DeviceName == image.RootDeviceName {
rootDevice = &device
@@ -82,7 +84,9 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
log.Printf("Create args: %+v", createVolume)
createVolumeResp, err := oapiconn.POST_CreateVolume(*createVolume)
createVolumeResp, _, err := oscconn.VolumeApi.CreateVolume(context.Background(), &osc.CreateVolumeOpts{
CreateVolumeRequest: optional.NewInterface(*createVolume),
})
if err != nil {
err := fmt.Errorf("Error creating root volume: %s", err)
state.Put("error", err)
@@ -91,12 +95,12 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
}
// Set the volume ID so we remember to delete it later
s.volumeId = createVolumeResp.OK.Volume.VolumeId
s.volumeId = createVolumeResp.Volume.VolumeId
log.Printf("Volume ID: %s", s.volumeId)
//Create tags for volume
if len(volTags) > 0 {
if err := osccommon.CreateTags(oapiconn, s.volumeId, ui, volTags); err != nil {
if err := osccommon.CreateOSCTags(oscconn, s.volumeId, ui, volTags); err != nil {
err := fmt.Errorf("Error creating tags for volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -105,7 +109,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
}
// Wait for the volume to become ready
err = osccommon.WaitUntilVolumeAvailable(oapiconn, s.volumeId)
err = osccommon.WaitUntilOscVolumeAvailable(oscconn, s.volumeId)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
@@ -122,32 +126,34 @@ func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting the created BSU volume...")
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: s.volumeId})
_, _, err := oscconn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
DeleteVolumeRequest: optional.NewInterface(osc.DeleteVolumeRequest{VolumeId: s.volumeId}),
})
if err != nil {
ui.Error(fmt.Sprintf("Error deleting BSU volume: %s", err))
}
}
func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *oapi.BlockDeviceMappingImage) (*oapi.CreateVolumeRequest, error) {
func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *osc.BlockDeviceMappingImage) (*osc.CreateVolumeRequest, error) {
if rootDevice == nil {
return nil, fmt.Errorf("Couldn't find root device!")
return nil, fmt.Errorf("Couldn't find root device")
}
//FIX: Temporary fix
gibSize := rootDevice.Bsu.VolumeSize / (1024 * 1024 * 1024)
createVolumeInput := &oapi.CreateVolumeRequest{
createVolumeInput := &osc.CreateVolumeRequest{
SubregionName: suregionName,
Size: gibSize,
SnapshotId: rootDevice.Bsu.SnapshotId,
VolumeType: rootDevice.Bsu.VolumeType,
Iops: rootDevice.Bsu.Iops,
}
if s.RootVolumeSize > rootDevice.Bsu.VolumeSize {
createVolumeInput.Size = s.RootVolumeSize
if int32(s.RootVolumeSize) > rootDevice.Bsu.VolumeSize {
createVolumeInput.Size = int32(s.RootVolumeSize)
}
if s.RootVolumeType == "" || s.RootVolumeType == rootDevice.Bsu.VolumeType {
+17 -11
View File
@@ -4,10 +4,11 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepLinkVolume attaches the previously created volume to an
@@ -22,9 +23,9 @@ type StepLinkVolume struct {
}
func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
device := state.Get("device").(string)
vm := state.Get("vm").(oapi.Vm)
vm := state.Get("vm").(osc.Vm)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
@@ -33,10 +34,12 @@ func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) mult
linkVolume := device
ui.Say(fmt.Sprintf("Attaching the root volume to %s", linkVolume))
_, err := oapiconn.POST_LinkVolume(oapi.LinkVolumeRequest{
VmId: vm.VmId,
VolumeId: volumeId,
DeviceName: linkVolume,
_, _, err := oscconn.VolumeApi.LinkVolume(context.Background(), &osc.LinkVolumeOpts{
LinkVolumeRequest: optional.NewInterface(osc.LinkVolumeRequest{
VmId: vm.VmId,
VolumeId: volumeId,
DeviceName: linkVolume,
}),
})
if err != nil {
@@ -51,7 +54,7 @@ func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) mult
s.volumeId = volumeId
// Wait for the volume to become attached
err = osccommon.WaitUntilVolumeIsLinked(oapiconn, s.volumeId)
err = osccommon.WaitUntilOscVolumeIsLinked(oscconn, s.volumeId)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
@@ -75,11 +78,14 @@ func (s *StepLinkVolume) CleanupFunc(state multistep.StateBag) error {
return nil
}
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Detaching BSU volume...")
_, err := oapiconn.POST_UnlinkVolume(oapi.UnlinkVolumeRequest{VolumeId: s.volumeId})
_, _, err := oscconn.VolumeApi.UnlinkVolume(context.Background(), &osc.UnlinkVolumeOpts{
UnlinkVolumeRequest: optional.NewInterface(osc.UnlinkVolumeRequest{VolumeId: s.volumeId}),
})
if err != nil {
return fmt.Errorf("Error detaching BSU volume: %s", err)
}
@@ -87,7 +93,7 @@ func (s *StepLinkVolume) CleanupFunc(state multistep.StateBag) error {
s.attached = false
// Wait for the volume to detach
err = osccommon.WaitUntilVolumeIsUnlinked(oapiconn, s.volumeId)
err = osccommon.WaitUntilOscVolumeIsUnlinked(oscconn, s.volumeId)
if err != nil {
return fmt.Errorf("Error waiting for volume: %s", err)
}
+16 -10
View File
@@ -5,10 +5,11 @@ import (
"fmt"
"time"
"github.com/antihax/optional"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepSnapshot creates a snapshot of the created volume.
@@ -17,19 +18,22 @@ import (
// snapshot_id string - ID of the created snapshot
type StepSnapshot struct {
snapshotId string
RawRegion string
}
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
ui.Say("Creating snapshot...")
description := fmt.Sprintf("Packer: %s", time.Now().String())
createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
createSnapResp, _, err := oscconn.SnapshotApi.CreateSnapshot(context.Background(), &osc.CreateSnapshotOpts{
CreateSnapshotRequest: optional.NewInterface(osc.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
}),
})
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
@@ -39,11 +43,11 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
}
// Set the snapshot ID so we can delete it later
s.snapshotId = createSnapResp.OK.Snapshot.SnapshotId
s.snapshotId = createSnapResp.Snapshot.SnapshotId
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
// Wait for the snapshot to be ready
err = osccommon.WaitUntilSnapshotDone(oapiconn, s.snapshotId)
err = osccommon.WaitUntilOscSnapshotDone(oscconn, s.snapshotId)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state.Put("error", err)
@@ -54,7 +58,7 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
state.Put("snapshot_id", s.snapshotId)
snapshots := map[string][]string{
oapiconn.GetConfig().Region: {s.snapshotId},
s.RawRegion: {s.snapshotId},
}
state.Put("snapshots", snapshots)
@@ -70,10 +74,12 @@ func (s *StepSnapshot) Cleanup(state multistep.StateBag) {
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
ui.Say("Removing snapshot since we cancelled or halted...")
_, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: s.snapshotId})
_, _, err := oscconn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{SnapshotId: s.snapshotId}),
})
if err != nil {
ui.Error(fmt.Sprintf("Error: %s", err))
}
+10 -7
View File
@@ -5,16 +5,17 @@ import (
"fmt"
"log"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepVmInfo verifies that this builder is running on an Outscale vm.
type StepVmInfo struct{}
func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
//session := state.Get("clientConfig").(*session.Session)
ui := state.Get("ui").(packer.Ui)
@@ -36,9 +37,11 @@ func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.
log.Printf("[Debug] VmID got: %s", string(vmID))
// Query the entire vm metadata
resp, err := oapiconn.POST_ReadVms(oapi.ReadVmsRequest{Filters: oapi.FiltersVm{
VmIds: []string{string(vmID)},
}})
resp, _, err := oscconn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{Filters: osc.FiltersVm{
VmIds: []string{string(vmID)},
}}),
})
if err != nil {
err := fmt.Errorf("Error getting vm data: %s", err)
state.Put("error", err)
@@ -46,10 +49,10 @@ func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.
return multistep.ActionHalt
}
vmsResp := resp.OK
vmsResp := resp
if len(vmsResp.Vms) == 0 {
err := fmt.Errorf("Error getting vm data: no vm found.")
err := fmt.Errorf("Error getting vm data: no vm found")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
+19 -33
View File
@@ -8,7 +8,7 @@ import (
"os"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// AccessConfig is for common configuration related to Outscale API access
@@ -23,20 +23,10 @@ type AccessConfig struct {
SkipValidation bool `mapstructure:"skip_region_validation"`
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
Token string `mapstructure:"token"`
clientConfig *oapi.Config
getOAPIConnection func() oapi.OAPIClient
}
// Config returns a valid oapi.Config object for access to Outscale services, or
// an error if the authentication and region couldn't be resolved
func (c *AccessConfig) Config() (*oapi.Config, error) {
if c.clientConfig != nil {
return c.clientConfig, nil
}
//Check env variables if access configuration is not set.
// NewOSCClient retrieves the Outscale OSC-SDK client
func (c *AccessConfig) NewOSCClient() *osc.APIClient {
if c.AccessKey == "" {
c.AccessKey = os.Getenv("OUTSCALE_ACCESSKEYID")
}
@@ -57,36 +47,32 @@ func (c *AccessConfig) Config() (*oapi.Config, error) {
c.CustomEndpointOAPI = "outscale.com/oapi/latest"
}
config := &oapi.Config{
AccessKey: c.AccessKey,
SecretKey: c.SecretKey,
Region: c.RawRegion,
URL: c.CustomEndpointOAPI,
Service: "api",
}
return config, nil
return c.NewOSCClientByRegion(c.RawRegion)
}
func (c *AccessConfig) NewOAPIConnection() (oapi.OAPIClient, error) {
if c.getOAPIConnection != nil {
return c.getOAPIConnection(), nil
}
oapicfg, err := c.Config()
if err != nil {
return nil, err
}
// GetRegion retrieves the Outscale OSC-SDK Region set
func (c *AccessConfig) GetRegion() string {
return c.RawRegion
}
// NewOSCClientByRegion returns the connection depdending of the region given
func (c *AccessConfig) NewOSCClientByRegion(region string) *osc.APIClient {
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.InsecureSkipTLSVerify},
Proxy: http.ProxyFromEnvironment,
},
}
oapiClient := oapi.NewClient(oapicfg, skipClient)
skipClient.Transport = NewTransport(c.AccessKey, c.SecretKey, c.RawRegion, skipClient.Transport)
return oapiClient, nil
return osc.NewAPIClient(&osc.Configuration{
BasePath: fmt.Sprintf("https://api.%s.%s", region, c.CustomEndpointOAPI),
DefaultHeader: make(map[string]string),
UserAgent: "packer-osc",
HTTPClient: skipClient,
Debug: true,
})
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
+6 -27
View File
@@ -2,50 +2,29 @@ package common
import (
"testing"
"github.com/outscale/osc-go/oapi"
)
type mockOAPIClient struct {
oapi.OAPIClient
}
func testAccessConfig() *AccessConfig {
return &AccessConfig{
getOAPIConnection: func() oapi.OAPIClient {
return &mockOAPIClient{}
},
}
}
func (m *mockOAPIClient) POST_ReadRegions(oapi.ReadRegionsRequest) (*oapi.POST_ReadRegionsResponses, error) {
return &oapi.POST_ReadRegionsResponses{
OK: &oapi.ReadRegionsResponse{
Regions: []oapi.Region{
{RegionEndpoint: "us-west1", RegionName: "us-west1"},
{RegionEndpoint: "us-east-1", RegionName: "us-east-1"},
},
},
}, nil
return &AccessConfig{}
}
func TestAccessConfigPrepare_Region(t *testing.T) {
c := testAccessConfig()
c.RawRegion = "us-east-12"
err := c.ValidateRegion(c.RawRegion)
err := c.ValidateOSCRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
c.RawRegion = "us-east-1"
err = c.ValidateRegion(c.RawRegion)
if err != nil {
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
err = c.ValidateOSCRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
c.RawRegion = "custom"
err = c.ValidateRegion(c.RawRegion)
err = c.ValidateOSCRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
+17 -31
View File
@@ -1,15 +1,15 @@
package common
import (
"crypto/tls"
"context"
"fmt"
"log"
"net/http"
"sort"
"strings"
"github.com/antihax/optional"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// Artifact is an artifact implementation that contains built OMIs.
@@ -20,9 +20,6 @@ type Artifact struct {
// BuilderId is the unique ID for the builder that created this OMI
BuilderIdValue string
// OAPI connection for performing API stuff.
Config *oapi.Config
// StateData should store data such as GeneratedData
// to be shared with post-processors
StateData map[string]interface{}
@@ -74,47 +71,36 @@ func (a *Artifact) State(name string) interface{} {
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
config := a.State("accessConfig").(*AccessConfig)
for region, imageId := range a.Omis {
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
newConfig := &oapi.Config{
UserAgent: a.Config.UserAgent,
AccessKey: a.Config.AccessKey,
SecretKey: a.Config.SecretKey,
Service: a.Config.Service,
Region: region, //New region
URL: a.Config.URL,
}
log.Printf("[DEBUG] New Client config %+v", newConfig)
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionConn := oapi.NewClient(newConfig, skipClient)
regionConn := config.NewOSCClientByRegion(region)
// Get image metadata
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{imageId},
},
imageResp, _, err := regionConn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{imageId},
},
}),
})
if err != nil {
errors = append(errors, err)
}
if len(imageResp.OK.Images) == 0 {
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Error retrieving details for OMI (%s), no images found", imageId)
errors = append(errors, err)
}
// Deregister ami
input := oapi.DeleteImageRequest{
input := osc.DeleteImageRequest{
ImageId: imageId,
}
if _, err := regionConn.POST_DeleteImage(input); err != nil {
if _, _, err := regionConn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(input),
}); err != nil {
errors = append(errors, err)
}
+19 -20
View File
@@ -5,9 +5,8 @@ import (
"log"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// BlockDevice
@@ -35,11 +34,11 @@ type LaunchBlockDevices struct {
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
}
func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
var blockDevices []oapi.BlockDeviceMappingImage
func buildOscBlockDevicesImage(b []BlockDevice) []osc.BlockDeviceMappingImage {
var blockDevices []osc.BlockDeviceMappingImage
for _, blockDevice := range b {
mapping := oapi.BlockDeviceMappingImage{
mapping := osc.BlockDeviceMappingImage{
DeviceName: blockDevice.DeviceName,
}
@@ -48,8 +47,8 @@ func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
mapping.VirtualDeviceName = blockDevice.VirtualName
}
} else {
bsu := oapi.BsuToCreate{
DeleteOnVmDeletion: aws.Bool(blockDevice.DeleteOnVmDeletion),
bsu := osc.BsuToCreate{
DeleteOnVmDeletion: blockDevice.DeleteOnVmDeletion,
}
if blockDevice.VolumeType != "" {
@@ -57,12 +56,12 @@ func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
}
if blockDevice.VolumeSize > 0 {
bsu.VolumeSize = blockDevice.VolumeSize
bsu.VolumeSize = int32(blockDevice.VolumeSize)
}
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
bsu.Iops = blockDevice.IOPS
bsu.Iops = int32(blockDevice.IOPS)
}
if blockDevice.SnapshotId != "" {
@@ -77,13 +76,13 @@ func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage {
return blockDevices
}
func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCreation {
func buildOscBlockDevicesVmCreation(b []BlockDevice) []osc.BlockDeviceMappingVmCreation {
log.Printf("[DEBUG] Launch Block Device %#v", b)
var blockDevices []oapi.BlockDeviceMappingVmCreation
var blockDevices []osc.BlockDeviceMappingVmCreation
for _, blockDevice := range b {
mapping := oapi.BlockDeviceMappingVmCreation{
mapping := osc.BlockDeviceMappingVmCreation{
DeviceName: blockDevice.DeviceName,
}
@@ -94,8 +93,8 @@ func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCre
mapping.VirtualDeviceName = blockDevice.VirtualName
}
} else {
bsu := oapi.BsuToCreate{
DeleteOnVmDeletion: aws.Bool(blockDevice.DeleteOnVmDeletion),
bsu := osc.BsuToCreate{
DeleteOnVmDeletion: blockDevice.DeleteOnVmDeletion,
}
if blockDevice.VolumeType != "" {
@@ -103,12 +102,12 @@ func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCre
}
if blockDevice.VolumeSize > 0 {
bsu.VolumeSize = blockDevice.VolumeSize
bsu.VolumeSize = int32(blockDevice.VolumeSize)
}
// IOPS is only valid for io1 type
if blockDevice.VolumeType == "io1" {
bsu.Iops = blockDevice.IOPS
bsu.Iops = int32(blockDevice.IOPS)
}
if blockDevice.SnapshotId != "" {
@@ -145,10 +144,10 @@ func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
return errs
}
func (b *OMIBlockDevices) BuildOMIDevices() []oapi.BlockDeviceMappingImage {
return buildBlockDevicesImage(b.OMIMappings)
func (b *OMIBlockDevices) BuildOscOMIDevices() []osc.BlockDeviceMappingImage {
return buildOscBlockDevicesImage(b.OMIMappings)
}
func (b *LaunchBlockDevices) BuildLaunchDevices() []oapi.BlockDeviceMappingVmCreation {
return buildBlockDevicesVmCreation(b.LaunchMappings)
func (b *LaunchBlockDevices) BuildOSCLaunchDevices() []osc.BlockDeviceMappingVmCreation {
return buildOscBlockDevicesVmCreation(b.LaunchMappings)
}
+52 -63
View File
@@ -4,19 +4,13 @@ import (
"reflect"
"testing"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
func TestBlockDevice_LaunchDevices(t *testing.T) {
tr := new(bool)
f := new(bool)
*tr = true
*f = false
cases := []struct {
Config *BlockDevice
Result oapi.BlockDeviceMappingVmCreation
Result osc.BlockDeviceMappingVmCreation
}{
{
Config: &BlockDevice{
@@ -27,13 +21,13 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
@@ -43,11 +37,11 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
VolumeSize: 8,
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeSize: 8,
DeleteOnVmDeletion: f,
DeleteOnVmDeletion: false,
},
},
},
@@ -60,12 +54,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
IOPS: 1000,
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
Iops: 1000,
},
},
@@ -78,12 +72,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
@@ -95,12 +89,12 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
@@ -111,11 +105,11 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "standard",
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
@@ -125,7 +119,7 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
VirtualName: "ephemeral0",
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
VirtualDeviceName: "ephemeral0",
},
@@ -136,7 +130,7 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
NoDevice: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
Result: osc.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
NoDevice: "",
},
@@ -149,9 +143,9 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
LaunchMappings: []BlockDevice{*tc.Config},
}
expected := []oapi.BlockDeviceMappingVmCreation{tc.Result}
expected := []osc.BlockDeviceMappingVmCreation{tc.Result}
launchResults := launchBlockDevices.BuildLaunchDevices()
launchResults := launchBlockDevices.BuildOSCLaunchDevices()
if !reflect.DeepEqual(expected, launchResults) {
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
expected, launchResults)
@@ -160,15 +154,9 @@ func TestBlockDevice_LaunchDevices(t *testing.T) {
}
func TestBlockDevice_OMI(t *testing.T) {
tr := new(bool)
f := new(bool)
*tr = true
*f = false
cases := []struct {
Config *BlockDevice
Result oapi.BlockDeviceMappingImage
Result osc.BlockDeviceMappingImage
}{
{
Config: &BlockDevice{
@@ -179,27 +167,28 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
Result: osc.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeSize: 8,
DeviceName: "/dev/sdb",
VolumeSize: 8,
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
Result: osc.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeSize: 8,
DeleteOnVmDeletion: f,
DeleteOnVmDeletion: true,
},
},
},
@@ -212,12 +201,12 @@ func TestBlockDevice_OMI(t *testing.T) {
IOPS: 1000,
},
Result: oapi.BlockDeviceMappingImage{
Result: osc.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
Iops: 1000,
},
},
@@ -230,12 +219,12 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
Result: osc.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
@@ -247,12 +236,12 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
Result: osc.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
@@ -263,11 +252,11 @@ func TestBlockDevice_OMI(t *testing.T) {
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
Result: osc.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
Bsu: osc.BsuToCreate{
VolumeType: "standard",
DeleteOnVmDeletion: tr,
DeleteOnVmDeletion: true,
},
},
},
@@ -277,24 +266,24 @@ func TestBlockDevice_OMI(t *testing.T) {
VirtualName: "ephemeral0",
},
Result: oapi.BlockDeviceMappingImage{
Result: osc.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
VirtualDeviceName: "ephemeral0",
},
},
}
for _, tc := range cases {
for i, tc := range cases {
omiBlockDevices := OMIBlockDevices{
OMIMappings: []BlockDevice{*tc.Config},
}
expected := []oapi.BlockDeviceMappingImage{tc.Result}
expected := []osc.BlockDeviceMappingImage{tc.Result}
omiResults := omiBlockDevices.BuildOMIDevices()
omiResults := omiBlockDevices.BuildOscOMIDevices()
if !reflect.DeepEqual(expected, omiResults) {
t.Fatalf("Bad block device, \nexpected: %+#v\n\ngot: %+#v",
expected, omiResults)
t.Fatalf("%d - Bad block device, \nexpected: %+#v\n\ngot: %+#v",
i, expected, omiResults)
}
}
}
+12 -77
View File
@@ -4,11 +4,11 @@ import (
"log"
"strconv"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
func buildNetFilters(input map[string]string) oapi.FiltersNet {
var filters oapi.FiltersNet
func buildOscNetFilters(input map[string]string) osc.FiltersNet {
var filters osc.FiltersNet
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
@@ -33,14 +33,14 @@ func buildNetFilters(input map[string]string) oapi.FiltersNet {
return filters
}
func buildSubnetFilters(input map[string]string) oapi.FiltersSubnet {
var filters oapi.FiltersSubnet
func buildOscSubnetFilters(input map[string]string) osc.FiltersSubnet {
var filters osc.FiltersSubnet
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
case "available-ips-counts":
if ipCount, err := strconv.Atoi(v); err == nil {
filters.AvailableIpsCounts = []int64{int64(ipCount)}
filters.AvailableIpsCounts = []int32{int32(ipCount)}
}
case "ip-ranges":
filters.IpRanges = filterValue
@@ -59,8 +59,8 @@ func buildSubnetFilters(input map[string]string) oapi.FiltersSubnet {
return filters
}
func buildOMIFilters(input map[string]string) oapi.FiltersImage {
var filters oapi.FiltersImage
func buildOSCOMIFilters(input map[string]string) osc.FiltersImage {
var filters osc.FiltersImage
for k, v := range input {
filterValue := []string{v}
@@ -75,14 +75,14 @@ func buildOMIFilters(input map[string]string) oapi.FiltersImage {
filters.ImageIds = filterValue
case "image-name":
filters.ImageNames = filterValue
case "image-type":
filters.ImageTypes = filterValue
// case "image-type":
// filters.ImageTypes = filterValue
case "virtualization-type":
filters.VirtualizationTypes = filterValue
case "root-device-type":
filters.RootDeviceTypes = filterValue
case "block-device-mapping-volume-type":
filters.BlockDeviceMappingVolumeType = filterValue
// case "block-device-mapping-volume-type":
// filters.BlockDeviceMappingVolumeType = filterValue
//Some params are missing.
default:
log.Printf("[WARN] Unknown Filter Name: %s.", name)
@@ -90,68 +90,3 @@ func buildOMIFilters(input map[string]string) oapi.FiltersImage {
}
return filters
}
func buildSecurityGroupFilters(input map[string]string) oapi.FiltersSecurityGroup {
var filters oapi.FiltersSecurityGroup
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
case "account-ids":
filters.AccountIds = filterValue
case "descriptions":
filters.Descriptions = filterValue
case "inbound-rule-account-ids":
filters.InboundRuleAccountIds = filterValue
case "inbound-rule-from-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.InboundRuleFromPortRanges = []int64{int64(val)}
}
case "inbound-rule-ip-ranges":
filters.InboundRuleIpRanges = filterValue
case "inbound-rule-protocols":
filters.InboundRuleProtocols = filterValue
case "inbound-rule-security-group-ids":
filters.InboundRuleSecurityGroupIds = filterValue
case "inbound-rule-security-group-names":
filters.InboundRuleSecurityGroupNames = filterValue
case "inbound-rule-to-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.InboundRuleToPortRanges = []int64{int64(val)}
}
case "net-ids":
filters.NetIds = filterValue
case "outbound-rule-account-ids":
filters.OutboundRuleAccountIds = filterValue
case "outbound-rule-from-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.OutboundRuleFromPortRanges = []int64{int64(val)}
}
case "outbound-rule-ip-ranges":
filters.OutboundRuleIpRanges = filterValue
case "outbound-rule-protocols":
filters.OutboundRuleProtocols = filterValue
case "outbound-rule-security-group-ids":
filters.OutboundRuleSecurityGroupIds = filterValue
case "outbound-rule-security-group-names":
filters.OutboundRuleSecurityGroupNames = filterValue
case "outbound-rule-to-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.OutboundRuleToPortRanges = []int64{int64(val)}
}
case "security-group-ids":
filters.SecurityGroupIds = filterValue
case "security-group-names":
filters.SecurityGroupNames = filterValue
case "tags-keys":
filters.TagKeys = filterValue
case "tags-values":
filters.TagValues = filterValue
//Some params are missing.
default:
log.Printf("[Debug] Unknown Filter Name: %s.", name)
}
}
return filters
}
+2 -2
View File
@@ -2,7 +2,7 @@ package common
import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type BuildInfoTemplate struct {
@@ -20,7 +20,7 @@ func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplat
}
}
sourceOMI := rawSourceOMI.(oapi.Image)
sourceOMI := rawSourceOMI.(osc.Image)
sourceOMITags := make(map[string]string, len(sourceOMI.Tags))
for _, tag := range sourceOMI.Tags {
sourceOMITags[tag.Key] = tag.Value
@@ -5,14 +5,14 @@ import (
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
func testImage() oapi.Image {
return oapi.Image{
func testImage() osc.Image {
return osc.Image{
ImageId: "ami-abcd1234",
ImageName: "ami_test_name",
Tags: []oapi.ResourceTag{
Tags: []osc.ResourceTag{
{
Key: "key-1",
Value: "value-1",
+2 -6
View File
@@ -36,17 +36,13 @@ func TestOMIConfigPrepare_regions(t *testing.T) {
c.OMIRegions = nil
var errs []error
var err error
accessConf := testAccessConfig()
mockConn := &mockOAPIClient{}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
c.OMIRegions, err = listOAPIRegions(mockConn)
if err != nil {
t.Fatalf("shouldn't have err: %s", err.Error())
}
c.OMIRegions = []string{"us-east-1", "us-west-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
+12 -11
View File
@@ -1,19 +1,23 @@
package common
import (
"context"
"fmt"
"github.com/outscale/osc-go/oapi"
"github.com/antihax/optional"
"github.com/outscale/osc-sdk-go/osc"
)
func listOAPIRegions(oapiconn oapi.OAPIClient) ([]string, error) {
func listOSCRegions(oscconn *osc.RegionApiService) ([]string, error) {
var regions []string
resp, err := oapiconn.POST_ReadRegions(oapi.ReadRegionsRequest{})
if resp.OK == nil || err != nil {
resp, _, err := oscconn.ReadRegions(context.Background(), &osc.ReadRegionsOpts{
ReadRegionsRequest: optional.NewInterface(osc.ReadRegionsRequest{}),
})
if err != nil {
return []string{}, err
}
resultRegions := resp.OK
resultRegions := resp
for _, region := range resultRegions.Regions {
regions = append(regions, region.RegionName)
@@ -24,13 +28,10 @@ func listOAPIRegions(oapiconn oapi.OAPIClient) ([]string, error) {
// ValidateRegion returns true if the supplied region is a valid Outscale
// region and false if it's not.
func (c *AccessConfig) ValidateRegion(regions ...string) error {
oapiconn, err := c.NewOAPIConnection()
if err != nil {
return err
}
func (c *AccessConfig) ValidateOSCRegion(regions ...string) error {
oscconn := c.NewOSCClient()
validRegions, err := listOAPIRegions(oapiconn)
validRegions, err := listOSCRegions(oscconn.RegionApi)
if err != nil {
return err
}
+86 -11
View File
@@ -1,16 +1,20 @@
package common
import (
"context"
"errors"
"fmt"
"time"
"net/http"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type oapiDescriber interface {
POST_ReadVms(oapi.ReadVmsRequest) (*oapi.POST_ReadVmsResponses, error)
type oscDescriber interface {
ReadVms(ctx context.Context, localVarOptionals *osc.ReadVmsOpts) (osc.ReadVmsResponse, *http.Response, error)
}
var (
@@ -20,13 +24,13 @@ var (
// SSHHost returns a function that can be given to the SSH communicator
// for determining the SSH address based on the vm DNS name.
func SSHHost(e oapiDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
func SSHHost(e oscDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
const tries = 2
// <= with current structure to check result of describing `tries` times
for j := 0; j <= tries; j++ {
var host string
i := state.Get("vm").(oapi.Vm)
i := state.Get("vm").(osc.Vm)
if sshInterface != "" {
switch sshInterface {
@@ -63,20 +67,91 @@ func SSHHost(e oapiDescriber, sshInterface string) func(multistep.StateBag) (str
return host, nil
}
r, err := e.POST_ReadVms(oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []string{i.VmId},
},
r, _, err := e.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
Filters: osc.FiltersVm{
VmIds: []string{i.VmId},
},
}),
})
if err != nil {
return "", err
}
if len(r.OK.Vms) == 0 {
if len(r.Vms) == 0 {
return "", fmt.Errorf("vm not found: %s", i.VmId)
}
state.Put("vm", r.OK.Vms[0])
state.Put("vm", r.Vms[0])
time.Sleep(sshHostSleepDuration)
}
return "", errors.New("couldn't determine address for vm")
}
}
// SSHHost returns a function that can be given to the SSH communicator
// for determining the SSH address based on the vm DNS name.
func OscSSHHost(e oscDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
const tries = 2
// <= with current structure to check result of describing `tries` times
for j := 0; j <= tries; j++ {
var host string
i := state.Get("vm").(osc.Vm)
if sshInterface != "" {
switch sshInterface {
case "public_ip":
if i.PublicIp != "" {
host = i.PublicIp
}
case "public_dns":
if i.PublicDnsName != "" {
host = i.PublicDnsName
}
case "private_ip":
if i.PrivateIp != "" {
host = i.PrivateIp
}
case "private_dns":
if i.PrivateDnsName != "" {
host = i.PrivateDnsName
}
default:
panic(fmt.Sprintf("Unknown interface type: %s", sshInterface))
}
} else if i.NetId != "" {
if i.PublicIp != "" {
host = i.PublicIp
} else if i.PrivateIp != "" {
host = i.PrivateIp
}
} else if i.PublicDnsName != "" {
host = i.PublicDnsName
}
if host != "" {
return host, nil
}
r, _, err := e.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
Filters: osc.FiltersVm{
VmIds: []string{i.VmId},
},
}),
})
if err != nil {
return "", err
}
if len(r.Vms) == 0 {
return "", fmt.Errorf("vm not found: %s", i.VmId)
}
state.Put("vm", r.Vms[0])
time.Sleep(sshHostSleepDuration)
}
+96 -152
View File
@@ -1,78 +1,73 @@
package common
import (
"context"
"fmt"
"log"
"github.com/antihax/optional"
"github.com/hashicorp/packer/common"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type stateRefreshFunc func() (string, error)
func waitForSecurityGroup(conn *oapi.Client, securityGroupID string) error {
func waitUntilForOscVmRunning(conn *osc.APIClient, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "exists", securityGroupWaitFunc(conn, securityGroupID))
go waitForState(errCh, "running", waitUntilOscVmStateFunc(conn, vmID))
err := <-errCh
return err
}
func waitUntilForVmRunning(conn *oapi.Client, vmID string) error {
func waitUntilOscVmDeleted(conn *osc.APIClient, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "running", waitUntilVmStateFunc(conn, vmID))
err := <-errCh
return err
}
func waitUntilVmDeleted(conn *oapi.Client, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "terminated", waitUntilVmStateFunc(conn, vmID))
go waitForState(errCh, "terminated", waitUntilOscVmStateFunc(conn, vmID))
return <-errCh
}
func waitUntilVmStopped(conn *oapi.Client, vmID string) error {
func waitUntilOscVmStopped(conn *osc.APIClient, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "stopped", waitUntilVmStateFunc(conn, vmID))
go waitForState(errCh, "stopped", waitUntilOscVmStateFunc(conn, vmID))
return <-errCh
}
func WaitUntilSnapshotCompleted(conn *oapi.Client, id string) error {
func WaitUntilOscSnapshotCompleted(conn *osc.APIClient, id string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "completed", waitUntilSnapshotStateFunc(conn, id))
go waitForState(errCh, "completed", waitUntilOscSnapshotStateFunc(conn, id))
return <-errCh
}
func WaitUntilImageAvailable(conn *oapi.Client, imageID string) error {
func WaitUntilOscImageAvailable(conn *osc.APIClient, imageID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "available", waitUntilImageStateFunc(conn, imageID))
go waitForState(errCh, "available", waitUntilOscImageStateFunc(conn, imageID))
return <-errCh
}
func WaitUntilVolumeAvailable(conn *oapi.Client, volumeID string) error {
func WaitUntilOscVolumeAvailable(conn *osc.APIClient, volumeID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "available", volumeWaitFunc(conn, volumeID))
go waitForState(errCh, "available", volumeOscWaitFunc(conn, volumeID))
return <-errCh
}
func WaitUntilVolumeIsLinked(conn *oapi.Client, volumeID string) error {
func WaitUntilOscVolumeIsLinked(conn *osc.APIClient, volumeID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "attached", waitUntilVolumeLinkedStateFunc(conn, volumeID))
go waitForState(errCh, "attached", waitUntilOscVolumeLinkedStateFunc(conn, volumeID))
return <-errCh
}
func WaitUntilVolumeIsUnlinked(conn *oapi.Client, volumeID string) error {
func WaitUntilOscVolumeIsUnlinked(conn *osc.APIClient, volumeID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "dettached", waitUntilVolumeUnLinkedStateFunc(conn, volumeID))
go waitForState(errCh, "dettached", waitUntilOscVolumeUnLinkedStateFunc(conn, volumeID))
return <-errCh
}
func WaitUntilSnapshotDone(conn *oapi.Client, snapshotID string) error {
func WaitUntilOscSnapshotDone(conn *osc.APIClient, snapshotID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "completed", waitUntilSnapshotDoneStateFunc(conn, snapshotID))
go waitForState(errCh, "completed", waitUntilOscSnapshotDoneStateFunc(conn, snapshotID))
return <-errCh
}
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error {
func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
err := common.Retry(2, 2, 0, func(_ uint) (bool, error) {
state, err := refresh()
if err != nil {
@@ -83,91 +78,83 @@ func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) e
return false, nil
})
errCh <- err
return err
}
func waitUntilVmStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
func waitUntilOscVmStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Retrieving state for VM with id %s", id)
resp, err := conn.POST_ReadVms(oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []string{id},
},
resp, _, err := conn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(osc.ReadVmsRequest{
Filters: osc.FiltersVm{
VmIds: []string{id},
},
}),
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s not Found", id)
}
//TODO: check if needed
// if resp == nil {
// return "", fmt.Errorf("Vm with ID %s not Found", id)
// }
if len(resp.OK.Vms) == 0 {
if len(resp.Vms) == 0 {
return "pending", nil
}
return resp.OK.Vms[0].State, nil
return resp.Vms[0].State, nil
}
}
func waitUntilVolumeLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
func waitUntilOscVolumeLinkedStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if volume with id %s exists", id)
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: []string{id},
},
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: []string{id},
},
}),
})
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
}
log.Printf("[Debug] Read Response %+v", resp.OK)
if len(resp.OK.Volumes) == 0 {
if len(resp.Volumes) == 0 {
return "pending", nil
}
if len(resp.OK.Volumes[0].LinkedVolumes) == 0 {
if len(resp.Volumes[0].LinkedVolumes) == 0 {
return "pending", nil
}
return resp.OK.Volumes[0].LinkedVolumes[0].State, nil
return resp.Volumes[0].LinkedVolumes[0].State, nil
}
}
func waitUntilVolumeUnLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
func waitUntilOscVolumeUnLinkedStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if volume with id %s exists", id)
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: []string{id},
},
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: []string{id},
},
}),
})
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
}
log.Printf("[Debug] Read Response %+v", resp.OK)
if len(resp.OK.Volumes) == 0 {
if len(resp.Volumes) == 0 {
return "pending", nil
}
if len(resp.OK.Volumes[0].LinkedVolumes) == 0 {
if len(resp.Volumes[0].LinkedVolumes) == 0 {
return "dettached", nil
}
@@ -175,149 +162,106 @@ func waitUntilVolumeUnLinkedStateFunc(conn *oapi.Client, id string) stateRefresh
}
}
func waitUntilSnapshotStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
func waitUntilOscSnapshotStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if Snapshot with id %s exists", id)
resp, err := conn.POST_ReadSnapshots(oapi.ReadSnapshotsRequest{
Filters: oapi.FiltersSnapshot{
SnapshotIds: []string{id},
},
resp, _, err := conn.SnapshotApi.ReadSnapshots(context.Background(), &osc.ReadSnapshotsOpts{
ReadSnapshotsRequest: optional.NewInterface(osc.ReadSnapshotsRequest{
Filters: osc.FiltersSnapshot{
SnapshotIds: []string{id},
},
}),
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
}
if len(resp.OK.Snapshots) == 0 {
if len(resp.Snapshots) == 0 {
return "pending", nil
}
return resp.OK.Snapshots[0].State, nil
return resp.Snapshots[0].State, nil
}
}
func waitUntilImageStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
func waitUntilOscImageStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if Image with id %s exists", id)
resp, err := conn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{id},
},
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: []string{id},
},
}),
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Vm with ID %s. Not Found", id)
}
if len(resp.OK.Images) == 0 {
if len(resp.Images) == 0 {
return "pending", nil
}
if resp.OK.Images[0].State == "failed" {
return resp.OK.Images[0].State, fmt.Errorf("Image (%s) creation is failed", id)
if resp.Images[0].State == "failed" {
return resp.Images[0].State, fmt.Errorf("Image (%s) creation is failed", id)
}
return resp.OK.Images[0].State, nil
return resp.Images[0].State, nil
}
}
func securityGroupWaitFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if SG with id %s exists", id)
resp, err := conn.POST_ReadSecurityGroups(oapi.ReadSecurityGroupsRequest{
Filters: oapi.FiltersSecurityGroup{
SecurityGroupIds: []string{id},
},
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Security Group with ID %s. Not Found", id)
}
if len(resp.OK.SecurityGroups) == 0 {
return "waiting", nil
}
return "exists", nil
}
}
func waitUntilSnapshotDoneStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
func waitUntilOscSnapshotDoneStateFunc(conn *osc.APIClient, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if Snapshot with id %s exists", id)
resp, err := conn.POST_ReadSnapshots(oapi.ReadSnapshotsRequest{
Filters: oapi.FiltersSnapshot{
SnapshotIds: []string{id},
},
resp, _, err := conn.SnapshotApi.ReadSnapshots(context.Background(), &osc.ReadSnapshotsOpts{
ReadSnapshotsRequest: optional.NewInterface(osc.ReadSnapshotsRequest{
Filters: osc.FiltersSnapshot{
SnapshotIds: []string{id},
},
}),
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
if len(resp.Snapshots) == 0 {
return "", fmt.Errorf("Snapshot with ID %s. Not Found", id)
}
if len(resp.OK.Snapshots) == 0 {
return "", fmt.Errorf("Snapshot with ID %s. Not Found", id)
if resp.Snapshots[0].State == "error" {
return resp.Snapshots[0].State, fmt.Errorf("Snapshot (%s) creation is failed", id)
}
if resp.OK.Snapshots[0].State == "error" {
return resp.OK.Snapshots[0].State, fmt.Errorf("Snapshot (%s) creation is failed", id)
}
return resp.OK.Snapshots[0].State, nil
return resp.Snapshots[0].State, nil
}
}
func volumeWaitFunc(conn *oapi.Client, id string) stateRefreshFunc {
func volumeOscWaitFunc(conn *osc.APIClient, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if SvolumeG with id %s exists", id)
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: []string{id},
},
resp, _, err := conn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: []string{id},
},
}),
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Volume with ID %s. Not Found", id)
}
if len(resp.OK.Volumes) == 0 {
if len(resp.Volumes) == 0 {
return "waiting", nil
}
if resp.OK.Volumes[0].State == "error" {
return resp.OK.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id)
if resp.Volumes[0].State == "error" {
return resp.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id)
}
return resp.OK.Volumes[0].State, nil
return resp.Volumes[0].State, nil
}
}
+20 -14
View File
@@ -5,29 +5,32 @@ import (
"fmt"
"reflect"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// stepCleanupVolumes cleans up any orphaned volumes that were not designated to
// StepCleanupVolumes cleans up any orphaned volumes that were not designated to
// remain after termination of the vm. These volumes are typically ones
// that are marked as "delete on terminate:false" in the source_ami of a build.
type StepCleanupVolumes struct {
BlockDevices BlockDevices
}
//Run ...
func (s *StepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// stepCleanupVolumes is for Cleanup only
return multistep.ActionContinue
}
// Cleanup ...
func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
vmRaw := state.Get("vm")
var vm oapi.Vm
var vm osc.Vm
if vmRaw != nil {
vm = vmRaw.(oapi.Vm)
vm = vmRaw.(osc.Vm)
}
ui := state.Get("ui").(packer.Ui)
if vm.VmId == "" {
@@ -42,7 +45,7 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
var vl []string
volList := make(map[string]string)
for _, bdm := range vm.BlockDeviceMappings {
if !reflect.DeepEqual(bdm.Bsu, oapi.BsuCreated{}) {
if !reflect.DeepEqual(bdm.Bsu, osc.BsuCreated{}) {
vl = append(vl, bdm.Bsu.VolumeId)
volList[bdm.Bsu.VolumeId] = bdm.DeviceName
}
@@ -50,10 +53,12 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
// Using the volume list from the cached Vm, check with Outscale for up to
// date information on them
resp, err := oapiconn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: vl,
},
resp, _, err := oscconn.VolumeApi.ReadVolumes(context.Background(), &osc.ReadVolumesOpts{
ReadVolumesRequest: optional.NewInterface(osc.ReadVolumesRequest{
Filters: osc.FiltersVolume{
VolumeIds: vl,
},
}),
})
if err != nil {
@@ -63,13 +68,13 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
// If any of the returned volumes are in a "deleting" stage or otherwise not
// available, remove them from the list of volumes
for _, v := range resp.OK.Volumes {
for _, v := range resp.Volumes {
if v.State != "" && v.State != "available" {
delete(volList, v.VolumeId)
}
}
if len(resp.OK.Volumes) == 0 {
if len(resp.Volumes) == 0 {
ui.Say("No volumes to clean up, skipping")
return
}
@@ -87,10 +92,11 @@ func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
// Destroy remaining volumes
for k := range volList {
ui.Say(fmt.Sprintf("Destroying volume (%s)...", k))
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: k})
_, _, err := oscconn.VolumeApi.DeleteVolume(context.Background(), &osc.DeleteVolumeOpts{
DeleteVolumeRequest: optional.NewInterface(osc.DeleteVolumeRequest{VolumeId: k}),
})
if err != nil {
ui.Say(fmt.Sprintf("Error deleting volume: %s", err))
}
}
}
+24 -34
View File
@@ -2,16 +2,15 @@ package common
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/antihax/optional"
"github.com/aws/aws-sdk-go/aws/awserr"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type StepCreateTags struct {
@@ -21,8 +20,7 @@ type StepCreateTags struct {
}
func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
config := state.Get("clientConfig").(*oapi.Config)
config := state.Get("accessConfig").(*AccessConfig)
ui := state.Get("ui").(packer.Ui)
omis := state.Get("omis").(map[string]string)
@@ -34,28 +32,16 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
for region, ami := range omis {
ui.Say(fmt.Sprintf("Adding tags to OMI (%s)...", ami))
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionConn := oapi.NewClient(newConfig, skipClient)
regionconn := config.NewOSCClientByRegion(region)
// Retrieve image list for given OMI
resourceIds := []string{ami}
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: resourceIds,
},
imageResp, _, err := regionconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageIds: resourceIds,
},
}),
})
if err != nil {
@@ -65,14 +51,14 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
return multistep.ActionHalt
}
if len(imageResp.OK.Images) == 0 {
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Error retrieving details for OMI (%s), no images found", ami)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := imageResp.OK.Images[0]
image := imageResp.Images[0]
snapshotIds := []string{}
// Add only those with a Snapshot ID, i.e. not Ephemeral
@@ -86,7 +72,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
// Convert tags to oapi.Tag format
ui.Say("Creating OMI tags")
amiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
amiTags, err := s.Tags.OSCTags(s.Ctx, config.RawRegion, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
@@ -95,7 +81,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
amiTags.Report(ui)
ui.Say("Creating snapshot tags")
snapshotTags, err := s.SnapshotTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
snapshotTags, err := s.SnapshotTags.OSCTags(s.Ctx, config.RawRegion, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
@@ -106,9 +92,11 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
// Tag images and snapshots
_, err := regionConn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: resourceIds,
Tags: amiTags,
_, _, err := regionconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: resourceIds,
Tags: amiTags,
}),
})
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidOMIID.NotFound" ||
@@ -119,9 +107,11 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
// Override tags on snapshots
if len(snapshotTags) > 0 {
_, err = regionConn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: snapshotIds,
Tags: snapshotTags,
_, _, err = regionconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: snapshotIds,
Tags: snapshotTags,
}),
})
}
if err == nil {
+36 -39
View File
@@ -2,13 +2,14 @@ package common
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"log"
"github.com/antihax/optional"
"github.com/outscale/osc-sdk-go/osc"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
type StepDeregisterOMI struct {
@@ -26,76 +27,72 @@ func (s *StepDeregisterOMI) Run(_ context.Context, state multistep.StateBag) mul
}
ui := state.Get("ui").(packer.Ui)
oapiconn := state.Get("oapi").(*oapi.Client)
// Add the session region to list of regions will will deregister OMIs in
regions := append(s.Regions, oapiconn.GetConfig().Region)
for _, region := range regions {
s.Regions = append(s.Regions, s.AccessConfig.GetRegion())
log.Printf("LOG_ s.Regions: %#+v\n", s.Regions)
for _, region := range s.Regions {
// get new connection for each region in which we need to deregister vms
config, err := s.AccessConfig.Config()
if err != nil {
return multistep.ActionHalt
}
conn := s.AccessConfig.NewOSCClientByRegion(region)
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionconn := oapi.NewClient(newConfig, skipClient)
resp, err := regionconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageNames: []string{s.OMIName},
AccountAliases: []string{"self"},
},
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageNames: []string{s.OMIName},
//AccountAliases: []string{"self"},
},
}),
})
if err != nil {
err := fmt.Errorf("Error describing OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("LOG_ resp.Images: %#+v\n", resp.Images)
// Deregister image(s) by name
for _, i := range resp.OK.Images {
for i := range resp.Images {
//We are supposing that DeleteImage does the same action as DeregisterImage
_, err := regionconn.POST_DeleteImage(oapi.DeleteImageRequest{
ImageId: i.ImageId,
_, _, err := conn.ImageApi.DeleteImage(context.Background(), &osc.DeleteImageOpts{
DeleteImageRequest: optional.NewInterface(osc.DeleteImageRequest{
ImageId: resp.Images[i].ImageId,
}),
})
if err != nil {
err := fmt.Errorf("Error deregistering existing OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deregistered OMI %s, id: %s", s.OMIName, i.ImageId))
ui.Say(fmt.Sprintf("Deregistered OMI %s, id: %s", s.OMIName, resp.Images[i].ImageId))
// Delete snapshot(s) by image
if s.ForceDeleteSnapshot {
for _, b := range i.BlockDeviceMappings {
for _, b := range resp.Images[i].BlockDeviceMappings {
if b.Bsu.SnapshotId != "" {
_, err := regionconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{
SnapshotId: b.Bsu.SnapshotId,
_, _, err := conn.SnapshotApi.DeleteSnapshot(context.Background(), &osc.DeleteSnapshotOpts{
DeleteSnapshotRequest: optional.NewInterface(osc.DeleteSnapshotRequest{
SnapshotId: b.Bsu.SnapshotId,
}),
})
if err != nil {
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deleted snapshot: %s", b.Bsu.SnapshotId))
}
}
+10 -7
View File
@@ -11,10 +11,11 @@ import (
"log"
"time"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepGetPassword reads the password from a Windows server and sets it
@@ -102,8 +103,8 @@ WaitLoop:
func (s *StepGetPassword) Cleanup(multistep.StateBag) {}
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
privateKey := s.Comm.SSHPrivateKey
for {
@@ -114,17 +115,19 @@ func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-cha
case <-time.After(5 * time.Second):
}
resp, err := oapiconn.POST_ReadAdminPassword(oapi.ReadAdminPasswordRequest{
VmId: vm.VmId,
resp, _, err := oscconn.VmApi.ReadAdminPassword(context.Background(), &osc.ReadAdminPasswordOpts{
ReadAdminPasswordRequest: optional.NewInterface(osc.ReadAdminPasswordRequest{
VmId: vm.VmId,
}),
})
if err != nil {
err := fmt.Errorf("Error retrieving auto-generated vm password: %s", err)
return "", err
}
if resp.OK.AdminPassword != "" {
if resp.AdminPassword != "" {
decryptedPassword, err := decryptPasswordDataWithPrivateKey(
resp.OK.AdminPassword, []byte(privateKey))
resp.AdminPassword, privateKey)
if err != nil {
err := fmt.Errorf("Error decrypting auto-generated vm password: %s", err)
return "", err
+22 -9
View File
@@ -6,10 +6,11 @@ import (
"os"
"runtime"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type StepKeyPair struct {
@@ -52,11 +53,16 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
return multistep.ActionContinue
}
oapiconn := state.Get("oapi").(*oapi.Client)
conn := state.Get("osc").(*osc.APIClient)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
keyResp, err := oapiconn.POST_CreateKeypair(oapi.CreateKeypairRequest{
KeypairName: s.Comm.SSHTemporaryKeyPairName})
resp, _, err := conn.KeypairApi.CreateKeypair(context.Background(), &osc.CreateKeypairOpts{
CreateKeypairRequest: optional.NewInterface(osc.CreateKeypairRequest{
KeypairName: s.Comm.SSHTemporaryKeyPairName,
}),
})
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
@@ -66,7 +72,7 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
// Set some data for use in future steps
s.Comm.SSHKeyPairName = s.Comm.SSHTemporaryKeyPairName
s.Comm.SSHPrivateKey = []byte(keyResp.OK.Keypair.PrivateKey)
s.Comm.SSHPrivateKey = []byte(resp.Keypair.PrivateKey)
// If we're in debug mode, output the private key to the working
// directory.
@@ -80,7 +86,7 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(keyResp.OK.Keypair.PrivateKey)); err != nil {
if _, err := f.Write([]byte(resp.Keypair.PrivateKey)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
@@ -102,12 +108,19 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
var (
conn = state.Get("osc").(*osc.APIClient)
ui = state.Get("ui").(packer.Ui)
)
// Remove the keypair
ui.Say("Deleting temporary keypair...")
_, err := oapiconn.POST_DeleteKeypair(oapi.DeleteKeypairRequest{KeypairName: s.Comm.SSHTemporaryKeyPairName})
_, _, err := conn.KeypairApi.DeleteKeypair(context.Background(), &osc.DeleteKeypairOpts{
DeleteKeypairRequest: optional.NewInterface(osc.DeleteKeypairRequest{
KeypairName: s.Comm.SSHTemporaryKeyPairName,
}),
})
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
+40 -32
View File
@@ -7,9 +7,10 @@ import (
"math/rand"
"sort"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepNetworkInfo queries OUTSCALE for information about
@@ -29,34 +30,37 @@ type StepNetworkInfo struct {
SecurityGroupFilter SecurityGroupFilterOptions
}
type subnetsSort []oapi.Subnet
type subnetsOscSort []osc.Subnet
func (a subnetsSort) Len() int { return len(a) }
func (a subnetsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a subnetsSort) Less(i, j int) bool {
func (a subnetsOscSort) Len() int { return len(a) }
func (a subnetsOscSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a subnetsOscSort) Less(i, j int) bool {
return a[i].AvailableIpsCount < a[j].AvailableIpsCount
}
// Returns the most recent OMI out of a slice of images.
func mostFreeSubnet(subnets []oapi.Subnet) oapi.Subnet {
func mostFreeOscSubnet(subnets []osc.Subnet) osc.Subnet {
sortedSubnets := subnets
sort.Sort(subnetsSort(sortedSubnets))
sort.Sort(subnetsOscSort(sortedSubnets))
return sortedSubnets[len(sortedSubnets)-1]
}
//Run ...
func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
// NET
if s.NetId == "" && !s.NetFilter.Empty() {
params := oapi.ReadNetsRequest{}
params.Filters = buildNetFilters(s.NetFilter.Filters)
params := osc.ReadNetsRequest{}
params.Filters = buildOscNetFilters(s.NetFilter.Filters)
s.NetFilter.Filters["state"] = "available"
log.Printf("Using NET Filters %v", params)
vpcResp, err := oapiconn.POST_ReadNets(params)
vpcResp, _, err := oscconn.NetApi.ReadNets(context.Background(), &osc.ReadNetsOpts{
ReadNetsRequest: optional.NewInterface(params),
})
if err != nil {
err := fmt.Errorf("Error querying NETs: %s", err)
state.Put("error", err)
@@ -64,20 +68,20 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
if len(vpcResp.OK.Nets) != 1 {
err := fmt.Errorf("Exactly one NET should match the filter, but %d NET's was found matching filters: %v", len(vpcResp.OK.Nets), params)
if len(vpcResp.Nets) != 1 {
err := fmt.Errorf("Exactly one NET should match the filter, but %d NET's was found matching filters: %v", len(vpcResp.Nets), params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.NetId = vpcResp.OK.Nets[0].NetId
s.NetId = vpcResp.Nets[0].NetId
ui.Message(fmt.Sprintf("Found NET ID: %s", s.NetId))
}
// Subnet
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
params := oapi.ReadSubnetsRequest{}
params := osc.ReadSubnetsRequest{}
s.SubnetFilter.Filters["state"] = "available"
if s.NetId != "" {
@@ -86,39 +90,41 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
if s.SubregionName != "" {
s.SubnetFilter.Filters["availability-zone"] = s.SubregionName
}
params.Filters = buildSubnetFilters(s.SubnetFilter.Filters)
params.Filters = buildOscSubnetFilters(s.SubnetFilter.Filters)
log.Printf("Using Subnet Filters %v", params)
subnetsResp, err := oapiconn.POST_ReadSubnets(params)
subnetsResp, _, err := oscconn.SubnetApi.ReadSubnets(context.Background(), &osc.ReadSubnetsOpts{
ReadSubnetsRequest: optional.NewInterface(params),
})
if err != nil {
err := fmt.Errorf("Error querying Subnets: %s", err)
err := fmt.Errorf("error querying Subnets: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(subnetsResp.OK.Subnets) == 0 {
if len(subnetsResp.Subnets) == 0 {
err := fmt.Errorf("No Subnets was found matching filters: %v", params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(subnetsResp.OK.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
err := fmt.Errorf("Your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true.", len(subnetsResp.OK.Subnets))
if len(subnetsResp.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
err := fmt.Errorf("your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true", len(subnetsResp.Subnets))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var subnet oapi.Subnet
var subnet osc.Subnet
switch {
case s.SubnetFilter.MostFree:
subnet = mostFreeSubnet(subnetsResp.OK.Subnets)
subnet = mostFreeOscSubnet(subnetsResp.Subnets)
case s.SubnetFilter.Random:
subnet = subnetsResp.OK.Subnets[rand.Intn(len(subnetsResp.OK.Subnets))]
subnet = subnetsResp.Subnets[rand.Intn(len(subnetsResp.Subnets))]
default:
subnet = subnetsResp.OK.Subnets[0]
subnet = subnetsResp.Subnets[0]
}
s.SubnetId = subnet.SubnetId
ui.Message(fmt.Sprintf("Found Subnet ID: %s", s.SubnetId))
@@ -127,24 +133,25 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
// Try to find Subregion and NET Id from Subnet if they are not yet found/given
if s.SubnetId != "" && (s.SubregionName == "" || s.NetId == "") {
log.Printf("[INFO] Finding Subregion and NetId for the given subnet '%s'", s.SubnetId)
resp, err := oapiconn.POST_ReadSubnets(
oapi.ReadSubnetsRequest{
Filters: oapi.FiltersSubnet{
resp, _, err := oscconn.SubnetApi.ReadSubnets(context.Background(), &osc.ReadSubnetsOpts{
ReadSubnetsRequest: optional.NewInterface(osc.ReadSubnetsRequest{
Filters: osc.FiltersSubnet{
SubnetIds: []string{s.SubnetId},
},
})
}),
})
if err != nil {
err := fmt.Errorf("Describing the subnet: %s returned error: %s.", s.SubnetId, err)
err := fmt.Errorf("describing the subnet: %s returned error: %s", s.SubnetId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.SubregionName == "" {
s.SubregionName = resp.OK.Subnets[0].SubregionName
s.SubregionName = resp.Subnets[0].SubregionName
log.Printf("[INFO] SubregionName found: '%s'", s.SubregionName)
}
if s.NetId == "" {
s.NetId = resp.OK.Subnets[0].NetId
s.NetId = resp.Subnets[0].NetId
log.Printf("[INFO] NetId found: '%s'", s.NetId)
}
}
@@ -155,4 +162,5 @@ func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionContinue
}
//Cleanup ...
func (s *StepNetworkInfo) Cleanup(multistep.StateBag) {}
+16 -10
View File
@@ -4,9 +4,10 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepPreValidate provides an opportunity to pre-validate any configuration for
@@ -15,6 +16,7 @@ import (
type StepPreValidate struct {
DestOmiName string
ForceDeregister bool
API string
}
func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
@@ -24,11 +26,19 @@ func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionContinue
}
oapiconn := state.Get("oapi").(*oapi.Client)
var (
conn = state.Get("osc").(*osc.APIClient)
images []interface{}
)
ui.Say(fmt.Sprintf("Prevalidating OMI Name: %s", s.DestOmiName))
resp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{ImageNames: []string{s.DestOmiName}},
resp, _, err := conn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(osc.ReadImagesRequest{
Filters: osc.FiltersImage{
ImageNames: []string{s.DestOmiName},
},
}),
})
if err != nil {
@@ -38,18 +48,14 @@ func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
//FIXME: Remove when the oAPI filters works
images := make([]oapi.Image, 0)
for _, omi := range resp.OK.Images {
for _, omi := range resp.Images {
if omi.ImageName == s.DestOmiName {
images = append(images, omi)
}
}
//if len(resp.OK.Images) > 0 {
if len(images) > 0 {
err := fmt.Errorf("Error: name conflicts with an existing OMI: %s", resp.OK.Images[0].ImageId)
err := fmt.Errorf("Error: name conflicts with an existing OMI: %s", s.DestOmiName)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
+27 -17
View File
@@ -4,10 +4,11 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type StepPublicIp struct {
@@ -20,20 +21,24 @@ type StepPublicIp struct {
}
func (s *StepPublicIp) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
oapiconn := state.Get("oapi").(*oapi.Client)
netId := state.Get("net_id").(string)
subnetId := state.Get("subnet_id").(string)
var (
ui = state.Get("ui").(packer.Ui)
conn = state.Get("osc").(*osc.APIClient)
)
if !s.AssociatePublicIpAddress {
if netId == "" || subnetId == "" || !s.AssociatePublicIpAddress {
// In this case, we are in the public Cloud, so we'll
// not explicitely allocate a public IP.
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Creating temporary PublicIp for instance in subnet %s (net %s)", subnetId, netId))
ui.Say("Creating temporary PublicIp for instance ")
resp, _, err := conn.PublicIpApi.CreatePublicIp(context.Background(), &osc.CreatePublicIpOpts{
CreatePublicIpRequest: optional.NewInterface(osc.CreatePublicIpRequest{}),
})
publicIpResp, err := oapiconn.POST_CreatePublicIp(oapi.CreatePublicIpRequest{})
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary PublicIp: %s", err))
return multistep.ActionHalt
@@ -43,8 +48,8 @@ func (s *StepPublicIp) Run(_ context.Context, state multistep.StateBag) multiste
s.doCleanup = true
// Set some data for use in future steps
s.publicIpId = publicIpResp.OK.PublicIp.PublicIpId
state.Put("publicip_id", publicIpResp.OK.PublicIp.PublicIpId)
s.publicIpId = resp.PublicIp.PublicIpId
state.Put("publicip_id", resp.PublicIp.PublicIpId)
return multistep.ActionContinue
}
@@ -54,15 +59,20 @@ func (s *StepPublicIp) Cleanup(state multistep.StateBag) {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
var (
conn = state.Get("osc").(*osc.APIClient)
ui = state.Get("ui").(packer.Ui)
)
// Remove the Public IP
ui.Say("Deleting temporary PublicIp...")
_, err := oapiconn.POST_DeletePublicIp(oapi.DeletePublicIpRequest{PublicIpId: s.publicIpId})
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up PublicIp. Please delete the PublicIp manually: %s", s.publicIpId))
}
_, _, err := conn.PublicIpApi.DeletePublicIp(context.Background(), &osc.DeletePublicIpOpts{
DeletePublicIpRequest: optional.NewInterface(osc.DeletePublicIpRequest{
PublicIpId: s.publicIpId,
}),
})
if err != nil {
ui.Error(fmt.Sprintf("Error cleaning up PublicIp. Please delete the PublicIp manually: %s", s.publicIpId))
}
}
+51 -70
View File
@@ -8,8 +8,9 @@ import (
"log"
"reflect"
"github.com/antihax/optional"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
@@ -39,13 +40,13 @@ type StepRunSourceVm struct {
UserData string
UserDataFile string
VolumeTags TagMap
RawRegion string
vmId string
}
func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
securityGroupIds := state.Get("securityGroupIds").([]string)
ui := state.Get("ui").(packer.Ui)
@@ -67,7 +68,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
ui.Say("Launching a source OUTSCALE vm...")
image, ok := state.Get("source_image").(oapi.Image)
image, ok := state.Get("source_image").(osc.Image)
if !ok {
state.Put("error", fmt.Errorf("source_image type assertion failed"))
return multistep.ActionHalt
@@ -89,7 +90,9 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
s.Tags["Name"] = "Packer Builder"
}
oapiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
rawRegion := s.RawRegion
oscTags, err := s.Tags.OSCTags(s.Ctx, rawRegion, state)
if err != nil {
err := fmt.Errorf("Error tagging source vm: %s", err)
state.Put("error", err)
@@ -97,7 +100,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
return multistep.ActionHalt
}
volTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
volTags, err := s.VolumeTags.OSCTags(s.Ctx, rawRegion, state)
if err != nil {
err := fmt.Errorf("Error tagging volumes: %s", err)
state.Put("error", err)
@@ -106,73 +109,42 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
subregion := state.Get("subregion_name").(string)
runOpts := oapi.CreateVmsRequest{
runOpts := osc.CreateVmsRequest{
ImageId: s.SourceOMI,
VmType: s.VmType,
UserData: userData,
MaxVmsCount: 1,
MinVmsCount: 1,
Placement: oapi.Placement{SubregionName: subregion},
Placement: osc.Placement{SubregionName: subregion},
BsuOptimized: s.BsuOptimized,
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
//IamVmProfile: oapi.IamVmProfileSpecification{Name: &s.IamVmProfile},
BlockDeviceMappings: s.BlockDevices.BuildOSCLaunchDevices(),
}
// if s.EnableT2Unlimited {
// creditOption := "unlimited"
// runOpts.CreditSpecification = &oapi.CreditSpecificationRequest{CpuCredits: &creditOption}
// }
// Collect tags for tagging on resource creation
// var tagSpecs []oapi.ResourceTag
// if len(oapiTags) > 0 {
// runTags := &oapi.ResourceTag{
// ResourceType: aws.String("vm"),
// Tags: oapiTags,
// }
// tagSpecs = append(tagSpecs, runTags)
// }
// if len(volTags) > 0 {
// runVolTags := &oapi.TagSpecification{
// ResourceType: aws.String("volume"),
// Tags: volTags,
// }
// tagSpecs = append(tagSpecs, runVolTags)
// }
// // If our region supports it, set tag specifications
// if len(tagSpecs) > 0 && !s.IsRestricted {
// runOpts.SetTagSpecifications(tagSpecs)
// oapiTags.Report(ui)
// volTags.Report(ui)
// }
if s.Comm.SSHKeyPairName != "" {
runOpts.KeypairName = s.Comm.SSHKeyPairName
}
subnetId := state.Get("subnet_id").(string)
subnetID := state.Get("subnet_id").(string)
runOpts.SubnetId = subnetId
runOpts.SubnetId = subnetID
runOpts.SecurityGroupIds = securityGroupIds
if s.ExpectedRootDevice == "bsu" {
runOpts.VmInitiatedShutdownBehavior = s.VmInitiatedShutdownBehavior
}
runResp, err := oapiconn.POST_CreateVms(runOpts)
runResp, _, err := oscconn.VmApi.CreateVms(context.Background(), &osc.CreateVmsOpts{
CreateVmsRequest: optional.NewInterface(runOpts),
})
if err != nil {
err := fmt.Errorf("Error launching source vm: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vmId = runResp.OK.Vms[0].VmId
volumeId := runResp.OK.Vms[0].BlockDeviceMappings[0].Bsu.VolumeId
vmId = runResp.Vms[0].VmId
volumeId := runResp.Vms[0].BlockDeviceMappings[0].Bsu.VolumeId
// Set the vm ID so that the cleanup works properly
s.vmId = vmId
@@ -180,12 +152,12 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
ui.Message(fmt.Sprintf("Vm ID: %s", vmId))
ui.Say(fmt.Sprintf("Waiting for vm (%v) to become ready...", vmId))
request := oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
request := osc.ReadVmsRequest{
Filters: osc.FiltersVm{
VmIds: []string{vmId},
},
}
if err := waitUntilForVmRunning(oapiconn, vmId); err != nil {
if err := waitUntilForOscVmRunning(oscconn, vmId); err != nil {
err := fmt.Errorf("Error waiting for vm (%s) to become ready: %s", vmId, err)
state.Put("error", err)
ui.Error(err.Error())
@@ -193,8 +165,8 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
//Set Vm tags and vollume tags
if len(oapiTags) > 0 {
if err := CreateTags(oapiconn, s.vmId, ui, oapiTags); err != nil {
if len(oscTags) > 0 {
if err := CreateOSCTags(oscconn, s.vmId, ui, oscTags); err != nil {
err := fmt.Errorf("Error creating tags for vm (%s): %s", s.vmId, err)
state.Put("error", err)
ui.Error(err.Error())
@@ -203,7 +175,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
if len(volTags) > 0 {
if err := CreateTags(oapiconn, volumeId, ui, volTags); err != nil {
if err := CreateOSCTags(oscconn, volumeId, ui, volTags); err != nil {
err := fmt.Errorf("Error creating tags for volume (%s): %s", volumeId, err)
state.Put("error", err)
ui.Error(err.Error())
@@ -213,7 +185,9 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
if publicip_id, ok := state.Get("publicip_id").(string); ok {
ui.Say(fmt.Sprintf("Linking temporary PublicIp %s to instance %s", publicip_id, vmId))
_, err := oapiconn.POST_LinkPublicIp(oapi.LinkPublicIpRequest{PublicIpId: publicip_id, VmId: vmId})
_, _, err := oscconn.PublicIpApi.LinkPublicIp(context.Background(), &osc.LinkPublicIpOpts{
LinkPublicIpRequest: optional.NewInterface(osc.LinkPublicIpRequest{PublicIpId: publicip_id, VmId: vmId}),
})
if err != nil {
state.Put("error", fmt.Errorf("Error linking PublicIp to VM: %s", err))
ui.Error(err.Error())
@@ -221,9 +195,11 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
}
resp, err := oapiconn.POST_ReadVms(request)
resp, _, err := oscconn.VmApi.ReadVms(context.Background(), &osc.ReadVmsOpts{
ReadVmsRequest: optional.NewInterface(request),
})
r := resp.OK
r := resp
if err != nil || len(r.Vms) == 0 {
err := fmt.Errorf("Error finding source vm.")
@@ -256,12 +232,14 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
// do that now.
if s.IsRestricted {
oapiTags.Report(ui)
oscTags.Report(ui)
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
Tags: oapiTags,
ResourceIds: []string{vmId},
_, _, err := oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
Tags: oscTags,
ResourceIds: []string{vmId},
}),
})
if err == nil {
return true, nil
@@ -286,7 +264,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
volumeIds := make([]string, 0)
for _, v := range vm.BlockDeviceMappings {
if bsu := v.Bsu; !reflect.DeepEqual(bsu, oapi.BsuCreated{}) {
if bsu := v.Bsu; !reflect.DeepEqual(bsu, osc.BsuCreated{}) {
volumeIds = append(volumeIds, bsu.VolumeId)
}
}
@@ -294,7 +272,7 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
if len(volumeIds) > 0 && s.VolumeTags.IsSet() {
ui.Say("Adding tags to source BSU Volumes")
volumeTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
volumeTags, err := s.VolumeTags.OSCTags(s.Ctx, rawRegion, state)
if err != nil {
err := fmt.Errorf("Error tagging source BSU Volumes on %s: %s", vm.VmId, err)
state.Put("error", err)
@@ -303,9 +281,11 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
volumeTags.Report(ui)
_, err = oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: volumeIds,
Tags: volumeTags,
_, _, err = oscconn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: volumeIds,
Tags: volumeTags,
}),
})
if err != nil {
@@ -321,19 +301,20 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul
}
func (s *StepRunSourceVm) Cleanup(state multistep.StateBag) {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
// Terminate the source vm if it exists
if s.vmId != "" {
ui.Say("Terminating the source OUTSCALE vm...")
if _, err := oapiconn.POST_DeleteVms(oapi.DeleteVmsRequest{VmIds: []string{s.vmId}}); err != nil {
if _, _, err := oscconn.VmApi.DeleteVms(context.Background(), &osc.DeleteVmsOpts{
DeleteVmsRequest: optional.NewInterface(osc.DeleteVmsRequest{VmIds: []string{s.vmId}}),
}); err != nil {
ui.Error(fmt.Sprintf("Error terminating vm, may still be around: %s", err))
return
}
if err := waitUntilVmDeleted(oapiconn, s.vmId); err != nil {
if err := waitUntilOscVmDeleted(oscconn, s.vmId); err != nil {
ui.Error(err.Error())
}
}
+88 -64
View File
@@ -5,13 +5,13 @@ import (
"fmt"
"log"
"strings"
"time"
"github.com/antihax/optional"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type StepSecurityGroup struct {
@@ -24,50 +24,56 @@ type StepSecurityGroup struct {
}
func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
netId := state.Get("net_id").(string)
var (
ui = state.Get("ui").(packer.Ui)
conn = state.Get("osc").(*osc.APIClient)
netID = state.Get("net_id").(string)
)
if len(s.SecurityGroupIds) > 0 {
resp, err := oapiconn.POST_ReadSecurityGroups(
oapi.ReadSecurityGroupsRequest{
Filters: oapi.FiltersSecurityGroup{
resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
Filters: osc.FiltersSecurityGroup{
SecurityGroupIds: s.SecurityGroupIds,
},
},
)
if err != nil || resp.OK == nil || len(resp.OK.SecurityGroups) <= 0 {
}),
})
if err != nil || len(resp.SecurityGroups) == 0 {
err := fmt.Errorf("Couldn't find specified security group: %s", err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
log.Printf("Using specified security groups: %v", s.SecurityGroupIds)
state.Put("securityGroupIds", s.SecurityGroupIds)
return multistep.ActionContinue
}
if !s.SecurityGroupFilter.Empty() {
filterReq := buildSecurityGroupFilters(s.SecurityGroupFilter.Filters)
params := oapi.ReadSecurityGroupsRequest{}
if netId != "" {
s.SecurityGroupFilter.Filters["net-id"] = netId
}
params.Filters = buildSecurityGroupFilters(s.SecurityGroupFilter.Filters)
log.Printf("Using SecurityGroup Filters %v", filterReq)
log.Printf("Using SecurityGroup Filters %v", params)
resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
Filters: filterReq,
}),
})
sgResp, err := oapiconn.POST_ReadSecurityGroups(params)
if err != nil || sgResp.OK == nil {
if err != nil || len(resp.SecurityGroups) == 0 {
err := fmt.Errorf("Couldn't find security groups for filter: %s", err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
securityGroupIds := []string{}
for _, sg := range sgResp.OK.SecurityGroups {
for _, sg := range resp.SecurityGroups {
securityGroupIds = append(securityGroupIds, sg.SecurityGroupId)
}
@@ -77,24 +83,20 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
return multistep.ActionContinue
}
port := s.CommConfig.Port()
if port == 0 {
if s.CommConfig.Type != "none" {
panic("port must be set to a non-zero value.")
}
}
// Create the group
groupName := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
/* Create the group */
groupName := fmt.Sprintf("packer_osc_%s", uuid.TimeOrderedUUID())
ui.Say(fmt.Sprintf("Creating temporary security group for this instance: %s", groupName))
group := oapi.CreateSecurityGroupRequest{
createSGReq := osc.CreateSecurityGroupRequest{
SecurityGroupName: groupName,
NetId: netID,
Description: "Temporary group for Packer",
}
group.NetId = netId
resp, _, err := conn.SecurityGroupApi.CreateSecurityGroup(context.Background(), &osc.CreateSecurityGroupOpts{
CreateSecurityGroupRequest: optional.NewInterface(createSGReq),
})
groupResp, err := oapiconn.POST_CreateSecurityGroup(group)
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
@@ -102,38 +104,36 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
}
// Set the group ID so we can delete it later
s.createdGroupId = groupResp.OK.SecurityGroup.SecurityGroupId
s.createdGroupId = resp.SecurityGroup.SecurityGroupId
// Wait for the security group become available for authorizing
log.Printf("[DEBUG] Waiting for temporary security group: %s", s.createdGroupId)
err = waitForSecurityGroup(oapiconn, s.createdGroupId)
if err == nil {
log.Printf("[DEBUG] Found security group %s", s.createdGroupId)
} else {
err := fmt.Errorf("Timed out waiting for security group %s: %s", s.createdGroupId, err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
port := s.CommConfig.Port()
if port == 0 {
if s.CommConfig.Type != "none" {
state.Put("error", "port must be set to a non-zero value.")
return multistep.ActionHalt
}
}
// Authorize the SSH access for the security group
groupRules := oapi.CreateSecurityGroupRuleRequest{
SecurityGroupId: groupResp.OK.SecurityGroup.SecurityGroupId,
createSGRReq := osc.CreateSecurityGroupRuleRequest{
SecurityGroupId: resp.SecurityGroup.SecurityGroupId,
Flow: "Inbound",
Rules: []oapi.SecurityGroupRule{
Rules: []osc.SecurityGroupRule{
{
FromPortRange: int64(port),
ToPortRange: int64(port),
FromPortRange: int32(port),
ToPortRange: int32(port),
IpRanges: []string{s.TemporarySGSourceCidr},
IpProtocol: "tcp",
},
},
}
ui.Say(fmt.Sprintf(
"Authorizing access to port %d from %s in the temporary security group...",
port, s.TemporarySGSourceCidr))
_, err = oapiconn.POST_CreateSecurityGroupRule(groupRules)
ui.Say(fmt.Sprintf("Authorizing access to port %d from %s in the temporary security group...", port, s.TemporarySGSourceCidr))
_, _, err = conn.SecurityGroupRuleApi.CreateSecurityGroupRule(context.Background(), &osc.CreateSecurityGroupRuleOpts{
CreateSecurityGroupRuleRequest: optional.NewInterface(createSGRReq),
})
if err != nil {
err := fmt.Errorf("Error authorizing temporary security group: %s", err)
state.Put("error", err)
@@ -152,24 +152,48 @@ func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
var (
ui = state.Get("ui").(packer.Ui)
conn = state.Get("osc").(*osc.APIClient)
)
ui.Say("Deleting temporary security group...")
var err error
for i := 0; i < 5; i++ {
_, err = oapiconn.POST_DeleteSecurityGroup(oapi.DeleteSecurityGroupRequest{SecurityGroupId: s.createdGroupId})
if err == nil {
break
}
log.Printf("Error deleting security group: %s", err)
time.Sleep(5 * time.Second)
}
_, _, err := conn.SecurityGroupApi.DeleteSecurityGroup(context.Background(), &osc.DeleteSecurityGroupOpts{
DeleteSecurityGroupRequest: optional.NewInterface(osc.DeleteSecurityGroupRequest{
SecurityGroupId: s.createdGroupId,
}),
})
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
}
}
func buildSecurityGroupFilters(input map[string]string) osc.FiltersSecurityGroup {
var filters osc.FiltersSecurityGroup
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
case "account_ids":
filters.AccountIds = filterValue
case "security_group_ids":
filters.SecurityGroupIds = filterValue
case "security_group_names":
filters.SecurityGroupNames = filterValue
case "tag_keys":
filters.TagKeys = filterValue
case "tag_values":
filters.TagValues = filterValue
case "tags":
filters.Tags = filterValue
default:
log.Printf("[Debug] Unknown Filter Name: %s.", name)
}
}
return filters
}
+22 -19
View File
@@ -7,45 +7,46 @@ import (
"sort"
"time"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
// StepSourceOMIInfo extracts critical information from the source OMI
// that is used throughout the OMI creation process.
//
// Produces:
// source_image *oapi.Image - the source OMI info
// source_image *osc.Image - the source OMI info
type StepSourceOMIInfo struct {
SourceOmi string
OMIVirtType string
OmiFilters OmiFilterOptions
}
type imageSort []oapi.Image
type imageOscSort []osc.Image
func (a imageSort) Len() int { return len(a) }
func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a imageSort) Less(i, j int) bool {
func (a imageOscSort) Len() int { return len(a) }
func (a imageOscSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a imageOscSort) Less(i, j int) bool {
itime, _ := time.Parse(time.RFC3339, a[i].CreationDate)
jtime, _ := time.Parse(time.RFC3339, a[j].CreationDate)
return itime.Unix() < jtime.Unix()
}
// Returns the most recent OMI out of a slice of images.
func mostRecentOmi(images []oapi.Image) oapi.Image {
func mostRecentOscOmi(images []osc.Image) osc.Image {
sortedImages := images
sort.Sort(imageSort(sortedImages))
sort.Sort(imageOscSort(sortedImages))
return sortedImages[len(sortedImages)-1]
}
func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
oscconn := state.Get("osc").(*osc.APIClient)
ui := state.Get("ui").(packer.Ui)
params := oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{},
params := osc.ReadImagesRequest{
Filters: osc.FiltersImage{},
}
if s.SourceOmi != "" {
@@ -54,7 +55,7 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
// We have filters to apply
if len(s.OmiFilters.Filters) > 0 {
params.Filters = buildOMIFilters(s.OmiFilters.Filters)
params.Filters = buildOSCOMIFilters(s.OmiFilters.Filters)
}
//TODO:Check if AccountIds correspond to Owners.
if len(s.OmiFilters.Owners) > 0 {
@@ -62,7 +63,9 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
}
log.Printf("Using OMI Filters %#v", params)
imageResp, err := oapiconn.POST_ReadImages(params)
imageResp, _, err := oscconn.ImageApi.ReadImages(context.Background(), &osc.ReadImagesOpts{
ReadImagesRequest: optional.NewInterface(params),
})
if err != nil {
err := fmt.Errorf("Error querying OMI: %s", err)
state.Put("error", err)
@@ -70,25 +73,25 @@ func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) mul
return multistep.ActionHalt
}
if len(imageResp.OK.Images) == 0 {
if len(imageResp.Images) == 0 {
err := fmt.Errorf("No OMI was found matching filters: %#v", params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.OK.Images) > 1 && !s.OmiFilters.MostRecent {
err := fmt.Errorf("Your query returned more than one result. Please try a more specific search, or set most_recent to true.")
if len(imageResp.Images) > 1 && !s.OmiFilters.MostRecent {
err := fmt.Errorf("your query returned more than one result. Please try a more specific search, or set most_recent to true")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var image oapi.Image
var image osc.Image
if s.OmiFilters.MostRecent {
image = mostRecentOmi(imageResp.OK.Images)
image = mostRecentOscOmi(imageResp.Images)
} else {
image = imageResp.OK.Images[0]
image = imageResp.Images[0]
}
ui.Message(fmt.Sprintf("Found Image ID: %s", image.ImageId))
@@ -4,11 +4,12 @@ import (
"context"
"fmt"
"github.com/antihax/optional"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type StepStopBSUBackedVm struct {
@@ -17,8 +18,8 @@ type StepStopBSUBackedVm struct {
}
func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
oscconn := state.Get("osc").(*osc.APIClient)
vm := state.Get("vm").(osc.Vm)
ui := state.Get("ui").(packer.Ui)
// Skip when it is a spot vm
@@ -43,8 +44,10 @@ func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag)
err := common.Retry(10, 60, 6, func(i uint) (bool, error) {
ui.Message(fmt.Sprintf("Stopping vm, attempt %d", i+1))
_, err = oapiconn.POST_StopVms(oapi.StopVmsRequest{
VmIds: []string{vm.VmId},
_, _, err = oscconn.VmApi.StopVms(context.Background(), &osc.StopVmsOpts{
StopVmsRequest: optional.NewInterface(osc.StopVmsRequest{
VmIds: []string{vm.VmId},
}),
})
if err == nil {
@@ -78,7 +81,7 @@ func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag)
// Wait for the vm to actually stop
ui.Say("Waiting for the vm to stop...")
err = waitUntilVmStopped(oapiconn, vm.VmId)
err = waitUntilOscVmStopped(oscconn, vm.VmId)
if err != nil {
err := fmt.Errorf("Error waiting for vm to stop: %s", err)
+35 -44
View File
@@ -2,25 +2,24 @@ package common
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type StepUpdateOMIAttributes struct {
AccountIds []string
SnapshotAccountIds []string
RawRegion string
Ctx interpolate.Context
}
func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
config := state.Get("clientConfig").(*oapi.Config)
config := state.Get("accessConfig").(*AccessConfig)
ui := state.Get("ui").(packer.Ui)
omis := state.Get("omis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
@@ -34,20 +33,20 @@ func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBa
return multistep.ActionContinue
}
s.Ctx.Data = extractBuildInfo(oapiconn.GetConfig().Region, state)
s.Ctx.Data = extractBuildInfo(s.RawRegion, state)
updateSnapshoptRequest := oapi.UpdateSnapshotRequest{
PermissionsToCreateVolume: oapi.PermissionsOnResourceCreation{
Additions: oapi.PermissionsOnResource{
updateSnapshoptRequest := osc.UpdateSnapshotRequest{
PermissionsToCreateVolume: osc.PermissionsOnResourceCreation{
Additions: osc.PermissionsOnResource{
AccountIds: s.AccountIds,
GlobalPermission: false,
},
},
}
updateImageRequest := oapi.UpdateImageRequest{
PermissionsToLaunch: oapi.PermissionsOnResourceCreation{
Additions: oapi.PermissionsOnResource{
updateImageRequest := osc.UpdateImageRequest{
PermissionsToLaunch: osc.PermissionsOnResourceCreation{
Additions: osc.PermissionsOnResource{
AccountIds: s.AccountIds,
GlobalPermission: false,
},
@@ -57,26 +56,31 @@ func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBa
// Updating image attributes
for region, omi := range omis {
ui.Say(fmt.Sprintf("Updating attributes on OMI (%s)...", omi))
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
regionconn := config.NewOSCClientByRegion(region)
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// newConfig := &osc.Configuration{
// UserAgent: config.UserAgent,
// AccessKey: config.AccessKey,
// SecretKey: config.SecretKey,
// Service: config.Service,
// Region: region, //New region
// URL: config.URL,
// }
regionconn := oapi.NewClient(newConfig, skipClient)
// skipClient := &http.Client{
// Transport: &http.Transport{
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
// },
// }
//regionconn := oapi.NewClient(newConfig, skipClient)
ui.Message(fmt.Sprintf("Updating: %s", omi))
updateImageRequest.ImageId = omi
_, err := regionconn.POST_UpdateImage(updateImageRequest)
_, _, err := regionconn.ImageApi.UpdateImage(context.Background(), &osc.UpdateImageOpts{
UpdateImageRequest: optional.NewInterface(updateImageRequest),
})
if err != nil {
err := fmt.Errorf("Error updating OMI: %s", err)
state.Put("error", err)
@@ -89,26 +93,13 @@ func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBa
for region, region_snapshots := range snapshots {
for _, snapshot := range region_snapshots {
ui.Say(fmt.Sprintf("Updating attributes on snapshot (%s)...", snapshot))
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionconn := oapi.NewClient(newConfig, skipClient)
regionconn := config.NewOSCClientByRegion(region)
ui.Message(fmt.Sprintf("Updating: %s", snapshot))
updateSnapshoptRequest.SnapshotId = snapshot
_, err := regionconn.POST_UpdateSnapshot(updateSnapshoptRequest)
_, _, err := regionconn.SnapshotApi.UpdateSnapshot(context.Background(), &osc.UpdateSnapshotOpts{
UpdateSnapshotRequest: optional.NewInterface(updateSnapshoptRequest),
})
if err != nil {
err := fmt.Errorf("Error updating snapshot: %s", err)
state.Put("error", err)
+15 -11
View File
@@ -1,18 +1,20 @@
package common
import (
"context"
"fmt"
"github.com/antihax/optional"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
"github.com/outscale/osc-sdk-go/osc"
)
type TagMap map[string]string
type OAPITags []oapi.ResourceTag
type OSCTags []osc.ResourceTag
func (t OAPITags) Report(ui packer.Ui) {
func (t OSCTags) Report(ui packer.Ui) {
for _, tag := range t {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"",
tag.Key, tag.Value))
@@ -23,8 +25,8 @@ func (t TagMap) IsSet() bool {
return len(t) > 0
}
func (t TagMap) OAPITags(ctx interpolate.Context, region string, state multistep.StateBag) (OAPITags, error) {
var oapiTags []oapi.ResourceTag
func (t TagMap) OSCTags(ctx interpolate.Context, region string, state multistep.StateBag) (OSCTags, error) {
var oscTags []osc.ResourceTag
ctx.Data = extractBuildInfo(region, state)
for key, value := range t {
@@ -36,20 +38,22 @@ func (t TagMap) OAPITags(ctx interpolate.Context, region string, state multistep
if err != nil {
return nil, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
}
oapiTags = append(oapiTags, oapi.ResourceTag{
oscTags = append(oscTags, osc.ResourceTag{
Key: interpolatedKey,
Value: interpolatedValue,
})
}
return oapiTags, nil
return oscTags, nil
}
func CreateTags(conn *oapi.Client, resourceID string, ui packer.Ui, tags OAPITags) error {
func CreateOSCTags(conn *osc.APIClient, resourceID string, ui packer.Ui, tags OSCTags) error {
tags.Report(ui)
_, err := conn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: []string{resourceID},
Tags: tags,
_, _, err := conn.TagApi.CreateTags(context.Background(), &osc.CreateTagsOpts{
CreateTagsRequest: optional.NewInterface(osc.CreateTagsRequest{
ResourceIds: []string{resourceID},
Tags: tags,
}),
})
return err
+60
View File
@@ -0,0 +1,60 @@
package common
import (
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws/credentials"
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
)
// Transport is used to sing the user for each request
type Transport struct {
transport http.RoundTripper
signer *v4.Signer
region string
}
func (t *Transport) sign(req *http.Request, body []byte) error {
reader := strings.NewReader(string(body))
timestamp := time.Now()
_, err := t.signer.Sign(req, reader, "osc", t.region, timestamp)
return err
}
// RoundTrip is implemented according with the interface RoundTrip to sing for each request
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
//Get the body
getBody := req.GetBody
copyBody, err := getBody()
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(copyBody)
if err != nil {
return nil, err
}
if err := t.sign(req, body); err != nil {
return nil, err
}
resp, err := t.transport.RoundTrip(req)
if err != nil {
return resp, err
}
return resp, nil
}
// NewTransport returns the transport signing with the given credentials
func NewTransport(accessKey, accessSecret, region string, t http.RoundTripper) *Transport {
s := &v4.Signer{
Credentials: credentials.NewStaticCredentials(accessKey,
accessSecret, ""),
}
return &Transport{t, s, region}
}
+2
View File
@@ -192,6 +192,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact.state["generated_data"] = state.Get("generated_data")
artifact.state["diskName"] = b.config.VMName
// placed in state in step_create_disk.go
diskpaths, ok := state.Get("qemu_disk_paths").([]string)
if ok {
artifact.state["diskPaths"] = diskpaths
+16 -8
View File
@@ -208,19 +208,27 @@ type Config struct {
// the builder. By default this is output-BUILDNAME where "BUILDNAME" is the
// name of the build.
OutputDir string `mapstructure:"output_directory" required:"false"`
// Allows complete control over the qemu command line (though not, at this
// time, qemu-img). Each array of strings makes up a command line switch
// Allows complete control over the qemu command line (though not qemu-img).
// Each array of strings makes up a command line switch
// that overrides matching default switch/value pairs. Any value specified
// as an empty string is ignored. All values after the switch are
// concatenated with no separator.
//
// ~> **Warning:** The qemu command line allows extreme flexibility, so
// beware of conflicting arguments causing failures of your run. For
// instance, using --no-acpi could break the ability to send power signal
// type commands (e.g., shutdown -P now) to the virtual machine, thus
// preventing proper shutdown. To see the defaults, look in the packer.log
// file and search for the qemu-system-x86 command. The arguments are all
// printed for review.
// beware of conflicting arguments causing failures of your run.
// For instance adding a "--drive" or "--device" override will mean that
// none of the default configuration Packer sets will be used. To see the
// defaults that Packer sets, look in your packer.log
// file (set PACKER_LOG=1 to get verbose logging) and search for the
// qemu-system-x86 command. The arguments are all printed for review, and
// you can use those arguments along with the template engines allowed
// by qemu-args to set up a working configuration that includes both the
// Packer defaults and your extra arguments.
//
// Another pitfall could be setting arguments like --no-acpi, which could
// break the ability to send power signal type commands
// (e.g., shutdown -P now) to the virtual machine, thus preventing proper
// shutdown.
//
// The following shows a sample usage:
//
+214 -173
View File
@@ -16,41 +16,45 @@ import (
// stepRun runs the virtual machine
type stepRun struct {
DiskImage bool
}
type qemuArgsTemplateData struct {
HTTPIP string
HTTPPort int
HTTPDir string
OutputDir string
Name string
SSHHostPort int
atLeastVersion2 bool
ui packer.Ui
}
func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
s.ui = state.Get("ui").(packer.Ui)
// Run command is different depending whether we're booting from an
// installation CD or a pre-baked image
bootDrive := "once=d"
message := "Starting VM, booting from CD-ROM"
if s.DiskImage {
bootDrive = "c"
message = "Starting VM, booting disk image"
// Figure out version of qemu; store on step for later use
rawVersion, err := driver.Version()
if err != nil {
err := fmt.Errorf("Error determining qemu version: %s", err)
s.ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(message)
qemuVersion, err := version.NewVersion(rawVersion)
if err != nil {
err := fmt.Errorf("Error parsing qemu version: %s", err)
s.ui.Error(err.Error())
return multistep.ActionHalt
}
v2 := version.Must(version.NewVersion("2.0"))
command, err := getCommandArgs(bootDrive, state)
s.atLeastVersion2 = qemuVersion.GreaterThanOrEqual(v2)
// Generate the qemu command
command, err := s.getCommandArgs(config, state)
if err != nil {
err := fmt.Errorf("Error processing QemuArgs: %s", err)
ui.Error(err.Error())
s.ui.Error(err.Error())
return multistep.ActionHalt
}
// run the qemu command
if err := driver.Qemu(command...); err != nil {
err := fmt.Errorf("Error launching VM: %s", err)
ui.Error(err.Error())
s.ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -66,140 +70,187 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
}
}
func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
config := state.Get("config").(*Config)
isoPath := state.Get("iso_path").(string)
vncIP := config.VNCBindAddress
vncPort := state.Get("vnc_port").(int)
ui := state.Get("ui").(packer.Ui)
driver := state.Get("driver").(Driver)
vmName := config.VMName
imgPath := filepath.Join(config.OutputDir, vmName)
func (s *stepRun) getDefaultArgs(config *Config, state multistep.StateBag) map[string]interface{} {
defaultArgs := make(map[string]interface{})
var deviceArgs []string
var driveArgs []string
var commHostPort int
// Configure "boot" arguement
// Run command is different depending whether we're booting from an
// installation CD or a pre-baked image
bootDrive := "once=d"
message := "Starting VM, booting from CD-ROM"
if s.DiskImage {
bootDrive = "c"
message = "Starting VM, booting disk image"
}
s.ui.Say(message)
defaultArgs["-boot"] = bootDrive
// configure "-qmp" arguments
if config.QMPEnable {
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
}
// configure "-name" arguments
defaultArgs["-name"] = config.VMName
// Configure "-machine" arguments
if config.Accelerator == "none" {
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
s.ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
"The installation may take considerably longer to finish.\n")
} else {
defaultArgs["-machine"] = fmt.Sprintf("type=%s,accel=%s",
config.MachineType, config.Accelerator)
}
// Configure "-netdev" arguments
defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge)
if config.NetBridge == "" {
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
if config.CommConfig.Comm.Type != "none" {
commHostPort := state.Get("commHostPort").(int)
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", commHostPort, config.CommConfig.Comm.Port())
}
}
// Configure "-vnc" arguments
// vncPort is always set in stepConfigureVNC, so we don't need to
// defensively assert
vncPort := state.Get("vnc_port").(int)
vncIP := config.VNCBindAddress
vncPort = vncPort - config.VNCPortMin
vnc := fmt.Sprintf("%s:%d", vncIP, vncPort)
if config.VNCUsePassword {
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort)
}
defaultArgs["-vnc"] = vnc
if config.QMPEnable {
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
// Track the connection for the user
vncPass, _ := state.Get("vnc_password").(string)
message = getVncConnectionMessage(config.Headless, vnc, vncPass)
if message != "" {
s.ui.Message(message)
}
defaultArgs["-name"] = vmName
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
// Configure "-m" memory argument
defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize)
if config.NetBridge == "" {
if config.CommConfig.Comm.Type != "none" {
commHostPort = state.Get("commHostPort").(int)
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", commHostPort, config.CommConfig.Comm.Port())
} else {
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
}
// Configure "-smp" processor hardware arguments
if config.CpuCount > 1 {
defaultArgs["-smp"] = fmt.Sprintf("cpus=%d,sockets=%d", config.CpuCount, config.CpuCount)
}
// Configure "-fda" floppy disk attachment
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
defaultArgs["-fda"] = floppyPathRaw.(string)
} else {
defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge)
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
}
rawVersion, err := driver.Version()
if err != nil {
return nil, err
}
qemuVersion, err := version.NewVersion(rawVersion)
if err != nil {
return nil, err
}
v2 := version.Must(version.NewVersion("2.0"))
if qemuVersion.GreaterThanOrEqual(v2) {
if config.DiskInterface == "virtio-scsi" {
if config.DiskImage {
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0", "scsi-hd,bus=scsi0.0,drive=drive0")
driveArgumentString := fmt.Sprintf("if=none,file=%s,id=drive0,cache=%s,discard=%s,format=%s", imgPath, config.DiskCache, config.DiskDiscard, config.Format)
if config.DetectZeroes != "off" {
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
}
driveArgs = append(driveArgs, driveArgumentString)
} else {
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0")
diskFullPaths := state.Get("qemu_disk_paths").([]string)
for i, diskFullPath := range diskFullPaths {
deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi0.0,drive=drive%d", i))
driveArgumentString := fmt.Sprintf("if=none,file=%s,id=drive%d,cache=%s,discard=%s,format=%s", diskFullPath, i, config.DiskCache, config.DiskDiscard, config.Format)
if config.DetectZeroes != "off" {
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
}
driveArgs = append(driveArgs, driveArgumentString)
}
}
} else {
if config.DiskImage {
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
if config.DetectZeroes != "off" {
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
}
driveArgs = append(driveArgs, driveArgumentString)
} else {
diskFullPaths := state.Get("qemu_disk_paths").([]string)
for _, diskFullPath := range diskFullPaths {
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", diskFullPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
if config.DetectZeroes != "off" {
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
}
driveArgs = append(driveArgs, driveArgumentString)
}
}
}
} else {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
}
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
if config.Headless == true {
vncPortRaw, vncPortOk := state.GetOk("vnc_port")
vncPass := state.Get("vnc_password")
if vncPortOk && vncPass != nil && len(vncPass.(string)) > 0 {
vncPort := vncPortRaw.(int)
ui.Message(fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC to vnc://%s:%d\n"+
"with the password: %s", vncIP, vncPort, vncPass))
} else if vncPortOk {
vncPort := vncPortRaw.(int)
ui.Message(fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC without a password to\n"+
"vnc://%s:%d", vncIP, vncPort))
} else {
ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
"If the run isn't succeeding as you expect, please enable the GUI\n" +
"to inspect the progress of the build.")
}
} else {
if qemuVersion.GreaterThanOrEqual(v2) {
if len(config.Display) > 0 {
if config.Display != "none" {
defaultArgs["-display"] = config.Display
}
// Configure GUI display
if !config.Headless {
if s.atLeastVersion2 {
// FIXME: "none" is a valid display option in qemu but we have
// departed from the qemu usage here to instaed mean "let qemu
// set a reasonable default". We need to deprecate this behavior
// and let users just set "UseDefaultDisplay" if they want to let
// qemu do its thing.
if len(config.Display) > 0 && config.Display != "none" {
defaultArgs["-display"] = config.Display
} else if !config.UseDefaultDisplay {
defaultArgs["-display"] = "gtk"
}
} else {
ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" +
s.ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" +
"The display parameter will be ignored.")
}
}
deviceArgs, driveArgs := s.getDeviceAndDriveArgs(config, state)
defaultArgs["-device"] = deviceArgs
defaultArgs["-drive"] = driveArgs
return defaultArgs
}
func getVncConnectionMessage(headless bool, vnc string, vncPass string) string {
// Configure GUI display
if headless {
if vnc == "" {
return "The VM will be run headless, without a GUI, as configured.\n" +
"If the run isn't succeeding as you expect, please enable the GUI\n" +
"to inspect the progress of the build."
}
if vncPass != "" {
return fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC to vnc://%s\n"+
"with the password: %s", vnc, vncPass)
}
return fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC without a password to\n"+
"vnc://%s", vnc)
}
return ""
}
func (s *stepRun) getDeviceAndDriveArgs(config *Config, state multistep.StateBag) ([]string, []string) {
var deviceArgs []string
var driveArgs []string
vmName := config.VMName
imgPath := filepath.Join(config.OutputDir, vmName)
// Configure virtual hard drives
if s.atLeastVersion2 {
// We have different things to attach based on whether we are booting
// from an iso or a boot image.
drivesToAttach := []string{}
if config.DiskImage {
drivesToAttach = append(drivesToAttach, imgPath)
}
diskFullPaths := state.Get("qemu_disk_paths").([]string)
drivesToAttach = append(drivesToAttach, diskFullPaths...)
for i, drivePath := range drivesToAttach {
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", drivePath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
if config.DiskInterface == "virtio-scsi" {
// TODO: Megan: Remove this conditional. This, and the code
// under the TODO below, reproduce the old behavior. While it
// may be broken, the goal of this commit is to refactor in a way
// that creates a result that is testably the same as the old
// code. A pr will follow fixing this broken behavior.
if i == 0 {
deviceArgs = append(deviceArgs, fmt.Sprintf("virtio-scsi-pci,id=scsi%d", i))
}
// TODO: Megan: When you remove above conditional,
// set deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi%d.0,drive=drive%d", i, i))
deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi0.0,drive=drive%d", i))
driveArgumentString = fmt.Sprintf("if=none,file=%s,id=drive%d,cache=%s,discard=%s,format=%s", drivePath, i, config.DiskCache, config.DiskDiscard, config.Format)
}
if config.DetectZeroes != "off" {
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
}
driveArgs = append(driveArgs, driveArgumentString)
}
} else {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
}
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
// Configure virtual CDs
cdPaths := []string{}
// Add the installation CD to the run command
if !config.DiskImage {
isoPath := state.Get("iso_path").(string)
cdPaths = append(cdPaths, isoPath)
}
// Add our custom CD created from cd_files, if it exists
@@ -220,64 +271,48 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
}
}
defaultArgs["-device"] = deviceArgs
defaultArgs["-drive"] = driveArgs
return deviceArgs, driveArgs
}
defaultArgs["-boot"] = bootDrive
defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize)
if config.CpuCount > 1 {
defaultArgs["-smp"] = fmt.Sprintf("cpus=%d,sockets=%d", config.CpuCount, config.CpuCount)
}
defaultArgs["-vnc"] = vnc
// Append the accelerator to the machine type if it is specified
if config.Accelerator != "none" {
defaultArgs["-machine"] = fmt.Sprintf("%s,accel=%s", defaultArgs["-machine"], config.Accelerator)
} else {
ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
"The installation may take considerably longer to finish.\n")
}
// Determine if we have a floppy disk to attach
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
defaultArgs["-fda"] = floppyPathRaw.(string)
} else {
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
}
func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config *Config, state multistep.StateBag) ([]string, error) {
// Done setting up defaults; time to process user args and defaults together
// and generate output args
inArgs := make(map[string][]string)
if len(config.QemuArgs) > 0 {
ui.Say("Overriding default Qemu arguments with QemuArgs...")
s.ui.Say("Overriding default Qemu arguments with qemuargs template option...")
commHostPort := state.Get("commHostPort").(int)
httpIp := state.Get("http_ip").(string)
httpPort := state.Get("http_port").(int)
ictx := config.ctx
if config.CommConfig.Comm.Type != "none" {
ictx.Data = qemuArgsTemplateData{
httpIp,
httpPort,
config.HTTPDir,
config.OutputDir,
config.VMName,
commHostPort,
}
} else {
ictx.Data = qemuArgsTemplateData{
HTTPIP: httpIp,
HTTPPort: httpPort,
HTTPDir: config.HTTPDir,
OutputDir: config.OutputDir,
Name: config.VMName,
}
type qemuArgsTemplateData struct {
HTTPIP string
HTTPPort int
HTTPDir string
OutputDir string
Name string
SSHHostPort int
}
ictx := config.ctx
ictx.Data = qemuArgsTemplateData{
HTTPIP: httpIp,
HTTPPort: httpPort,
HTTPDir: config.HTTPDir,
OutputDir: config.OutputDir,
Name: config.VMName,
SSHHostPort: commHostPort,
}
// Interpolate each string in qemuargs
newQemuArgs, err := processArgs(config.QemuArgs, &ictx)
if err != nil {
return nil, err
}
// because qemu supports multiple appearances of the same
// switch, just different values, each key in the args hash
// will have an array of string values
// Qemu supports multiple appearances of the same switch. This means
// each key in the args hash will have an array of string values
for _, qemuArgs := range newQemuArgs {
key := qemuArgs[0]
val := strings.Join(qemuArgs[1:], "")
@@ -326,6 +361,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
return outArgs, nil
}
func (s *stepRun) getCommandArgs(config *Config, state multistep.StateBag) ([]string, error) {
defaultArgs := s.getDefaultArgs(config, state)
return s.applyUserOverrides(defaultArgs, config, state)
}
func processArgs(args [][]string, ctx *interpolate.Context) ([][]string, error) {
var err error
+632 -73
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"testing"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/stretchr/testify/assert"
@@ -11,8 +13,6 @@ import (
func runTestState(t *testing.T, config *Config) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("config", config)
d := new(DriverMock)
@@ -31,86 +31,396 @@ func runTestState(t *testing.T, config *Config) multistep.StateBag {
return state
}
func Test_getCommandArgs(t *testing.T) {
state := runTestState(t, &Config{})
args, err := getCommandArgs("", state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
func Test_UserOverrides(t *testing.T) {
type testCase struct {
Config *Config
Expected []string
Reason string
}
testcases := []testCase{
{
&Config{
HTTPConfig: common.HTTPConfig{
HTTPDir: "http/directory",
},
OutputDir: "output/directory",
VMName: "myvm",
QemuArgs: [][]string{
{"-randomflag1", "{{.HTTPIP}}-{{.HTTPPort}}-{{.HTTPDir}}"},
{"-randomflag2", "{{.OutputDir}}-{{.Name}}"},
},
},
[]string{
"-display", "gtk",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-randomflag1", "127.0.0.1-1234-http/directory",
"-randomflag2", "output/directory-myvm",
"-device", ",netdev=user.0",
},
"Test that interpolation overrides work.",
},
{
&Config{
VMName: "myvm",
QemuArgs: [][]string{{"-display", "partydisplay"}},
},
[]string{
"-display", "partydisplay",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-device", ",netdev=user.0",
},
"User input overrides default, rest is populated as normal",
},
{
&Config{
VMName: "myvm",
NetDevice: "mynetdevice",
QemuArgs: [][]string{{"-device", "somerandomdevice"}},
},
[]string{
"-display", "gtk",
"-device", "somerandomdevice",
"-device", "mynetdevice,netdev=user.0",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
},
"Net device gets added",
},
}
expected := []string{
"-display", "gtk",
"-m", "0M",
"-boot", "",
"-fda", "fake_floppy_path",
"-name", "",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5905",
"-machine", "type=,accel=",
"-device", ",netdev=user.0",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
for _, tc := range testcases {
state := runTestState(t, tc.Config)
step := &stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
}
args, err := step.getCommandArgs(tc.Config, state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
expected := append([]string{
"-m", "0M",
"-boot", "once=d",
"-fda", "fake_floppy_path",
"-name", "myvm",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5905",
"-machine", "type=,accel="},
tc.Expected...)
assert.ElementsMatch(t, args, expected,
fmt.Sprintf("%s, \nRecieved: %#v", tc.Reason, args))
}
assert.ElementsMatch(t, args, expected, "unexpected generated args")
}
func Test_CDFilesPath(t *testing.T) {
// cd_path is set and DiskImage is false
state := runTestState(t, &Config{})
state.Put("cd_path", "fake_cd_path.iso")
args, err := getCommandArgs("", state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
func Test_DriveAndDeviceArgs(t *testing.T) {
type testCase struct {
Config *Config
ExtraState map[string]interface{}
Step *stepRun
Expected []string
Reason string
}
expected := []string{
"-display", "gtk",
"-m", "0M",
"-boot", "",
"-fda", "fake_floppy_path",
"-name", "",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5905",
"-machine", "type=,accel=",
"-device", ",netdev=user.0",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
testcases := []testCase{
{
&Config{},
map[string]interface{}{},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
},
"Boot value should default to once=d when diskImage isn't set",
},
{
&Config{
DiskImage: true,
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "off",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
},
&stepRun{
DiskImage: true,
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "c",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2",
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
},
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes off",
},
{
&Config{
DiskImage: true,
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "on",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
},
&stepRun{
DiskImage: true,
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "c",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
},
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes on",
},
{
&Config{
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "off",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2",
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
},
"virtio-scsi interface, bootable iso, cdrom",
},
{
&Config{
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "on",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
},
"virtio-scsi interface, DiskImage false, extra cdrom, detect zeroes on",
},
{
&Config{
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=output/dir/path/mydisk.qcow2,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
},
"virtio-scsi interface, DiskImage false, no extra disks or cds",
},
{
&Config{},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
},
"cd_path is set and DiskImage is false",
},
{
&Config{},
map[string]interface{}{
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
},
"empty config",
},
{
&Config{
OutputDir: "path_to_output",
DiskInterface: "virtio",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: false,
ui: packer.TestUi(t),
},
[]string{
"-boot", "once=d",
"-drive", "file=path_to_output,if=virtio,cache=writeback,format=qcow2",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
},
"version less than 2",
},
{
&Config{
OutputDir: "path_to_output",
DiskInterface: "virtio",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=qemupath1,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=qemupath2,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
},
"virtio interface with extra disks",
},
{
&Config{
DiskImage: true,
OutputDir: "path_to_output",
DiskInterface: "virtio",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
},
&stepRun{
DiskImage: true,
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "c",
"-drive", "file=path_to_output,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
},
"virtio interface with disk image",
},
}
for _, tc := range testcases {
state := runTestState(t, &Config{})
for k, v := range tc.ExtraState {
state.Put(k, v)
}
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", args))
args, err := tc.Step.getCommandArgs(tc.Config, state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
// cd_path is set and DiskImage is true
config := &Config{
DiskImage: true,
DiskInterface: "virtio-scsi",
expected := append([]string{
"-m", "0M",
"-fda", "fake_floppy_path",
"-name", "",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5905",
"-machine", "type=,accel=",
"-device", ",netdev=user.0"},
tc.Expected...)
assert.ElementsMatch(t, args, expected,
fmt.Sprintf("%s, \nRecieved: %#v", tc.Reason, args))
}
state = runTestState(t, config)
state.Put("cd_path", "fake_cd_path.iso")
args, err = getCommandArgs("c", state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
expected = []string{
"-display", "gtk",
"-m", "0M",
"-boot", "c",
"-fda", "fake_floppy_path",
"-name", "",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5905",
"-machine", "type=,accel=",
"-device", ",netdev=user.0",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=,id=drive0,cache=,discard=,format=,detect-zeroes=",
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
}
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", args))
}
func Test_OptionalConfigOptionsGetSet(t *testing.T) {
@@ -124,8 +434,11 @@ func Test_OptionalConfigOptionsGetSet(t *testing.T) {
}
state := runTestState(t, c)
args, err := getCommandArgs("once=d", state)
step := &stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
}
args, err := step.getCommandArgs(c, state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
@@ -144,5 +457,251 @@ func Test_OptionalConfigOptionsGetSet(t *testing.T) {
"-qmp", "unix:qmp_path,server,nowait",
}
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set.")
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set: %s", args)
}
// Tests for presence of Packer-generated arguments. Doesn't test that
// arguments which shouldn't be there are absent.
func Test_Defaults(t *testing.T) {
type testCase struct {
Config *Config
ExtraState map[string]interface{}
Step *stepRun
Expected []string
Reason string
}
testcases := []testCase{
{
&Config{},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-boot", "once=d"},
"Boot value should default to once=d",
},
{
&Config{},
map[string]interface{}{},
&stepRun{
DiskImage: true,
ui: packer.TestUi(t),
},
[]string{"-boot", "c"},
"Boot value should be set to c when DiskImage is set on step",
},
{
&Config{
QMPEnable: true,
QMPSocketPath: "/path/to/socket",
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-qmp", "unix:/path/to/socket,server,nowait"},
"Args should contain -qmp when qmp_enable is set",
},
{
&Config{
QMPEnable: true,
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-qmp", "unix:,server,nowait"},
"Args contain -qmp even when socket path isn't set, if qmp enabled",
},
{
&Config{
VMName: "partyname",
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-name", "partyname"},
"Name is set from config",
},
{
&Config{},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-name", ""},
"Name is set from config, even when name is blank (which won't " +
"happen for real thanks to defaulting in build prepare)",
},
{
&Config{
Accelerator: "none",
MachineType: "fancymachine",
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-machine", "type=fancymachine"},
"Don't add accelerator tag when no accelerator is set.",
},
{
&Config{
Accelerator: "kvm",
MachineType: "fancymachine",
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-machine", "type=fancymachine,accel=kvm"},
"Add accelerator tag when accelerator is set.",
},
{
&Config{
NetBridge: "fakebridge",
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-netdev", "bridge,id=user.0,br=fakebridge"},
"Add netbridge tag when netbridge is set.",
},
{
&Config{
CommConfig: CommConfig{
Comm: communicator.Config{
Type: "none",
},
},
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-netdev", "user,id=user.0"},
"No host forwarding when no net bridge and no communicator",
},
{
&Config{
CommConfig: CommConfig{
Comm: communicator.Config{
Type: "ssh",
SSH: communicator.SSH{
SSHPort: 4567,
},
},
},
},
map[string]interface{}{
"commHostPort": 1111,
},
&stepRun{ui: packer.TestUi(t)},
[]string{"-netdev", "user,id=user.0,hostfwd=tcp::1111-:4567"},
"Host forwarding when a communicator is configured",
},
{
&Config{
VNCBindAddress: "1.1.1.1",
},
map[string]interface{}{
"vnc_port": 5959,
},
&stepRun{ui: packer.TestUi(t)},
[]string{"-vnc", "1.1.1.1:5959"},
"no VNC password should be set",
},
{
&Config{
VNCBindAddress: "1.1.1.1",
VNCUsePassword: true,
},
map[string]interface{}{
"vnc_port": 5959,
},
&stepRun{ui: packer.TestUi(t)},
[]string{"-vnc", "1.1.1.1:5959,password"},
"VNC password should be set",
},
{
&Config{
MemorySize: 2345,
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-m", "2345M"},
"Memory is set, with unit M",
},
{
&Config{
CpuCount: 2,
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-smp", "cpus=2,sockets=2"},
"both cpus and sockets are set to config's CpuCount",
},
{
&Config{
CpuCount: 2,
},
map[string]interface{}{},
&stepRun{ui: packer.TestUi(t)},
[]string{"-smp", "cpus=2,sockets=2"},
"both cpus and sockets are set to config's CpuCount",
},
{
&Config{
CpuCount: 2,
},
map[string]interface{}{
"floppy_path": "/path/to/floppy",
},
&stepRun{ui: packer.TestUi(t)},
[]string{"-fda", "/path/to/floppy"},
"floppy path should be set under fda flag, when it exists",
},
{
&Config{
Headless: false,
Display: "fakedisplay",
UseDefaultDisplay: false,
},
map[string]interface{}{},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{"-display", "fakedisplay"},
"Display option should value config display",
},
{
&Config{
Headless: false,
},
map[string]interface{}{},
&stepRun{
atLeastVersion2: true,
ui: packer.TestUi(t),
},
[]string{"-display", "gtk"},
"Display option should default to gtk",
},
}
for _, tc := range testcases {
state := runTestState(t, &Config{})
for k, v := range tc.ExtraState {
state.Put(k, v)
}
args, err := tc.Step.getCommandArgs(tc.Config, state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
if !matchArgument(args, tc.Expected) {
t.Fatalf("Couldn't find %#v in result. Got: %#v, Reason: %s",
tc.Expected, args, tc.Reason)
}
}
}
// This test makes sure that arguments don't end up in the final boot command
// if they aren't configured in the config.
// func TestDefaultsAbsentValues(t *testing.T) {}
func matchArgument(actual []string, expected []string) bool {
key := expected[0]
for i, k := range actual {
if key == k {
if expected[1] == actual[i+1] {
return true
}
}
}
return false
}
+3 -4
View File
@@ -44,12 +44,11 @@ type Config struct {
// that will be used to launch a new server and provision it. See
// the images list
// get the complete list of the accepted image UUID.
// The marketplace image label (eg `ubuntu_focal`) also works.
Image string `mapstructure:"image" required:"true"`
// The name of the server commercial type:
// ARM64-128GB, ARM64-16GB, ARM64-2GB, ARM64-32GB, ARM64-4GB,
// ARM64-64GB, ARM64-8GB, C1, C2L, C2M, C2S, START1-L,
// START1-M, START1-S, START1-XS, X64-120GB, X64-15GB, X64-30GB,
// X64-60GB
// C1, C2L, C2M, C2S, DEV1-S, DEV1-M, DEV1-L, DEV1-XL,
// GP1-XS, GP1-S, GP1-M, GP1-L, GP1-XL, RENDER-S
CommercialType string `mapstructure:"commercial_type" required:"true"`
// The name of the resulting snapshot that will
// appear in your account. Default packer-TIMESTAMP
+22 -1
View File
@@ -5,9 +5,11 @@ import (
"fmt"
"log"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/api/marketplace/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
@@ -22,8 +24,27 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
imageID := c.Image
// if not a UUID, we check the Marketplace API
_, err := uuid.ParseUUID(c.Image)
if err != nil {
apiMarketplace := marketplace.NewAPI(state.Get("client").(*scw.Client))
imageID, err = apiMarketplace.GetLocalImageIDByLabel(&marketplace.GetLocalImageIDByLabelRequest{
ImageLabel: c.Image,
Zone: scw.Zone(c.Zone),
CommercialType: c.CommercialType,
})
if err != nil {
err := fmt.Errorf("Error getting initial image info from marketplace: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
imageResp, err := instanceAPI.GetImage(&instance.GetImageRequest{
ImageID: c.Image,
ImageID: imageID,
})
if err != nil {
err := fmt.Errorf("Error getting initial image info: %s", err)
+8 -5
View File
@@ -14,15 +14,18 @@ type LocationConfig struct {
VMName string `mapstructure:"vm_name"`
// VM folder to create the VM in.
Folder string `mapstructure:"folder"`
// ESXi cluster where target VM is created. See
// [Working with Clusters](#working-with-clusters).
// ESXi cluster where target VM is created. See the
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
// section above for more details.
Cluster string `mapstructure:"cluster"`
// ESXi host where target VM is created. A full path must be specified if
// the host is in a folder. For example `folder/host`. See the
// `Specifying Clusters and Hosts` section above for more details.
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
// section above for more details.
Host string `mapstructure:"host"`
// VMWare resource pool. If not set, it will look for the root resource pool of the `host` or `cluster`.
// If a root resource is not found, it will then look for a default resource pool.
// VMWare resource pool. If not set, it will look for the root resource
// pool of the `host` or `cluster`. If a root resource is not found, it
// will then look for a default resource pool.
ResourcePool string `mapstructure:"resource_pool"`
// VMWare datastore. Required if `host` is a cluster, or if `host` has
// multiple datastores.
+95
View File
@@ -0,0 +1,95 @@
package main
import (
"flag"
"io/ioutil"
"log"
"os"
"os/user"
"path/filepath"
"github.com/hashicorp/packer/helper/communicator/sshkey"
)
type options struct {
Type string
Bits int
Filename string
}
func (o *options) AddFlagSets(fs *flag.FlagSet) {
fs.StringVar(&o.Type, "type", "rsa", `dsa | ecdsa | ed25519 | rsa
Specifies the type of key to create. The possible values are 'dsa', 'ecdsa',
'ed25519', or 'rsa'.
`)
fs.IntVar(&o.Bits, "bits", 0, `Specifies the number of bits in the key to create. By default maximum
number will be picked. For RSA keys, the minimum size is 1024 bits and the
default is 3072 bits. Generally, 3072 bits is considered sufficient. DSA
keys must be exactly 1024 bits as specified by FIPS 186-2. For ECDSA keys,
the bits flag determines the key length by selecting from one of three
elliptic curve sizes: 256, 384 or 521 bits. Attempting to use bit lengths
other than these three values for ECDSA keys will fail. Ed25519 keys have a
fixed length and the bits flag will be ignored.
`)
defaultPath := ""
user, err := user.Current()
if err == nil {
defaultPath = filepath.Join(user.HomeDir, ".ssh", "tests")
}
fs.StringVar(&o.Filename, "filename", defaultPath, `Specifies the filename of the key file.
`)
}
func main() {
log.SetFlags(0)
log.SetPrefix("ssh-keygen: ")
fs := flag.NewFlagSet("ssh-keygen", flag.ContinueOnError)
cla := options{}
cla.AddFlagSets(fs)
if err := fs.Parse(os.Args[1:]); err != nil {
log.Fatal(err)
}
algo, err := sshkey.AlgorithmString(cla.Type)
if err != nil {
log.Fatal(err)
}
log.Printf("Generating public/private %s key pair.", algo)
keypair, err := sshkey.GeneratePair(algo, nil, cla.Bits)
if err != nil {
log.Fatal(err)
}
if isDir(cla.Filename) {
cla.Filename = filepath.Join(cla.Filename, "id_"+algo.String())
}
if fileExists(cla.Filename) {
log.Fatalf("%s already exists.", cla.Filename)
}
log.Printf("Saving private key to %s", cla.Filename)
if err := ioutil.WriteFile(cla.Filename, keypair.Private, 0600); err != nil {
log.Fatal(err)
}
publicFilename := cla.Filename + ".pub"
log.Printf("Saving public key to %s", publicFilename)
if err := ioutil.WriteFile(publicFilename, keypair.Public, 0644); err != nil {
log.Fatal(err)
}
}
func isDir(filename string) bool {
info, err := os.Stat(filename)
if err != nil {
log.Fatal(err)
}
return info.IsDir()
}
func fileExists(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
+2 -2
View File
@@ -382,7 +382,7 @@ Options:
-color=false Disable color output. (Default: color)
-debug Debug mode enabled for builds.
-except=foo,bar,baz Run all builds and post-procesors other than these.
-except=foo,bar,baz Run all builds and post-processors other than these.
-only=foo,bar,baz Build only the specified builds.
-force Force a build to continue if artifacts exist, deletes existing artifacts.
-machine-readable Produce machine-readable output.
@@ -390,7 +390,7 @@ Options:
-parallel-builds=1 Number of builds to run in parallel. 1 disables parallelization. 0 means no limit (Default: 0)
-timestamp-ui Enable prefixing of each ui output with an RFC3339 timestamp.
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.
-var-file=path JSON or HCL2 file containing user variables.
`
return strings.TrimSpace(helpText)
+12 -2
View File
@@ -138,7 +138,7 @@ func TestBuild(t *testing.T) {
},
{
name: "build name: HCL",
name: "source name: HCL",
args: []string{
"-parallel-builds=1", // to ensure order is kept
testFixture("build-name-and-type"),
@@ -297,7 +297,6 @@ func TestBuild(t *testing.T) {
},
},
},
{
name: "hcl - recipes - only recipes",
args: []string{
@@ -314,6 +313,17 @@ func TestBuild(t *testing.T) {
},
},
},
{
name: "hcl - build.name accessible",
args: []string{
filepath.Join(testFixture("build-name-and-type"), "buildname.pkr.hcl"),
},
fileCheck: fileCheck{
expected: []string{
"pineapple.pizza.txt",
},
},
},
}
for _, tt := range tc {
+10
View File
@@ -2,6 +2,7 @@ package command
import (
"bytes"
"io/ioutil"
"path/filepath"
"testing"
@@ -27,6 +28,15 @@ func outputCommand(t *testing.T, m Meta) (string, string) {
return out.String(), err.String()
}
func testFixtureContent(n ...string) string {
path := filepath.Join(append([]string{fixturesDir}, n...)...)
b, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
return string(b)
}
func testFixture(n ...string) string {
paths := []string{fixturesDir}
paths = append(paths, n...)
+1 -1
View File
@@ -81,7 +81,7 @@ Usage: packer console [options] [TEMPLATE]
Options:
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables. [ Note that even in HCL mode this expects file to contain JSON, a fix is comming soon ]
-var-file=path JSON or HCL2 file containing user variables. [ Note that even in HCL mode this expects file to contain JSON, a fix is comming soon ]
`
return strings.TrimSpace(helpText)
+7 -7
View File
@@ -55,7 +55,7 @@ func (c *HCL2UpgradeCommand) ParseArgs(args []string) (*HCL2UpgradeArgs, int) {
}
const (
hcl2UpgradeFileHeader = `# This file was autogenerate by the BETA 'packer hcl2_upgrade' command. We
hcl2UpgradeFileHeader = `# This file was autogenerated by the BETA 'packer hcl2_upgrade' command. We
# recommend double checking that everything is correct before going forward. We
# also recommend treating this file as disposable. The HCL2 blocks in this
# file can be moved to other files. For example, the variable blocks could be
@@ -64,20 +64,20 @@ const (
# once they also need to be in the same folder. 'packer inspect folder/'
# will describe to you what is in that folder.
# All generated input variables will be of string type as this how Packer JSON
# views them; you can later on change their type. Read the variables type
# All generated input variables will be of 'string' type as this is how Packer JSON
# views them; you can change their type later on. Read the variables type
# constraints documentation
# https://www.packer.io/docs/from-1.5/variables#type-constraints for more info.
`
sourcesHeader = `
# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors onto a
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/from-1.5/blocks/source`
buildHeader = `
# a build block invokes sources and runs provisionning steps on them. The
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/from-1.5/blocks/build
build {
@@ -413,14 +413,14 @@ func (*HCL2UpgradeCommand) Help() string {
helpText := `
Usage: packer hcl2_upgrade -output-file=JSON_TEMPLATE.pkr.hcl JSON_TEMPLATE...
Will transform your JSON template to a HCL2 configuration.
Will transform your JSON template into an HCL2 configuration.
`
return strings.TrimSpace(helpText)
}
func (*HCL2UpgradeCommand) Synopsis() string {
return "transform a JSON template into a HCL2 configuration"
return "transform a JSON template into an HCL2 configuration"
}
func (*HCL2UpgradeCommand) AutocompleteArgs() complete.Predictor {
+7
View File
@@ -153,6 +153,13 @@ Note: If your build names contain user variables or template
functions such as 'timestamp', these are processed at build time,
and therefore only show in their raw form here.
`},
{
[]string{
"inspect", filepath.Join(testFixture("hcl-inspect-with-sensitive-vars")),
},
nil,
testFixtureContent("hcl-inspect-with-sensitive-vars", "expected-output.txt"),
},
}
for _, tc := range tc {
@@ -0,0 +1,16 @@
source "null" "pizza" {
communicator = "none"
}
build {
name = "pineapple"
sources = [
"sources.null.pizza",
]
provisioner "shell-local" {
inline = [
"echo '' > ${build.name}.${source.name}.txt"
]
}
}
@@ -0,0 +1,16 @@
Packer Inspect: HCL2 mode
> input-variables:
var.not_sensitive: "I am soooo not sensitive"
var.not_sensitive_unknown: "<unknown>"
var.sensitive: "<sensitive>"
var.sensitive_array: "[\n \"<sensitive>\",\n \"<sensitive>\",\n]"
var.sensitive_tags: "{\n \"first_key\" = \"<sensitive>\"\n \"second_key\" = \"<sensitive>\"\n}"
var.sensitive_unknown: "<unknown>"
> local-variables:
> builds:

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