Compare commits

..

72 Commits

Author SHA1 Message Date
Wilken Rivera 8de1eddcb2 Update to run exeternal commands via a script block 2020-09-03 09:08:49 -04:00
Wilken Rivera 9df4129d6d provisioner/powershell: Add wrapper script to default provisioner runs
This change adds a wrapper script to the provisioner for improved error
handling. The wrapper script is enabled by default, but will be disabled
when using a custom ExecuteCommand or ElevatedExecuteCommand.

The wrapper script will include the contents of any provided Inline
commands or Scripts as part of its payload and run as a single script
with environment variables loaded within the script. This changes the
existing behavior of uploading any defined script(s) unmodified to the
remote host.
2020-09-03 06:32:34 -04:00
Wilken Rivera ab93bc8a5d Add Wrapper command with support for execute command 2020-09-02 10:23:17 -04:00
Wilken Rivera e90913fcd2 Fix up LASTEXITCODE and script cleanup 2020-09-02 10:23:17 -04:00
Wilken Rivera b1fec8f0bc Change script value 2020-09-02 10:23:17 -04:00
Wilken Rivera ed1a2b1deb provisioner/powershell: Implement error handling 2020-09-02 10:23:17 -04:00
Sylvia Moss 903deb9e6a vSphere StepHardware tests (#9866) 2020-09-02 10:19:57 +02:00
Megan Marsh bddf3b03f7 Merge pull request #9832 from hashicorp/azr-inspect-allow-unsed-variables
Inspect allow unset variables in HCL2 and JSON
2020-09-01 11:16:54 -07:00
Megan Marsh e96cc07a1a Merge pull request #9853 from remyleone/boottype_local
change default scaleway boottype to local
2020-09-01 10:44:42 -07:00
Wilken Rivera 79bc643c17 post-processor/digitalocean-import: Update documentation (#9865)
* Add a note to use the DigitialOcean Builder when working directly on Digital Ocean
* Add HCL2 example to the documentation
2020-09-01 11:08:06 -04:00
Wilken Rivera 10e74961d2 Add check for empty artifact.Files slice (#9857)
* Add check for empty artifact.Files slice

Tests before change
```
⇶  go test ./post-processor/digitalocean-import/... -run=TestPostProcsor_extractImageArtifact
2020/08/31 13:51:25 Looking for image in artifact
--- FAIL: TestPostProcsor_extractImageArtifact (0.00s)
panic: runtime error: index out of range [0] with length 0 [recovered]
        panic: runtime error: index out of range [0] with length 0

goroutine 7 [running]:
testing.tRunner.func1.1(0xfb0300, 0xc000456460)
        /usr/local/go/src/testing/testing.go:940 +0x2f5
testing.tRunner.func1(0xc0003ab560)
        /usr/local/go/src/testing/testing.go:943 +0x3f9
panic(0xfb0300, 0xc000456460)
        /usr/local/go/src/runtime/panic.go:969 +0x166
github.com/hashicorp/packer/post-processor/digitalocean-import.extractImageArtifact(0x0, 0x0, 0x0, 0x24, 0xc000060ea0, 0x453937, 0x1431250)
        /home/wilken/Development/packer/post-processor/digitalocean-import/post-processor.go:262 +0x36d
github.com/hashicorp/packer/post-processor/digitalocean-import.TestPostProcsor_extractImageArtifact(0xc0003ab560)
        /home/wilken/Development/packer/post-processor/digitalocean-import/post-processor_test.go:28 +0x2b0
testing.tRunner(0xc0003ab560, 0x1077208)
        /usr/local/go/src/testing/testing.go:991 +0xdc
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:1042 +0x357
FAIL    github.com/hashicorp/packer/post-processor/digitalocean-import  0.009s
FAIL
```

Tests after change
```
[go-1.14.2] [1] wilken@automaton in ~/Development/packer/ on fix_9848 (ahead 1)
⇶  go test ./post-processor/digitalocean-import/... -run=TestPostProcsor_extractImageArtifact
ok      github.com/hashicorp/packer/post-processor/digitalocean-import  0.006s
```

* Update to reflect review feedback
2020-09-01 10:59:01 -04:00
Adrien Delorme bdf198594e hcl2 inspect: sort variables to have a consistent output 2020-09-01 15:28:16 +02:00
Adrien Delorme a25f057984 add tests for unknown values 2020-09-01 15:28:16 +02:00
Adrien Delorme 161879b98a test unknown variables and locals 2020-09-01 11:59:07 +02:00
Adrien Delorme 652878059c inspect command: ignore init errors as some value can be unset 2020-09-01 11:59:07 +02:00
Adrien Delorme eb4069a1b7 hcl variables: return an unknown value in when no default is set 2020-09-01 11:59:07 +02:00
Adrien Delorme 91c5a4613c HCL2 inspect remove debug values 2020-09-01 11:59:07 +02:00
Sylvia Moss 9eaa2e17f7 add netlify node option to increase old space size (#9859) 2020-09-01 11:24:30 +02:00
Sylvia Moss 24dbd0a28b Fix iso_path validation regex (#9855) 2020-09-01 10:11:48 +02:00
Rémy Léone 05aecc56ea Fix 2020-09-01 09:14:07 +02:00
Wilken Rivera f4556fd3f1 Merge pull request #9831 from hashicorp/remedial_remote_export_config_tests
add tests to make sure prepare for export defaults properly
2020-08-31 15:47:05 -04:00
Sylvia Moss 58a0bdd780 Look for a default resource pool when root resource pool is not found (#9809) 2020-08-31 16:26:48 +02:00
Wilken Rivera 03a374f4b6 Fix linting issues for SA6005 check (#9854)
Before change
```
⇶  golangci-lint run --disable-all --no-config --enable=staticcheck | ack SA6005
builder/profitbricks/step_create_server.go:254:22: SA6005: should use strings.EqualFold(a, b) instead of strings.ToLower(a) == strings.ToLower(b) (staticcheck)
builder/oneandone/config.go:97:7: SA6005: should use strings.EqualFold(a, b) instead of strings.ToLower(a) == strings.ToLower(b) (staticcheck)
builder/vmware/common/driver_parser.go:1199:7: SA6005: should use strings.EqualFold(a, b) instead of strings.ToLower(a) == strings.ToLower(b) (staticcheck)
```

After change
```
⇶  golangci-lint run --disable-all --no-config --enable=staticcheck | ack SA6005

```
2020-08-31 15:44:42 +02:00
GennadySpb 804fefef17 yandex-import: allow set custom API endpoint (#9850)
* Separate Access Config from yandex builder Config

* make use of Access Config explicit

* Move `MaxRetries` into AccessConfig

* NewDriverYC use AccessConfig instead Config

* yandex-import PP use common Access Config

Now support set custom API Endpoint

* yandex-export PP use common Access Config

Now support set custom API Endpoint too (as yandex-import)

* fix test

* Tiny doc updates.
2020-08-31 15:29:20 +02:00
Rémy Léone f578b93f7e update scaleway code owners (#9852) 2020-08-31 14:41:45 +02:00
GennadySpb 0df2e15d9f If proposed exit code not equal 0 set proper metadata key to expected 'cloud-init-error'. (#9849)
Last one checked at `StepWaitCloudInitScript`.
2020-08-31 14:38:22 +02:00
OblateSpheroid 0ad26cce01 Feat (oracle-oci): allow freeform and defined tags to be added instance (#9802)
* feat (oracle-oci): allow freeform and defined tags to be added to instance (#6313)

* docs (oracle-oci): add descriptions for instance_tags and instance_defined_tags (#6313)

* fix: fmt

* fix: generate hcl2spec
2020-08-31 14:36:09 +02:00
Rui Lopes 281af9a03d prefer $APPDATA over $HOME in Windows (#9830) 2020-08-31 14:35:15 +02:00
Rémy Léone ecd1a49a35 change default scaleway boottype to local 2020-08-31 13:15:37 +02:00
Andreas Botzner e4f975fae1 Allows for the mounting of ISOs when a Proxmox VM s created. Same as … (#9653)
Allows the mounting of additional ISOs when the VM is created. The config option was taken from PR #9055 and slightly changed. Users can specify an array of bus names, bus numbers and filenames.

"cd_drive":[
{
"bus": "ide",
"bus_number": 3,
"filename": "isos:iso/virtio-win-0.1.187.iso"
},
{
"bus": "sata",
"bus_number": 3,
"filename": "isos:iso/someother.iso"
}
]

Closes: #7950
Co-authored-by: Calle Pettersson <carlpett@users.noreply.github.com>
2020-08-31 10:48:24 +02:00
Sylvia Moss 942bfbf221 Add driver mocks and write tests to steps Remote Upload and Create VM (#9833) 2020-08-31 10:34:41 +02:00
packer-ci cf622346ab Putting source back into Dev Mode 2020-08-28 18:23:03 +00:00
packer-ci e3ac7de965 Cut version 1.6.2 2020-08-28 15:06:28 +00:00
packer-ci 576e227e60 cut version 1.6.2 2020-08-28 15:06:26 +00:00
packer-ci 4565119694 update changelog 2020-08-28 15:06:26 +00:00
Wilken Rivera bd0cb85bb6 Reset change entries made by the packer-ci release bot
This reverts commit c35837ee49.
2020-08-28 10:55:55 -04:00
packer-ci a56acabe23 Cut version 1.6.2 2020-08-27 20:58:08 +00:00
packer-ci 10f34a3b12 cut version 1.6.2 2020-08-27 20:58:07 +00:00
packer-ci c35837ee49 update changelog 2020-08-27 20:58:07 +00:00
Wilken Rivera c4b039391a update changelog 2020-08-27 14:36:22 -04:00
Megan Marsh 1c5bc41beb refactor ovftool validation into vmware driver 2020-08-27 10:55:57 -07:00
Megan Marsh bbc3a5b0d1 add tests to make sure prepare for export defaults properly 2020-08-27 10:55:57 -07:00
Wilken Rivera 67cd123d1c Merge pull request #9834 from hashicorp/azr-fix-hcl2_upgrade_random_generate
hcl2_upgrade: fix a case where the generated type is wrong
2020-08-27 13:03:34 -04:00
Wilken Rivera e4a37c37b9 Merge pull request #9835 from hashicorp/b-hcl2_upgrade_command-description
command/hcl2_upgrade: Update description text for command
2020-08-27 13:03:04 -04:00
Wilken Rivera 0e2a3e1058 command/hcl2_upgrade: Update description text for command
Before change
```
Usage: packer [--version] [--help] <command> [<args>]

Available commands are:
    build           build image(s) from template
    console         creates a console for testing variable interpolation
    fix             fixes templates from old versions of packer
    hcl2_upgrade    build image(s) from template
    inspect         see components of a template
    validate        check that a template is valid
    version         Prints the Packer version
```

After change
```
Usage: packer [--version] [--help] <command> [<args>]

Available commands are:
    build           build image(s) from template
    console         creates a console for testing variable interpolation
    fix             fixes templates from old versions of packer
    hcl2_upgrade    transform a JSON template into a HCL2 configuration
    inspect         see components of a template
    validate        check that a template is valid
    version         Prints the Packer version

```
2020-08-27 11:52:09 -04:00
Adrien Delorme 0f00709fb6 hcl2_upgrade: fix a case where the generated type is wrong
when it encounters map[string]interface{} or []interface{} types,  hcl2_upgrade now takes the 'most complex' entry from those in order to tell wether this is going to be a body `body {}` or an attribute `attribute = {}`. Before that the hcl2_upgrade command could be a bit random there.

A way better ( but may be somewhat hard ) way to do this would be to use the actual plugins structs in order to generate the HCL2.
2020-08-27 16:47:14 +02:00
Wilken Rivera 279e44e51d upate changelog 2020-08-26 15:56:11 -04:00
Wilken Rivera fe94fae2b2 Merge pull request #9829 from hashicorp/fix_9789
add variable gotcha to the variables docs not just the from-json hcl …
2020-08-26 14:07:36 -04:00
Megan Marsh cf1a39a4e8 add variable gotcha to the variables docs not just the from-json hcl guides. 2020-08-26 10:54:39 -07:00
Megan Marsh df4ce6fd34 Merge pull request #9821 from homedepot/ansible_ssh_extra_args
Ansible ssh extra args
2020-08-26 10:44:51 -07:00
Megan Marsh 60d124dcaf Merge pull request #9825 from hashicorp/do_7165
Allow "export" to ovf/ova for local vmware builds in addition to esx …
2020-08-26 10:43:49 -07:00
Megan Marsh b6e277fb05 Merge pull request #9828 from hashicorp/d-salt-masterless-HCL2-examples
provisioner/salt-masterless: Add HCL2 example to docs
2020-08-26 10:43:22 -07:00
Wilken Rivera 7e81e3fbda provisioner/salt-masterless: Add HCL2 example to docs 2020-08-26 13:17:59 -04:00
Megan Marsh a6d5106cd7 Allow "export" to ovf/ova for local vmware builds in addition to esx ones.
Refactor step_export and the driver interface to move the ovftool call
into the vmware driver. This refactor allows us to add meaningful tests
to step_export, which I have also added here.
2020-08-26 09:45:12 -07:00
Viktor A. Danilov cd60f32866 fix yandex-export aws: (#9814)
1. move aws validation before disk image creation
2. add `--region` option
2020-08-26 12:41:05 +02:00
Megan Marsh e9b526ee2d Move step_create_disk into common folder, and add to vmx builder (#9815)
Pull additional disk related config options into their own file.
2020-08-26 10:13:11 +02:00
Megan Marsh b90957d11c Merge pull request #9824 from hashicorp/d-windows-restart-hcl2-example
provisioner/windows-restart: Add HCL2 example to documentation
2020-08-25 16:35:36 -07:00
Megan Marsh 0113aae27d Merge pull request #9823 from hashicorp/b-windows-shell-hcl-docs
provisioner/windows-shell: Add HCL2 example to documentation
2020-08-25 16:35:08 -07:00
Wilken Rivera 4dff73cec2 provisioner/windows-shell: Add HCL2 example to documentation 2020-08-25 17:03:15 -04:00
Wilken Rivera 0e388db795 provisioner/windows-restart: Add HCL2 example to documentation 2020-08-25 17:01:05 -04:00
Upo 3506b8876f Update the GCE Builder Documentation (#9820)
* update documentation
* add HCL2 examples
2020-08-25 11:33:02 -04:00
Larry 0bcf4f2613 Update provisioner.hcl2spec.go 2020-08-25 10:11:56 -05:00
Larry 33f391ae37 Update Config-not-required.mdx 2020-08-25 10:05:29 -05:00
Larry 20472bc12f Update provisioner_test.go 2020-08-25 09:49:51 -05:00
Larry f4a2838716 Added Tests for AnsibleSSHExtraArgs 2020-08-25 08:54:25 -05:00
Larry 7cb17f64a6 Added AnsibleSSHExtraArgs 2020-08-25 08:53:41 -05:00
Adrien Delorme 0d0bd9ce75 name fields of PolicyDocument correctly in HCL (#9812)
withouth this fix we would have had to do

```hcl
  temporary_iam_instance_profile_policy_document {
    statement {
      action   = ["*"]
      effect   = "Allow"
      resource = ["*"]
    }
    version = "2012-10-17"
  }
```

instead of the same document but with capitalised fields
2020-08-25 10:53:56 +02:00
Adrien Delorme 5ba134ac5b JSON to HCL2 (minimal best-effort) transpiler (#9659)
hcl2_upgrade transforms a JSON build-file in a HCL2 build-file.
This starts a validated Packer core and from that core we generate an HCL 'block' per plugin/configuration. So for a builder, a provisioner, a post-processor or a variable. The contents of each block is just transformed as is and basically all fields are HCL2-ified.
A generated field can be valid in JSON but invalid on HCL2; for example JSON templating (in mapstructure) allows to set arrays of strings - like `x = ["a", "b"]` - with single strings - like `x="a"` -, HCL does not allow this.
Since JSON does not make the distinction between variables and locals, everything will be a variable. So variables that use other variables will not work.
hcl2_upgrade tries to transform go templating interpolation calls to HCL2 calls when possible, leaving the go templating calls like they are in case it cannot.

Work:
* transpiler
* tests
* update hcl v2 library so that output looks great.
* update docs
2020-08-25 10:51:43 +02:00
Megan Marsh 9f2cb0d560 make the default target generate dev builds. (#9811) 2020-08-25 10:11:38 +02:00
Adrien Delorme a0c09e85df retry spot instance creation when an "Invalid IAM Instance Profile name" error pops up (#9810)
PutRolePolicy & AddRoleToInstanceProfile are eventually consistent but it is not possible to wait for them to be done here: https://github.com/hashicorp/packer/blob/0785c2f6fca9c22bf25528e0176042799dd79df9/builder/amazon/common/step_iam_instance_profile.go#L117-L134 which was causing the `CreateFleet` to fail (100% for me). So for now we retry a bit later. Waiting 5 seconds after the previously linked code also fixed this.

Test file:

```json
{
	"builders": [
		{
			"type": "amazon-ebs",
			"region": "eu-west-1",
			"ami_name": "ubuntu-16.04 test {{timestamp}}",
			"ami_description": "Ubuntu 16.04 LTS - expand root partition",
			"source_ami_filter": {
				"filters": {
					"virtualization-type": "hvm",
					"name": "ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*",
					"root-device-type": "ebs"
				},
				"owners": [
					"099720109477"
				],
				"most_recent": true
			},
			"spot_price": "0.03",
			"spot_instance_types": [
				"t2.small"
			],
			"encrypt_boot": true,
			"ssh_username": "ubuntu",
			"ssh_interface": "session_manager",
			"temporary_iam_instance_profile_policy_document": {
				"Version": "2012-10-17",
				"Statement": [
					{
						"Effect": "Allow",
						"Action": [
							"*"
						],
						"Resource": "*"
					}
				]
			},
			"communicator": "ssh"
		}
]}
```
2020-08-25 10:10:32 +02:00
Wilken Rivera 1252658848 Merge pull request #9813 from raygervais/documentation/inspec
adds: note in documentation of inspec on host machine required
2020-08-24 16:38:39 -04:00
raygervais 759ae006df adds: note in documentation of inspec on host machine required 2020-08-24 12:39:57 -04:00
273 changed files with 89117 additions and 14379 deletions
+29 -1
View File
@@ -1,4 +1,13 @@
## 1.6.2 (Upcoming)
## 1.6.3 (Upcoming)
## 1.6.2 (August 28, 2020)
### FEATURES:
* **New command** `hcl2_upgrade` is a JSON to HCL2 transpiler that allows users
to transform an existing JSON configuration template into its HCL2 template
equivalent. Please see [hcl2_upgrade command
docs](https://packer.io/docs/commands/hcl2_upgrade) for more details.
[GH-9659]
### IMPROVEMENTS:
* builder/amazon: Add all of the custom AWS template engines to `build`
@@ -8,14 +17,22 @@
* builder/azure: Add FreeBSD support to azure/chroot builder. [GH-9697]
* builder/vmware-esx: Add `network_name` option to vmware so that users can set
a network without using vmx data. [GH-9718]
* builder/vmware-vmx: Add additional disk configuration option. Previously
only implemented for vmware-iso builder [GH-9815]
* builder/vmware: Add a `remote_output_directory option` so users can tell
Packer where on a datastore to create a vm. [GH-9784]
* builder/vmware: Add option to export to ovf or ova from a local vmware build
[GH-9825]
* builder/vmware: Add progress tracker to vmware-esx5 iso upload. [GH-9779]
* builder/vsphere-iso: Add support for building on a single ESXi host
[GH-9793]
* builder/vsphere: Add new `directory_permission` config export option.
[GH-9704]
* builder/vsphere: Add option to import OVF templates to the Content Library
[GH-9755]
* builder/vsphere: Add step and options to customize cloned VMs. [GH-9665]
* builder/vsphere: Update `iso_paths` to support reading ISOs from Content
Library paths [GH-9801]
* core/hcl: Add provisioner "override" option to HCL2 templates. [GH-9764]
* core/hcl: Add vault integration as an HCL2 function function. [GH-9746]
* core: Add colored prefix to progress bar so it's clearer what build each
@@ -27,6 +44,8 @@
[GH-9773]
* post-processor/vsphere: Improve UI to catch bad credentials and print errors.
[GH-9649]
* provisioner/ansible-remote: Add `ansible_ssh_extra_args` so users can specify
extra arguments to pass to ssh [GH-9821]
* provisioner/file: Clean up, bugfix, and document previously-hidden `sources`
option. [GH-9725] [GH-9735]
* provisioner/salt-masterless: Add option to option to download community
@@ -39,6 +58,11 @@
binaries. [GH-9706]
* builder/amazon-ebssurrogate: Make skip_save_build_region option work in the
ebssurrogate builder, not just the ebs builder. [GH-9666]
* builder/amazon: Add retry logic to the spot instance creation step to handle
"Invalid IAM Instance Profile name" errors [GH-9810]
* builder/amazon: Update the `aws_secretsmanager` function to read from the AWS
credentials file for obtaining default region information; fixes the
'MissingRegion' error when AWS_REGION is not set [GH-9781]
* builder/file: Make sure that UploadDir receives the interpolated destination.
[GH-9698]
* builder/googlecompute: Fix bug where startup script hang would cause export
@@ -61,6 +85,10 @@
interpolation. [GH-9673]
* post-processor/vsphere-template: Fix ReregisterVM to default to true instead
of false. [GH-9736]
* post-processor/yandex-export: Fix issue when validating region_name [GH-9814]
* provisioner/inspec: Fix the 'Unsupported argument; An argument named
"command"' error when using the inspec provisioner in an HCL2 configuration
[GH-9800]
## 1.6.1 (July 30, 2020)
+2 -2
View File
@@ -49,8 +49,8 @@
/builder/proxmox/ @carlpett
/website/pages/docs/builders/proxmox* @carlpett
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
/website/pages/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/scaleway/ @scaleway/devtools
/website/pages/docs/builders/scaleway* @scaleway/devtools
/builder/hcloud/ @LKaemmerling
/website/pages/docs/builders/hcloud* @LKaemmerling
+1 -1
View File
@@ -25,7 +25,7 @@ export GOLDFLAGS
.PHONY: bin checkversion ci ci-lint default install-build-deps install-gen-deps fmt fmt-docs fmt-examples generate install-lint-deps lint \
releasebin test testacc testrace
default: install-build-deps install-gen-deps generate bin
default: install-build-deps install-gen-deps generate dev
ci: testrace ## Test in continuous integration
+5 -5
View File
@@ -53,14 +53,14 @@ type VpcFilterOptions struct {
}
type Statement struct {
Effect string
Action []string
Resource []string
Effect string `mapstructure:"Effect" required:"false"`
Action []string `mapstructure:"Action" required:"false"`
Resource []string `mapstructure:"Resource" required:"false"`
}
type PolicyDocument struct {
Version string
Statement []Statement
Version string `mapstructure:"Version" required:"false"`
Statement []Statement `mapstructure:"Statement" required:"false"`
}
type SecurityGroupFilterOptions struct {
+10 -10
View File
@@ -39,8 +39,8 @@ func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatPolicyDocument struct {
Version *string `cty:"version" hcl:"version"`
Statement []FlatStatement `cty:"statement" hcl:"statement"`
Version *string `mapstructure:"Version" required:"false" cty:"Version" hcl:"Version"`
Statement []FlatStatement `mapstructure:"Statement" required:"false" cty:"Statement" hcl:"Statement"`
}
// FlatMapstructure returns a new FlatPolicyDocument.
@@ -55,8 +55,8 @@ func (*PolicyDocument) FlatMapstructure() interface{ HCL2Spec() map[string]hclde
// The decoded values from this spec will then be applied to a FlatPolicyDocument.
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
"statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
"Version": &hcldec.AttrSpec{Name: "Version", Type: cty.String, Required: false},
"Statement": &hcldec.BlockListSpec{TypeName: "Statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
}
return s
}
@@ -89,9 +89,9 @@ func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec {
// FlatStatement is an auto-generated flat version of Statement.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatStatement struct {
Effect *string `cty:"effect" hcl:"effect"`
Action []string `cty:"action" hcl:"action"`
Resource []string `cty:"resource" hcl:"resource"`
Effect *string `mapstructure:"Effect" required:"false" cty:"Effect" hcl:"Effect"`
Action []string `mapstructure:"Action" required:"false" cty:"Action" hcl:"Action"`
Resource []string `mapstructure:"Resource" required:"false" cty:"Resource" hcl:"Resource"`
}
// FlatMapstructure returns a new FlatStatement.
@@ -106,9 +106,9 @@ func (*Statement) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spe
// The decoded values from this spec will then be applied to a FlatStatement.
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"effect": &hcldec.AttrSpec{Name: "effect", Type: cty.String, Required: false},
"action": &hcldec.AttrSpec{Name: "action", Type: cty.List(cty.String), Required: false},
"resource": &hcldec.AttrSpec{Name: "resource", Type: cty.List(cty.String), Required: false},
"Effect": &hcldec.AttrSpec{Name: "Effect", Type: cty.String, Required: false},
"Action": &hcldec.AttrSpec{Name: "Action", Type: cty.List(cty.String), Required: false},
"Resource": &hcldec.AttrSpec{Name: "Resource", Type: cty.List(cty.String), Required: false},
}
return s
}
+31 -10
View File
@@ -6,9 +6,11 @@ import (
"fmt"
"io/ioutil"
"log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/common/random"
"github.com/hashicorp/packer/common/retry"
@@ -278,23 +280,39 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
Type: aws.String("instant"),
}
var createOutput *ec2.CreateFleetOutput
err = retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
if strings.Contains(err.Error(), "Invalid IAM Instance Profile name") {
// eventual consistency of the profile. PutRolePolicy &
// AddRoleToInstanceProfile are eventually consistent and once
// we can wait on those operations, this can be removed.
return true
}
return request.IsErrorRetryable(err)
},
RetryDelay: (&retry.Backoff{InitialBackoff: 500 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
createOutput, err = ec2conn.CreateFleet(createFleetInput)
if err == nil && createOutput.Errors != nil {
err = fmt.Errorf("errors: %v", createOutput.Errors)
}
if err != nil {
log.Printf("create request failed %v", err)
}
return err
})
// Create the request for the spot instance.
req, createOutput := ec2conn.CreateFleetRequest(createFleetInput)
ui.Message(fmt.Sprintf("Sending spot request (%s)...", req.RequestID))
// Actually send the spot connection request.
err = req.Send()
if err != nil {
if createOutput.FleetId != nil {
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
} else {
err = fmt.Errorf("Error waiting for fleet request: %s", err)
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(createOutput.Instances) == 0 {
// We can end up with errors because one of the allowed availability
// zones doesn't have one of the allowed instance types; as long as
// an instance is launched, these errors aren't important.
@@ -308,6 +326,9 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *createOutput.Instances[0].InstanceIds[0]
+1 -1
View File
@@ -94,7 +94,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs, err)
}
for _, dc := range dcs {
if strings.ToLower(dc.CountryCode) == strings.ToLower(c.DataCenterName) {
if strings.EqualFold(dc.CountryCode, c.DataCenterName) {
c.DataCenterId = dc.Id
break
}
+3 -1
View File
@@ -61,7 +61,9 @@ type Config struct {
ImageName string `mapstructure:"image_name"`
// 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"`
// Metadata optionally contains custom metadata key/value pairs provided in the
// configuration. While this can be used to set metadata["user_data"] the explicit
+4
View File
@@ -79,6 +79,8 @@ type FlatConfig struct {
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
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"`
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,6 +171,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", 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},
"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},
+2
View File
@@ -54,6 +54,8 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
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,
+1 -1
View File
@@ -251,7 +251,7 @@ func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui
if i != "" {
alias = i
}
if alias != "" && strings.ToLower(alias) == strings.ToLower(imageAlias) {
if alias != "" && strings.EqualFold(alias, imageAlias) {
return alias
}
}
+16 -2
View File
@@ -71,8 +71,21 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ResultKey: downloadPathKey,
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
},
}}
for idx := range b.config.AdditionalISOFiles {
steps = append(steps, &common.StepDownload{
Checksum: b.config.AdditionalISOFiles[idx].ISOChecksum,
Description: "additional ISO",
Extension: b.config.AdditionalISOFiles[idx].TargetExtension,
ResultKey: b.config.AdditionalISOFiles[idx].downloadPathKey,
TargetPath: b.config.AdditionalISOFiles[idx].downloadPathKey,
Url: b.config.AdditionalISOFiles[idx].ISOUrls,
})
}
steps = append(steps,
&stepUploadISO{},
&stepUploadAdditionalISOs{},
&stepStartVM{},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
@@ -96,7 +109,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&stepConvertToTemplate{},
&stepFinalizeTemplateConfig{},
&stepSuccess{},
}
)
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
+68 -1
View File
@@ -1,4 +1,4 @@
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig
package proxmox
@@ -8,6 +8,7 @@ import (
"log"
"net/url"
"os"
"strconv"
"strings"
"time"
@@ -64,6 +65,8 @@ type Config struct {
shouldUploadISO bool
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
ctx interpolate.Context
}
@@ -87,6 +90,15 @@ type vgaConfig struct {
Type string `mapstructure:"type"`
Memory int `mapstructure:"memory"`
}
type storageConfig struct {
common.ISOConfig `mapstructure:",squash"`
Device string `mapstructure:"device"`
ISOFile string `mapstructure:"iso_file"`
ISOStoragePool string `mapstructure:"iso_storage_pool"`
Unmount bool `mapstructure:"unmount"`
shouldUploadISO bool
downloadPathKey string
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
// Agent defaults to true
@@ -183,6 +195,61 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("disk format must be specified for pool type %q", c.Disks[idx].StoragePoolType))
}
}
for idx := range c.AdditionalISOFiles {
// Check AdditionalISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c.AdditionalISOFiles[idx].ISOFile != "" {
c.AdditionalISOFiles[idx].shouldUploadISO = false
} else {
c.AdditionalISOFiles[idx].downloadPathKey = "downloaded_additional_iso_path_" + strconv.Itoa(idx)
isoWarnings, isoErrors := c.AdditionalISOFiles[idx].ISOConfig.Prepare(&c.ctx)
errs = packer.MultiErrorAppend(errs, isoErrors...)
warnings = append(warnings, isoWarnings...)
c.AdditionalISOFiles[idx].shouldUploadISO = true
}
if c.AdditionalISOFiles[idx].Device == "" {
log.Printf("AdditionalISOFile %d Device not set, using default 'ide3'", idx)
c.AdditionalISOFiles[idx].Device = "ide3"
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "ide") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[3:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[3:]))
}
if busnumber == 2 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus 2 is used by boot ISO"))
}
if busnumber > 3 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus index can't be higher than 3"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "sata") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 5 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SATA bus index can't be higher than 5"))
}
}
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "scsi") {
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
}
if busnumber > 30 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30"))
}
}
if (c.AdditionalISOFiles[idx].ISOFile == "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) == 0) || (c.AdditionalISOFiles[idx].ISOFile != "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) != 0) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("either iso_file or iso_url, but not both, must be specified for AdditionalISO file %s", c.AdditionalISOFiles[idx].Device))
}
if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" {
errs = packer.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified"))
}
}
if c.SCSIController == "" {
log.Printf("SCSI controller not set, using default 'lsi'")
c.SCSIController = "lsi"
+136 -95
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig"; DO NOT EDIT.
package proxmox
import (
@@ -9,100 +9,101 @@ import (
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
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"`
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
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"`
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
}
// FlatMapstructure returns a new FlatConfig.
@@ -211,6 +212,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())},
}
return s
}
@@ -281,6 +283,45 @@ func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec {
return s
}
// FlatstorageConfig is an auto-generated flat version of storageConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatstorageConfig struct {
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
Device *string `mapstructure:"device" cty:"device" hcl:"device"`
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"`
}
// FlatMapstructure returns a new FlatstorageConfig.
// FlatstorageConfig is an auto-generated flat version of storageConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*storageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatstorageConfig)
}
// HCL2Spec returns the hcl spec of a storageConfig.
// This spec is used by HCL to read the fields of storageConfig.
// The decoded values from this spec will then be applied to a FlatstorageConfig.
func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false},
"device": &hcldec.AttrSpec{Name: "device", Type: cty.String, Required: false},
"iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false},
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
"unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false},
}
return s
}
// FlatvgaConfig is an auto-generated flat version of vgaConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatvgaConfig struct {
@@ -93,6 +93,29 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St
}
}
if len(c.AdditionalISOFiles) > 0 {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
err := fmt.Errorf("Error fetching template config: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for idx := range c.AdditionalISOFiles {
cdrom := c.AdditionalISOFiles[idx].Device
if c.AdditionalISOFiles[idx].Unmount {
if vmParams[cdrom] == nil || !strings.Contains(vmParams[cdrom].(string), "media=cdrom") {
err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present or not a cdrom media", cdrom)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
changes[cdrom] = "none,media=cdrom"
} else {
changes[cdrom] = c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom"
}
}
}
if len(changes) > 0 {
_, err := client.SetVmConfig(vmRef, changes)
if err != nil {
+13
View File
@@ -91,6 +91,19 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", vmRef)
for idx := range c.AdditionalISOFiles {
params := map[string]interface{}{
c.AdditionalISOFiles[idx].Device: c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom",
}
_, err = client.SetVmConfig(vmRef, params)
if err != nil {
err := fmt.Errorf("Error configuring VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Starting VM")
_, err = client.StartVm(vmRef)
if err != nil {
@@ -0,0 +1,68 @@
package proxmox
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// stepUploadAdditionalISOs uploads all additional ISO files that are mountet
// to the VM
type stepUploadAdditionalISOs struct{}
type uploader interface {
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
}
var _ uploader = &proxmox.Client{}
func (s *stepUploadAdditionalISOs) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(uploader)
c := state.Get("config").(*Config)
for idx := range c.AdditionalISOFiles {
if !c.AdditionalISOFiles[idx].shouldUploadISO {
state.Put("additional_iso_files", c.AdditionalISOFiles)
continue
}
p := state.Get(c.AdditionalISOFiles[idx].downloadPathKey).(string)
if p == "" {
err := fmt.Errorf("Path to downloaded ISO was empty")
state.Put("erroe", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
isoPath, _ := filepath.EvalSymlinks(p)
r, err := os.Open(isoPath)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
filename := filepath.Base(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls[0])
err = client.Upload(c.Node, c.AdditionalISOFiles[idx].ISOStoragePool, "iso", filename, r)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.AdditionalISOFiles[idx].ISOStoragePool, filename)
c.AdditionalISOFiles[idx].ISOFile = isoStoragePath
state.Put("additional_iso_files", c.AdditionalISOFiles)
}
return multistep.ActionContinue
}
func (s *stepUploadAdditionalISOs) Cleanup(state multistep.StateBag) {
}
-6
View File
@@ -3,7 +3,6 @@ package proxmox
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
@@ -15,10 +14,6 @@ import (
// stepUploadISO uploads an ISO file to Proxmox so we can boot from it
type stepUploadISO struct{}
type uploader interface {
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
}
var _ uploader = &proxmox.Client{}
func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@@ -58,7 +53,6 @@ func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multi
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename)
state.Put("iso_file", isoStoragePath)
return multistep.ActionContinue
}
+1 -1
View File
@@ -127,7 +127,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
if c.BootType == "" {
c.BootType = "bootscript"
c.BootType = "local"
}
var errs *packer.MultiError
+69
View File
@@ -0,0 +1,69 @@
//go:generate struct-markdown
package common
import (
"github.com/hashicorp/packer/template/interpolate"
)
type DiskConfig struct {
// The size(s) of any additional
// hard disks for the VM in megabytes. If this is not specified then the VM
// will only contain a primary hard disk. The builder uses expandable, not
// fixed-size virtual hard disks, so the actual file representing the disk will
// not use the full size unless it is full.
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false"`
// The adapter type of the VMware virtual disk to create. This option is
// for advanced usage, modify only if you know what you're doing. Some of
// the options you can specify are `ide`, `sata`, `nvme` or `scsi` (which
// uses the "lsilogic" scsi interface by default). If you specify another
// option, Packer will assume that you're specifying a `scsi` interface of
// that specified type. For more information, please consult [Virtual Disk
// Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf)
// for desktop VMware clients. For ESXi, refer to the proper ESXi
// documentation.
DiskAdapterType string `mapstructure:"disk_adapter_type" required:"false"`
// The filename of the virtual disk that'll be created,
// without the extension. This defaults to "disk".
DiskName string `mapstructure:"vmdk_name" required:"false"`
// The type of VMware virtual disk to create. This
// option is for advanced usage.
//
// For desktop VMware clients:
//
// Type ID | Description
// ------- | ---
// `0` | Growable virtual disk contained in a single file (monolithic sparse).
// `1` | Growable virtual disk split into 2GB files (split sparse).
// `2` | Preallocated virtual disk contained in a single file (monolithic flat).
// `3` | Preallocated virtual disk split into 2GB files (split flat).
// `4` | Preallocated virtual disk compatible with ESX server (VMFS flat).
// `5` | Compressed disk optimized for streaming.
//
// The default is `1`.
//
// For ESXi, this defaults to `zeroedthick`. The available options for ESXi
// are: `zeroedthick`, `eagerzeroedthick`, `thin`. `rdm:dev`, `rdmp:dev`,
// `2gbsparse` are not supported. Due to default disk compaction, when using
// `zeroedthick` or `eagerzeroedthick` set `skip_compaction` to `true`.
//
// For more information, please consult the [Virtual Disk Manager User's
// Guide](https://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop
// VMware clients. For ESXi, refer to the proper ESXi documentation.
DiskTypeId string `mapstructure:"disk_type_id" required:"false"`
}
func (c *DiskConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.DiskName == "" {
c.DiskName = "disk"
}
if c.DiskAdapterType == "" {
// Default is lsilogic
c.DiskAdapterType = "lsilogic"
}
return errs
}
+50
View File
@@ -77,6 +77,12 @@ type Driver interface {
// Get the host ip address for the vm
HostIP(multistep.StateBag) (string, error)
// Export the vm to ovf or ova format using ovftool
Export([]string) error
// OvfTool
VerifyOvfTool(bool, bool) error
}
// NewDriver returns a new driver implementation for this operating
@@ -600,3 +606,47 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
}
return "", fmt.Errorf("Unable to find host IP from devices %v, last error: %s", devices, lastError)
}
func GetOVFTool() string {
ovftool := "ovftool"
if runtime.GOOS == "windows" {
ovftool = "ovftool.exe"
}
if _, err := exec.LookPath(ovftool); err != nil {
return ""
}
return ovftool
}
func (d *VmwareDriver) Export(args []string) error {
ovftool := GetOVFTool()
if ovftool == "" {
return fmt.Errorf("Error: ovftool not found")
}
cmd := exec.Command(ovftool, args...)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
return nil
}
func (d *VmwareDriver) VerifyOvfTool(SkipExport, _ bool) error {
if SkipExport {
return nil
}
log.Printf("Verifying that ovftool exists...")
// Validate that tool exists, but no need to validate credentials.
ovftool := GetOVFTool()
if ovftool != "" {
return nil
} else {
return fmt.Errorf("Couldn't find ovftool in path! Please either " +
"set `skip_export = true` and remove the `format` option " +
"from your template, or make sure ovftool is installed on " +
"your build system. ")
}
}
+4 -55
View File
@@ -3,14 +3,8 @@
package common
import (
"bytes"
"context"
"fmt"
"net/url"
"os"
"os/exec"
"strings"
"time"
"github.com/hashicorp/packer/template/interpolate"
)
@@ -84,58 +78,13 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
}
func (c *DriverConfig) Validate(SkipExport bool) error {
if c.RemoteType == "" || SkipExport == true {
return nil
}
if c.RemotePassword == "" {
return fmt.Errorf("exporting the vm (with ovftool) requires that " +
"you set a value for remote_password")
}
if c.SkipValidateCredentials {
if SkipExport {
return nil
}
// check that password is valid by sending a dummy ovftool command
// now, so that we don't fail for a simple mistake after a long
// build
ovftool := GetOVFTool()
// Generate the uri of the host, with embedded credentials
ovftool_uri := fmt.Sprintf("vi://%s", c.RemoteHost)
u, err := url.Parse(ovftool_uri)
if err != nil {
return fmt.Errorf("Couldn't generate uri for ovftool: %s", err)
}
u.User = url.UserPassword(c.RemoteUser, c.RemotePassword)
ovfToolArgs := []string{"--noSSLVerify", "--verifyOnly", u.String()}
var out bytes.Buffer
cmdCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
cmd := exec.CommandContext(cmdCtx, ovftool, ovfToolArgs...)
cmd.Stdout = &out
// Need to manually close stdin or else the ofvtool call will hang
// forever in a situation where the user has provided an invalid
// password or username
stdin, _ := cmd.StdinPipe()
defer stdin.Close()
if err := cmd.Run(); err != nil {
outString := out.String()
// The command *should* fail with this error, if it
// authenticates properly.
if !strings.Contains(outString, "Found wrong kind of object") {
err := fmt.Errorf("ovftool validation error: %s; %s",
err, outString)
if strings.Contains(outString,
"Enter login information for source") {
err = fmt.Errorf("The username or password you " +
"provided to ovftool is invalid.")
}
return err
}
if c.RemoteType != "" && c.RemotePassword == "" {
return fmt.Errorf("exporting the vm from esxi with ovftool requires " +
"that you set a value for remote_password")
}
return nil
+68
View File
@@ -11,7 +11,9 @@ import (
"io"
"log"
"net"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
@@ -263,6 +265,68 @@ func (d *ESX5Driver) Verify() error {
return nil
}
func (d *ESX5Driver) VerifyOvfTool(SkipExport, skipValidateCredentials bool) error {
err := d.base.VerifyOvfTool(SkipExport, skipValidateCredentials)
if err != nil {
return err
}
log.Printf("Verifying that ovftool credentials are valid...")
// check that password is valid by sending a dummy ovftool command
// now, so that we don't fail for a simple mistake after a long
// build
ovftool := GetOVFTool()
if skipValidateCredentials {
return nil
}
if d.Password == "" {
return fmt.Errorf("exporting the vm from esxi with ovftool requires " +
"that you set a value for remote_password")
}
// Generate the uri of the host, with embedded credentials
ovftool_uri := fmt.Sprintf("vi://%s", d.Host)
u, err := url.Parse(ovftool_uri)
if err != nil {
return fmt.Errorf("Couldn't generate uri for ovftool: %s", err)
}
u.User = url.UserPassword(d.Username, d.Password)
ovfToolArgs := []string{"--noSSLVerify", "--verifyOnly", u.String()}
var out bytes.Buffer
cmdCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
cmd := exec.CommandContext(cmdCtx, ovftool, ovfToolArgs...)
cmd.Stdout = &out
// Need to manually close stdin or else the ofvtool call will hang
// forever in a situation where the user has provided an invalid
// password or username
stdin, _ := cmd.StdinPipe()
defer stdin.Close()
if err := cmd.Run(); err != nil {
outString := out.String()
// The command *should* fail with this error, if it
// authenticates properly.
if !strings.Contains(outString, "Found wrong kind of object") {
err := fmt.Errorf("ovftool validation error: %s; %s",
err, outString)
if strings.Contains(outString,
"Enter login information for source") {
err = fmt.Errorf("The username or password you " +
"provided to ovftool is invalid.")
}
return err
}
}
return nil
}
func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
if err != nil {
@@ -699,6 +763,10 @@ func (d *ESX5Driver) Download(src, dst string) error {
return d.comm.Download(d.datastorePath(src), file)
}
func (d *ESX5Driver) Export(args []string) error {
return d.base.Export(args)
}
// VerifyChecksum checks that file on the esxi instance matches hash
func (d *ESX5Driver) VerifyChecksum(hash string, file string) bool {
if hash == "none" {
+15
View File
@@ -27,6 +27,9 @@ type DriverMock struct {
CreateDiskTypeId string
CreateDiskErr error
ExportCalled bool
ExportArgs []string
IsRunningCalled bool
IsRunningPath string
IsRunningResult bool
@@ -92,6 +95,8 @@ type DriverMock struct {
VerifyCalled bool
VerifyErr error
VerifyOvftoolCalled bool
}
type NetworkMapperMock struct {
@@ -254,6 +259,12 @@ func (d *DriverMock) Verify() error {
return d.VerifyErr
}
func (d *DriverMock) Export(args []string) error {
d.ExportCalled = true
d.ExportArgs = args
return nil
}
func (d *DriverMock) GetVmwareDriver() VmwareDriver {
var state VmwareDriver
state.DhcpLeasesPath = func(string) string {
@@ -270,3 +281,7 @@ func (d *DriverMock) GetVmwareDriver() VmwareDriver {
}
return state
}
func (d *DriverMock) VerifyOvfTool(_ bool, _ bool) error {
return nil
}
+3 -3
View File
@@ -1196,7 +1196,7 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) {
switch entry.id[0].(type) {
case pDeclarationHost:
id := entry.id[0].(pDeclarationHost)
if strings.ToLower(id.name) == strings.ToLower(host) {
if strings.EqualFold(id.name, host) {
result = append(result, entry)
}
}
@@ -1237,7 +1237,7 @@ func (e NetworkMap) NameIntoDevices(name string) ([]string, error) {
var devices []string
for _, val := range e {
if strings.ToLower(val["name"]) == strings.ToLower(name) {
if strings.EqualFold(val["name"], name) {
devices = append(devices, val["device"])
}
}
@@ -1250,7 +1250,7 @@ func (e NetworkMap) NameIntoDevices(name string) ([]string, error) {
}
func (e NetworkMap) DeviceIntoName(device string) (string, error) {
for _, val := range e {
if strings.ToLower(val["device"]) == strings.ToLower(device) {
if strings.EqualFold(val["device"], device) {
return val["name"], nil
}
}
+19 -21
View File
@@ -10,33 +10,30 @@ import (
type ExportConfig struct {
// Either "ovf", "ova" or "vmx", this specifies the output
// format of the exported virtual machine. This defaults to "ovf".
// Before using this option, you need to install ovftool. This option
// currently only works when option remote_type is set to "esx5".
// format of the exported virtual machine. This defaults to "ovf" for
// remote (esx) builds, and "vmx" for local builds.
// Before using this option, you need to install ovftool.
// Since ovftool is only capable of password based authentication
// remote_password must be set when exporting the VM.
// remote_password must be set when exporting the VM from a remote instance.
// If you are building locally, Packer will create a vmx and then
// export that vm to an ovf or ova. Packer will not delete the vmx and vmdk
// files; this is left up to the user if you don't want to keep those
// files.
Format string `mapstructure:"format" required:"false"`
// Extra options to pass to ovftool during export. Each item in the array
// is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`,
// and `--targetType` are reserved, and should not be passed to this
// argument. Currently, exporting the build VM (with ovftool) is only
// supported when building on ESXi e.g. when `remote_type` is set to
// `esx5`. See the [Building on a Remote vSphere
// Hypervisor](/docs/builders/vmware-iso#building-on-a-remote-vsphere-hypervisor)
// section below for more info.
// and `--targetType` are used by Packer for remote exports, and should not
// be passed to this argument. For ovf/ova exports from local builds, Packer
// does not automatically set any ovftool options.
OVFToolOptions []string `mapstructure:"ovftool_options" required:"false"`
// Defaults to `false`. When enabled, Packer will not export the VM. Useful
// if the build output is not the resultant image, but created inside the
// VM. Currently, exporting the build VM is only supported when building on
// ESXi e.g. when `remote_type` is set to `esx5`. See the [Building on a
// Remote vSphere
// Hypervisor](/docs/builders/vmware-iso#building-on-a-remote-vsphere-hypervisor)
// section below for more info.
// Defaults to `false`. When true, Packer will not export the VM. This can
// be useful if the build output is not the resultant image, but created
// inside the VM.
SkipExport bool `mapstructure:"skip_export" required:"false"`
// Set this to true if you would like to keep
// the VM registered with the remote ESXi server. If you do not need to export
// the vm, then also set skip_export: true in order to avoid an unnecessary
// step of using ovftool to export the vm. Defaults to false.
// Set this to true if you would like to keep a remotely-built
// VM registered with the remote ESXi server. If you do not need to export
// the vm, then also set `skip_export: true` in order to avoid unnecessarily
// using ovftool to export the vm. Defaults to false.
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
// VMware-created disks are defragmented and
// compacted at the end of the build process using vmware-vdiskmanager or
@@ -56,5 +53,6 @@ func (c *ExportConfig) Prepare(ctx *interpolate.Context) []error {
errs, fmt.Errorf("format must be one of ova, ovf, or vmx"))
}
}
return errs
}
@@ -1,4 +1,4 @@
package iso
package common
import (
"context"
@@ -6,7 +6,6 @@ import (
"log"
"path/filepath"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@@ -20,11 +19,18 @@ import (
//
// Produces:
// disk_full_paths ([]string) - The full paths to all created disks
type stepCreateDisk struct{}
type StepCreateDisks struct {
OutputDir *string
CreateMainDisk bool
DiskName string
MainDiskSize uint
AdditionalDiskSize []uint
DiskAdapterType string
DiskTypeId string
}
func (stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(vmwcommon.Driver)
func (s *StepCreateDisks) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating required virtual machine disks")
@@ -32,13 +38,15 @@ func (stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multist
// Users can configure disks at several locations in the template so
// first collate all the disk requirements
var diskFullPaths, diskSizes []string
// The 'main' or 'default' disk
diskFullPaths = append(diskFullPaths, filepath.Join(config.OutputDir, config.DiskName+".vmdk"))
diskSizes = append(diskSizes, fmt.Sprintf("%dM", uint64(config.DiskSize)))
// The 'main' or 'default' disk, only used in vmware-iso
if s.CreateMainDisk {
diskFullPaths = append(diskFullPaths, filepath.Join(*s.OutputDir, s.DiskName+".vmdk"))
diskSizes = append(diskSizes, fmt.Sprintf("%dM", uint64(s.MainDiskSize)))
}
// Additional disks
if len(config.AdditionalDiskSize) > 0 {
for i, diskSize := range config.AdditionalDiskSize {
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d.vmdk", config.DiskName, i+1))
if len(s.AdditionalDiskSize) > 0 {
for i, diskSize := range s.AdditionalDiskSize {
path := filepath.Join(*s.OutputDir, fmt.Sprintf("%s-%d.vmdk", s.DiskName, i+1))
diskFullPaths = append(diskFullPaths, path)
size := fmt.Sprintf("%dM", uint64(diskSize))
diskSizes = append(diskSizes, size)
@@ -50,7 +58,7 @@ func (stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multist
log.Printf("[INFO] Creating disk with Path: %s and Size: %s", diskFullPath, diskSizes[i])
// Additional disks currently use the same adapter type and disk
// type as specified for the main disk
if err := driver.CreateDisk(diskFullPath, diskSizes[i], config.DiskAdapterType, config.DiskTypeId); err != nil {
if err := driver.CreateDisk(diskFullPath, diskSizes[i], s.DiskAdapterType, s.DiskTypeId); err != nil {
err := fmt.Errorf("Error creating disk: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@@ -63,4 +71,4 @@ func (stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multist
return multistep.ActionContinue
}
func (stepCreateDisk) Cleanup(multistep.StateBag) {}
func (s *StepCreateDisks) Cleanup(multistep.StateBag) {}
@@ -0,0 +1,144 @@
package common
import (
"context"
"path/filepath"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/stretchr/testify/assert"
)
func TestStepCreateDisks_impl(t *testing.T) {
var _ multistep.Step = new(StepCreateDisks)
}
func strPtr(s string) *string {
return &s
}
func NewTestCreateDiskStep() *StepCreateDisks {
return &StepCreateDisks{
OutputDir: strPtr("output_dir"),
CreateMainDisk: true,
DiskName: "disk_name",
MainDiskSize: uint(1024),
AdditionalDiskSize: []uint{},
DiskAdapterType: "fake_adapter",
DiskTypeId: "1",
}
}
func TestStepCreateDisks_MainOnly(t *testing.T) {
state := testState(t)
step := NewTestCreateDiskStep()
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
driver := state.Get("driver").(*DriverMock)
if !driver.CreateDiskCalled {
t.Fatalf("Should have called create disk.")
}
diskFullPaths, ok := state.Get("disk_full_paths").([]string)
if !ok {
t.Fatalf("Should be able to load disk_full_paths from state")
}
assert.Equal(t, diskFullPaths, []string{filepath.Join("output_dir", "disk_name.vmdk")})
// Cleanup
step.Cleanup(state)
}
func TestStepCreateDisks_MainAndExtra(t *testing.T) {
state := testState(t)
step := NewTestCreateDiskStep()
step.AdditionalDiskSize = []uint{1024, 2048, 4096}
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
driver := state.Get("driver").(*DriverMock)
if !driver.CreateDiskCalled {
t.Fatalf("Should have called create disk.")
}
diskFullPaths, ok := state.Get("disk_full_paths").([]string)
if !ok {
t.Fatalf("Should be able to load disk_full_paths from state")
}
assert.Equal(t, diskFullPaths,
[]string{
filepath.Join("output_dir", "disk_name.vmdk"),
filepath.Join("output_dir", "disk_name-1.vmdk"),
filepath.Join("output_dir", "disk_name-2.vmdk"),
filepath.Join("output_dir", "disk_name-3.vmdk"),
})
// Cleanup
step.Cleanup(state)
}
func TestStepCreateDisks_ExtraOnly(t *testing.T) {
state := testState(t)
step := NewTestCreateDiskStep()
step.CreateMainDisk = false
step.AdditionalDiskSize = []uint{1024, 2048, 4096}
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
driver := state.Get("driver").(*DriverMock)
if !driver.CreateDiskCalled {
t.Fatalf("Should have called create disk.")
}
diskFullPaths, ok := state.Get("disk_full_paths").([]string)
if !ok {
t.Fatalf("Should be able to load disk_full_paths from state")
}
assert.Equal(t, diskFullPaths,
[]string{
filepath.Join("output_dir", "disk_name-1.vmdk"),
filepath.Join("output_dir", "disk_name-2.vmdk"),
filepath.Join("output_dir", "disk_name-3.vmdk"),
})
// Cleanup
step.Cleanup(state)
}
func TestStepCreateDisks_Nothing(t *testing.T) {
state := testState(t)
step := NewTestCreateDiskStep()
step.CreateMainDisk = false
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
driver := state.Get("driver").(*DriverMock)
if driver.CreateDiskCalled {
t.Fatalf("Should not have called create disk.")
}
// Cleanup
step.Cleanup(state)
}
+61 -54
View File
@@ -1,13 +1,11 @@
package common
import (
"bytes"
"context"
"fmt"
"net/url"
"os"
"os/exec"
"runtime"
"path/filepath"
"strings"
"github.com/hashicorp/packer/helper/multistep"
@@ -15,30 +13,15 @@ import (
)
// This step exports a VM built on ESXi using ovftool
//
// Uses:
// display_name string
type StepExport struct {
Format string
SkipExport bool
VMName string
OVFToolOptions []string
OutputDir string
OutputDir *string
}
func GetOVFTool() string {
ovftool := "ovftool"
if runtime.GOOS == "windows" {
ovftool = "ovftool.exe"
}
if _, err := exec.LookPath(ovftool); err != nil {
return ""
}
return ovftool
}
func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassword bool) ([]string, error) {
func (s *StepExport) generateRemoteExportArgs(c *DriverConfig, displayName string, hidePassword bool, exportOutputPath string) ([]string, error) {
ovftool_uri := fmt.Sprintf("vi://%s/%s", c.RemoteHost, displayName)
u, err := url.Parse(ovftool_uri)
@@ -57,7 +40,15 @@ func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassw
"--skipManifestCheck",
"-tt=" + s.Format,
u.String(),
s.OutputDir,
filepath.Join(exportOutputPath, s.VMName+"."+s.Format),
}
return append(s.OVFToolOptions, args...), nil
}
func (s *StepExport) generateLocalExportArgs(exportOutputPath string) ([]string, error) {
args := []string{
filepath.Join(exportOutputPath, s.VMName+".vmx"),
filepath.Join(exportOutputPath, s.VMName+"."+s.Format),
}
return append(s.OVFToolOptions, args...), nil
}
@@ -65,6 +56,7 @@ func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassw
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
c := state.Get("driverConfig").(*DriverConfig)
ui := state.Get("ui").(packer.Ui)
driver := state.Get("driver").(Driver)
// Skip export if requested
if s.SkipExport {
@@ -72,57 +64,72 @@ func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multiste
return multistep.ActionContinue
}
if c.RemoteType != "esx5" {
ui.Say("Skipping export of virtual machine (export is allowed only for ESXi)...")
return multistep.ActionContinue
// load output path from state. If it doesn't exist, just use the local
// outputdir.
exportOutputPath, ok := state.Get("export_output_path").(string)
if !ok || exportOutputPath == "" {
if *s.OutputDir != "" {
exportOutputPath = *s.OutputDir
} else {
exportOutputPath = s.VMName
}
}
ovftool := GetOVFTool()
if ovftool == "" {
err := fmt.Errorf("Error ovftool not found")
state.Put("error", err)
err := os.MkdirAll(exportOutputPath, 0755)
if err != nil {
state.Put("error creating export directory", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Export the VM
if s.OutputDir == "" {
s.OutputDir = s.VMName + "." + s.Format
}
os.MkdirAll(s.OutputDir, 0755)
ui.Say("Exporting virtual machine...")
var displayName string
if v, ok := state.GetOk("display_name"); ok {
displayName = v.(string)
}
ui_args, err := s.generateArgs(c, displayName, true)
if err != nil {
err := fmt.Errorf("Couldn't generate ovftool uri: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
var args, ui_args []string
ovftool := GetOVFTool()
if c.RemoteType == "esx5" {
// Generate arguments for the ovftool command, but obfuscating the
// password that we can log the command to the UI for debugging.
ui_args, err := s.generateRemoteExportArgs(c, displayName, true, exportOutputPath)
if err != nil {
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool,
strings.Join(ui_args, " ")))
// Re-run the generate command, this time without obfuscating the
// password, so we can actually use it.
args, err = s.generateRemoteExportArgs(c, displayName, false, exportOutputPath)
if err != nil {
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
args, err = s.generateLocalExportArgs(exportOutputPath)
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool,
strings.Join(ui_args, " ")))
}
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(ui_args, " ")))
var out bytes.Buffer
args, err := s.generateArgs(c, displayName, false)
if err != nil {
err := fmt.Errorf("Couldn't generate ovftool uri: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := exec.Command(ovftool, args...)
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
err := fmt.Errorf("Error exporting virtual machine: %s\n%s\n", err, out.String())
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message(out.String())
if err := driver.Export(args); err != nil {
err := fmt.Errorf("Error performing ovftool export: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
+204 -7
View File
@@ -2,22 +2,41 @@ package common
import (
"context"
"path/filepath"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/stretchr/testify/assert"
)
func TestStepExport_impl(t *testing.T) {
var _ multistep.Step = new(StepExport)
}
func testStepExport_wrongtype_impl(t *testing.T, remoteType string) {
func stringPointer(s string) *string {
return &s
}
func remoteExportTestState(t *testing.T) multistep.StateBag {
state := testState(t)
driverConfig := &DriverConfig{
RemoteHost: "123.45.67.8",
RemotePassword: "password",
RemoteUser: "user",
RemoteType: "esx5",
}
state.Put("driverConfig", driverConfig)
state.Put("display_name", "vm_name")
return state
}
func TestStepExport_ReturnIfSkip(t *testing.T) {
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
step := new(StepExport)
var config DriverConfig
config.RemoteType = "foo"
state.Put("driverConfig", &config)
step.SkipExport = true
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
@@ -26,11 +45,189 @@ func testStepExport_wrongtype_impl(t *testing.T, remoteType string) {
t.Fatal("should NOT have error")
}
// We told step to skip so it should not have reached the driver's Export
// func.
d := state.Get("driver").(*DriverMock)
if d.ExportCalled {
t.Fatal("Should not have called the driver export func")
}
// Cleanup
step.Cleanup(state)
}
func TestStepExport_wrongtype_impl(t *testing.T) {
testStepExport_wrongtype_impl(t, "foo")
testStepExport_wrongtype_impl(t, "")
func TestStepExport_localArgs(t *testing.T) {
// even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs,
[]string{
filepath.Join("test-output", "test-name.vmx"),
filepath.Join("test-output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_localArgsExportOutputPath(t *testing.T) {
// even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
state.Put("export_output_path", "local_output")
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs,
[]string{
filepath.Join("local_output", "test-name.vmx"),
filepath.Join("local_output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_localArgs_OvftoolOptions(t *testing.T) {
// even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
step.OVFToolOptions = []string{"--option=value", "--second-option=\"quoted value\""}
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs, []string{"--option=value",
"--second-option=\"quoted value\"",
filepath.Join("test-output", "test-name.vmx"),
filepath.Join("test-output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_RemoteArgs(t *testing.T) {
// Even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := remoteExportTestState(t)
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs, []string{"--noSSLVerify=true",
"--skipManifestCheck",
"-tt=ova",
"vi://user:password@123.45.67.8/vm_name",
filepath.Join("test-output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_RemoteArgsWithExportOutputPath(t *testing.T) {
// Even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := remoteExportTestState(t)
state.Put("export_output_path", "local_output")
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs, []string{"--noSSLVerify=true",
"--skipManifestCheck",
"-tt=ova",
"vi://user:password@123.45.67.8/vm_name",
filepath.Join("local_output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
+15 -1
View File
@@ -35,6 +35,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
if err != nil {
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
}
// Before we get deep into the build, make sure ovftool is present and
// credentials are valid, if we're going to use ovftool.
if err := driver.VerifyOvfTool(b.config.SkipExport, b.config.SkipValidateCredentials); err != nil {
return nil, err
}
// Setup the state bag
state := new(multistep.BasicStateBag)
@@ -83,7 +88,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DoCleanup: b.config.DriverConfig.CleanUpRemoteCache,
Checksum: b.config.ISOChecksum,
},
&stepCreateDisk{},
&vmwcommon.StepCreateDisks{
OutputDir: &b.config.OutputDir,
CreateMainDisk: true,
DiskName: b.config.DiskName,
MainDiskSize: b.config.DiskSize,
AdditionalDiskSize: b.config.AdditionalDiskSize,
DiskAdapterType: b.config.DiskAdapterType,
DiskTypeId: b.config.DiskTypeId,
},
&stepCreateVMX{},
&vmwcommon.StepConfigureVMX{
CustomData: b.config.VMXData,
@@ -163,6 +176,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SkipExport: b.config.SkipExport,
VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions,
OutputDir: &b.config.OutputConfig.OutputDir,
},
}
+118
View File
@@ -127,6 +127,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
var b Builder
config := testConfig()
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
b = Builder{}
_, _, errs := b.Prepare(config)
if errs == nil {
@@ -197,6 +198,123 @@ func TestBuilderPrepare_RemoteType(t *testing.T) {
}
}
func TestBuilderPrepare_Export(t *testing.T) {
type testCase struct {
InputConfigVals map[string]string
ExpectedSkipExportValue bool
ExpectedFormat string
ExpectedErr bool
Reason string
}
testCases := []testCase{
{
InputConfigVals: map[string]string{
"remote_type": "",
"format": "",
},
ExpectedSkipExportValue: true,
ExpectedFormat: "vmx",
ExpectedErr: false,
Reason: "should have defaulted format to vmx.",
},
{
InputConfigVals: map[string]string{
"remote_type": "esx5",
"format": "",
"remote_host": "fakehost.com",
"remote_password": "fakepassword",
"remote_username": "fakeuser",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ovf",
ExpectedErr: false,
Reason: "should have defaulted format to ovf with remote set to esx5.",
},
{
InputConfigVals: map[string]string{
"remote_type": "esx5",
"format": "",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ovf",
ExpectedErr: true,
Reason: "should have errored because remote host isn't set for remote build.",
},
{
InputConfigVals: map[string]string{
"remote_type": "invalid",
"format": "",
"remote_host": "fakehost.com",
"remote_password": "fakepassword",
"remote_username": "fakeuser",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ovf",
ExpectedErr: true,
Reason: "should error with invalid remote type",
},
{
InputConfigVals: map[string]string{
"remote_type": "",
"format": "invalid",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "invalid",
ExpectedErr: true,
Reason: "should error with invalid format",
},
{
InputConfigVals: map[string]string{
"remote_type": "",
"format": "ova",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ova",
ExpectedErr: false,
Reason: "should set user-given ova format",
},
{
InputConfigVals: map[string]string{
"remote_type": "esx5",
"format": "ova",
"remote_host": "fakehost.com",
"remote_password": "fakepassword",
"remote_username": "fakeuser",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ova",
ExpectedErr: false,
Reason: "should set user-given ova format",
},
}
for _, tc := range testCases {
config := testConfig()
for k, v := range tc.InputConfigVals {
config[k] = v
}
config["skip_validate_credentials"] = true
outCfg := &Config{}
warns, errs := (outCfg).Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if (errs != nil) != tc.ExpectedErr {
t.Fatalf("received error: \n %s \n but 'expected err' was %t", errs, tc.ExpectedErr)
}
if outCfg.Format != tc.ExpectedFormat {
t.Fatalf("Expected: %s. Actual: %s. Reason: %s", tc.ExpectedFormat,
outCfg.Format, tc.Reason)
}
if outCfg.SkipExport != tc.ExpectedSkipExportValue {
t.Fatalf("For SkipExport expected %t but recieved %t",
tc.ExpectedSkipExportValue, outCfg.SkipExport)
}
}
}
func TestBuilderPrepare_RemoteExport(t *testing.T) {
var b Builder
config := testConfig()
+13 -70
View File
@@ -33,55 +33,12 @@ type Config struct {
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
vmwcommon.ExportConfig `mapstructure:",squash"`
// The size(s) of any additional
// hard disks for the VM in megabytes. If this is not specified then the VM
// will only contain a primary hard disk. The builder uses expandable, not
// fixed-size virtual hard disks, so the actual file representing the disk will
// not use the full size unless it is full.
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false"`
// The adapter type of the VMware virtual disk to create. This option is
// for advanced usage, modify only if you know what you're doing. Some of
// the options you can specify are `ide`, `sata`, `nvme` or `scsi` (which
// uses the "lsilogic" scsi interface by default). If you specify another
// option, Packer will assume that you're specifying a `scsi` interface of
// that specified type. For more information, please consult [Virtual Disk
// Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf)
// for desktop VMware clients. For ESXi, refer to the proper ESXi
// documentation.
DiskAdapterType string `mapstructure:"disk_adapter_type" required:"false"`
// The filename of the virtual disk that'll be created,
// without the extension. This defaults to packer.
DiskName string `mapstructure:"vmdk_name" required:"false"`
vmwcommon.DiskConfig `mapstructure:",squash"`
// The size of the hard disk for the VM in megabytes.
// The builder uses expandable, not fixed-size virtual hard disks, so the
// actual file representing the disk will not use the full size unless it
// is full. By default this is set to 40000 (about 40 GB).
DiskSize uint `mapstructure:"disk_size" required:"false"`
// The type of VMware virtual disk to create. This
// option is for advanced usage.
//
// For desktop VMware clients:
//
// Type ID | Description
// ------- | ---
// `0` | Growable virtual disk contained in a single file (monolithic sparse).
// `1` | Growable virtual disk split into 2GB files (split sparse).
// `2` | Preallocated virtual disk contained in a single file (monolithic flat).
// `3` | Preallocated virtual disk split into 2GB files (split flat).
// `4` | Preallocated virtual disk compatible with ESX server (VMFS flat).
// `5` | Compressed disk optimized for streaming.
//
// The default is `1`.
//
// For ESXi, this defaults to `zeroedthick`. The available options for ESXi
// are: `zeroedthick`, `eagerzeroedthick`, `thin`. `rdm:dev`, `rdmp:dev`,
// `2gbsparse` are not supported. Due to default disk compaction, when using
// `zeroedthick` or `eagerzeroedthick` set `skip_compaction` to `true`.
//
// For more information, please consult the [Virtual Disk Manager User's
// Guide](https://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop
// VMware clients. For ESXi, refer to the proper ESXi documentation.
DiskTypeId string `mapstructure:"disk_type_id" required:"false"`
// The adapter type (or bus) that will be used
// by the cdrom device. This is chosen by default based on the disk adapter
// type. VMware tends to lean towards ide for the cdrom device unless
@@ -155,34 +112,19 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
if c.DiskName == "" {
c.DiskName = "disk"
}
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)
if c.DiskSize == 0 {
c.DiskSize = 40000
}
if c.DiskAdapterType == "" {
// Default is lsilogic
c.DiskAdapterType = "lsilogic"
}
if !c.SkipCompaction {
if c.RemoteType == "esx5" {
if c.DiskTypeId == "" {
c.SkipCompaction = true
}
}
}
if c.DiskTypeId == "" {
// Default is growable virtual disk split in 2GB files.
c.DiskTypeId = "1"
if c.RemoteType == "esx5" {
c.DiskTypeId = "zeroedthick"
c.SkipCompaction = true
}
}
@@ -234,18 +176,19 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
}
if c.Format != "" {
if c.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("format is only valid when RemoteType=esx5"))
if c.Format == "" {
if c.RemoteType == "" {
c.Format = "vmx"
} else {
c.Format = "ovf"
}
} else {
c.Format = "ovf"
}
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("format must be one of ova, ovf, or vmx"))
if c.RemoteType == "" && c.Format == "vmx" {
// if we're building locally and want a vmx, there's nothing to export.
// Set skip export flag here to keep the export step from attempting
// an unneded export
c.SkipExport = true
}
err = c.DriverConfig.Validate(c.SkipExport)
+2 -2
View File
@@ -126,8 +126,8 @@ type FlatConfig struct {
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
DiskAdapterType *string `mapstructure:"disk_adapter_type" required:"false" cty:"disk_adapter_type" hcl:"disk_adapter_type"`
DiskName *string `mapstructure:"vmdk_name" required:"false" cty:"vmdk_name" hcl:"vmdk_name"`
DiskSize *uint `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
DiskTypeId *string `mapstructure:"disk_type_id" required:"false" cty:"disk_type_id" hcl:"disk_type_id"`
DiskSize *uint `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
CdromAdapterType *string `mapstructure:"cdrom_adapter_type" required:"false" cty:"cdrom_adapter_type" hcl:"cdrom_adapter_type"`
GuestOSType *string `mapstructure:"guest_os_type" required:"false" cty:"guest_os_type" hcl:"guest_os_type"`
Version *string `mapstructure:"version" required:"false" cty:"version" hcl:"version"`
@@ -265,8 +265,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
"disk_adapter_type": &hcldec.AttrSpec{Name: "disk_adapter_type", Type: cty.String, Required: false},
"vmdk_name": &hcldec.AttrSpec{Name: "vmdk_name", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"disk_type_id": &hcldec.AttrSpec{Name: "disk_type_id", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"cdrom_adapter_type": &hcldec.AttrSpec{Name: "cdrom_adapter_type", Type: cty.String, Required: false},
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
+15
View File
@@ -40,6 +40,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
if err != nil {
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
}
// Before we get deep into the build, make sure ovftool is present and
// credentials are valid, if we're going to use ovftool.
if err := driver.VerifyOvfTool(b.config.SkipExport, b.config.SkipValidateCredentials); err != nil {
return nil, err
}
// Set up the state.
state := new(multistep.BasicStateBag)
@@ -74,6 +79,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DoCleanup: true,
Checksum: "none",
},
&vmwcommon.StepCreateDisks{
OutputDir: &b.config.OutputDir,
CreateMainDisk: false,
DiskName: b.config.DiskName,
MainDiskSize: 0,
AdditionalDiskSize: b.config.AdditionalDiskSize,
DiskAdapterType: b.config.DiskAdapterType,
DiskTypeId: b.config.DiskTypeId,
},
&StepCloneVMX{
Path: b.config.SourcePath,
OutputDir: &b.config.OutputDir,
@@ -161,6 +175,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SkipExport: b.config.SkipExport,
VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions,
OutputDir: &b.config.OutputConfig.OutputDir,
},
}
-1
View File
@@ -46,7 +46,6 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
t.Fatalf("bad: %#v", b.config.FloppyFiles)
+26 -14
View File
@@ -30,6 +30,7 @@ type Config struct {
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
vmwcommon.ExportConfig `mapstructure:",squash"`
vmwcommon.DiskConfig `mapstructure:",squash"`
// By default Packer creates a 'full' clone of the virtual machine
// specified in source_path. The resultant virtual machine is fully
// independant from the parent it was cloned from.
@@ -89,6 +90,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)
if c.RemoteType == "" {
if c.SourcePath == "" {
@@ -112,25 +114,35 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
}
if c.DiskTypeId == "" {
// Default is growable virtual disk split in 2GB files.
c.DiskTypeId = "1"
if c.RemoteType == "esx5" {
c.DiskTypeId = "zeroedthick"
}
}
if c.Format == "" {
if c.RemoteType == "" {
c.Format = "vmx"
} else {
c.Format = "ovf"
}
}
if c.RemoteType == "" && c.Format == "vmx" {
// if we're building locally and want a vmx, there's nothing to export.
// Set skip export flag here to keep the export step from attempting
// an unneded export
c.SkipExport = true
}
err = c.DriverConfig.Validate(c.SkipExport)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
if c.Format != "" {
if c.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("format is only valid when remote_type=esx5"))
}
} else {
c.Format = "ovf"
}
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("format must be one of ova, ovf, or vmx"))
}
// Warnings
var warnings []string
if c.ShutdownCommand == "" {
+8
View File
@@ -108,6 +108,10 @@ type FlatConfig struct {
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export" hcl:"skip_export"`
KeepRegistered *bool `mapstructure:"keep_registered" required:"false" cty:"keep_registered" hcl:"keep_registered"`
SkipCompaction *bool `mapstructure:"skip_compaction" required:"false" cty:"skip_compaction" hcl:"skip_compaction"`
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
DiskAdapterType *string `mapstructure:"disk_adapter_type" required:"false" cty:"disk_adapter_type" hcl:"disk_adapter_type"`
DiskName *string `mapstructure:"vmdk_name" required:"false" cty:"vmdk_name" hcl:"vmdk_name"`
DiskTypeId *string `mapstructure:"disk_type_id" required:"false" cty:"disk_type_id" hcl:"disk_type_id"`
Linked *bool `mapstructure:"linked" required:"false" cty:"linked" hcl:"linked"`
SourcePath *string `mapstructure:"source_path" required:"true" cty:"source_path" hcl:"source_path"`
VMName *string `mapstructure:"vm_name" required:"false" cty:"vm_name" hcl:"vm_name"`
@@ -224,6 +228,10 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false},
"keep_registered": &hcldec.AttrSpec{Name: "keep_registered", Type: cty.Bool, Required: false},
"skip_compaction": &hcldec.AttrSpec{Name: "skip_compaction", Type: cty.Bool, Required: false},
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
"disk_adapter_type": &hcldec.AttrSpec{Name: "disk_adapter_type", Type: cty.String, Required: false},
"vmdk_name": &hcldec.AttrSpec{Name: "vmdk_name", Type: cty.String, Required: false},
"disk_type_id": &hcldec.AttrSpec{Name: "disk_type_id", Type: cty.String, Required: false},
"linked": &hcldec.AttrSpec{Name: "linked", Type: cty.Bool, Required: false},
"source_path": &hcldec.AttrSpec{Name: "source_path", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
+117
View File
@@ -58,3 +58,120 @@ func TestNewConfig_sourcePath(t *testing.T) {
warns, errs = (&Config{}).Prepare(cfg)
testConfigOk(t, warns, errs)
}
func TestNewConfig_exportConfig(t *testing.T) {
type testCase struct {
InputConfigVals map[string]string
ExpectedSkipExportValue bool
ExpectedFormat string
ExpectedErr bool
Reason string
}
testCases := []testCase{
{
InputConfigVals: map[string]string{
"remote_type": "",
"format": "",
},
ExpectedSkipExportValue: true,
ExpectedFormat: "vmx",
ExpectedErr: false,
Reason: "should have defaulted format to vmx.",
},
{
InputConfigVals: map[string]string{
"remote_type": "esx5",
"format": "",
"remote_host": "fakehost.com",
"remote_password": "fakepassword",
"remote_username": "fakeuser",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ovf",
ExpectedErr: false,
Reason: "should have defaulted format to ovf with remote set to esx5.",
},
{
InputConfigVals: map[string]string{
"remote_type": "esx5",
"format": "",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ovf",
ExpectedErr: true,
Reason: "should have errored because remote host isn't set for remote build.",
},
{
InputConfigVals: map[string]string{
"remote_type": "invalid",
"format": "",
"remote_host": "fakehost.com",
"remote_password": "fakepassword",
"remote_username": "fakeuser",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ovf",
ExpectedErr: true,
Reason: "should error with invalid remote type",
},
{
InputConfigVals: map[string]string{
"remote_type": "",
"format": "invalid",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "invalid",
ExpectedErr: true,
Reason: "should error with invalid format",
},
{
InputConfigVals: map[string]string{
"remote_type": "",
"format": "ova",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ova",
ExpectedErr: false,
Reason: "should set user-given ova format",
},
{
InputConfigVals: map[string]string{
"remote_type": "esx5",
"format": "ova",
"remote_host": "fakehost.com",
"remote_password": "fakepassword",
"remote_username": "fakeuser",
},
ExpectedSkipExportValue: false,
ExpectedFormat: "ova",
ExpectedErr: false,
Reason: "should set user-given ova format",
},
}
for _, tc := range testCases {
cfg := testConfig(t)
for k, v := range tc.InputConfigVals {
cfg[k] = v
}
cfg["skip_validate_credentials"] = true
outCfg := &Config{}
warns, errs := (outCfg).Prepare(cfg)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if (errs != nil) != tc.ExpectedErr {
t.Fatalf("received error: \n %s \n but 'expected err' was %t", errs, tc.ExpectedErr)
}
if outCfg.Format != tc.ExpectedFormat {
t.Fatalf("Expected: %s. Actual: %s. Reason: %s", tc.ExpectedFormat,
outCfg.Format, tc.Reason)
}
if outCfg.SkipExport != tc.ExpectedSkipExportValue {
t.Fatalf("For SkipExport expected %t but recieved %t",
tc.ExpectedSkipExportValue, outCfg.SkipExport)
}
}
}
+1 -1
View File
@@ -139,7 +139,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
artifact := &common.Artifact{
Name: b.config.VMName,
VM: state.Get("vm").(*driver.VirtualMachine),
VM: state.Get("vm").(*driver.VirtualMachineDriver),
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
if b.config.Export != nil {
+2 -2
View File
@@ -67,7 +67,7 @@ type StepCloneVM struct {
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
d := state.Get("driver").(*driver.VCenterDriver)
vmPath := path.Join(s.Location.Folder, s.Location.VMName)
err := d.PreCleanVM(ui, vmPath, s.Force)
@@ -128,7 +128,7 @@ func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
if st == nil {
return
}
vm := st.(*driver.VirtualMachine)
vm := st.(*driver.VirtualMachineDriver)
ui.Say("Destroying VM...")
+1 -1
View File
@@ -123,7 +123,7 @@ func (c *CustomizeConfig) Prepare() []error {
}
func (s *StepCustomize) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
ui := state.Get("ui").(packer.Ui)
identity, err := s.identitySettings()
+1 -1
View File
@@ -11,7 +11,7 @@ const BuilderId = "jetbrains.vsphere"
type Artifact struct {
Outconfig *OutputConfig
Name string
VM *driver.VirtualMachine
VM *driver.VirtualMachineDriver
// StateData should store data such as GeneratedData
// to be shared with post-processors
+19
View File
@@ -0,0 +1,19 @@
package common
import (
"bytes"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func basicStateBag(errorBuffer *strings.Builder) *multistep.BasicStateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
ErrorWriter: errorBuffer,
})
return state
}
+2 -2
View File
@@ -21,8 +21,8 @@ type LocationConfig struct {
// the host is in a folder. For example `folder/host`. See the
// `Specifying Clusters and Hosts` section above for more details.
Host string `mapstructure:"host"`
// VMWare resource pool. Defaults to the root resource pool of the
// `host` or `cluster`.
// 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.
+1 -1
View File
@@ -43,7 +43,7 @@ type StepBootCommand struct {
func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
debug := state.Get("debug").(bool)
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.Config.BootCommand == nil {
return multistep.ActionContinue
+1 -1
View File
@@ -33,7 +33,7 @@ type StepConfigParams struct {
func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
configParams := make(map[string]string)
if s.Config.ConfigParams != nil {
+1 -1
View File
@@ -154,7 +154,7 @@ func (s *StepExport) Cleanup(multistep.StateBag) {
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
ui.Message("Starting export...")
lease, err := vm.Export()
+1 -1
View File
@@ -65,7 +65,7 @@ type StepConfigureHardware struct {
func (s *StepConfigureHardware) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(driver.VirtualMachine)
if *s.Config != (HardwareConfig{}) {
ui.Say("Customizing hardware...")
@@ -0,0 +1,181 @@
package common
import (
"context"
"errors"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
)
func TestHardwareConfig_Prepare(t *testing.T) {
tc := []struct {
name string
config *HardwareConfig
fail bool
expectedErrMsg string
}{
{
name: "Validate empty config",
config: &HardwareConfig{},
fail: false,
},
{
name: "Validate RAMReservation RAMReserveAll cannot be used together",
config: &HardwareConfig{
RAMReservation: 2,
RAMReserveAll: true,
},
fail: true,
expectedErrMsg: "'RAM_reservation' and 'RAM_reserve_all' cannot be used together",
},
{
name: "Invalid firmware",
config: &HardwareConfig{
Firmware: "invalid",
},
fail: true,
expectedErrMsg: "'firmware' must be '', 'bios', 'efi' or 'efi-secure'",
},
{
name: "Validate 'bios' firmware",
config: &HardwareConfig{
Firmware: "bios",
},
fail: false,
},
{
name: "Validate 'efi' firmware",
config: &HardwareConfig{
Firmware: "efi",
},
fail: false,
},
{
name: "Validate 'efi-secure' firmware",
config: &HardwareConfig{
Firmware: "efi-secure",
},
fail: false,
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
errs := c.config.Prepare()
if c.fail {
if len(errs) == 0 {
t.Fatalf("Config preprare should fail")
}
if errs[0].Error() != c.expectedErrMsg {
t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error())
}
} else {
if len(errs) != 0 {
t.Fatalf("Config preprare should not fail")
}
}
})
}
}
func TestStepConfigureHardware_Run(t *testing.T) {
tc := []struct {
name string
step *StepConfigureHardware
action multistep.StepAction
configureError error
configureCalled bool
hardwareConfig *driver.HardwareConfig
}{
{
name: "Configure hardware",
step: basicStepConfigureHardware(),
action: multistep.ActionContinue,
configureError: nil,
configureCalled: true,
hardwareConfig: driverHardwareConfigFromConfig(basicStepConfigureHardware().Config),
},
{
name: "Don't configure hardware when config is empty",
step: &StepConfigureHardware{Config: &HardwareConfig{}},
action: multistep.ActionContinue,
configureError: nil,
configureCalled: false,
},
{
name: "Halt when configure return error",
step: basicStepConfigureHardware(),
action: multistep.ActionHalt,
configureError: errors.New("failed to configure"),
configureCalled: true,
hardwareConfig: driverHardwareConfigFromConfig(basicStepConfigureHardware().Config),
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
vmMock := new(driver.VirtualMachineMock)
vmMock.ConfigureError = c.configureError
state.Put("vm", vmMock)
action := c.step.Run(context.TODO(), state)
if action != c.action {
t.Fatalf("expected action '%v' but actual action was '%v'", c.action, action)
}
if vmMock.ConfigureCalled != c.configureCalled {
t.Fatalf("expecting vm.Configure called to %t but was %t", c.configureCalled, vmMock.ConfigureCalled)
}
if diff := cmp.Diff(vmMock.ConfigureHardwareConfig, c.hardwareConfig); diff != "" {
t.Fatalf("wrong driver.HardwareConfig: %s", diff)
}
err, ok := state.GetOk("error")
containsError := c.configureError != nil
if containsError != ok {
t.Fatalf("Contain error - expecting %t but was %t", containsError, ok)
}
if containsError {
if !strings.Contains(err.(error).Error(), c.configureError.Error()) {
t.Fatalf("Destroy should fail with error message '%s' but failed with '%s'", c.configureError.Error(), err.(error).Error())
}
}
})
}
}
func basicStepConfigureHardware() *StepConfigureHardware {
return &StepConfigureHardware{
Config: &HardwareConfig{
CPUs: 1,
CpuCores: 1,
CPUReservation: 1,
CPULimit: 4000,
RAM: 1024,
RAMReserveAll: true,
Firmware: "efi-secure",
ForceBIOSSetup: true,
},
}
}
func driverHardwareConfigFromConfig(config *HardwareConfig) *driver.HardwareConfig {
return &driver.HardwareConfig{
CPUs: config.CPUs,
CpuCores: config.CpuCores,
CPUReservation: config.CPUReservation,
CPULimit: config.CPULimit,
RAM: config.RAM,
RAMReservation: config.RAMReservation,
RAMReserveAll: config.RAMReserveAll,
NestedHV: config.NestedHV,
CpuHotAddEnabled: config.CpuHotAddEnabled,
MemoryHotAddEnabled: config.MemoryHotAddEnabled,
VideoRAM: config.VideoRAM,
VGPUProfile: config.VGPUProfile,
Firmware: config.Firmware,
ForceBIOSSetup: config.ForceBIOSSetup,
}
}
@@ -118,7 +118,7 @@ type StepImportToContentLibrary struct {
func (s *StepImportToContentLibrary) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
var err error
if s.ContentLibConfig.Ovf {
@@ -142,7 +142,7 @@ func (s *StepImportToContentLibrary) Run(_ context.Context, state multistep.Stat
return multistep.ActionContinue
}
func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachine) error {
func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachineDriver) error {
ovf := vcenter.OVF{
Spec: vcenter.CreateSpec{
Name: s.ContentLibConfig.Name,
@@ -154,7 +154,7 @@ func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachine
return vm.ImportOvfToContentLibrary(ovf)
}
func (s *StepImportToContentLibrary) importVmTemplate(vm *driver.VirtualMachine) error {
func (s *StepImportToContentLibrary) importVmTemplate(vm *driver.VirtualMachineDriver) error {
template := vcenter.Template{
Name: s.ContentLibConfig.Name,
Description: s.ContentLibConfig.Description,
+2 -2
View File
@@ -24,7 +24,7 @@ type StepRun struct {
func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.Config.BootOrder != "" {
ui.Say("Set boot order...")
@@ -55,7 +55,7 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste
func (s *StepRun) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.Config.BootOrder == "" && s.SetOrder {
ui.Say("Clear boot order...")
+1 -1
View File
@@ -48,7 +48,7 @@ type StepShutdown struct {
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
comm := state.Get("communicator").(packer.Communicator)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if off, _ := vm.IsPoweredOff(); off {
// Probably power off initiated by last provisioner, though disable_shutdown is not set
+1 -1
View File
@@ -14,7 +14,7 @@ type StepCreateSnapshot struct {
func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.CreateSnapshot {
ui.Say("Creating snapshot...")
+1 -1
View File
@@ -81,7 +81,7 @@ func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) mult
s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
s.Comm.SSHClearAuthorizedKeys = true
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
err = vm.AddPublicKeys(ctx, string(s.Comm.SSHPublicKey))
if err != nil {
state.Put("error", fmt.Errorf("error saving temporary keypair in the vm: %s", err))
+1 -1
View File
@@ -14,7 +14,7 @@ type StepConvertToTemplate struct {
func (s *StepConvertToTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.ConvertToTemplate {
ui.Say("Convert VM into template...")
+2 -2
View File
@@ -75,7 +75,7 @@ func (c *WaitIpConfig) GetIPNet() *net.IPNet {
func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
var ip string
var err error
@@ -128,7 +128,7 @@ func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multi
}
}
func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig) (string, error) {
func doGetIp(vm *driver.VirtualMachineDriver, ctx context.Context, c *WaitIpConfig) (string, error) {
var prevIp = ""
var stopTime time.Time
var interval time.Duration
+2 -2
View File
@@ -34,7 +34,7 @@ func RenderConfig(config map[string]interface{}) string {
return string(j)
}
func TestConn(t *testing.T) *driver.Driver {
func TestConn(t *testing.T) driver.Driver {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
@@ -56,7 +56,7 @@ func TestConn(t *testing.T) *driver.Driver {
return d
}
func GetVM(t *testing.T, d *driver.Driver, artifacts []packer.Artifact) *driver.VirtualMachine {
func GetVM(t *testing.T, d driver.Driver, artifacts []packer.Artifact) driver.VirtualMachine {
artifactRaw := artifacts[0]
artifact, _ := artifactRaw.(*common.Artifact)
+2 -2
View File
@@ -3,11 +3,11 @@ package driver
import "github.com/vmware/govmomi/object"
type Cluster struct {
driver *Driver
driver *VCenterDriver
cluster *object.ClusterComputeResource
}
func (d *Driver) FindCluster(name string) (*Cluster, error) {
func (d *VCenterDriver) FindCluster(name string) (*Cluster, error) {
c, err := d.finder.ClusterComputeResource(d.ctx, name)
if err != nil {
return nil, err
+36 -20
View File
@@ -13,20 +13,31 @@ import (
"github.com/vmware/govmomi/vim25/types"
)
type Datastore struct {
ds *object.Datastore
driver *Driver
type Datastore interface {
Info(params ...string) (*mo.Datastore, error)
FileExists(path string) bool
Name() string
ResolvePath(path string) string
UploadFile(src, dst, host string, setHost bool) error
Delete(path string) error
MakeDirectory(path string) error
Reference() types.ManagedObjectReference
}
func (d *Driver) NewDatastore(ref *types.ManagedObjectReference) *Datastore {
return &Datastore{
type DatastoreDriver struct {
ds *object.Datastore
driver *VCenterDriver
}
func (d *VCenterDriver) NewDatastore(ref *types.ManagedObjectReference) Datastore {
return &DatastoreDriver{
ds: object.NewDatastore(d.client.Client, *ref),
driver: d,
}
}
// If name is an empty string, then resolve host's one
func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) {
func (d *VCenterDriver) FindDatastore(name string, host string) (Datastore, error) {
if name == "" {
h, err := d.FindHost(host)
if err != nil {
@@ -55,13 +66,13 @@ func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) {
return nil, err
}
return &Datastore{
return &DatastoreDriver{
ds: ds,
driver: d,
}, nil
}
func (d *Driver) GetDatastoreName(id string) (string, error) {
func (d *VCenterDriver) GetDatastoreName(id string) (string, error) {
obj := types.ManagedObjectReference{
Type: "Datastore",
Value: id,
@@ -76,7 +87,7 @@ func (d *Driver) GetDatastoreName(id string) (string, error) {
return me.Name, nil
}
func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) {
func (ds *DatastoreDriver) Info(params ...string) (*mo.Datastore, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
@@ -91,21 +102,25 @@ func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) {
return &info, nil
}
func (ds *Datastore) FileExists(path string) bool {
func (ds *DatastoreDriver) FileExists(path string) bool {
_, err := ds.ds.Stat(ds.driver.ctx, path)
return err == nil
}
func (ds *Datastore) Name() string {
func (ds *DatastoreDriver) Name() string {
return ds.ds.Name()
}
func (ds *Datastore) ResolvePath(path string) string {
func (ds *DatastoreDriver) Reference() types.ManagedObjectReference {
return ds.ds.Reference()
}
func (ds *DatastoreDriver) ResolvePath(path string) string {
return ds.ds.Path(path)
}
// The file ID isn't available via the API, so we use DatastoreBrowser to search
func (d *Driver) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) {
func (d *VCenterDriver) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) {
ref := types.ManagedObjectReference{Type: "Datastore", Value: datastoreID}
ds := object.NewDatastore(d.vimClient, ref)
@@ -140,11 +155,11 @@ func (d *Driver) GetDatastoreFilePath(datastoreID, dir, filename string) (string
return res.File[0].GetFileInfo().Path, nil
}
func (ds *Datastore) UploadFile(src, dst, host string, set_host_for_datastore_uploads bool) error {
func (ds *DatastoreDriver) UploadFile(src, dst, host string, setHost bool) error {
p := soap.DefaultUpload
ctx := ds.driver.ctx
if set_host_for_datastore_uploads && host != "" {
if setHost && host != "" {
h, err := ds.driver.FindHost(host)
if err != nil {
return err
@@ -155,7 +170,7 @@ func (ds *Datastore) UploadFile(src, dst, host string, set_host_for_datastore_up
return ds.ds.UploadFile(ctx, src, dst, &p)
}
func (ds *Datastore) Delete(path string) error {
func (ds *DatastoreDriver) Delete(path string) error {
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
if err != nil {
return err
@@ -164,7 +179,7 @@ func (ds *Datastore) Delete(path string) error {
return fm.Delete(ds.driver.ctx, path)
}
func (ds *Datastore) MakeDirectory(path string) error {
func (ds *DatastoreDriver) MakeDirectory(path string) error {
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
if err != nil {
return err
@@ -193,8 +208,9 @@ func (d *DatastoreIsoPath) Validate() bool {
// [datastore] /dir/subdir/file
// [datastore] dir/subdir/file
// [] /dir/subdir/file
// /dir/subdir/file or dir/subdir/file
matched, _ := regexp.MatchString(`^((\[\w*\])?\s*([^\[\]]+))$`, d.path)
// [data-store] /dir/subdir/file
// dir/subdir/file or dir/subdir/file
matched, _ := regexp.MatchString(`^\s*(\[[^\[\]\/]*\])?\s*[^\[\]]+\s*$`, d.path)
return matched
}
@@ -204,7 +220,7 @@ func (d *DatastoreIsoPath) GetFilePath() string {
if len(parts) > 1 {
// removes datastore name from path
filePath = parts[1]
filePath = strings.TrimLeft(filePath, " ")
filePath = strings.TrimSpace(filePath)
}
return filePath
}
+47
View File
@@ -0,0 +1,47 @@
package driver
import (
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type DatastoreMock struct {
FileExistsCalled bool
MakeDirectoryCalled bool
UploadFileCalled bool
}
func (ds *DatastoreMock) Info(params ...string) (*mo.Datastore, error) {
return nil, nil
}
func (ds *DatastoreMock) FileExists(path string) bool {
ds.FileExistsCalled = true
return false
}
func (ds *DatastoreMock) Name() string {
return "datastore-mock"
}
func (ds *DatastoreMock) Reference() types.ManagedObjectReference {
return types.ManagedObjectReference{}
}
func (ds *DatastoreMock) ResolvePath(path string) string {
return ""
}
func (ds *DatastoreMock) UploadFile(src, dst, host string, setHost bool) error {
ds.UploadFileCalled = true
return nil
}
func (ds *DatastoreMock) Delete(path string) error {
return nil
}
func (ds *DatastoreMock) MakeDirectory(path string) error {
ds.MakeDirectoryCalled = true
return nil
}
+38 -3
View File
@@ -36,19 +36,54 @@ func TestDatastoreIsoPath(t *testing.T) {
isoPath: "[datastore][] /dir/subdir/file",
valid: false,
},
{
isoPath: "[data/store] /dir/subdir/file",
valid: false,
},
{
isoPath: "[data store] /dir/sub dir/file",
filePath: "/dir/sub dir/file",
valid: true,
},
{
isoPath: " [datastore] /dir/subdir/file",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "[datastore] /dir/subdir/file",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "[datastore] /dir/subdir/file ",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "[привѣ́тъ] /привѣ́тъ/привѣ́тъ/привѣ́тъ",
filePath: "/привѣ́тъ/привѣ́тъ/привѣ́тъ",
valid: true,
},
// Test case for #9846
{
isoPath: "[ISO-StorageLun9] Linux/rhel-8.0-x86_64-dvd.iso",
filePath: "Linux/rhel-8.0-x86_64-dvd.iso",
valid: true,
},
}
for _, c := range tc {
for i, c := range tc {
dsIsoPath := &DatastoreIsoPath{path: c.isoPath}
if dsIsoPath.Validate() != c.valid {
t.Fatalf("Expecting %s to be %t but was %t", c.isoPath, c.valid, !c.valid)
t.Fatalf("%d Expecting %s to be %t but was %t", i, c.isoPath, c.valid, !c.valid)
}
if !c.valid {
continue
}
filePath := dsIsoPath.GetFilePath()
if filePath != c.filePath {
t.Fatalf("Expecting %s but got %s", c.filePath, filePath)
t.Fatalf("%d Expecting %s but got %s", i, c.filePath, filePath)
}
}
}
+34 -4
View File
@@ -6,16 +6,46 @@ import (
"net/url"
"time"
"github.com/hashicorp/packer/packer"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vapi/library"
"github.com/vmware/govmomi/vapi/rest"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type Driver struct {
type Driver interface {
NewVM(ref *types.ManagedObjectReference) VirtualMachine
FindVM(name string) (VirtualMachine, error)
FindCluster(name string) (*Cluster, error)
PreCleanVM(ui packer.Ui, vmPath string, force bool) error
CreateVM(config *CreateConfig) (VirtualMachine, error)
NewDatastore(ref *types.ManagedObjectReference) Datastore
FindDatastore(name string, host string) (Datastore, error)
GetDatastoreName(id string) (string, error)
GetDatastoreFilePath(datastoreID, dir, filename string) (string, error)
NewFolder(ref *types.ManagedObjectReference) *Folder
FindFolder(name string) (*Folder, error)
NewHost(ref *types.ManagedObjectReference) *Host
FindHost(name string) (*Host, error)
NewNetwork(ref *types.ManagedObjectReference) *Network
FindNetwork(name string) (*Network, error)
FindNetworks(name string) ([]*Network, error)
NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool
FindResourcePool(cluster string, host string, name string) (*ResourcePool, error)
FindContentLibraryByName(name string) (*Library, error)
FindContentLibraryItem(libraryId string, name string) (*library.Item, error)
FindContentLibraryFileDatastorePath(isoPath string) (string, error)
}
type VCenterDriver struct {
// context that controls the authenticated sessions used to run the VM commands
ctx context.Context
client *govmomi.Client
@@ -33,7 +63,7 @@ type ConnectConfig struct {
Datacenter string
}
func NewDriver(config *ConnectConfig) (*Driver, error) {
func NewDriver(config *ConnectConfig) (Driver, error) {
ctx := context.TODO()
vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer))
@@ -67,7 +97,7 @@ func NewDriver(config *ConnectConfig) (*Driver, error) {
}
finder.SetDatacenter(datacenter)
d := Driver{
d := &VCenterDriver{
ctx: ctx,
client: client,
vimClient: vimClient,
@@ -78,7 +108,7 @@ func NewDriver(config *ConnectConfig) (*Driver, error) {
datacenter: datacenter,
finder: finder,
}
return &d, nil
return d, nil
}
// The rest.Client requires vCenter.
+106
View File
@@ -0,0 +1,106 @@
package driver
import (
"fmt"
"github.com/hashicorp/packer/packer"
"github.com/vmware/govmomi/vapi/library"
"github.com/vmware/govmomi/vim25/types"
)
type DriverMock struct {
FindDatastoreCalled bool
DatastoreMock *DatastoreMock
PreCleanShouldFail bool
PreCleanVMCalled bool
PreCleanForce bool
PreCleanVMPath string
CreateVMShouldFail bool
CreateVMCalled bool
CreateConfig *CreateConfig
VM VirtualMachine
}
func NewDriverMock() *DriverMock {
return new(DriverMock)
}
func (d *DriverMock) FindDatastore(name string, host string) (Datastore, error) {
d.FindDatastoreCalled = true
if d.DatastoreMock == nil {
d.DatastoreMock = new(DatastoreMock)
}
return d.DatastoreMock, nil
}
func (d *DriverMock) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
return nil
}
func (d *DriverMock) FindVM(name string) (VirtualMachine, error) {
return nil, nil
}
func (d *DriverMock) FindCluster(name string) (*Cluster, error) {
return nil, nil
}
func (d *DriverMock) PreCleanVM(ui packer.Ui, vmPath string, force bool) error {
d.PreCleanVMCalled = true
if d.PreCleanShouldFail {
return fmt.Errorf("pre clean failed")
}
d.PreCleanForce = true
d.PreCleanVMPath = vmPath
return nil
}
func (d *DriverMock) CreateVM(config *CreateConfig) (VirtualMachine, error) {
d.CreateVMCalled = true
if d.CreateVMShouldFail {
return nil, fmt.Errorf("create vm failed")
}
d.CreateConfig = config
d.VM = new(VirtualMachineDriver)
return d.VM, nil
}
func (d *DriverMock) NewDatastore(ref *types.ManagedObjectReference) Datastore { return nil }
func (d *DriverMock) GetDatastoreName(id string) (string, error) { return "", nil }
func (d *DriverMock) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) {
return "", nil
}
func (d *DriverMock) NewFolder(ref *types.ManagedObjectReference) *Folder { return nil }
func (d *DriverMock) FindFolder(name string) (*Folder, error) { return nil, nil }
func (d *DriverMock) NewHost(ref *types.ManagedObjectReference) *Host { return nil }
func (d *DriverMock) FindHost(name string) (*Host, error) { return nil, nil }
func (d *DriverMock) NewNetwork(ref *types.ManagedObjectReference) *Network { return nil }
func (d *DriverMock) FindNetwork(name string) (*Network, error) { return nil, nil }
func (d *DriverMock) FindNetworks(name string) ([]*Network, error) { return nil, nil }
func (d *DriverMock) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool { return nil }
func (d *DriverMock) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) {
return nil, nil
}
func (d *DriverMock) FindContentLibraryByName(name string) (*Library, error) { return nil, nil }
func (d *DriverMock) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) {
return nil, nil
}
func (d *DriverMock) FindContentLibraryFileDatastorePath(isoPath string) (string, error) {
return "", nil
}
+68 -1
View File
@@ -1,17 +1,29 @@
package driver
import (
"context"
"crypto/tls"
"fmt"
"math/rand"
"net/http"
"net/url"
"os"
"testing"
"time"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vapi/rest"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
)
// Defines whether acceptance tests should be run
const TestHostName = "esxi-1.vsphere65.test"
func newTestDriver(t *testing.T) *Driver {
func newTestDriver(t *testing.T) Driver {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
@@ -37,3 +49,58 @@ func newVMName() string {
rand.Seed(time.Now().UTC().UnixNano())
return fmt.Sprintf("test-%v", rand.Intn(1000))
}
func NewSimulatorServer(model *simulator.Model) (*simulator.Server, error) {
err := model.Create()
if err != nil {
return nil, err
}
model.Service.RegisterEndpoints = true
model.Service.TLS = new(tls.Config)
model.Service.ServeMux = http.NewServeMux()
return model.Service.NewServer(), nil
}
func NewSimulatorDriver(s *simulator.Server) (*VCenterDriver, error) {
ctx := context.TODO()
user := &url.Userinfo{}
s.URL.User = user
soapClient := soap.NewClient(s.URL, true)
vimClient, err := vim25.NewClient(ctx, soapClient)
if err != nil {
return nil, err
}
vimClient.RoundTripper = session.KeepAlive(vimClient.RoundTripper, 10*time.Minute)
client := &govmomi.Client{
Client: vimClient,
SessionManager: session.NewManager(vimClient),
}
err = client.SessionManager.Login(ctx, user)
if err != nil {
return nil, err
}
finder := find.NewFinder(client.Client, false)
datacenter, err := finder.DatacenterOrDefault(ctx, "")
if err != nil {
return nil, err
}
finder.SetDatacenter(datacenter)
d := &VCenterDriver{
ctx: ctx,
client: client,
vimClient: vimClient,
restClient: &RestClient{
client: rest.NewClient(vimClient),
credentials: user,
},
datacenter: datacenter,
finder: finder,
}
return d, nil
}
+3 -3
View File
@@ -12,18 +12,18 @@ import (
)
type Folder struct {
driver *Driver
driver *VCenterDriver
folder *object.Folder
}
func (d *Driver) NewFolder(ref *types.ManagedObjectReference) *Folder {
func (d *VCenterDriver) NewFolder(ref *types.ManagedObjectReference) *Folder {
return &Folder{
folder: object.NewFolder(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindFolder(name string) (*Folder, error) {
func (d *VCenterDriver) FindFolder(name string) (*Folder, error) {
if name != "" {
// create folders if they don't exist
parent := ""
+3 -3
View File
@@ -7,18 +7,18 @@ import (
)
type Host struct {
driver *Driver
driver *VCenterDriver
host *object.HostSystem
}
func (d *Driver) NewHost(ref *types.ManagedObjectReference) *Host {
func (d *VCenterDriver) NewHost(ref *types.ManagedObjectReference) *Host {
return &Host{
host: object.NewHostSystem(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindHost(name string) (*Host, error) {
func (d *VCenterDriver) FindHost(name string) (*Host, error) {
h, err := d.finder.HostSystem(d.ctx, name)
if err != nil {
return nil, err
+4 -4
View File
@@ -10,11 +10,11 @@ import (
)
type Library struct {
driver *Driver
driver *VCenterDriver
library *library.Library
}
func (d *Driver) FindContentLibraryByName(name string) (*Library, error) {
func (d *VCenterDriver) FindContentLibraryByName(name string) (*Library, error) {
lm := library.NewManager(d.restClient.client)
l, err := lm.GetLibraryByName(d.ctx, name)
if err != nil {
@@ -26,7 +26,7 @@ func (d *Driver) FindContentLibraryByName(name string) (*Library, error) {
}, nil
}
func (d *Driver) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) {
func (d *VCenterDriver) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) {
lm := library.NewManager(d.restClient.client)
items, err := lm.GetLibraryItems(d.ctx, libraryId)
if err != nil {
@@ -40,7 +40,7 @@ func (d *Driver) FindContentLibraryItem(libraryId string, name string) (*library
return nil, fmt.Errorf("Item %s not found", name)
}
func (d *Driver) FindContentLibraryFileDatastorePath(isoPath string) (string, error) {
func (d *VCenterDriver) FindContentLibraryFileDatastorePath(isoPath string) (string, error) {
log.Printf("Check if ISO path is a Content Library path")
err := d.restClient.Login(d.ctx)
if err != nil {
+4 -4
View File
@@ -9,18 +9,18 @@ import (
)
type Network struct {
driver *Driver
driver *VCenterDriver
network object.NetworkReference
}
func (d *Driver) NewNetwork(ref *types.ManagedObjectReference) *Network {
func (d *VCenterDriver) NewNetwork(ref *types.ManagedObjectReference) *Network {
return &Network{
network: object.NewNetwork(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindNetwork(name string) (*Network, error) {
func (d *VCenterDriver) FindNetwork(name string) (*Network, error) {
n, err := d.finder.Network(d.ctx, name)
if err != nil {
return nil, err
@@ -31,7 +31,7 @@ func (d *Driver) FindNetwork(name string) (*Network, error) {
}, nil
}
func (d *Driver) FindNetworks(name string) ([]*Network, error) {
func (d *VCenterDriver) FindNetworks(name string) ([]*Network, error) {
ns, err := d.finder.NetworkList(d.ctx, name)
if err != nil {
return nil, err
+21 -5
View File
@@ -2,7 +2,9 @@ package driver
import (
"fmt"
"log"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
@@ -10,17 +12,17 @@ import (
type ResourcePool struct {
pool *object.ResourcePool
driver *Driver
driver *VCenterDriver
}
func (d *Driver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool {
func (d *VCenterDriver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool {
return &ResourcePool{
pool: object.NewResourcePool(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) {
func (d *VCenterDriver) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) {
var res string
if cluster != "" {
res = cluster
@@ -28,10 +30,24 @@ func (d *Driver) FindResourcePool(cluster string, host string, name string) (*Re
res = host
}
p, err := d.finder.ResourcePool(d.ctx, fmt.Sprintf("%v/Resources/%v", res, name))
resourcePath := fmt.Sprintf("%v/Resources/%v", res, name)
p, err := d.finder.ResourcePool(d.ctx, resourcePath)
if err != nil {
return nil, err
log.Printf("[WARN] %s not found. Looking for default resource pool.", resourcePath)
dp, dperr := d.finder.DefaultResourcePool(d.ctx)
if _, ok := dperr.(*find.NotFoundError); ok {
// VirtualApp extends ResourcePool, so it should support VirtualApp types.
vapp, verr := d.finder.VirtualApp(d.ctx, name)
if verr != nil {
return nil, err
}
dp = vapp.ResourcePool
} else if dperr != nil {
return nil, err
}
p = dp
}
return &ResourcePool{
pool: p,
driver: d,
@@ -0,0 +1,85 @@
package driver
import (
"testing"
"github.com/vmware/govmomi/simulator"
)
func TestVCenterDriver_FindResourcePool(t *testing.T) {
model := simulator.VPX()
defer model.Remove()
s, err := NewSimulatorServer(model)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
res, err := driverSim.FindResourcePool("", "DC0_H0", "")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if res == nil {
t.Fatalf("resource pool should not be nil")
}
expectedResourcePool := "Resources"
if res.pool.Name() != expectedResourcePool {
t.Fatalf("resource name expected %s but was %s", expectedResourcePool, res.pool.Name())
}
}
func TestVCenterDriver_FindResourcePoolStandaloneESX(t *testing.T) {
// standalone ESX host without any vCenter
model := simulator.ESX()
defer model.Remove()
opts := simulator.VPX()
model.Datastore = opts.Datastore
model.Machine = opts.Machine
model.Autostart = opts.Autostart
model.DelayConfig.Delay = opts.DelayConfig.Delay
model.DelayConfig.MethodDelay = opts.DelayConfig.MethodDelay
model.DelayConfig.DelayJitter = opts.DelayConfig.DelayJitter
s, err := NewSimulatorServer(model)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//
res, err := driverSim.FindResourcePool("", "localhost.localdomain", "")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if res == nil {
t.Fatalf("resource pool should not be nil")
}
expectedResourcePool := "Resources"
if res.pool.Name() != expectedResourcePool {
t.Fatalf("resource name expected %s but was %s", expectedResourcePool, res.pool.Name())
}
// Invalid resource name should look for default resource pool
res, err = driverSim.FindResourcePool("", "localhost.localdomain", "invalid")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if res == nil {
t.Fatalf("resource pool should not be nil")
}
if res.pool.Name() != expectedResourcePool {
t.Fatalf("resource name expected %s but was %s", expectedResourcePool, res.pool.Name())
}
}
+79 -45
View File
@@ -21,9 +21,43 @@ import (
"github.com/vmware/govmomi/vim25/types"
)
type VirtualMachine struct {
type VirtualMachine interface {
Info(params ...string) (*mo.VirtualMachine, error)
Devices() (object.VirtualDeviceList, error)
Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error)
updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error)
AddPublicKeys(ctx context.Context, publicKeys string) error
Properties(ctx context.Context) (*mo.VirtualMachine, error)
Destroy() error
Configure(config *HardwareConfig) error
Customize(spec types.CustomizationSpec) error
ResizeDisk(diskSize int64) error
PowerOn() error
WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error)
PowerOff() error
IsPoweredOff() (bool, error)
StartShutdown() error
WaitForShutdown(ctx context.Context, timeout time.Duration) error
CreateSnapshot(name string) error
ConvertToTemplate() error
ImportOvfToContentLibrary(ovf vcenter.OVF) error
ImportToContentLibrary(template vcenter.Template) error
GetDir() (string, error)
AddCdrom(controllerType string, datastoreIsoPath string) error
AddFloppy(imgPath string) error
SetBootOrder(order []string) error
RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error
addDevice(device types.BaseVirtualDevice) error
AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error
Export() (*nfc.Lease, error)
CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error)
NewOvfManager() *ovf.Manager
GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error)
}
type VirtualMachineDriver struct {
vm *object.VirtualMachine
driver *Driver
driver *VCenterDriver
}
type CloneConfig struct {
@@ -87,25 +121,25 @@ type Disk struct {
ControllerIndex int
}
func (d *Driver) NewVM(ref *types.ManagedObjectReference) *VirtualMachine {
return &VirtualMachine{
func (d *VCenterDriver) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
return &VirtualMachineDriver{
vm: object.NewVirtualMachine(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindVM(name string) (*VirtualMachine, error) {
func (d *VCenterDriver) FindVM(name string) (VirtualMachine, error) {
vm, err := d.finder.VirtualMachine(d.ctx, name)
if err != nil {
return nil, err
}
return &VirtualMachine{
return &VirtualMachineDriver{
vm: vm,
driver: d,
}, nil
}
func (d *Driver) PreCleanVM(ui packer.Ui, vmPath string, force bool) error {
func (d *VCenterDriver) PreCleanVM(ui packer.Ui, vmPath string, force bool) error {
vm, err := d.FindVM(vmPath)
if err != nil {
if _, ok := err.(*find.NotFoundError); !ok {
@@ -130,7 +164,7 @@ func (d *Driver) PreCleanVM(ui packer.Ui, vmPath string, force bool) error {
return nil
}
func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) {
func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) {
createSpec := types.VirtualMachineConfigSpec{
Name: config.Name,
Annotation: config.Annotation,
@@ -219,7 +253,7 @@ func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) {
return d.NewVM(&vmRef), nil
}
func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) {
func (vm *VirtualMachineDriver) Info(params ...string) (*mo.VirtualMachine, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
@@ -234,7 +268,7 @@ func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) {
return &info, nil
}
func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) {
func (vm *VirtualMachineDriver) Devices() (object.VirtualDeviceList, error) {
vmInfo, err := vm.Info("config.hardware.device")
if err != nil {
return nil, err
@@ -243,7 +277,7 @@ func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) {
return vmInfo.Config.Hardware.Device, nil
}
func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*VirtualMachine, error) {
func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) {
folder, err := vm.driver.FindFolder(config.Folder)
if err != nil {
return nil, err
@@ -262,7 +296,7 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt
if err != nil {
return nil, err
}
datastoreRef := datastore.ds.Reference()
datastoreRef := datastore.Reference()
relocateSpec.Datastore = &datastoreRef
var cloneSpec types.VirtualMachineCloneSpec
@@ -350,7 +384,7 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt
return created, nil
}
func (vm *VirtualMachine) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) {
func (vm *VirtualMachineDriver) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) {
if len(newProps) == 0 {
return nil, nil
}
@@ -398,7 +432,7 @@ func (vm *VirtualMachine) updateVAppConfig(ctx context.Context, newProps map[str
}, nil
}
func (vm *VirtualMachine) AddPublicKeys(ctx context.Context, publicKeys string) error {
func (vm *VirtualMachineDriver) AddPublicKeys(ctx context.Context, publicKeys string) error {
newProps := map[string]string{"public-keys": publicKeys}
config, err := vm.updateVAppConfig(ctx, newProps)
if err != nil {
@@ -415,7 +449,7 @@ func (vm *VirtualMachine) AddPublicKeys(ctx context.Context, publicKeys string)
return err
}
func (vm *VirtualMachine) Properties(ctx context.Context) (*mo.VirtualMachine, error) {
func (vm *VirtualMachineDriver) Properties(ctx context.Context) (*mo.VirtualMachine, error) {
log.Printf("fetching properties for VM %q", vm.vm.InventoryPath)
var props mo.VirtualMachine
if err := vm.vm.Properties(ctx, vm.vm.Reference(), nil, &props); err != nil {
@@ -424,7 +458,7 @@ func (vm *VirtualMachine) Properties(ctx context.Context) (*mo.VirtualMachine, e
return &props, nil
}
func (vm *VirtualMachine) Destroy() error {
func (vm *VirtualMachineDriver) Destroy() error {
task, err := vm.vm.Destroy(vm.driver.ctx)
if err != nil {
return err
@@ -433,7 +467,7 @@ func (vm *VirtualMachine) Destroy() error {
return err
}
func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error {
var confSpec types.VirtualMachineConfigSpec
confSpec.NumCPUs = config.CPUs
confSpec.NumCoresPerSocket = config.CpuCores
@@ -524,7 +558,7 @@ func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
return err
}
func (vm *VirtualMachine) Customize(spec types.CustomizationSpec) error {
func (vm *VirtualMachineDriver) Customize(spec types.CustomizationSpec) error {
task, err := vm.vm.Customize(vm.driver.ctx, spec)
if err != nil {
return err
@@ -532,7 +566,7 @@ func (vm *VirtualMachine) Customize(spec types.CustomizationSpec) error {
return task.Wait(vm.driver.ctx)
}
func (vm *VirtualMachine) ResizeDisk(diskSize int64) error {
func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) error {
var confSpec types.VirtualMachineConfigSpec
devices, err := vm.vm.Device(vm.driver.ctx)
@@ -581,7 +615,7 @@ func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) {
return nil, errors.New("VM has multiple disks")
}
func (vm *VirtualMachine) PowerOn() error {
func (vm *VirtualMachineDriver) PowerOn() error {
task, err := vm.vm.PowerOn(vm.driver.ctx)
if err != nil {
return err
@@ -590,7 +624,7 @@ func (vm *VirtualMachine) PowerOn() error {
return err
}
func (vm *VirtualMachine) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) {
func (vm *VirtualMachineDriver) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) {
netIP, err := vm.vm.WaitForNetIP(ctx, false)
if err != nil {
return "", err
@@ -614,7 +648,7 @@ func (vm *VirtualMachine) WaitForIP(ctx context.Context, ipNet *net.IPNet) (stri
return "", fmt.Errorf("unable to find an IP")
}
func (vm *VirtualMachine) PowerOff() error {
func (vm *VirtualMachineDriver) PowerOff() error {
state, err := vm.vm.PowerState(vm.driver.ctx)
if err != nil {
return err
@@ -632,7 +666,7 @@ func (vm *VirtualMachine) PowerOff() error {
return err
}
func (vm *VirtualMachine) IsPoweredOff() (bool, error) {
func (vm *VirtualMachineDriver) IsPoweredOff() (bool, error) {
state, err := vm.vm.PowerState(vm.driver.ctx)
if err != nil {
return false, err
@@ -641,12 +675,12 @@ func (vm *VirtualMachine) IsPoweredOff() (bool, error) {
return state == types.VirtualMachinePowerStatePoweredOff, nil
}
func (vm *VirtualMachine) StartShutdown() error {
func (vm *VirtualMachineDriver) StartShutdown() error {
err := vm.vm.ShutdownGuest(vm.driver.ctx)
return err
}
func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Duration) error {
func (vm *VirtualMachineDriver) WaitForShutdown(ctx context.Context, timeout time.Duration) error {
shutdownTimer := time.After(timeout)
for {
off, err := vm.IsPoweredOff()
@@ -670,7 +704,7 @@ func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Dura
return nil
}
func (vm *VirtualMachine) CreateSnapshot(name string) error {
func (vm *VirtualMachineDriver) CreateSnapshot(name string) error {
task, err := vm.vm.CreateSnapshot(vm.driver.ctx, name, "", false, false)
if err != nil {
return err
@@ -679,11 +713,11 @@ func (vm *VirtualMachine) CreateSnapshot(name string) error {
return err
}
func (vm *VirtualMachine) ConvertToTemplate() error {
func (vm *VirtualMachineDriver) ConvertToTemplate() error {
return vm.vm.MarkAsTemplate(vm.driver.ctx)
}
func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
func (vm *VirtualMachineDriver) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
err := vm.driver.restClient.Login(vm.driver.ctx)
if err != nil {
return err
@@ -717,7 +751,7 @@ func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
return vm.driver.restClient.Logout(vm.driver.ctx)
}
func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) error {
func (vm *VirtualMachineDriver) ImportToContentLibrary(template vcenter.Template) error {
err := vm.driver.restClient.Login(vm.driver.ctx)
if err != nil {
return err
@@ -769,7 +803,7 @@ func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) erro
if err != nil {
return err
}
template.VMHomeStorage.Datastore = d.ds.Reference().Value
template.VMHomeStorage.Datastore = d.Reference().Value
}
vcm := vcenter.NewManager(vm.driver.restClient.client)
@@ -781,7 +815,7 @@ func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) erro
return vm.driver.restClient.Logout(vm.driver.ctx)
}
func (vm *VirtualMachine) GetDir() (string, error) {
func (vm *VirtualMachineDriver) GetDir() (string, error) {
vmInfo, err := vm.Info("name", "layoutEx.file")
if err != nil {
return "", err
@@ -789,14 +823,14 @@ func (vm *VirtualMachine) GetDir() (string, error) {
vmxName := fmt.Sprintf("/%s.vmx", vmInfo.Name)
for _, file := range vmInfo.LayoutEx.File {
if strings.HasSuffix(file.Name, vmxName) {
if strings.Contains(file.Name, vmInfo.Name) {
return RemoveDatastorePrefix(file.Name[:len(file.Name)-len(vmxName)]), nil
}
}
return "", fmt.Errorf("cannot find '%s'", vmxName)
}
func addDisk(_ *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
func addDisk(_ *VCenterDriver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
if len(config.Storage) == 0 {
return nil, errors.New("no storage devices have been defined")
}
@@ -839,7 +873,7 @@ func addDisk(_ *Driver, devices object.VirtualDeviceList, config *CreateConfig)
return devices, nil
}
func addNetwork(d *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
func addNetwork(d *VCenterDriver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
if len(config.NICs) == 0 {
return nil, errors.New("no network adapters have been defined")
}
@@ -872,7 +906,7 @@ func addNetwork(d *Driver, devices object.VirtualDeviceList, config *CreateConfi
return devices, nil
}
func findNetwork(network string, host string, d *Driver) (object.NetworkReference, error) {
func findNetwork(network string, host string, d *VCenterDriver) (object.NetworkReference, error) {
if network != "" {
var err error
networks, err := d.FindNetworks(network)
@@ -941,7 +975,7 @@ func newVGPUProfile(vGPUProfile string) types.VirtualPCIPassthrough {
}
}
func (vm *VirtualMachine) AddCdrom(controllerType string, datastoreIsoPath string) error {
func (vm *VirtualMachineDriver) AddCdrom(controllerType string, datastoreIsoPath string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
@@ -985,7 +1019,7 @@ func (vm *VirtualMachine) AddCdrom(controllerType string, datastoreIsoPath strin
return vm.addDevice(cdrom)
}
func (vm *VirtualMachine) AddFloppy(imgPath string) error {
func (vm *VirtualMachineDriver) AddFloppy(imgPath string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
@@ -1003,7 +1037,7 @@ func (vm *VirtualMachine) AddFloppy(imgPath string) error {
return vm.addDevice(floppy)
}
func (vm *VirtualMachine) SetBootOrder(order []string) error {
func (vm *VirtualMachineDriver) SetBootOrder(order []string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
@@ -1016,11 +1050,11 @@ func (vm *VirtualMachine) SetBootOrder(order []string) error {
return vm.vm.SetBootOptions(vm.driver.ctx, &bootOptions)
}
func (vm *VirtualMachine) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
func (vm *VirtualMachineDriver) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
return vm.vm.RemoveDevice(vm.driver.ctx, keepFiles, device...)
}
func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error {
func (vm *VirtualMachineDriver) addDevice(device types.BaseVirtualDevice) error {
newDevices := object.VirtualDeviceList{device}
confSpec := types.VirtualMachineConfigSpec{}
var err error
@@ -1038,7 +1072,7 @@ func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error {
return err
}
func (vm *VirtualMachine) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error {
func (vm *VirtualMachineDriver) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error {
var confSpec types.VirtualMachineConfigSpec
var ov []types.BaseOptionValue
@@ -1066,19 +1100,19 @@ func (vm *VirtualMachine) AddConfigParams(params map[string]string, info *types.
return nil
}
func (vm *VirtualMachine) Export() (*nfc.Lease, error) {
func (vm *VirtualMachineDriver) Export() (*nfc.Lease, error) {
return vm.vm.Export(vm.driver.ctx)
}
func (vm *VirtualMachine) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
func (vm *VirtualMachineDriver) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
return m.CreateDescriptor(vm.driver.ctx, vm.vm, cdp)
}
func (vm *VirtualMachine) NewOvfManager() *ovf.Manager {
func (vm *VirtualMachineDriver) NewOvfManager() *ovf.Manager {
return ovf.NewManager(vm.vm.Client())
}
func (vm *VirtualMachine) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) {
func (vm *VirtualMachineDriver) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) {
var mgr mo.OvfManager
err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.ctx, m.Reference(), nil, &mgr)
if err != nil {
+5 -5
View File
@@ -10,12 +10,12 @@ var (
ErrNoSataController = errors.New("no available SATA controller")
)
func (vm *VirtualMachine) AddSATAController() error {
func (vm *VirtualMachineDriver) AddSATAController() error {
sata := &types.VirtualAHCIController{}
return vm.addDevice(sata)
}
func (vm *VirtualMachine) FindSATAController() (*types.VirtualAHCIController, error) {
func (vm *VirtualMachineDriver) FindSATAController() (*types.VirtualAHCIController, error) {
l, err := vm.Devices()
if err != nil {
return nil, err
@@ -29,7 +29,7 @@ func (vm *VirtualMachine) FindSATAController() (*types.VirtualAHCIController, er
return c.(*types.VirtualAHCIController), nil
}
func (vm *VirtualMachine) CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error) {
func (vm *VirtualMachineDriver) CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error) {
l, err := vm.Devices()
if err != nil {
return nil, err
@@ -52,7 +52,7 @@ func (vm *VirtualMachine) CreateCdrom(c *types.VirtualController) (*types.Virtua
return device, nil
}
func (vm *VirtualMachine) RemoveCdroms() error {
func (vm *VirtualMachineDriver) RemoveCdroms() error {
devices, err := vm.Devices()
if err != nil {
return err
@@ -69,7 +69,7 @@ func (vm *VirtualMachine) RemoveCdroms() error {
return nil
}
func (vm *VirtualMachine) EjectCdroms() error {
func (vm *VirtualMachineDriver) EjectCdroms() error {
devices, err := vm.Devices()
if err != nil {
return err
+16 -16
View File
@@ -13,7 +13,7 @@ func TestVMAcc_clone(t *testing.T) {
testCases := []struct {
name string
config *CloneConfig
checkFunction func(*testing.T, *VirtualMachine, *CloneConfig)
checkFunction func(*testing.T, VirtualMachine, *CloneConfig)
}{
{"Default", &CloneConfig{}, cloneDefaultCheck},
{"LinkedClone", &CloneConfig{LinkedClone: true}, cloneLinkedCloneCheck},
@@ -53,8 +53,8 @@ func TestVMAcc_clone(t *testing.T) {
}
}
func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
d := vm.driver
func cloneDefaultCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
d := vm.(*VirtualMachineDriver).driver
// Check that the clone can be found by its name
if _, err := d.FindVM(config.Name); err != nil {
@@ -112,7 +112,7 @@ func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
}
}
func configureCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
func configureCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
log.Printf("[DEBUG] Configuring the vm")
hwConfig := &HardwareConfig{
CPUs: 2,
@@ -170,7 +170,7 @@ func configureCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
}
}
func configureRAMReserveAllCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
func configureRAMReserveAllCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
log.Printf("[DEBUG] Configuring the vm")
err := vm.Configure(&HardwareConfig{RAMReserveAll: true})
if err != nil {
@@ -188,7 +188,7 @@ func configureRAMReserveAllCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfi
}
}
func cloneLinkedCloneCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
func cloneLinkedCloneCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
@@ -199,13 +199,13 @@ func cloneLinkedCloneCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
}
}
func cloneFolderCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
func cloneFolderCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
vmInfo, err := vm.Info("parent")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
f := vm.driver.NewFolder(vmInfo.Parent)
f := vm.(*VirtualMachineDriver).driver.NewFolder(vmInfo.Parent)
path, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
@@ -215,13 +215,13 @@ func cloneFolderCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
}
}
func cloneResourcePoolCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
func cloneResourcePoolCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
vmInfo, err := vm.Info("resourcePool")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
p := vm.driver.NewResourcePool(vmInfo.ResourcePool)
p := vm.(*VirtualMachineDriver).driver.NewResourcePool(vmInfo.ResourcePool)
path, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
@@ -231,7 +231,7 @@ func cloneResourcePoolCheck(t *testing.T, vm *VirtualMachine, config *CloneConfi
}
}
func startAndStopCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
func startAndStopCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
stopper := startVM(t, vm, config.Name)
defer stopper()
@@ -253,7 +253,7 @@ func startAndStopCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
}
}
func snapshotCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
func snapshotCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
stopper := startVM(t, vm, config.Name)
defer stopper()
@@ -273,7 +273,7 @@ func snapshotCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
}
}
func templateCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
func templateCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
err := vm.ConvertToTemplate()
if err != nil {
t.Fatalf("Failed to convert to template: %v", err)
@@ -286,7 +286,7 @@ func templateCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) {
}
}
func startVM(t *testing.T, vm *VirtualMachine, vmName string) (stopper func()) {
func startVM(t *testing.T, vm VirtualMachine, vmName string) (stopper func()) {
log.Printf("[DEBUG] Starting the vm")
if err := vm.PowerOn(); err != nil {
t.Fatalf("Cannot start vm '%v': %v", vmName, err)
@@ -299,14 +299,14 @@ func startVM(t *testing.T, vm *VirtualMachine, vmName string) (stopper func()) {
}
}
func destroyVM(t *testing.T, vm *VirtualMachine, vmName string) {
func destroyVM(t *testing.T, vm VirtualMachine, vmName string) {
log.Printf("[DEBUG] Deleting the VM")
if err := vm.Destroy(); err != nil {
t.Errorf("!!! ERROR DELETING VM '%v': %v!!!", vmName, err)
}
// Check that the clone is no longer exists
if _, err := vm.driver.FindVM(vmName); err == nil {
if _, err := vm.(*VirtualMachineDriver).driver.FindVM(vmName); err == nil {
t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", vmName)
}
}
+3 -3
View File
@@ -10,7 +10,7 @@ func TestVMAcc_create(t *testing.T) {
testCases := []struct {
name string
config *CreateConfig
checkFunction func(*testing.T, *VirtualMachine, *CreateConfig)
checkFunction func(*testing.T, VirtualMachine, *CreateConfig)
}{
{"MinimalConfiguration", &CreateConfig{}, createDefaultCheck},
}
@@ -36,8 +36,8 @@ func TestVMAcc_create(t *testing.T) {
}
}
func createDefaultCheck(t *testing.T, vm *VirtualMachine, config *CreateConfig) {
d := vm.driver
func createDefaultCheck(t *testing.T, vm VirtualMachine, config *CreateConfig) {
d := vm.(*VirtualMachineDriver).driver
// Check that the clone can be found by its name
if _, err := d.FindVM(config.Name); err != nil {
+1 -1
View File
@@ -13,7 +13,7 @@ type KeyInput struct {
Shift bool
}
func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) {
func (vm *VirtualMachineDriver) TypeOnKeyboard(input KeyInput) (int32, error) {
var spec types.UsbScanCodeSpec
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
+156
View File
@@ -0,0 +1,156 @@
package driver
import (
"context"
"net"
"time"
"github.com/vmware/govmomi/nfc"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/ovf"
"github.com/vmware/govmomi/vapi/vcenter"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type VirtualMachineMock struct {
DestroyError error
DestroyCalled bool
ConfigureError error
ConfigureCalled bool
ConfigureHardwareConfig *HardwareConfig
}
func (vm *VirtualMachineMock) Info(params ...string) (*mo.VirtualMachine, error) {
return nil, nil
}
func (vm *VirtualMachineMock) Devices() (object.VirtualDeviceList, error) {
return object.VirtualDeviceList{}, nil
}
func (vm *VirtualMachineMock) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) {
return nil, nil
}
func (vm *VirtualMachineMock) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) {
return nil, nil
}
func (vm *VirtualMachineMock) AddPublicKeys(ctx context.Context, publicKeys string) error {
return nil
}
func (vm *VirtualMachineMock) Properties(ctx context.Context) (*mo.VirtualMachine, error) {
return nil, nil
}
func (vm *VirtualMachineMock) Destroy() error {
vm.DestroyCalled = true
if vm.DestroyError != nil {
return vm.DestroyError
}
return nil
}
func (vm *VirtualMachineMock) Configure(config *HardwareConfig) error {
vm.ConfigureCalled = true
vm.ConfigureHardwareConfig = config
if vm.ConfigureError != nil {
return vm.ConfigureError
}
return nil
}
func (vm *VirtualMachineMock) Customize(spec types.CustomizationSpec) error {
return nil
}
func (vm *VirtualMachineMock) ResizeDisk(diskSize int64) error {
return nil
}
func (vm *VirtualMachineMock) PowerOn() error {
return nil
}
func (vm *VirtualMachineMock) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) {
return "", nil
}
func (vm *VirtualMachineMock) PowerOff() error {
return nil
}
func (vm *VirtualMachineMock) IsPoweredOff() (bool, error) {
return false, nil
}
func (vm *VirtualMachineMock) StartShutdown() error {
return nil
}
func (vm *VirtualMachineMock) WaitForShutdown(ctx context.Context, timeout time.Duration) error {
return nil
}
func (vm *VirtualMachineMock) CreateSnapshot(name string) error {
return nil
}
func (vm *VirtualMachineMock) ConvertToTemplate() error {
return nil
}
func (vm *VirtualMachineMock) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
return nil
}
func (vm *VirtualMachineMock) ImportToContentLibrary(template vcenter.Template) error {
return nil
}
func (vm *VirtualMachineMock) GetDir() (string, error) {
return "", nil
}
func (vm *VirtualMachineMock) AddCdrom(controllerType string, datastoreIsoPath string) error {
return nil
}
func (vm *VirtualMachineMock) AddFloppy(imgPath string) error {
return nil
}
func (vm *VirtualMachineMock) SetBootOrder(order []string) error {
return nil
}
func (vm *VirtualMachineMock) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
return nil
}
func (vm *VirtualMachineMock) addDevice(device types.BaseVirtualDevice) error {
return nil
}
func (vm *VirtualMachineMock) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error {
return nil
}
func (vm *VirtualMachineMock) Export() (*nfc.Lease, error) {
return nil, nil
}
func (vm *VirtualMachineMock) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
return nil, nil
}
func (vm *VirtualMachineMock) NewOvfManager() *ovf.Manager {
return nil
}
func (vm *VirtualMachineMock) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) {
return nil, nil
}
+74
View File
@@ -0,0 +1,74 @@
package driver
import (
"testing"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
// ReconfigureFail changes the behavior of simulator.VirtualMachine
type ReconfigureFail struct {
*simulator.VirtualMachine
}
// Override simulator.VirtualMachine.ReconfigVMTask to inject faults
func (vm *ReconfigureFail) ReconfigVMTask(req *types.ReconfigVM_Task) soap.HasFault {
task := simulator.CreateTask(req.This, "reconfigure", func(*simulator.Task) (types.AnyType, types.BaseMethodFault) {
return nil, &types.TaskInProgress{}
})
return &methods.ReconfigVM_TaskBody{
Res: &types.ReconfigVM_TaskResponse{
Returnval: task.Run(),
},
}
}
func TestVirtualMachineDriver_Configure(t *testing.T) {
model := simulator.VPX()
model.Machine = 1
defer model.Remove()
s, err := NewSimulatorServer(model)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//Simulator shortcut to choose any pre created VM.
machine := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine)
ref := machine.Reference()
vm := driverSim.NewVM(&ref)
// Happy test
hardwareConfig := &HardwareConfig{
CPUs: 1,
CpuCores: 1,
CPUReservation: 2500,
CPULimit: 1,
RAM: 1024,
RAMReserveAll: true,
VideoRAM: 512,
VGPUProfile: "grid_m10-8q",
Firmware: "efi-secure",
ForceBIOSSetup: true,
}
if err = vm.Configure(hardwareConfig); err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//Fail test
//Wrap the existing vm object with the mocked reconfigure task which will return a fault
simulator.Map.Put(&ReconfigureFail{machine})
if err = vm.Configure(&HardwareConfig{}); err == nil {
t.Fatalf("Configure should fail")
}
}
+1 -1
View File
@@ -170,7 +170,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
artifact := &common.Artifact{
Name: b.config.VMName,
VM: state.Get("vm").(*driver.VirtualMachine),
VM: state.Get("vm").(*driver.VirtualMachineDriver),
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
+17
View File
@@ -0,0 +1,17 @@
package iso
import (
"bytes"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func basicStateBag() *multistep.BasicStateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
+1 -1
View File
@@ -42,7 +42,7 @@ func (c *CDRomConfig) Prepare() []error {
func (s *StepAddCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.Config.CdromType == "sata" {
if _, err := vm.FindSATAController(); err == driver.ErrNoSataController {
+3 -3
View File
@@ -37,8 +37,8 @@ type StepAddFloppy struct {
func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
d := state.Get("driver").(*driver.Driver)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
d := state.Get("driver").(*driver.VCenterDriver)
if floppyPath, ok := state.GetOk("floppy_path"); ok {
ui.Say("Uploading created floppy image")
@@ -90,7 +90,7 @@ func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
}
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
d := state.Get("driver").(*driver.VCenterDriver)
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting Floppy image ...")
+2 -2
View File
@@ -216,7 +216,7 @@ type StepCreateVM struct {
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
d := state.Get("driver").(driver.Driver)
vmPath := path.Join(s.Location.Folder, s.Location.VMName)
err := d.PreCleanVM(ui, vmPath, s.Force)
@@ -287,7 +287,7 @@ func (s *StepCreateVM) Cleanup(state multistep.StateBag) {
if st == nil {
return
}
vm := st.(*driver.VirtualMachine)
vm := st.(driver.VirtualMachine)
ui.Say("Destroying VM...")
err := vm.Destroy()
+351
View File
@@ -0,0 +1,351 @@
package iso
import (
"context"
"errors"
"io/ioutil"
"path"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func TestCreateConfig_Prepare(t *testing.T) {
// Empty config - check defaults
config := &CreateConfig{}
if errs := config.Prepare(); len(errs) != 0 {
t.Fatalf("Config preprare should not fail")
}
if config.GuestOSType != "otherGuest" {
t.Fatalf("GuestOSType should default to 'otherGuest'")
}
if len(config.DiskControllerType) != 1 {
t.Fatalf("DiskControllerType should have at least one element as default")
}
// Data validation
tc := []struct {
name string
config *CreateConfig
fail bool
expectedErrMsg string
}{
{
name: "Storage validate disk_size",
config: &CreateConfig{
Storage: []DiskConfig{
{
DiskSize: 0,
DiskThinProvisioned: true,
},
},
},
fail: true,
expectedErrMsg: "storage[0].'disk_size' is required",
},
{
name: "Storage validate disk_controller_index",
config: &CreateConfig{
Storage: []DiskConfig{
{
DiskSize: 32768,
DiskControllerIndex: 3,
},
},
},
fail: true,
expectedErrMsg: "storage[0].'disk_controller_index' references an unknown disk controller",
},
{
name: "USBController validate 'usb' and 'xhci' can be set together",
config: &CreateConfig{
USBController: []string{"usb", "xhci"},
},
fail: false,
},
{
name: "USBController validate '1' and '0' can be set together",
config: &CreateConfig{
USBController: []string{"1", "0"},
},
fail: false,
},
{
name: "USBController validate 'true' and 'false' can be set together",
config: &CreateConfig{
USBController: []string{"true", "false"},
},
fail: false,
},
{
name: "USBController validate 'true' and 'usb' cannot be set together",
config: &CreateConfig{
USBController: []string{"true", "usb"},
},
fail: true,
expectedErrMsg: "there can only be one usb controller and one xhci controller",
},
{
name: "USBController validate '1' and 'usb' cannot be set together",
config: &CreateConfig{
USBController: []string{"1", "usb"},
},
fail: true,
expectedErrMsg: "there can only be one usb controller and one xhci controller",
},
{
name: "USBController validate 'xhci' cannot be set more that once",
config: &CreateConfig{
USBController: []string{"xhci", "xhci"},
},
fail: true,
expectedErrMsg: "there can only be one usb controller and one xhci controller",
},
{
name: "USBController validate unknown value cannot be set",
config: &CreateConfig{
USBController: []string{"unknown"},
},
fail: true,
expectedErrMsg: "usb_controller[0] references an unknown usb controller",
},
}
for _, c := range tc {
errs := c.config.Prepare()
if c.fail {
if len(errs) == 0 {
t.Fatalf("Config preprare should fail")
}
if errs[0].Error() != c.expectedErrMsg {
t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error())
}
} else {
if len(errs) != 0 {
t.Fatalf("Config preprare should not fail")
}
}
}
}
func TestStepCreateVM_Run(t *testing.T) {
state := basicStateBag()
driverMock := driver.NewDriverMock()
state.Put("driver", driverMock)
step := basicStepCreateVM()
step.Force = true
vmPath := path.Join(step.Location.Folder, step.Location.VMName)
if action := step.Run(context.TODO(), state); action == multistep.ActionHalt {
t.Fatalf("Should not halt.")
}
// Pre clean VM
if !driverMock.PreCleanVMCalled {
t.Fatalf("driver.PreCleanVM should be called.")
}
if driverMock.PreCleanForce != step.Force {
t.Fatalf("Force PreCleanVM should be %t but was %t.", step.Force, driverMock.PreCleanForce)
}
if driverMock.PreCleanVMPath != vmPath {
t.Fatalf("VM path expected to be %s but was %s", vmPath, driverMock.PreCleanVMPath)
}
if !driverMock.CreateVMCalled {
t.Fatalf("driver.CreateVM should be called.")
}
if diff := cmp.Diff(driverMock.CreateConfig, driverCreateConfig(step.Config, step.Location)); diff != "" {
t.Fatalf("wrong driver.CreateConfig: %s", diff)
}
vm, ok := state.GetOk("vm")
if !ok {
t.Fatal("state must contain the VM")
}
if vm != driverMock.VM {
t.Fatalf("state doesn't contain the created VM.")
}
}
func TestStepCreateVM_RunHalt(t *testing.T) {
state := basicStateBag()
step := basicStepCreateVM()
// PreCleanVM fails
driverMock := driver.NewDriverMock()
driverMock.PreCleanShouldFail = true
state.Put("driver", driverMock)
if action := step.Run(context.TODO(), state); action != multistep.ActionHalt {
t.Fatalf("Step should halt.")
}
if !driverMock.PreCleanVMCalled {
t.Fatalf("driver.PreCleanVM should be called")
}
// CreateVM fails
driverMock = driver.NewDriverMock()
driverMock.CreateVMShouldFail = true
state.Put("driver", driverMock)
if action := step.Run(context.TODO(), state); action != multistep.ActionHalt {
t.Fatalf("Step should halt.")
}
if !driverMock.PreCleanVMCalled {
t.Fatalf("driver.PreCleanVM should be called")
}
if !driverMock.CreateVMCalled {
t.Fatalf("driver.PreCleanVM should be called")
}
if _, ok := state.GetOk("vm"); ok {
t.Fatal("state should not contain a VM")
}
}
func TestStepCreateVM_Cleanup(t *testing.T) {
state := basicStateBag()
step := basicStepCreateVM()
vm := new(driver.VirtualMachineMock)
state.Put("vm", vm)
// Clean up when state is cancelled
state.Put(multistep.StateCancelled, true)
step.Cleanup(state)
if !vm.DestroyCalled {
t.Fatalf("vm.Destroy should be called")
}
vm.DestroyCalled = false
state.Remove(multistep.StateCancelled)
// Clean up when state is halted
state.Put(multistep.StateHalted, true)
step.Cleanup(state)
if !vm.DestroyCalled {
t.Fatalf("vm.Destroy should be called")
}
vm.DestroyCalled = false
state.Remove(multistep.StateHalted)
// Clean up when state is destroy_vm is set
state.Put("destroy_vm", true)
step.Cleanup(state)
if !vm.DestroyCalled {
t.Fatalf("vm.Destroy should be called")
}
vm.DestroyCalled = false
state.Remove("destroy_vm")
// Don't clean up if state is not set with previous values
step.Cleanup(state)
if vm.DestroyCalled {
t.Fatalf("vm.Destroy should not be called")
}
// Destroy fail
errorBuffer := &strings.Builder{}
ui := &packer.BasicUi{
Reader: strings.NewReader(""),
Writer: ioutil.Discard,
ErrorWriter: errorBuffer,
}
state.Put("ui", ui)
state.Put(multistep.StateCancelled, true)
vm.DestroyError = errors.New("destroy failed")
step.Cleanup(state)
if !vm.DestroyCalled {
t.Fatalf("vm.Destroy should be called")
}
if !strings.Contains(errorBuffer.String(), vm.DestroyError.Error()) {
t.Fatalf("Destroy should fail with error message '%s' but failed with '%s'", vm.DestroyError.Error(), errorBuffer.String())
}
vm.DestroyCalled = false
state.Remove(multistep.StateCancelled)
// Should not destroy if VM is not set
state.Remove("vm")
state.Put(multistep.StateCancelled, true)
step.Cleanup(state)
if vm.DestroyCalled {
t.Fatalf("vm.Destroy should not be called")
}
}
func basicStepCreateVM() *StepCreateVM {
step := &StepCreateVM{
Config: createConfig(),
Location: basicLocationConfig(),
}
return step
}
func basicLocationConfig() *common.LocationConfig {
return &common.LocationConfig{
VMName: "test-vm",
Folder: "test-folder",
Cluster: "test-cluster",
Host: "test-host",
ResourcePool: "test-resource-pool",
Datastore: "test-datastore",
}
}
func createConfig() *CreateConfig {
return &CreateConfig{
Version: 1,
GuestOSType: "ubuntu64Guest",
DiskControllerType: []string{"pvscsi"},
Storage: []DiskConfig{
{
DiskSize: 32768,
DiskThinProvisioned: true,
},
},
NICs: []NIC{
{
Network: "VM Network",
NetworkCard: "vmxnet3",
},
},
}
}
func driverCreateConfig(config *CreateConfig, location *common.LocationConfig) *driver.CreateConfig {
var networkCards []driver.NIC
for _, nic := range config.NICs {
networkCards = append(networkCards, driver.NIC{
Network: nic.Network,
NetworkCard: nic.NetworkCard,
MacAddress: nic.MacAddress,
Passthrough: nic.Passthrough,
})
}
var disks []driver.Disk
for _, disk := range config.Storage {
disks = append(disks, driver.Disk{
DiskSize: disk.DiskSize,
DiskEagerlyScrub: disk.DiskEagerlyScrub,
DiskThinProvisioned: disk.DiskThinProvisioned,
ControllerIndex: disk.DiskControllerIndex,
})
}
return &driver.CreateConfig{
DiskControllerType: config.DiskControllerType,
Storage: disks,
Annotation: config.Notes,
Name: location.VMName,
Folder: location.Folder,
Cluster: location.Cluster,
Host: location.Host,
ResourcePool: location.ResourcePool,
Datastore: location.Datastore,
GuestOS: config.GuestOSType,
NICs: networkCards,
USBController: config.USBController,
Version: config.Version,
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ type StepRemoteUpload struct {
func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
d := state.Get("driver").(driver.Driver)
if path, ok := state.GetOk("iso_path"); ok {
filename := filepath.Base(path.(string))
@@ -0,0 +1,67 @@
package iso
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepRemoteUpload_Run(t *testing.T) {
state := basicStateBag()
driverMock := driver.NewDriverMock()
state.Put("driver", driverMock)
state.Put("iso_path", "[datastore] iso/path")
step := &StepRemoteUpload{
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: false,
}
if action := step.Run(context.TODO(), state); action == multistep.ActionHalt {
t.Fatalf("Should not halt.")
}
if !driverMock.FindDatastoreCalled {
t.Fatalf("driver.FindDatastore should be called.")
}
if !driverMock.DatastoreMock.FileExistsCalled {
t.Fatalf("datastore.FindDatastore should be called.")
}
if !driverMock.DatastoreMock.MakeDirectoryCalled {
t.Fatalf("datastore.MakeDirectory should be called.")
}
if !driverMock.DatastoreMock.UploadFileCalled {
t.Fatalf("datastore.UploadFile should be called.")
}
remotePath, ok := state.GetOk("iso_remote_path")
if !ok {
t.Fatalf("state should contain iso_remote_path")
}
expectedRemovePath := fmt.Sprintf("[%s] packer_cache//path", driverMock.DatastoreMock.Name())
if remotePath != expectedRemovePath {
t.Fatalf("iso_remote_path expected to be %s but was %s", expectedRemovePath, remotePath)
}
}
func TestStepRemoteUpload_SkipRun(t *testing.T) {
state := basicStateBag()
driverMock := driver.NewDriverMock()
state.Put("driver", driverMock)
step := &StepRemoteUpload{}
if action := step.Run(context.TODO(), state); action == multistep.ActionHalt {
t.Fatalf("Should not halt.")
}
if driverMock.FindDatastoreCalled {
t.Fatalf("driver.FindDatastore should not be called.")
}
if _, ok := state.GetOk("iso_remote_path"); ok {
t.Fatalf("state should not contain iso_remote_path")
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ type StepRemoveCDRom struct {
func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
ui.Say("Eject CD-ROM drives...")
err := vm.EjectCdroms()
+2 -2
View File
@@ -16,8 +16,8 @@ type StepRemoveFloppy struct {
func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
d := state.Get("driver").(*driver.Driver)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
d := state.Get("driver").(*driver.VCenterDriver)
ui.Say("Deleting Floppy drives...")
devices, err := vm.Devices()
+67
View File
@@ -0,0 +1,67 @@
//go:generate struct-markdown
package yandex
import (
"errors"
"fmt"
"os"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/yandex-cloud/go-sdk/iamkey"
)
const defaultEndpoint = "api.cloud.yandex.net:443"
// AccessConfig is for common configuration related to Yandex.Cloud API access
type AccessConfig struct {
// Non standard API endpoint. Default is `api.cloud.yandex.net:443`.
Endpoint string `mapstructure:"endpoint" required:"false"`
// Path to file with Service Account key in json format. This
// is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable
// `YC_SERVICE_ACCOUNT_KEY_FILE`.
ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"`
// OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set
// value by environment variable `YC_TOKEN`.
Token string `mapstructure:"token" required:"true"`
// The maximum number of times an API request is being executed.
MaxRetries int `mapstructure:"max_retries"`
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.Endpoint == "" {
c.Endpoint = defaultEndpoint
}
// provision config by OS environment variables
if c.Token == "" {
c.Token = os.Getenv("YC_TOKEN")
}
if c.ServiceAccountKeyFile == "" {
c.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE")
}
if c.Token != "" && c.ServiceAccountKeyFile != "" {
errs = append(errs, errors.New("one of token or service account key file must be specified, not both"))
}
if c.Token != "" {
packer.LogSecretFilter.Set(c.Token)
}
if c.ServiceAccountKeyFile != "" {
if _, err := iamkey.ReadFromJSONFile(c.ServiceAccountKeyFile); err != nil {
errs = append(errs, fmt.Errorf("fail to read service account key file: %s", err))
}
}
if len(errs) > 0 {
return errs
}
return nil
}
+1 -1
View File
@@ -50,7 +50,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
// Run executes a yandex Packer build and returns a packer.Artifact
// representing a Yandex.Cloud compute image.
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
driver, err := NewDriverYC(ui, &b.config)
driver, err := NewDriverYC(ui, &b.config.AccessConfig)
ctx = requestid.ContextWithClientTraceID(ctx, uuid.New().String())
if err != nil {
+12 -53
View File
@@ -16,11 +16,8 @@ import (
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/yandex-cloud/go-sdk/iamkey"
)
const defaultEndpoint = "api.cloud.yandex.net:443"
const defaultGpuPlatformID = "gpu-standard-v1"
const defaultPlatformID = "standard-v1"
const defaultMaxRetries = 3
@@ -31,23 +28,15 @@ var reImageFamily = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$`)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Communicator communicator.Config `mapstructure:",squash"`
AccessConfig `mapstructure:",squash"`
// Non standard api endpoint URL.
Endpoint string `mapstructure:"endpoint" required:"false"`
// The folder ID that will be used to launch instances and store images.
// Alternatively you may set value by environment variable YC_FOLDER_ID.
// Alternatively you may set value by environment variable `YC_FOLDER_ID`.
// To use a different folder for looking up the source image or saving the target image to
// check options 'source_image_folder_id' and 'target_image_folder_id'.
FolderID string `mapstructure:"folder_id" required:"true"`
// Path to file with Service Account key in json format. This
// is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable
// YC_SERVICE_ACCOUNT_KEY_FILE.
ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"`
// Service account identifier to assign to instance
// Service account identifier to assign to instance.
ServiceAccountID string `mapstructure:"service_account_id" required:"false"`
// OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set
// value by environment variable YC_TOKEN.
Token string `mapstructure:"token" required:"true"`
// The name of the disk, if unset the instance name
// will be used.
DiskName string `mapstructure:"disk_name" required:"false"`
@@ -59,8 +48,7 @@ type Config struct {
ImageDescription string `mapstructure:"image_description" required:"false"`
// The family name of the resulting image.
ImageFamily string `mapstructure:"image_family" required:"false"`
// Key/value pair labels to
// apply to the created image.
// Key/value pair labels to apply to the created image.
ImageLabels map[string]string `mapstructure:"image_labels" required:"false"`
// Minimum size of the disk that will be created from built image, specified in gigabytes.
// Should be more or equal to `disk_size_gb`.
@@ -78,16 +66,14 @@ type Config struct {
InstanceMemory int `mapstructure:"instance_mem_gb" required:"false"`
// The name assigned to the instance.
InstanceName string `mapstructure:"instance_name" required:"false"`
// Key/value pair labels to apply to
// the launched instance.
// Key/value pair labels to apply to the launched instance.
Labels map[string]string `mapstructure:"labels" required:"false"`
// Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`.
PlatformID string `mapstructure:"platform_id" required:"false"`
// The maximum number of times an API request is being executed
MaxRetries int `mapstructure:"max_retries"`
// Metadata applied to the launched instance.
Metadata map[string]string `mapstructure:"metadata" required:"false"`
// Metadata applied to the launched instance. Value are file paths.
// Metadata applied to the launched instance.
// The values in this map are the paths to the content files for the corresponding metadata keys.
MetadataFromFile map[string]string `mapstructure:"metadata_from_file"`
// Launch a preemptible instance. This defaults to `false`.
Preemptible bool `mapstructure:"preemptible"`
@@ -95,12 +81,11 @@ type Config struct {
SerialLogFile string `mapstructure:"serial_log_file" required:"false"`
// The source image family to create the new image
// from. You can also specify source_image_id instead. Just one of a source_image_id or
// source_image_family must be specified. Example: `ubuntu-1804-lts`
// source_image_family must be specified. Example: `ubuntu-1804-lts`.
SourceImageFamily string `mapstructure:"source_image_family" required:"true"`
// The ID of the folder containing the source image.
SourceImageFolderID string `mapstructure:"source_image_folder_id" required:"false"`
// The source image ID to use to create the new image
// from.
// The source image ID to use to create the new image from.
SourceImageID string `mapstructure:"source_image_id" required:"false"`
// The source image name to use to create the new image
// from. Name will be looked up in `source_image_folder_id`.
@@ -142,8 +127,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
return nil, err
}
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, c.AccessConfig.Prepare(&c.ctx)...)
if c.SerialLogFile != "" {
if _, err := os.Stat(c.SerialLogFile); os.IsExist(err) {
errs = packer.MultiErrorAppend(errs,
@@ -236,10 +224,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
}
if c.Endpoint == "" {
c.Endpoint = defaultEndpoint
}
if c.Zone == "" {
c.Zone = defaultZone
}
@@ -248,35 +232,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.MaxRetries = defaultMaxRetries
}
// provision config by OS environment variables
if c.Token == "" {
c.Token = os.Getenv("YC_TOKEN")
}
if c.ServiceAccountKeyFile == "" {
c.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE")
}
if c.FolderID == "" {
c.FolderID = os.Getenv("YC_FOLDER_ID")
}
if c.Token != "" && c.ServiceAccountKeyFile != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("one of token or service account key file must be specified, not both"))
}
if c.Token != "" {
packer.LogSecretFilter.Set(c.Token)
}
if c.ServiceAccountKeyFile != "" {
if _, err := iamkey.ReadFromJSONFile(c.ServiceAccountKeyFile); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("fail to read service account key file: %s", err))
}
}
if c.FolderID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a folder_id must be specified"))
+6 -6
View File
@@ -64,10 +64,11 @@ 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"`
Endpoint *string `mapstructure:"endpoint" required:"false" cty:"endpoint" hcl:"endpoint"`
FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"`
ServiceAccountKeyFile *string `mapstructure:"service_account_key_file" required:"false" cty:"service_account_key_file" hcl:"service_account_key_file"`
ServiceAccountID *string `mapstructure:"service_account_id" required:"false" cty:"service_account_id" hcl:"service_account_id"`
Token *string `mapstructure:"token" required:"true" cty:"token" hcl:"token"`
MaxRetries *int `mapstructure:"max_retries" cty:"max_retries" hcl:"max_retries"`
FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"`
ServiceAccountID *string `mapstructure:"service_account_id" required:"false" cty:"service_account_id" hcl:"service_account_id"`
DiskName *string `mapstructure:"disk_name" required:"false" cty:"disk_name" hcl:"disk_name"`
DiskSizeGb *int `mapstructure:"disk_size_gb" required:"false" cty:"disk_size_gb" hcl:"disk_size_gb"`
DiskType *string `mapstructure:"disk_type" required:"false" cty:"disk_type" hcl:"disk_type"`
@@ -83,7 +84,6 @@ type FlatConfig struct {
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"`
Labels map[string]string `mapstructure:"labels" required:"false" cty:"labels" hcl:"labels"`
PlatformID *string `mapstructure:"platform_id" required:"false" cty:"platform_id" hcl:"platform_id"`
MaxRetries *int `mapstructure:"max_retries" cty:"max_retries" hcl:"max_retries"`
Metadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata" hcl:"metadata"`
MetadataFromFile map[string]string `mapstructure:"metadata_from_file" cty:"metadata_from_file" hcl:"metadata_from_file"`
Preemptible *bool `mapstructure:"preemptible" cty:"preemptible" hcl:"preemptible"`
@@ -168,10 +168,11 @@ 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},
"endpoint": &hcldec.AttrSpec{Name: "endpoint", Type: cty.String, Required: false},
"folder_id": &hcldec.AttrSpec{Name: "folder_id", Type: cty.String, Required: false},
"service_account_key_file": &hcldec.AttrSpec{Name: "service_account_key_file", Type: cty.String, Required: false},
"service_account_id": &hcldec.AttrSpec{Name: "service_account_id", Type: cty.String, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
"folder_id": &hcldec.AttrSpec{Name: "folder_id", Type: cty.String, Required: false},
"service_account_id": &hcldec.AttrSpec{Name: "service_account_id", Type: cty.String, Required: false},
"disk_name": &hcldec.AttrSpec{Name: "disk_name", Type: cty.String, Required: false},
"disk_size_gb": &hcldec.AttrSpec{Name: "disk_size_gb", Type: cty.Number, Required: false},
"disk_type": &hcldec.AttrSpec{Name: "disk_type", Type: cty.String, Required: false},
@@ -187,7 +188,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
"labels": &hcldec.AttrSpec{Name: "labels", Type: cty.Map(cty.String), Required: false},
"platform_id": &hcldec.AttrSpec{Name: "platform_id", Type: cty.String, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
"metadata_from_file": &hcldec.AttrSpec{Name: "metadata_from_file", Type: cty.Map(cty.String), Required: false},
"preemptible": &hcldec.AttrSpec{Name: "preemptible", Type: cty.Bool, Required: false},
+10 -10
View File
@@ -33,27 +33,27 @@ type driverYC struct {
ui packer.Ui
}
func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) {
func NewDriverYC(ui packer.Ui, ac *AccessConfig) (Driver, error) {
log.Printf("[INFO] Initialize Yandex.Cloud client...")
sdkConfig := ycsdk.Config{}
if config.Endpoint != "" {
sdkConfig.Endpoint = config.Endpoint
if ac.Endpoint != "" {
sdkConfig.Endpoint = ac.Endpoint
}
switch {
case config.Token == "" && config.ServiceAccountKeyFile == "":
case ac.Token == "" && ac.ServiceAccountKeyFile == "":
log.Printf("[INFO] Use Instance Service Account for authentication")
sdkConfig.Credentials = ycsdk.InstanceServiceAccount()
case config.Token != "":
case ac.Token != "":
log.Printf("[INFO] Use OAuth token for authentication")
sdkConfig.Credentials = ycsdk.OAuthToken(config.Token)
sdkConfig.Credentials = ycsdk.OAuthToken(ac.Token)
case config.ServiceAccountKeyFile != "":
log.Printf("[INFO] Use Service Account key file %q for authentication", config.ServiceAccountKeyFile)
key, err := iamkey.ReadFromJSONFile(config.ServiceAccountKeyFile)
case ac.ServiceAccountKeyFile != "":
log.Printf("[INFO] Use Service Account key file %q for authentication", ac.ServiceAccountKeyFile)
key, err := iamkey.ReadFromJSONFile(ac.ServiceAccountKeyFile)
if err != nil {
return nil, err
}
@@ -69,7 +69,7 @@ func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) {
requestIDInterceptor := requestid.Interceptor()
retryInterceptor := retry.Interceptor(
retry.WithMax(config.MaxRetries),
retry.WithMax(ac.MaxRetries),
retry.WithCodes(codes.Unavailable),
retry.WithAttemptHeader(true),
retry.WithBackoff(retry.BackoffExponentialWithJitter(defaultExponentialBackoffBase, defaultExponentialBackoffCap)))
+12
View File
@@ -130,3 +130,15 @@ func (va *InspectArgs) AddFlagSets(flags *flag.FlagSet) {
type InspectArgs struct {
MetaArgs
}
func (va *HCL2UpgradeArgs) AddFlagSets(flags *flag.FlagSet) {
flags.StringVar(&va.OutputFile, "output-file", "", "File where to put the hcl2 generated config. Defaults to JSON_TEMPLATE.pkr.hcl")
va.MetaArgs.AddFlagSets(flags)
}
// HCL2UpgradeArgs represents a parsed cli line for a `packer build`
type HCL2UpgradeArgs struct {
MetaArgs
OutputFile string
}
+6 -2
View File
@@ -8,6 +8,7 @@ import (
"runtime"
"testing"
"github.com/hashicorp/packer/builder/amazon/ebs"
"github.com/hashicorp/packer/builder/file"
"github.com/hashicorp/packer/builder/null"
"github.com/hashicorp/packer/packer"
@@ -89,6 +90,8 @@ func TestHelperProcess(*testing.T) {
os.Exit((&InspectCommand{Meta: commandMeta()}).Run(args))
case "build":
os.Exit((&BuildCommand{Meta: commandMeta()}).Run(args))
case "hcl2_upgrade":
os.Exit((&HCL2UpgradeCommand{Meta: commandMeta()}).Run(args))
default:
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
os.Exit(2)
@@ -115,8 +118,9 @@ func commandMeta() Meta {
func getBareComponentFinder() packer.ComponentFinder {
return packer.ComponentFinder{
BuilderStore: packer.MapOfBuilder{
"file": func() (packer.Builder, error) { return &file.Builder{}, nil },
"null": func() (packer.Builder, error) { return &null.Builder{}, nil },
"file": func() (packer.Builder, error) { return &file.Builder{}, nil },
"null": func() (packer.Builder, error) { return &null.Builder{}, nil },
"amazon-ebs": func() (packer.Builder, error) { return &ebs.Builder{}, nil },
},
ProvisionerStore: packer.MapOfProvisioner{
"shell-local": func() (packer.Provisioner, error) { return &shell_local.Provisioner{}, nil },
+432
View File
@@ -0,0 +1,432 @@
package command
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
texttemplate "text/template"
"github.com/hashicorp/hcl/v2/hclwrite"
hcl2shim "github.com/hashicorp/packer/hcl2template/shim"
"github.com/hashicorp/packer/template"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
)
type HCL2UpgradeCommand struct {
Meta
}
func (c *HCL2UpgradeCommand) Run(args []string) int {
ctx, cleanup := handleTermInterrupt(c.Ui)
defer cleanup()
cfg, ret := c.ParseArgs(args)
if ret != 0 {
return ret
}
return c.RunContext(ctx, cfg)
}
func (c *HCL2UpgradeCommand) ParseArgs(args []string) (*HCL2UpgradeArgs, int) {
var cfg HCL2UpgradeArgs
flags := c.Meta.FlagSet("hcl2_upgrade", FlagSetNone)
flags.Usage = func() { c.Ui.Say(c.Help()) }
cfg.AddFlagSets(flags)
if err := flags.Parse(args); err != nil {
return &cfg, 1
}
args = flags.Args()
if len(args) != 1 {
flags.Usage()
return &cfg, 1
}
cfg.Path = args[0]
if cfg.OutputFile == "" {
cfg.OutputFile = cfg.Path + ".pkr.hcl"
}
return &cfg, 0
}
const (
hcl2UpgradeFileHeader = `# This file was autogenerate 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
# moved to their own 'variables.pkr.hcl' file, etc. Those files need to be
# suffixed with '.pkr.hcl' to be visible to Packer. To use multiple files at
# 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
# 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
# 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
# documentation for build blocks can be found here:
# https://www.packer.io/docs/from-1.5/blocks/build
build {
`
)
func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2UpgradeArgs) int {
out := &bytes.Buffer{}
var output io.Writer
if err := os.MkdirAll(filepath.Dir(cla.OutputFile), 0); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to create output directory: %v", err))
return 1
}
if f, err := os.Create(cla.OutputFile); err == nil {
output = f
defer f.Close()
} else {
c.Ui.Error(fmt.Sprintf("Failed to create output file: %v", err))
return 1
}
if _, err := output.Write([]byte(hcl2UpgradeFileHeader)); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write to file: %v", err))
return 1
}
hdl, ret := c.GetConfigFromJSON(&cla.MetaArgs)
if ret != 0 {
return ret
}
core := hdl.(*CoreWrapper).Core
if err := core.Initialize(); err != nil {
c.Ui.Error(fmt.Sprintf("Initialization error, continuing: %v", err))
}
tpl := core.Template
// Output variables section
variables := []*template.Variable{}
{
// sort variables to avoid map's randomness
for _, variable := range tpl.Variables {
variables = append(variables, variable)
}
sort.Slice(variables, func(i, j int) bool {
return variables[i].Key < variables[j].Key
})
}
for _, variable := range variables {
variablesContent := hclwrite.NewEmptyFile()
variablesBody := variablesContent.Body()
variableBody := variablesBody.AppendNewBlock("variable", []string{variable.Key}).Body()
variableBody.SetAttributeRaw("type", hclwrite.Tokens{&hclwrite.Token{Bytes: []byte("string")}})
if variable.Default != "" || !variable.Required {
variableBody.SetAttributeValue("default", hcl2shim.HCL2ValueFromConfigValue(variable.Default))
}
if isSensitiveVariable(variable.Key, tpl.SensitiveVariables) {
variableBody.SetAttributeValue("sensitive", cty.BoolVal(true))
}
variablesBody.AppendNewline()
out.Write(transposeTemplatingCalls(variablesContent.Bytes()))
}
fmt.Fprintln(out, `# "timestamp" template function replacement`)
fmt.Fprintln(out, `locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }`)
// Output sources section
builders := []*template.Builder{}
{
// sort builders to avoid map's randomnes
for _, builder := range tpl.Builders {
builders = append(builders, builder)
}
sort.Slice(builders, func(i, j int) bool {
return builders[i].Type+builders[i].Name < builders[j].Type+builders[j].Name
})
}
out.Write([]byte(sourcesHeader))
for i, builderCfg := range builders {
sourcesContent := hclwrite.NewEmptyFile()
body := sourcesContent.Body()
body.AppendNewline()
if !c.Meta.CoreConfig.Components.BuilderStore.Has(builderCfg.Type) {
c.Ui.Error(fmt.Sprintf("unknown builder type: %q\n", builderCfg.Type))
return 1
}
if builderCfg.Name == "" || builderCfg.Name == builderCfg.Type {
builderCfg.Name = fmt.Sprintf("autogenerated_%d", i+1)
}
sourceBody := body.AppendNewBlock("source", []string{builderCfg.Type, builderCfg.Name}).Body()
jsonBodyToHCL2Body(sourceBody, builderCfg.Config)
_, _ = out.Write(transposeTemplatingCalls(sourcesContent.Bytes()))
}
// Output build section
out.Write([]byte(buildHeader))
buildContent := hclwrite.NewEmptyFile()
buildBody := buildContent.Body()
if tpl.Description != "" {
buildBody.SetAttributeValue("description", cty.StringVal(tpl.Description))
buildBody.AppendNewline()
}
sourceNames := []string{}
for _, builder := range builders {
sourceNames = append(sourceNames, fmt.Sprintf("source.%s.%s", builder.Type, builder.Name))
}
buildBody.SetAttributeValue("sources", hcl2shim.HCL2ValueFromConfigValue(sourceNames))
buildBody.AppendNewline()
_, _ = buildContent.WriteTo(out)
for _, provisioner := range tpl.Provisioners {
provisionerContent := hclwrite.NewEmptyFile()
body := provisionerContent.Body()
buildBody.AppendNewline()
block := body.AppendNewBlock("provisioner", []string{provisioner.Type})
cfg := provisioner.Config
if len(provisioner.Except) > 0 {
cfg["except"] = provisioner.Except
}
if len(provisioner.Only) > 0 {
cfg["only"] = provisioner.Only
}
if provisioner.MaxRetries != "" {
cfg["max_retries"] = provisioner.MaxRetries
}
if provisioner.Timeout > 0 {
cfg["timeout"] = provisioner.Timeout.String()
}
jsonBodyToHCL2Body(block.Body(), cfg)
out.Write(transposeTemplatingCalls(provisionerContent.Bytes()))
}
for _, pps := range tpl.PostProcessors {
postProcessorContent := hclwrite.NewEmptyFile()
body := postProcessorContent.Body()
switch len(pps) {
case 0:
continue
case 1:
default:
body = body.AppendNewBlock("post-processors", nil).Body()
}
for _, pp := range pps {
ppBody := body.AppendNewBlock("post-processor", []string{pp.Type}).Body()
if pp.KeepInputArtifact != nil {
ppBody.SetAttributeValue("keep_input_artifact", cty.BoolVal(*pp.KeepInputArtifact))
}
cfg := pp.Config
if len(pp.Except) > 0 {
cfg["except"] = pp.Except
}
if len(pp.Only) > 0 {
cfg["only"] = pp.Only
}
if pp.Name != "" && pp.Name != pp.Type {
cfg["name"] = pp.Name
}
jsonBodyToHCL2Body(ppBody, cfg)
}
_, _ = out.Write(transposeTemplatingCalls(postProcessorContent.Bytes()))
}
_, _ = out.Write([]byte("}\n"))
_, _ = output.Write(hclwrite.Format(out.Bytes()))
c.Ui.Say(fmt.Sprintf("Successfully created %s ", cla.OutputFile))
return 0
}
// transposeTemplatingCalls executes parts of blocks as go template files and replaces
// their result with their hcl2 variant. If something goes wrong the template
// containing the go template string is returned.
func transposeTemplatingCalls(s []byte) []byte {
fallbackReturn := func(err error) []byte {
return append([]byte(fmt.Sprintf("\n#could not parse template for following block: %q\n", err)), s...)
}
funcMap := texttemplate.FuncMap{
"timestamp": func() string {
return "${local.timestamp}"
},
"isotime": func() string {
return "${local.timestamp}"
},
"user": func(in string) string {
return fmt.Sprintf("${var.%s}", in)
},
"env": func(in string) string {
return fmt.Sprintf("${var.%s}", in)
},
"build": func(a string) string {
return fmt.Sprintf("${build.%s}", a)
},
}
tpl, err := texttemplate.New("generated").
Funcs(funcMap).
Parse(string(s))
if err != nil {
return fallbackReturn(err)
}
str := &bytes.Buffer{}
v := struct {
HTTPIP string
HTTPPort string
}{
HTTPIP: "{{ .HTTPIP }}",
HTTPPort: "{{ .HTTPPort }}",
}
if err := tpl.Execute(str, v); err != nil {
return fallbackReturn(err)
}
return str.Bytes()
}
func jsonBodyToHCL2Body(out *hclwrite.Body, kvs map[string]interface{}) {
ks := []string{}
for k := range kvs {
ks = append(ks, k)
}
sort.Strings(ks)
for _, k := range ks {
value := kvs[k]
switch value := value.(type) {
case map[string]interface{}:
var mostComplexElem interface{}
for _, randomElem := range value {
// HACK: we take the most complex element of that map because
// in HCL2, map of objects can be bodies, for example:
// map containing object: source_ami_filter {} ( body )
// simple string/string map: tags = {} ) ( attribute )
//
// if we could not find an object in this map then it's most
// likely a plain map and so we guess it should be and
// attribute. Though now if value refers to something that is
// an object but only contains a string or a bool; we could
// generate a faulty object. For example a (somewhat invalid)
// source_ami_filter where only `most_recent` is set.
switch randomElem.(type) {
case string, int, float64, bool:
if mostComplexElem != nil {
continue
}
mostComplexElem = randomElem
default:
mostComplexElem = randomElem
}
}
switch mostComplexElem.(type) {
case string, int, float64, bool:
out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
default:
nestedBlockBody := out.AppendNewBlock(k, nil).Body()
jsonBodyToHCL2Body(nestedBlockBody, value)
}
case map[string]string, map[string]int, map[string]float64:
out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
case []interface{}:
if len(value) == 0 {
continue
}
var mostComplexElem interface{}
for _, randomElem := range value {
// HACK: we take the most complex element of that slice because
// in hcl2 slices of plain types can be arrays, for example:
// simple string type: owners = ["0000000000"]
// object: launch_block_device_mappings {}
switch randomElem.(type) {
case string, int, float64, bool:
if mostComplexElem != nil {
continue
}
mostComplexElem = randomElem
default:
mostComplexElem = randomElem
}
}
switch mostComplexElem.(type) {
case map[string]interface{}:
// this is an object in a slice; so we unwrap it. We
// could try to remove any 's' suffix in the key, but
// this might not work everywhere.
for i := range value {
value := value[i].(map[string]interface{})
nestedBlockBody := out.AppendNewBlock(k, nil).Body()
jsonBodyToHCL2Body(nestedBlockBody, value)
}
continue
default:
out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
}
default:
out.SetAttributeValue(k, hcl2shim.HCL2ValueFromConfigValue(value))
}
}
}
func isSensitiveVariable(key string, vars []*template.Variable) bool {
for _, v := range vars {
if v.Key == key {
return true
}
}
return false
}
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.
`
return strings.TrimSpace(helpText)
}
func (*HCL2UpgradeCommand) Synopsis() string {
return "transform a JSON template into a HCL2 configuration"
}
func (*HCL2UpgradeCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (*HCL2UpgradeCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{}
}
+51
View File
@@ -0,0 +1,51 @@
package command
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
)
func Test_hcl2_upgrade(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd: %v", err)
}
_ = cwd
tc := []struct {
folder string
}{
{"hcl2_upgrade_basic"},
}
for _, tc := range tc {
t.Run(tc.folder, func(t *testing.T) {
inputPath := filepath.Join(testFixture(tc.folder, "input.json"))
outputPath := inputPath + ".pkr.hcl"
expectedPath := filepath.Join(testFixture(tc.folder, "expected.pkr.hcl"))
p := helperCommand(t, "hcl2_upgrade", inputPath)
bs, err := p.CombinedOutput()
if err != nil {
t.Fatalf("%v %s", err, bs)
}
expected := mustBytes(ioutil.ReadFile(expectedPath))
actual := mustBytes(ioutil.ReadFile(outputPath))
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("unexpected output: %s", diff)
}
os.Remove(outputPath)
})
}
}
func mustBytes(b []byte, e error) []byte {
if e != nil {
panic(e)
}
return b
}
+4 -5
View File
@@ -44,11 +44,10 @@ func (c *InspectCommand) RunContext(ctx context.Context, cla *InspectArgs) int {
if ret != 0 {
return ret
}
diags := packerStarter.Initialize()
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
}
// here we ignore init diags to allow unknown variables to be used
_ = packerStarter.Initialize()
return packerStarter.InspectConfig(packer.InspectConfigOptions{
Ui: c.Ui,
})
+55 -4
View File
@@ -15,11 +15,38 @@ func Test_commands(t *testing.T) {
env []string
expected string
}{
{[]string{"inspect", filepath.Join(testFixture("var-arg"), "fruit_builder.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode
> input-variables:
var.fruit: "<unknown>"
> local-variables:
local.fruit: "<unknown>"
> builds:
> <unnamed build 0>:
sources:
null.builder
provisioners:
shell-local
post-processors:
<no post-processor>
`},
{[]string{"inspect", "-var=fruit=banana", filepath.Join(testFixture("var-arg"), "fruit_builder.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode
> input-variables:
var.fruit: "banana" [debug: {Type:cty.String,CmdValue:banana,VarfileValue:null,EnvValue:null,DefaultValue:null}]
var.fruit: "banana"
> local-variables:
@@ -42,11 +69,13 @@ local.fruit: "banana"
<no post-processor>
`},
{[]string{"inspect", "-var=fruit=peach", filepath.Join(testFixture("hcl"), "inspect", "fruit_string.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode
{[]string{"inspect", "-var=fruit=peach", "-var=unknown_string=also_peach", `-var=unknown_unknown="peach_too"`, filepath.Join(testFixture("hcl"), "inspect", "fruit_string.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode
> input-variables:
var.fruit: "peach" [debug: {Type:cty.String,CmdValue:peach,VarfileValue:null,EnvValue:null,DefaultValue:banana}]
var.fruit: "peach"
var.unknown_string: "also_peach"
var.unknown_unknown: "peach_too"
> local-variables:
@@ -58,7 +87,9 @@ var.fruit: "peach" [debug: {Type:cty.String,CmdValue:peach,VarfileValue:null,Env
> input-variables:
var.fruit: "peach" [debug: {Type:cty.String,CmdValue:peach,VarfileValue:null,EnvValue:null,DefaultValue:banana}]
var.fruit: "peach"
var.unknown_string: "<unknown>"
var.unknown_unknown: "<unknown>"
> local-variables:
@@ -94,6 +125,26 @@ Use it at will.
manifest
shell-local
`},
{[]string{"inspect", filepath.Join(testFixture("inspect"), "unset_var.json")}, nil, `Packer Inspect: JSON mode
Required variables:
something
Optional variables and their defaults:
Builders:
<No builders>
Provisioners:
<No provisioners>
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.
`},
}
+1 -1
View File
@@ -66,7 +66,7 @@ func (m *Meta) Core(tpl *template.Template, cla *MetaArgs) (*packer.Core, error)
// command implements. The exact behavior of FlagSet can be configured
// using the flags as the second parameter, for example to disable
// build settings on the commands that don't handle builds.
func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
func (m *Meta) FlagSet(n string, _ FlagSetFlags) *flag.FlagSet {
f := flag.NewFlagSet(n, flag.ContinueOnError)
// Create an io.Writer that writes to our Ui properly for errors.

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