From a236f674edacb6ef815d57219edf4ec6fe1e8c71 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Thu, 14 Mar 2024 14:02:02 -0400 Subject: [PATCH] chore(post-processor): update `vsphere-template` - Updates error messages to use a more well-formed structure. - Updates and simplifies code comments. - Updates post-processor to generate documentation from structs. - Generates the updated post-processor documentation. Signed-off-by: Ryan Johnson --- .../post-processor/vsphere-template/README.md | 165 ++++++++---------- .../vsphere-template/Config-not-required.mdx | 18 ++ .../vsphere-template/Config-required.mdx | 9 + .../vsphere/Config-not-required.mdx | 35 ++++ docs/post-processors/vsphere-template.mdx | 161 +++++++---------- .../vsphere-template/post-processor.go | 64 ++++--- .../post-processor.hcl2spec.go | 8 +- .../vsphere-template/post-processor_test.go | 12 +- .../step_choose_datacenter.go | 3 + .../vsphere-template/step_create_folder.go | 13 +- .../vsphere-template/step_create_snapshot.go | 5 +- .../vsphere-template/step_mark_as_template.go | 21 +-- 12 files changed, 264 insertions(+), 250 deletions(-) create mode 100644 docs-partials/post-processor/vsphere-template/Config-not-required.mdx create mode 100644 docs-partials/post-processor/vsphere-template/Config-required.mdx create mode 100644 docs-partials/post-processor/vsphere/Config-not-required.mdx diff --git a/.web-docs/components/post-processor/vsphere-template/README.md b/.web-docs/components/post-processor/vsphere-template/README.md index 128a66a08..08f4de6d6 100644 --- a/.web-docs/components/post-processor/vsphere-template/README.md +++ b/.web-docs/components/post-processor/vsphere-template/README.md @@ -1,64 +1,61 @@ Type: `vsphere-template` Artifact BuilderId: `packer.post-processor.vsphere` -The Packer vSphere Template post-processor takes an artifact from the -`vmware-iso` builder, built on ESXi (i.e. remote) or an artifact from the -[vSphere](/packer/integrations/hashicorp/vsphere/latest/components/post-processor/vsphere) post-processor, marks the VM as a -template, and places it in the path of your choice. +This post-processor uses an artifact from the `vmware-iso` builder with an ESXi host or an artifact +from the [vSphere](/packer/integrations/hashicorp/vsphere/latest/components/post-processor/vsphere) post-processor. It then marks +the virtual machine as a template and moves it to your specified path. ## Configuration -There are many configuration options available for the post-processor. They are -provided below in two categories: required and optional parameters. +The following configuration options are available for the post-processor. Required: -- `host` (string) - The vSphere endpoint that contains the VM built by the - `vmware-iso` builder. + -- `password` (string) - Password to use to authenticate to the vSphere - endpoint. +- `host` (string) - Specifies the fully qualified domain name or IP address of the vSphere endpoint. + +- `username` (string) - Specifies the username to use to authenticate to the vSphere endpoint. + +- `password` (string) - Specifies the password to use to authenticate to the vSphere endpoint. + + -- `username` (string) - The username to use to authenticate to the vSphere - endpoint. Optional: -- `datacenter` (string) - If you have more than one, you will need to specify - which one the ESXi host used. + + +- `insecure` (bool) - Specifies whether to skip the verification of the server certificate. Defaults to `false`. -- `folder` (string) - Target path where the template will be created. +- `datacenter` (string) - Specifies the name of the datacenter to use. + Required when the vCenter Server instance endpoint has more than one datacenter. -- `insecure` (boolean) - If `true`, skips the verification of the server - certificate. Default is `false`. +- `folder` (string) - Specifies the name of the virtual machine folder path where the template will be created. -- `keep_input_artifact` (boolean) - Unlike most post-processors, this option - has no effect for `vsphere-template`. This is because in order for a template - to work, you can't delete the vm that you generate the template from. The - vSphere Template post-processor will therefore always preserve the original - VM. +- `snapshot_enable` (bool) - Specifies whether to create a snapshot before marking as a template. Defaults to `false`.\ -- `snapshot_enable` (boolean) - Create a snapshot before marking as a - template. Default is `false`. +- `snapshot_name` (string) - Specifies the name of the snapshot. Required when `snapshot_enable` is `true`. -- `snapshot_name` (string) - Name for the snapshot. Required when - `snapshot_enable` is `true`. +- `snapshot_description` (string) - Specifies a description for the snapshot. Required when `snapshot_enable` is `true`. -- `snapshot_description` (string) - Description for the snapshot. Required - when `snapshot_enable` is `true`. +- `reregister_vm` (boolean) - Specifies to keep the virtual machine registered after marking as a template. -- `reregister_vm` (boolean) - Use the method of unregister VM and reregister - as a template, rather than using the `markAsTemplate` method. + - NOTE: If you are getting permission denied errors when trying to mark as a - template, but it works fine in the vSphere UI, try setting this to `false`. - Default is `true`. -## Example +- `keep_input_artifact` (boolean) - This option is not applicable to `vsphere-template`. For a + template to function, the original virtual machine from which it was generated cannot be deleted. + Therefore, the vSphere Template post-processor always preserves the original virtual machine. + + ~> **Note**: If you are getting permission denied errors when trying to mark as a template, but it + works in the vSphere UI, set this to `false`. Default is `true`. + +## Example Usage An example is shown below, showing only the post-processor configuration: -**HCL2** +In HCL2: ```hcl source "null" "example" { @@ -76,15 +73,14 @@ build { insecure = false username = "administrator@vsphere.local" password = "VMw@re1!" - datacenter = "dc-01" + datacenter = "dc-01" folder = "/templates/os/distro" } } } ``` - -**JSON** +In JSON: ```json { @@ -110,17 +106,13 @@ build { } ``` +## Using the vSphere Template with Local Builders -## Using the vSphere Template with local builders - -Once the [vSphere](/packer/integrations/hashicorp/vsphere/latest/components/post-processor/vsphere) post-processor takes an -artifact from the builder and uploads it to a vSphere endpoint, you may want -the VM to be marked as a template. Packer can do this for you automatically -using a sequence definition (a collection of post-processors that are treated -as as single pipeline, see [Post-Processors](/packer/docs/templates/legacy_json_templates/post-processors) -for more information): +Once the [vSphere](/packer/integrations/hashicorp/vsphere/latest/components/post-processor/vsphere) post-processor takes an artifact +from the builder and uploads it to a vSphere endpoint, you may want the virtual machine to be marked +as a template. -**HCL2** +In HCL2: ```hcl build { @@ -140,8 +132,7 @@ build { } ``` - -**JSON** +In JSON: ```json { @@ -168,63 +159,57 @@ build { } ] } - - ``` - -In the example above, the result of each builder is passed through the defined -sequence of post-processors starting with the `vsphere` post-processor which -will upload the artifact to a vSphere endpoint. The resulting artifact is then -passed on to the `vsphere-template` post-processor which handles marking a VM -as a template. In JSON, note that the `vsphere` and `vsphere-template` +In the example above, the result of each builder is passed through the defined sequence of +post-processors starting with the `vsphere` post-processor which will upload the artifact to a +vSphere endpoint. The resulting artifact is then passed on to the `vsphere-template` post-processor +which handles marking a VM as a template. In JSON, note that the `vsphere` and `vsphere-template` post-processors can be paired together in their own array. ## Permissions -The vSphere post processor needs several privileges to be able to mark the -vm as a template. Rather than giving full administrator access, you can create -a role to give the post-processor the privileges necessary to run. Here is an -example role that will work. Please note that this is a user-supplied list so -there may be a few extraneous privilegess that are not strictly required. +The post processor needs several privileges to be able to mark the virtual as a template. + +Rather than giving full administrator access, you can create a role to give the post-processor the +privileges necessary to run. + +Below is an example role that will work. Please note that this is a user-supplied list so there may +be a few extraneous privileges that are not strictly required. For vSphere, the role needs the following privileges: - `Datastore.AllocateSpace` - `Host.Config.AdvancedConfig` - `Host.Config.NetService` - `Host.Config.Network` - `Network.Assign` - `System.Anonymous` - `System.Read` - `System.View` - `VApp.Import` - `VirtualMachine.Config.AddNewDisk` - `VirtualMachine.Config.AdvancedConfig` - `VirtualMachine.Inventory.Delete` +- `Datastore.AllocateSpace` +- `Host.Config.AdvancedConfig` +- `Host.Config.NetService` +- `Host.Config.Network` +- `Network.Assign` +- `System.Anonymous` +- `System.Read` +- `System.View` +- `VApp.Import` +- `VirtualMachine.Config.AddNewDisk` +- `VirtualMachine.Config.AdvancedConfig` +- `VirtualMachine.Inventory.Delete` -and either (if `reregister_vm` is `false`): + and either (if `reregister_vm` is `false`): - `VirtualMachine.Provisioning.MarkAsTemplate` + - `VirtualMachine.Provisioning.MarkAsTemplate` -or (if `reregister_vm` is `true` or unset): + or (if `reregister_vm` is `true` or unset): - `VirtualMachine.Inventory.Register` - `VirtualMachine.Inventory.Unregister` + - `VirtualMachine.Inventory.Register` + - `VirtualMachine.Inventory.Unregister` The role must be authorized on the: - `Cluster of the host` - `The destination folder` - `The destination datastore` - `The network to be assigned` +- Cluster of the host. +- The destination folder. +- The destination datastore. +- The network to be assigned. # Troubleshooting -Some users have reported that vSphere templates created from local vSphere builds -get their boot order reset to cdrom only instead of the original boot order -defined by the template. If this issue affects you, the solution is to set -`"bios.hddOrder": "scsi0:0"` in your builder's `vmx_data`. - -Packer doesn't automatically do this for you because it causes strange upload -behavior in certain versions of `ovftool`. +Some users have reported that vSphere templates created from local vSphere builds get their boot +order reset to CD-ROM only instead of the original boot order defined by the template. If this issue +affects you, the solution is to set `"bios.hddOrder": "scsi0:0"` in your builder's `vmx_data`. diff --git a/docs-partials/post-processor/vsphere-template/Config-not-required.mdx b/docs-partials/post-processor/vsphere-template/Config-not-required.mdx new file mode 100644 index 000000000..c8f5c460e --- /dev/null +++ b/docs-partials/post-processor/vsphere-template/Config-not-required.mdx @@ -0,0 +1,18 @@ + + +- `insecure` (bool) - Specifies whether to skip the verification of the server certificate. Defaults to `false`. + +- `datacenter` (string) - Specifies the name of the datacenter to use. + Required when the vCenter Server instance endpoint has more than one datacenter. + +- `folder` (string) - Specifies the name of the virtual machine folder path where the template will be created. + +- `snapshot_enable` (bool) - Specifies whether to create a snapshot before marking as a template. Defaults to `false`.\ + +- `snapshot_name` (string) - Specifies the name of the snapshot. Required when `snapshot_enable` is `true`. + +- `snapshot_description` (string) - Specifies a description for the snapshot. Required when `snapshot_enable` is `true`. + +- `reregister_vm` (boolean) - Specifies to keep the virtual machine registered after marking as a template. + + diff --git a/docs-partials/post-processor/vsphere-template/Config-required.mdx b/docs-partials/post-processor/vsphere-template/Config-required.mdx new file mode 100644 index 000000000..4cec1ef5c --- /dev/null +++ b/docs-partials/post-processor/vsphere-template/Config-required.mdx @@ -0,0 +1,9 @@ + + +- `host` (string) - Specifies the fully qualified domain name or IP address of the vSphere endpoint. + +- `username` (string) - Specifies the username to use to authenticate to the vSphere endpoint. + +- `password` (string) - Specifies the password to use to authenticate to the vSphere endpoint. + + diff --git a/docs-partials/post-processor/vsphere/Config-not-required.mdx b/docs-partials/post-processor/vsphere/Config-not-required.mdx new file mode 100644 index 000000000..52058aaf8 --- /dev/null +++ b/docs-partials/post-processor/vsphere/Config-not-required.mdx @@ -0,0 +1,35 @@ + + +- `cluster` (string) - Cluster + +- `datacenter` (string) - Datacenter + +- `datastore` (string) - Datastore + +- `disk_mode` (string) - Disk Mode + +- `host` (string) - Host + +- `esxi_host` (string) - ES Xi Host + +- `insecure` (bool) - Insecure + +- `options` ([]string) - Options + +- `overwrite` (bool) - Overwrite + +- `password` (string) - Password + +- `resource_pool` (string) - Resource Pool + +- `username` (string) - Username + +- `vm_folder` (string) - VM Folder + +- `vm_name` (string) - VM Name + +- `vm_network` (string) - VM Network + +- `hardware_version` (string) - Hardware Version + + diff --git a/docs/post-processors/vsphere-template.mdx b/docs/post-processors/vsphere-template.mdx index 430f89c3e..312566689 100644 --- a/docs/post-processors/vsphere-template.mdx +++ b/docs/post-processors/vsphere-template.mdx @@ -1,9 +1,8 @@ --- description: > - The Packer vSphere Template post-processor takes an artifact from the - VMware-iso builder, built on and ESXi host (i.e. remote) or an artifact from the - [vSphere](/packer/plugins/post-processors/vsphere/vsphere) post-processor, marks the VM as a - template, and leaves it in the path of your choice. + This post-processor uses an artifact from the `vmware-iso` builder with an ESXi host or an + artifact from the vSphere post-processor. It then marks the virtual machine as a template and + moves it to your specified path. page_title: vSphere Template - Post-Processors sidebar_title: vSphere Template --- @@ -13,64 +12,34 @@ sidebar_title: vSphere Template Type: `vsphere-template` Artifact BuilderId: `packer.post-processor.vsphere` -The Packer vSphere Template post-processor takes an artifact from the -`vmware-iso` builder, built on ESXi (i.e. remote) or an artifact from the -[vSphere](/packer/plugins/post-processors/vsphere/vsphere) post-processor, marks the VM as a -template, and places it in the path of your choice. +This post-processor uses an artifact from the `vmware-iso` builder with an ESXi host or an artifact +from the [vSphere](/packer/plugins/post-processors/vsphere/vsphere) post-processor. It then marks +the virtual machine as a template and moves it to your specified path. ## Configuration -There are many configuration options available for the post-processor. They are -provided below in two categories: required and optional parameters. +The following configuration options are available for the post-processor. Required: -- `host` (string) - The vSphere endpoint that contains the VM built by the - `vmware-iso` builder. - -- `password` (string) - Password to use to authenticate to the vSphere - endpoint. - -- `username` (string) - The username to use to authenticate to the vSphere - endpoint. +@include 'post-processor/vsphere-template/Config-required.mdx' Optional: -- `datacenter` (string) - If you have more than one, you will need to specify - which one the ESXi host used. - -- `folder` (string) - Target path where the template will be created. - -- `insecure` (boolean) - If `true`, skips the verification of the server - certificate. Default is `false`. - -- `keep_input_artifact` (boolean) - Unlike most post-processors, this option - has no effect for `vsphere-template`. This is because in order for a template - to work, you can't delete the vm that you generate the template from. The - vSphere Template post-processor will therefore always preserve the original - VM. +@include 'post-processor/vsphere-template/Config-not-required.mdx' -- `snapshot_enable` (boolean) - Create a snapshot before marking as a - template. Default is `false`. +- `keep_input_artifact` (boolean) - This option is not applicable to `vsphere-template`. For a + template to function, the original virtual machine from which it was generated cannot be deleted. + Therefore, the vSphere Template post-processor always preserves the original virtual machine. -- `snapshot_name` (string) - Name for the snapshot. Required when - `snapshot_enable` is `true`. + ~> **Note**: If you are getting permission denied errors when trying to mark as a template, but it + works in the vSphere UI, set this to `false`. Default is `true`. -- `snapshot_description` (string) - Description for the snapshot. Required - when `snapshot_enable` is `true`. - -- `reregister_vm` (boolean) - Use the method of unregister VM and reregister - as a template, rather than using the `markAsTemplate` method. - - NOTE: If you are getting permission denied errors when trying to mark as a - template, but it works fine in the vSphere UI, try setting this to `false`. - Default is `true`. - -## Example +## Example Usage An example is shown below, showing only the post-processor configuration: -**HCL2** +In HCL2: ```hcl source "null" "example" { @@ -88,15 +57,14 @@ build { insecure = false username = "administrator@vsphere.local" password = "VMw@re1!" - datacenter = "dc-01" + datacenter = "dc-01" folder = "/templates/os/distro" } } } ``` - -**JSON** +In JSON: ```json { @@ -122,17 +90,13 @@ build { } ``` +## Using the vSphere Template with Local Builders -## Using the vSphere Template with local builders +Once the [vSphere](/packer/plugins/post-processors/vsphere/vsphere) post-processor takes an artifact +from the builder and uploads it to a vSphere endpoint, you may want the virtual machine to be marked +as a template. -Once the [vSphere](/packer/plugins/post-processors/vsphere/vsphere) post-processor takes an -artifact from the builder and uploads it to a vSphere endpoint, you may want -the VM to be marked as a template. Packer can do this for you automatically -using a sequence definition (a collection of post-processors that are treated -as as single pipeline, see [Post-Processors](/packer/docs/templates/legacy_json_templates/post-processors) -for more information): - -**HCL2** +In HCL2: ```hcl build { @@ -152,8 +116,7 @@ build { } ``` - -**JSON** +In JSON: ```json { @@ -180,63 +143,57 @@ build { } ] } - - ``` - -In the example above, the result of each builder is passed through the defined -sequence of post-processors starting with the `vsphere` post-processor which -will upload the artifact to a vSphere endpoint. The resulting artifact is then -passed on to the `vsphere-template` post-processor which handles marking a VM -as a template. In JSON, note that the `vsphere` and `vsphere-template` +In the example above, the result of each builder is passed through the defined sequence of +post-processors starting with the `vsphere` post-processor which will upload the artifact to a +vSphere endpoint. The resulting artifact is then passed on to the `vsphere-template` post-processor +which handles marking a VM as a template. In JSON, note that the `vsphere` and `vsphere-template` post-processors can be paired together in their own array. ## Permissions -The vSphere post processor needs several privileges to be able to mark the -vm as a template. Rather than giving full administrator access, you can create -a role to give the post-processor the privileges necessary to run. Here is an -example role that will work. Please note that this is a user-supplied list so -there may be a few extraneous privilegess that are not strictly required. +The post processor needs several privileges to be able to mark the virtual as a template. + +Rather than giving full administrator access, you can create a role to give the post-processor the +privileges necessary to run. + +Below is an example role that will work. Please note that this is a user-supplied list so there may +be a few extraneous privileges that are not strictly required. For vSphere, the role needs the following privileges: - `Datastore.AllocateSpace` - `Host.Config.AdvancedConfig` - `Host.Config.NetService` - `Host.Config.Network` - `Network.Assign` - `System.Anonymous` - `System.Read` - `System.View` - `VApp.Import` - `VirtualMachine.Config.AddNewDisk` - `VirtualMachine.Config.AdvancedConfig` - `VirtualMachine.Inventory.Delete` +- `Datastore.AllocateSpace` +- `Host.Config.AdvancedConfig` +- `Host.Config.NetService` +- `Host.Config.Network` +- `Network.Assign` +- `System.Anonymous` +- `System.Read` +- `System.View` +- `VApp.Import` +- `VirtualMachine.Config.AddNewDisk` +- `VirtualMachine.Config.AdvancedConfig` +- `VirtualMachine.Inventory.Delete` -and either (if `reregister_vm` is `false`): + and either (if `reregister_vm` is `false`): - `VirtualMachine.Provisioning.MarkAsTemplate` + - `VirtualMachine.Provisioning.MarkAsTemplate` -or (if `reregister_vm` is `true` or unset): + or (if `reregister_vm` is `true` or unset): - `VirtualMachine.Inventory.Register` - `VirtualMachine.Inventory.Unregister` + - `VirtualMachine.Inventory.Register` + - `VirtualMachine.Inventory.Unregister` The role must be authorized on the: - `Cluster of the host` - `The destination folder` - `The destination datastore` - `The network to be assigned` +- Cluster of the host. +- The destination folder. +- The destination datastore. +- The network to be assigned. # Troubleshooting -Some users have reported that vSphere templates created from local vSphere builds -get their boot order reset to cdrom only instead of the original boot order -defined by the template. If this issue affects you, the solution is to set -`"bios.hddOrder": "scsi0:0"` in your builder's `vmx_data`. - -Packer doesn't automatically do this for you because it causes strange upload -behavior in certain versions of `ovftool`. +Some users have reported that vSphere templates created from local vSphere builds get their boot +order reset to CD-ROM only instead of the original boot order defined by the template. If this issue +affects you, the solution is to set `"bios.hddOrder": "scsi0:0"` in your builder's `vmx_data`. diff --git a/post-processor/vsphere-template/post-processor.go b/post-processor/vsphere-template/post-processor.go index b6120e16e..5f02ecb54 100644 --- a/post-processor/vsphere-template/post-processor.go +++ b/post-processor/vsphere-template/post-processor.go @@ -1,13 +1,13 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 +//go:generate packer-sdc struct-markdown //go:generate packer-sdc mapstructure-to-hcl2 -type Config package vsphere_template import ( "context" - "errors" "fmt" "net/url" "time" @@ -25,9 +25,7 @@ import ( ) const ( - // BuilderId for the local artifacts - BuilderIdESX = "mitchellh.vmware-esx" - + BuilderIdESX = "mitchellh.vmware-esx" ArtifactConfFormat = "artifact.conf.format" ArtifactConfKeepRegistered = "artifact.conf.keep_registered" ArtifactConfSkipExport = "artifact.conf.skip_export" @@ -42,16 +40,27 @@ var builtins = map[string]string{ type Config struct { common.PackerConfig `mapstructure:",squash"` - Host string `mapstructure:"host"` - Insecure bool `mapstructure:"insecure"` - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` - Datacenter string `mapstructure:"datacenter"` - Folder string `mapstructure:"folder"` - SnapshotEnable bool `mapstructure:"snapshot_enable"` - SnapshotName string `mapstructure:"snapshot_name"` - SnapshotDescription string `mapstructure:"snapshot_description"` - ReregisterVM config.Trilean `mapstructure:"reregister_vm"` + // Specifies the fully qualified domain name or IP address of the vSphere endpoint. + Host string `mapstructure:"host" required:"true"` + // Specifies the username to use to authenticate to the vSphere endpoint. + Username string `mapstructure:"username" required:"true"` + // Specifies the password to use to authenticate to the vSphere endpoint. + Password string `mapstructure:"password" required:"true"` + // Specifies whether to skip the verification of the server certificate. Defaults to `false`. + Insecure bool `mapstructure:"insecure"` + // Specifies the name of the datacenter to use. + // Required when the vCenter Server instance endpoint has more than one datacenter. + Datacenter string `mapstructure:"datacenter"` + // Specifies the name of the virtual machine folder path where the template will be created. + Folder string `mapstructure:"folder"` + // Specifies whether to create a snapshot before marking as a template. Defaults to `false`.\ + SnapshotEnable bool `mapstructure:"snapshot_enable"` + // Specifies the name of the snapshot. Required when `snapshot_enable` is `true`. + SnapshotName string `mapstructure:"snapshot_name"` + // Specifies a description for the snapshot. Required when `snapshot_enable` is `true`. + SnapshotDescription string `mapstructure:"snapshot_description"` + // Specifies to keep the virtual machine registered after marking as a template. + ReregisterVM config.Trilean `mapstructure:"reregister_vm"` ctx interpolate.Context } @@ -61,7 +70,9 @@ type PostProcessor struct { url *url.URL } -func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { + return p.config.FlatMapstructure().HCL2Spec() +} func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ @@ -87,14 +98,14 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { for key, ptr := range vc { if *ptr == "" { errs = packersdk.MultiErrorAppend( - errs, fmt.Errorf("%s must be set", key)) + errs, fmt.Errorf("error: %s must be set", key)) } } sdk, err := url.Parse(fmt.Sprintf("https://%v/sdk", p.config.Host)) if err != nil { errs = packersdk.MultiErrorAppend( - errs, fmt.Errorf("Error invalid vSphere sdk endpoint: %s", err)) + errs, fmt.Errorf("error using endpoint: %s", err)) return errs } @@ -108,28 +119,29 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, bool, error) { + // Check if the artifact is supported by the post-processor. if _, ok := builtins[artifact.BuilderId()]; !ok { - return nil, false, false, fmt.Errorf("The Packer vSphere Template post-processor "+ - "can only take an artifact from the VMware-iso builder, built on "+ - "ESXi (i.e. remote) or an artifact from the vSphere post-processor. "+ - "Artifact type %s does not fit this requirement", artifact.BuilderId()) + return nil, false, false, fmt.Errorf( + "error: unsupported artifact type %s. supported types: vmware-iso (ESXi) or vSphere post-processor", artifact.BuilderId()) } f := artifact.State(ArtifactConfFormat) k := artifact.State(ArtifactConfKeepRegistered) s := artifact.State(ArtifactConfSkipExport) + // Validate artifact configuration for export if f != "" && k != "true" && s == "false" { - return nil, false, false, errors.New("To use this post-processor with exporting behavior you need set keep_registered as true") + return nil, false, false, fmt.Errorf("error: `keep_registered` must be set to `true` for export") } - // In some occasions the VM state is powered on and if we immediately try to mark as template - // (after the ESXi creates it) it will fail. If vSphere is given a few seconds this behavior doesn't reappear. - ui.Message("Waiting 10s for VMware vSphere to start") + // If the virtual machine is still powered on and immediately marked as a template it will fail. + // Pause for a few seconds to allow the virtual machine to prepare for the next step. + + ui.Message("Pausing momentarily to prepare for the next step...") time.Sleep(10 * time.Second) c, err := govmomi.NewClient(context.Background(), p.url, p.config.Insecure) if err != nil { - return nil, false, false, fmt.Errorf("Error connecting to vSphere: %s", err) + return nil, false, false, fmt.Errorf("error connecting to vsphere endpoint: %s", err) } defer p.Logout(c) diff --git a/post-processor/vsphere-template/post-processor.hcl2spec.go b/post-processor/vsphere-template/post-processor.hcl2spec.go index 6ddf98c66..3a0c3e910 100644 --- a/post-processor/vsphere-template/post-processor.hcl2spec.go +++ b/post-processor/vsphere-template/post-processor.hcl2spec.go @@ -18,10 +18,10 @@ type FlatConfig struct { 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"` - Host *string `mapstructure:"host" cty:"host" hcl:"host"` + Host *string `mapstructure:"host" required:"true" cty:"host" hcl:"host"` + Username *string `mapstructure:"username" required:"true" cty:"username" hcl:"username"` + Password *string `mapstructure:"password" required:"true" cty:"password" hcl:"password"` Insecure *bool `mapstructure:"insecure" cty:"insecure" hcl:"insecure"` - Username *string `mapstructure:"username" cty:"username" hcl:"username"` - Password *string `mapstructure:"password" cty:"password" hcl:"password"` Datacenter *string `mapstructure:"datacenter" cty:"datacenter" hcl:"datacenter"` Folder *string `mapstructure:"folder" cty:"folder" hcl:"folder"` SnapshotEnable *bool `mapstructure:"snapshot_enable" cty:"snapshot_enable" hcl:"snapshot_enable"` @@ -51,9 +51,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, "host": &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false}, - "insecure": &hcldec.AttrSpec{Name: "insecure", Type: cty.Bool, Required: false}, "username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false}, "password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false}, + "insecure": &hcldec.AttrSpec{Name: "insecure", Type: cty.Bool, Required: false}, "datacenter": &hcldec.AttrSpec{Name: "datacenter", Type: cty.String, Required: false}, "folder": &hcldec.AttrSpec{Name: "folder", Type: cty.String, Required: false}, "snapshot_enable": &hcldec.AttrSpec{Name: "snapshot_enable", Type: cty.Bool, Required: false}, diff --git a/post-processor/vsphere-template/post-processor_test.go b/post-processor/vsphere-template/post-processor_test.go index 80674cf47..3a1b19a7b 100644 --- a/post-processor/vsphere-template/post-processor_test.go +++ b/post-processor/vsphere-template/post-processor_test.go @@ -9,9 +9,9 @@ import ( func getTestConfig() Config { return Config{ - Username: "me", - Password: "notpassword", - Host: "myhost", + Username: "administrator@vsphere.local", + Password: "password", + Host: "vcenter.example.com", } } @@ -22,7 +22,7 @@ func TestConfigure_Good(t *testing.T) { err := p.Configure(config) if err != nil { - t.Errorf("Error: %s", err) + t.Errorf("error: %s", err) } } @@ -33,10 +33,10 @@ func TestConfigure_ReRegisterVM(t *testing.T) { err := p.Configure(config) if err != nil { - t.Errorf("Error: %s", err) + t.Errorf("error: %s", err) } if p.config.ReregisterVM.False() { - t.Errorf("This should default to unset, not false.") + t.Errorf(("error: should be unset, not false")) } } diff --git a/post-processor/vsphere-template/step_choose_datacenter.go b/post-processor/vsphere-template/step_choose_datacenter.go index 062777639..6b0945e0b 100644 --- a/post-processor/vsphere-template/step_choose_datacenter.go +++ b/post-processor/vsphere-template/step_choose_datacenter.go @@ -5,6 +5,7 @@ package vsphere_template import ( "context" + "fmt" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" @@ -23,8 +24,10 @@ func (s *stepChooseDatacenter) Run(ctx context.Context, state multistep.StateBag ui.Message("Choosing datacenter...") + // Find the datacenter or use the default one if not specified. dc, err := finder.DatacenterOrDefault(context.Background(), s.Datacenter) if err != nil { + err = fmt.Errorf("error finding datacenter %s: %s", s.Datacenter, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt diff --git a/post-processor/vsphere-template/step_create_folder.go b/post-processor/vsphere-template/step_create_folder.go index 7e7e4f716..721307a79 100644 --- a/post-processor/vsphere-template/step_create_folder.go +++ b/post-processor/vsphere-template/step_create_folder.go @@ -23,7 +23,7 @@ func (s *stepCreateFolder) Run(ctx context.Context, state multistep.StateBag) mu cli := state.Get("client").(*govmomi.Client) dcPath := state.Get("dcPath").(string) - ui.Message("Creating or checking destination folders...") + ui.Message("Creating or checking destination folder...") base := path.Join(dcPath, "vm") fullPath := path.Join(base, s.Folder) @@ -33,9 +33,8 @@ func (s *stepCreateFolder) Run(ctx context.Context, state multistep.StateBag) mu var err error var ref object.Reference - // We iterate over the path starting with full path - // If we don't find it, we save the folder name and continue with the previous path - // The iteration ends when we find an existing path otherwise it throws error + // Iterate over the path, saving non-existent folders. + // Stop when an existing path is found; error if no existing path is found. for { ref, err = si.FindByInventoryPath(context.Background(), fullPath) if err != nil { @@ -49,7 +48,7 @@ func (s *stepCreateFolder) Run(ctx context.Context, state multistep.StateBag) mu fullPath = path.Clean(dir) if fullPath == dcPath { - err = fmt.Errorf("vSphere base path %s not found", base) + err = fmt.Errorf("error finding base path %s", base) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt @@ -63,7 +62,7 @@ func (s *stepCreateFolder) Run(ctx context.Context, state multistep.StateBag) mu if root, ok := ref.(*object.Folder); ok { for i := len(folders) - 1; i >= 0; i-- { - ui.Message(fmt.Sprintf("Creating folder: %v", folders[i])) + ui.Message(fmt.Sprintf("Creating virtual machine folder %v...", folders[i])) root, err = root.CreateFolder(context.Background(), folders[i]) if err != nil { @@ -77,7 +76,7 @@ func (s *stepCreateFolder) Run(ctx context.Context, state multistep.StateBag) mu root.SetInventoryPath(fullPath) state.Put("folder", root) } else { - err = fmt.Errorf("folder not found: '%v'", ref) + err = fmt.Errorf("error finding virtual machine folder at path %v", ref) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt diff --git a/post-processor/vsphere-template/step_create_snapshot.go b/post-processor/vsphere-template/step_create_snapshot.go index 4864521ed..22fcd4deb 100644 --- a/post-processor/vsphere-template/step_create_snapshot.go +++ b/post-processor/vsphere-template/step_create_snapshot.go @@ -22,6 +22,7 @@ type stepCreateSnapshot struct { } func NewStepCreateSnapshot(artifact packersdk.Artifact, p *PostProcessor) *stepCreateSnapshot { + // Set the default folder. remoteFolder := "Discovered virtual machine" vmname := artifact.Id() @@ -49,10 +50,9 @@ func (s *stepCreateSnapshot) Run(ctx context.Context, state multistep.StateBag) return multistep.ActionContinue } - ui.Message("Creating a Snapshot...") + ui.Message("Creating virtual machine snapshot...") vm, err := findRuntimeVM(cli, dcPath, s.VMName, s.RemoteFolder) - if err != nil { state.Put("error", err) ui.Error(err.Error()) @@ -60,7 +60,6 @@ func (s *stepCreateSnapshot) Run(ctx context.Context, state multistep.StateBag) } task, err := vm.CreateSnapshot(context.Background(), s.SnapshotName, s.SnapshotDescription, false, false) - if err != nil { state.Put("error", err) ui.Error(err.Error()) diff --git a/post-processor/vsphere-template/step_mark_as_template.go b/post-processor/vsphere-template/step_mark_as_template.go index 12c53496d..ff9711761 100644 --- a/post-processor/vsphere-template/step_mark_as_template.go +++ b/post-processor/vsphere-template/step_mark_as_template.go @@ -26,11 +26,14 @@ type stepMarkAsTemplate struct { } func NewStepMarkAsTemplate(artifact packersdk.Artifact, p *PostProcessor) *stepMarkAsTemplate { + // Set the default folder. remoteFolder := "Discovered virtual machine" - //if post-processor config folder is not null, use the folder as remoteFolder + + // If the post-processor configuration's folder is defined, use it as the `remoteFolder`. if p.config.Folder != "" { remoteFolder = p.config.Folder } + vmname := artifact.Id() if artifact.BuilderId() == vsphere.BuilderId { @@ -59,7 +62,7 @@ func (s *stepMarkAsTemplate) Run(ctx context.Context, state multistep.StateBag) return multistep.ActionHalt } - // Use a simple "MarkAsTemplate" method unless `reregister_vm` is true + // Use the MarkAsTemplate method unless the `reregister_vm` is set to `true`. if s.ReregisterVM.False() { ui.Message("Marking as a template...") @@ -71,7 +74,7 @@ func (s *stepMarkAsTemplate) Run(ctx context.Context, state multistep.StateBag) return multistep.ActionContinue } - ui.Message("Re-register VM as a template...") + ui.Message("Registering virtual machine as a template...") dsPath, err := datastorePath(vm) if err != nil { @@ -132,11 +135,10 @@ func datastorePath(vm *object.VirtualMachine) (*object.DatastorePath, error) { } if disk == "" { - return nil, fmt.Errorf("disk not found in '%v'", vm.Name()) + return nil, fmt.Errorf("error finding disk in '%v'", vm.Name()) } re := regexp.MustCompile(`\[(.*?)\]`) - datastore := re.FindStringSubmatch(disk)[1] vmxPath := path.Join("/", path.Dir(strings.Split(disk, " ")[1]), vm.Name()+".vmx") @@ -156,19 +158,16 @@ func findRuntimeVM(cli *govmomi.Client, dcPath, name, remoteFolder string) (*obj } if ref == nil { - return nil, fmt.Errorf("VM at path %s not found", fullPath) + return nil, fmt.Errorf("error finding virtual machine at path %s", fullPath) } vm := ref.(*object.VirtualMachine) if vm.InventoryPath == "" { vm.SetInventoryPath(fullPath) } - return vm, nil } -// If in the target folder a virtual machine or template already exists -// it will be removed to maintain consistency func unregisterPreviousVM(cli *govmomi.Client, folder *object.Folder, name string) error { si := object.NewSearchIndex(cli.Client) fullPath := path.Join(folder.InventoryPath, name) @@ -182,11 +181,9 @@ func unregisterPreviousVM(cli *govmomi.Client, folder *object.Folder, name strin if vm, ok := ref.(*object.VirtualMachine); ok { return vm.Unregister(context.Background()) } else { - return fmt.Errorf("an object name '%v' already exists", name) + return fmt.Errorf("object name '%v' already exists", name) } - } - return nil }