Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8de1eddcb2 | |||
| 9df4129d6d | |||
| ab93bc8a5d | |||
| e90913fcd2 | |||
| b1fec8f0bc | |||
| ed1a2b1deb | |||
| 903deb9e6a | |||
| bddf3b03f7 | |||
| e96cc07a1a | |||
| 79bc643c17 | |||
| 10e74961d2 | |||
| bdf198594e | |||
| a25f057984 | |||
| 161879b98a | |||
| 652878059c | |||
| eb4069a1b7 | |||
| 91c5a4613c | |||
| 9eaa2e17f7 | |||
| 24dbd0a28b | |||
| 05aecc56ea | |||
| f4556fd3f1 | |||
| 58a0bdd780 | |||
| 03a374f4b6 | |||
| 804fefef17 | |||
| f578b93f7e | |||
| 0df2e15d9f | |||
| 0ad26cce01 | |||
| 281af9a03d | |||
| ecd1a49a35 | |||
| e4f975fae1 | |||
| 942bfbf221 | |||
| cf622346ab | |||
| e3ac7de965 | |||
| 576e227e60 | |||
| 4565119694 | |||
| bd0cb85bb6 | |||
| a56acabe23 | |||
| 10f34a3b12 | |||
| c35837ee49 | |||
| c4b039391a | |||
| 1c5bc41beb | |||
| bbc3a5b0d1 | |||
| 67cd123d1c | |||
| e4a37c37b9 | |||
| 0e2a3e1058 | |||
| 0f00709fb6 | |||
| 279e44e51d | |||
| fe94fae2b2 | |||
| cf1a39a4e8 | |||
| df4ce6fd34 | |||
| 60d124dcaf | |||
| b6e277fb05 | |||
| 7e81e3fbda | |||
| a6d5106cd7 | |||
| cd60f32866 | |||
| e9b526ee2d | |||
| b90957d11c | |||
| 0113aae27d | |||
| 4dff73cec2 | |||
| 0e388db795 | |||
| 3506b8876f | |||
| 0bcf4f2613 | |||
| 33f391ae37 | |||
| 20472bc12f | |||
| f4a2838716 | |||
| 7cb17f64a6 | |||
| 0d0bd9ce75 | |||
| 5ba134ac5b | |||
| 9f2cb0d560 | |||
| a0c09e85df | |||
| 1252658848 | |||
| 759ae006df |
+29
-1
@@ -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:
|
### IMPROVEMENTS:
|
||||||
* builder/amazon: Add all of the custom AWS template engines to `build`
|
* 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/azure: Add FreeBSD support to azure/chroot builder. [GH-9697]
|
||||||
* builder/vmware-esx: Add `network_name` option to vmware so that users can set
|
* builder/vmware-esx: Add `network_name` option to vmware so that users can set
|
||||||
a network without using vmx data. [GH-9718]
|
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
|
* builder/vmware: Add a `remote_output_directory option` so users can tell
|
||||||
Packer where on a datastore to create a vm. [GH-9784]
|
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/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.
|
* builder/vsphere: Add new `directory_permission` config export option.
|
||||||
[GH-9704]
|
[GH-9704]
|
||||||
* builder/vsphere: Add option to import OVF templates to the Content Library
|
* builder/vsphere: Add option to import OVF templates to the Content Library
|
||||||
[GH-9755]
|
[GH-9755]
|
||||||
* builder/vsphere: Add step and options to customize cloned VMs. [GH-9665]
|
* 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 provisioner "override" option to HCL2 templates. [GH-9764]
|
||||||
* core/hcl: Add vault integration as an HCL2 function function. [GH-9746]
|
* 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
|
* core: Add colored prefix to progress bar so it's clearer what build each
|
||||||
@@ -27,6 +44,8 @@
|
|||||||
[GH-9773]
|
[GH-9773]
|
||||||
* post-processor/vsphere: Improve UI to catch bad credentials and print errors.
|
* post-processor/vsphere: Improve UI to catch bad credentials and print errors.
|
||||||
[GH-9649]
|
[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`
|
* provisioner/file: Clean up, bugfix, and document previously-hidden `sources`
|
||||||
option. [GH-9725] [GH-9735]
|
option. [GH-9725] [GH-9735]
|
||||||
* provisioner/salt-masterless: Add option to option to download community
|
* provisioner/salt-masterless: Add option to option to download community
|
||||||
@@ -39,6 +58,11 @@
|
|||||||
binaries. [GH-9706]
|
binaries. [GH-9706]
|
||||||
* builder/amazon-ebssurrogate: Make skip_save_build_region option work in the
|
* builder/amazon-ebssurrogate: Make skip_save_build_region option work in the
|
||||||
ebssurrogate builder, not just the ebs builder. [GH-9666]
|
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.
|
* builder/file: Make sure that UploadDir receives the interpolated destination.
|
||||||
[GH-9698]
|
[GH-9698]
|
||||||
* builder/googlecompute: Fix bug where startup script hang would cause export
|
* builder/googlecompute: Fix bug where startup script hang would cause export
|
||||||
@@ -61,6 +85,10 @@
|
|||||||
interpolation. [GH-9673]
|
interpolation. [GH-9673]
|
||||||
* post-processor/vsphere-template: Fix ReregisterVM to default to true instead
|
* post-processor/vsphere-template: Fix ReregisterVM to default to true instead
|
||||||
of false. [GH-9736]
|
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)
|
## 1.6.1 (July 30, 2020)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -49,8 +49,8 @@
|
|||||||
/builder/proxmox/ @carlpett
|
/builder/proxmox/ @carlpett
|
||||||
/website/pages/docs/builders/proxmox* @carlpett
|
/website/pages/docs/builders/proxmox* @carlpett
|
||||||
|
|
||||||
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
/builder/scaleway/ @scaleway/devtools
|
||||||
/website/pages/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
/website/pages/docs/builders/scaleway* @scaleway/devtools
|
||||||
|
|
||||||
/builder/hcloud/ @LKaemmerling
|
/builder/hcloud/ @LKaemmerling
|
||||||
/website/pages/docs/builders/hcloud* @LKaemmerling
|
/website/pages/docs/builders/hcloud* @LKaemmerling
|
||||||
|
|||||||
@@ -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 \
|
.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
|
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
|
ci: testrace ## Test in continuous integration
|
||||||
|
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ type VpcFilterOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Statement struct {
|
type Statement struct {
|
||||||
Effect string
|
Effect string `mapstructure:"Effect" required:"false"`
|
||||||
Action []string
|
Action []string `mapstructure:"Action" required:"false"`
|
||||||
Resource []string
|
Resource []string `mapstructure:"Resource" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PolicyDocument struct {
|
type PolicyDocument struct {
|
||||||
Version string
|
Version string `mapstructure:"Version" required:"false"`
|
||||||
Statement []Statement
|
Statement []Statement `mapstructure:"Statement" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecurityGroupFilterOptions struct {
|
type SecurityGroupFilterOptions struct {
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
|
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatPolicyDocument struct {
|
type FlatPolicyDocument struct {
|
||||||
Version *string `cty:"version" hcl:"version"`
|
Version *string `mapstructure:"Version" required:"false" cty:"Version" hcl:"Version"`
|
||||||
Statement []FlatStatement `cty:"statement" hcl:"statement"`
|
Statement []FlatStatement `mapstructure:"Statement" required:"false" cty:"Statement" hcl:"Statement"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatPolicyDocument.
|
// 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.
|
// The decoded values from this spec will then be applied to a FlatPolicyDocument.
|
||||||
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
|
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
|
||||||
s := map[string]hcldec.Spec{
|
s := map[string]hcldec.Spec{
|
||||||
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
"Version": &hcldec.AttrSpec{Name: "Version", Type: cty.String, Required: false},
|
||||||
"statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
|
"Statement": &hcldec.BlockListSpec{TypeName: "Statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -89,9 +89,9 @@ func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
// FlatStatement is an auto-generated flat version of Statement.
|
// FlatStatement is an auto-generated flat version of Statement.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatStatement struct {
|
type FlatStatement struct {
|
||||||
Effect *string `cty:"effect" hcl:"effect"`
|
Effect *string `mapstructure:"Effect" required:"false" cty:"Effect" hcl:"Effect"`
|
||||||
Action []string `cty:"action" hcl:"action"`
|
Action []string `mapstructure:"Action" required:"false" cty:"Action" hcl:"Action"`
|
||||||
Resource []string `cty:"resource" hcl:"resource"`
|
Resource []string `mapstructure:"Resource" required:"false" cty:"Resource" hcl:"Resource"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatStatement.
|
// 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.
|
// The decoded values from this spec will then be applied to a FlatStatement.
|
||||||
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
|
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
|
||||||
s := map[string]hcldec.Spec{
|
s := map[string]hcldec.Spec{
|
||||||
"effect": &hcldec.AttrSpec{Name: "effect", Type: 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},
|
"Action": &hcldec.AttrSpec{Name: "Action", Type: cty.List(cty.String), Required: false},
|
||||||
"resource": &hcldec.AttrSpec{Name: "resource", Type: cty.List(cty.String), Required: false},
|
"Resource": &hcldec.AttrSpec{Name: "Resource", Type: cty.List(cty.String), Required: false},
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"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/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/packer/common/random"
|
"github.com/hashicorp/packer/common/random"
|
||||||
"github.com/hashicorp/packer/common/retry"
|
"github.com/hashicorp/packer/common/retry"
|
||||||
@@ -278,23 +280,39 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
|||||||
Type: aws.String("instant"),
|
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.
|
// 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 err != nil {
|
||||||
if createOutput.FleetId != nil {
|
if createOutput.FleetId != nil {
|
||||||
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
|
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Error waiting for fleet request: %s", err)
|
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
|
// 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
|
// zones doesn't have one of the allowed instance types; as long as
|
||||||
// an instance is launched, these errors aren't important.
|
// 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())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceId = *createOutput.Instances[0].InstanceIds[0]
|
instanceId = *createOutput.Instances[0].InstanceIds[0]
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
errs, err)
|
errs, err)
|
||||||
}
|
}
|
||||||
for _, dc := range dcs {
|
for _, dc := range dcs {
|
||||||
if strings.ToLower(dc.CountryCode) == strings.ToLower(c.DataCenterName) {
|
if strings.EqualFold(dc.CountryCode, c.DataCenterName) {
|
||||||
c.DataCenterId = dc.Id
|
c.DataCenterId = dc.Id
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ type Config struct {
|
|||||||
|
|
||||||
// Instance
|
// 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
|
// Metadata optionally contains custom metadata key/value pairs provided in the
|
||||||
// configuration. While this can be used to set metadata["user_data"] the explicit
|
// configuration. While this can be used to set metadata["user_data"] the explicit
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ type FlatConfig struct {
|
|||||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
||||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
||||||
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_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"`
|
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
|
||||||
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
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"`
|
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},
|
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", 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_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},
|
"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": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
|||||||
instanceDetails := core.LaunchInstanceDetails{
|
instanceDetails := core.LaunchInstanceDetails{
|
||||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
||||||
CompartmentId: &d.cfg.CompartmentID,
|
CompartmentId: &d.cfg.CompartmentID,
|
||||||
|
DefinedTags: d.cfg.InstanceDefinedTags,
|
||||||
|
FreeformTags: d.cfg.InstanceTags,
|
||||||
ImageId: &d.cfg.BaseImageID,
|
ImageId: &d.cfg.BaseImageID,
|
||||||
Shape: &d.cfg.Shape,
|
Shape: &d.cfg.Shape,
|
||||||
SubnetId: &d.cfg.SubnetID,
|
SubnetId: &d.cfg.SubnetID,
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui
|
|||||||
if i != "" {
|
if i != "" {
|
||||||
alias = i
|
alias = i
|
||||||
}
|
}
|
||||||
if alias != "" && strings.ToLower(alias) == strings.ToLower(imageAlias) {
|
if alias != "" && strings.EqualFold(alias, imageAlias) {
|
||||||
return alias
|
return alias
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,21 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
ResultKey: downloadPathKey,
|
ResultKey: downloadPathKey,
|
||||||
TargetPath: b.config.TargetPath,
|
TargetPath: b.config.TargetPath,
|
||||||
Url: b.config.ISOUrls,
|
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{},
|
&stepUploadISO{},
|
||||||
|
&stepUploadAdditionalISOs{},
|
||||||
&stepStartVM{},
|
&stepStartVM{},
|
||||||
&common.StepHTTPServer{
|
&common.StepHTTPServer{
|
||||||
HTTPDir: b.config.HTTPDir,
|
HTTPDir: b.config.HTTPDir,
|
||||||
@@ -96,7 +109,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
&stepConvertToTemplate{},
|
&stepConvertToTemplate{},
|
||||||
&stepFinalizeTemplateConfig{},
|
&stepFinalizeTemplateConfig{},
|
||||||
&stepSuccess{},
|
&stepSuccess{},
|
||||||
}
|
)
|
||||||
|
|
||||||
// Run the steps
|
// Run the steps
|
||||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner.Run(ctx, state)
|
b.runner.Run(ctx, state)
|
||||||
|
|||||||
@@ -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
|
package proxmox
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -64,6 +65,8 @@ type Config struct {
|
|||||||
|
|
||||||
shouldUploadISO bool
|
shouldUploadISO bool
|
||||||
|
|
||||||
|
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +90,15 @@ type vgaConfig struct {
|
|||||||
Type string `mapstructure:"type"`
|
Type string `mapstructure:"type"`
|
||||||
Memory int `mapstructure:"memory"`
|
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) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
// Agent defaults to true
|
// 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))
|
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 == "" {
|
if c.SCSIController == "" {
|
||||||
log.Printf("SCSI controller not set, using default 'lsi'")
|
log.Printf("SCSI controller not set, using default 'lsi'")
|
||||||
c.SCSIController = "lsi"
|
c.SCSIController = "lsi"
|
||||||
|
|||||||
@@ -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
|
package proxmox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -103,6 +103,7 @@ type FlatConfig struct {
|
|||||||
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
|
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
|
||||||
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
|
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"`
|
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.
|
// 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},
|
"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": &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},
|
"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
|
return s
|
||||||
}
|
}
|
||||||
@@ -281,6 +283,45 @@ func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
return s
|
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.
|
// FlatvgaConfig is an auto-generated flat version of vgaConfig.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatvgaConfig struct {
|
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 {
|
if len(changes) > 0 {
|
||||||
_, err := client.SetVmConfig(vmRef, changes)
|
_, err := client.SetVmConfig(vmRef, changes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -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.
|
// instance id inside of the provisioners, used in step_provision.
|
||||||
state.Put("instance_id", vmRef)
|
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")
|
ui.Say("Starting VM")
|
||||||
_, err = client.StartVm(vmRef)
|
_, err = client.StartVm(vmRef)
|
||||||
if err != nil {
|
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) {
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package proxmox
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -15,10 +14,6 @@ import (
|
|||||||
// stepUploadISO uploads an ISO file to Proxmox so we can boot from it
|
// stepUploadISO uploads an ISO file to Proxmox so we can boot from it
|
||||||
type stepUploadISO struct{}
|
type stepUploadISO struct{}
|
||||||
|
|
||||||
type uploader interface {
|
|
||||||
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ uploader = &proxmox.Client{}
|
var _ uploader = &proxmox.Client{}
|
||||||
|
|
||||||
func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
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)
|
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename)
|
||||||
state.Put("iso_file", isoStoragePath)
|
state.Put("iso_file", isoStoragePath)
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.BootType == "" {
|
if c.BootType == "" {
|
||||||
c.BootType = "bootscript"
|
c.BootType = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs *packer.MultiError
|
var errs *packer.MultiError
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -77,6 +77,12 @@ type Driver interface {
|
|||||||
|
|
||||||
// Get the host ip address for the vm
|
// Get the host ip address for the vm
|
||||||
HostIP(multistep.StateBag) (string, error)
|
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
|
// 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)
|
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. ")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,8 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"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 {
|
func (c *DriverConfig) Validate(SkipExport bool) error {
|
||||||
if c.RemoteType == "" || SkipExport == true {
|
if SkipExport {
|
||||||
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 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that password is valid by sending a dummy ovftool command
|
if c.RemoteType != "" && c.RemotePassword == "" {
|
||||||
// now, so that we don't fail for a simple mistake after a long
|
return fmt.Errorf("exporting the vm from esxi with ovftool requires " +
|
||||||
// build
|
"that you set a value for remote_password")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -263,6 +265,68 @@ func (d *ESX5Driver) Verify() error {
|
|||||||
return nil
|
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) {
|
func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
|
||||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -699,6 +763,10 @@ func (d *ESX5Driver) Download(src, dst string) error {
|
|||||||
return d.comm.Download(d.datastorePath(src), file)
|
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
|
// VerifyChecksum checks that file on the esxi instance matches hash
|
||||||
func (d *ESX5Driver) VerifyChecksum(hash string, file string) bool {
|
func (d *ESX5Driver) VerifyChecksum(hash string, file string) bool {
|
||||||
if hash == "none" {
|
if hash == "none" {
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ type DriverMock struct {
|
|||||||
CreateDiskTypeId string
|
CreateDiskTypeId string
|
||||||
CreateDiskErr error
|
CreateDiskErr error
|
||||||
|
|
||||||
|
ExportCalled bool
|
||||||
|
ExportArgs []string
|
||||||
|
|
||||||
IsRunningCalled bool
|
IsRunningCalled bool
|
||||||
IsRunningPath string
|
IsRunningPath string
|
||||||
IsRunningResult bool
|
IsRunningResult bool
|
||||||
@@ -92,6 +95,8 @@ type DriverMock struct {
|
|||||||
|
|
||||||
VerifyCalled bool
|
VerifyCalled bool
|
||||||
VerifyErr error
|
VerifyErr error
|
||||||
|
|
||||||
|
VerifyOvftoolCalled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkMapperMock struct {
|
type NetworkMapperMock struct {
|
||||||
@@ -254,6 +259,12 @@ func (d *DriverMock) Verify() error {
|
|||||||
return d.VerifyErr
|
return d.VerifyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) Export(args []string) error {
|
||||||
|
d.ExportCalled = true
|
||||||
|
d.ExportArgs = args
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DriverMock) GetVmwareDriver() VmwareDriver {
|
func (d *DriverMock) GetVmwareDriver() VmwareDriver {
|
||||||
var state VmwareDriver
|
var state VmwareDriver
|
||||||
state.DhcpLeasesPath = func(string) string {
|
state.DhcpLeasesPath = func(string) string {
|
||||||
@@ -270,3 +281,7 @@ func (d *DriverMock) GetVmwareDriver() VmwareDriver {
|
|||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) VerifyOvfTool(_ bool, _ bool) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1196,7 +1196,7 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) {
|
|||||||
switch entry.id[0].(type) {
|
switch entry.id[0].(type) {
|
||||||
case pDeclarationHost:
|
case pDeclarationHost:
|
||||||
id := entry.id[0].(pDeclarationHost)
|
id := entry.id[0].(pDeclarationHost)
|
||||||
if strings.ToLower(id.name) == strings.ToLower(host) {
|
if strings.EqualFold(id.name, host) {
|
||||||
result = append(result, entry)
|
result = append(result, entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1237,7 +1237,7 @@ func (e NetworkMap) NameIntoDevices(name string) ([]string, error) {
|
|||||||
var devices []string
|
var devices []string
|
||||||
|
|
||||||
for _, val := range e {
|
for _, val := range e {
|
||||||
if strings.ToLower(val["name"]) == strings.ToLower(name) {
|
if strings.EqualFold(val["name"], name) {
|
||||||
devices = append(devices, val["device"])
|
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) {
|
func (e NetworkMap) DeviceIntoName(device string) (string, error) {
|
||||||
for _, val := range e {
|
for _, val := range e {
|
||||||
if strings.ToLower(val["device"]) == strings.ToLower(device) {
|
if strings.EqualFold(val["device"], device) {
|
||||||
return val["name"], nil
|
return val["name"], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,33 +10,30 @@ import (
|
|||||||
|
|
||||||
type ExportConfig struct {
|
type ExportConfig struct {
|
||||||
// Either "ovf", "ova" or "vmx", this specifies the output
|
// Either "ovf", "ova" or "vmx", this specifies the output
|
||||||
// format of the exported virtual machine. This defaults to "ovf".
|
// format of the exported virtual machine. This defaults to "ovf" for
|
||||||
// Before using this option, you need to install ovftool. This option
|
// remote (esx) builds, and "vmx" for local builds.
|
||||||
// currently only works when option remote_type is set to "esx5".
|
// Before using this option, you need to install ovftool.
|
||||||
// Since ovftool is only capable of password based authentication
|
// 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"`
|
Format string `mapstructure:"format" required:"false"`
|
||||||
// Extra options to pass to ovftool during export. Each item in the array
|
// Extra options to pass to ovftool during export. Each item in the array
|
||||||
// is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`,
|
// is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`,
|
||||||
// and `--targetType` are reserved, and should not be passed to this
|
// and `--targetType` are used by Packer for remote exports, and should not
|
||||||
// argument. Currently, exporting the build VM (with ovftool) is only
|
// be passed to this argument. For ovf/ova exports from local builds, Packer
|
||||||
// supported when building on ESXi e.g. when `remote_type` is set to
|
// does not automatically set any ovftool options.
|
||||||
// `esx5`. See the [Building on a Remote vSphere
|
|
||||||
// Hypervisor](/docs/builders/vmware-iso#building-on-a-remote-vsphere-hypervisor)
|
|
||||||
// section below for more info.
|
|
||||||
OVFToolOptions []string `mapstructure:"ovftool_options" required:"false"`
|
OVFToolOptions []string `mapstructure:"ovftool_options" required:"false"`
|
||||||
// Defaults to `false`. When enabled, Packer will not export the VM. Useful
|
// Defaults to `false`. When true, Packer will not export the VM. This can
|
||||||
// if the build output is not the resultant image, but created inside the
|
// be useful if the build output is not the resultant image, but created
|
||||||
// VM. Currently, exporting the build VM is only supported when building on
|
// inside the VM.
|
||||||
// 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.
|
|
||||||
SkipExport bool `mapstructure:"skip_export" required:"false"`
|
SkipExport bool `mapstructure:"skip_export" required:"false"`
|
||||||
// Set this to true if you would like to keep
|
// Set this to true if you would like to keep a remotely-built
|
||||||
// the VM registered with the remote ESXi server. If you do not need to export
|
// 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
|
// the vm, then also set `skip_export: true` in order to avoid unnecessarily
|
||||||
// step of using ovftool to export the vm. Defaults to false.
|
// using ovftool to export the vm. Defaults to false.
|
||||||
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
|
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
|
||||||
// VMware-created disks are defragmented and
|
// VMware-created disks are defragmented and
|
||||||
// compacted at the end of the build process using vmware-vdiskmanager or
|
// 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"))
|
errs, fmt.Errorf("format must be one of ova, ovf, or vmx"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-14
@@ -1,4 +1,4 @@
|
|||||||
package iso
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
@@ -20,11 +19,18 @@ import (
|
|||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// disk_full_paths ([]string) - The full paths to all created disks
|
// 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 {
|
func (s *StepCreateDisks) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
driver := state.Get("driver").(Driver)
|
||||||
driver := state.Get("driver").(vmwcommon.Driver)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Creating required virtual machine disks")
|
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
|
// Users can configure disks at several locations in the template so
|
||||||
// first collate all the disk requirements
|
// first collate all the disk requirements
|
||||||
var diskFullPaths, diskSizes []string
|
var diskFullPaths, diskSizes []string
|
||||||
// The 'main' or 'default' disk
|
// The 'main' or 'default' disk, only used in vmware-iso
|
||||||
diskFullPaths = append(diskFullPaths, filepath.Join(config.OutputDir, config.DiskName+".vmdk"))
|
if s.CreateMainDisk {
|
||||||
diskSizes = append(diskSizes, fmt.Sprintf("%dM", uint64(config.DiskSize)))
|
diskFullPaths = append(diskFullPaths, filepath.Join(*s.OutputDir, s.DiskName+".vmdk"))
|
||||||
|
diskSizes = append(diskSizes, fmt.Sprintf("%dM", uint64(s.MainDiskSize)))
|
||||||
|
}
|
||||||
// Additional disks
|
// Additional disks
|
||||||
if len(config.AdditionalDiskSize) > 0 {
|
if len(s.AdditionalDiskSize) > 0 {
|
||||||
for i, diskSize := range config.AdditionalDiskSize {
|
for i, diskSize := range s.AdditionalDiskSize {
|
||||||
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d.vmdk", config.DiskName, i+1))
|
path := filepath.Join(*s.OutputDir, fmt.Sprintf("%s-%d.vmdk", s.DiskName, i+1))
|
||||||
diskFullPaths = append(diskFullPaths, path)
|
diskFullPaths = append(diskFullPaths, path)
|
||||||
size := fmt.Sprintf("%dM", uint64(diskSize))
|
size := fmt.Sprintf("%dM", uint64(diskSize))
|
||||||
diskSizes = append(diskSizes, size)
|
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])
|
log.Printf("[INFO] Creating disk with Path: %s and Size: %s", diskFullPath, diskSizes[i])
|
||||||
// Additional disks currently use the same adapter type and disk
|
// Additional disks currently use the same adapter type and disk
|
||||||
// type as specified for the main 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)
|
err := fmt.Errorf("Error creating disk: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
@@ -63,4 +71,4 @@ func (stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multist
|
|||||||
return multistep.ActionContinue
|
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)
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
@@ -15,30 +13,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// This step exports a VM built on ESXi using ovftool
|
// This step exports a VM built on ESXi using ovftool
|
||||||
//
|
|
||||||
// Uses:
|
|
||||||
// display_name string
|
|
||||||
type StepExport struct {
|
type StepExport struct {
|
||||||
Format string
|
Format string
|
||||||
SkipExport bool
|
SkipExport bool
|
||||||
VMName string
|
VMName string
|
||||||
OVFToolOptions []string
|
OVFToolOptions []string
|
||||||
OutputDir string
|
OutputDir *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOVFTool() string {
|
func (s *StepExport) generateRemoteExportArgs(c *DriverConfig, displayName string, hidePassword bool, exportOutputPath string) ([]string, error) {
|
||||||
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) {
|
|
||||||
|
|
||||||
ovftool_uri := fmt.Sprintf("vi://%s/%s", c.RemoteHost, displayName)
|
ovftool_uri := fmt.Sprintf("vi://%s/%s", c.RemoteHost, displayName)
|
||||||
u, err := url.Parse(ovftool_uri)
|
u, err := url.Parse(ovftool_uri)
|
||||||
@@ -57,7 +40,15 @@ func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassw
|
|||||||
"--skipManifestCheck",
|
"--skipManifestCheck",
|
||||||
"-tt=" + s.Format,
|
"-tt=" + s.Format,
|
||||||
u.String(),
|
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
|
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 {
|
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
c := state.Get("driverConfig").(*DriverConfig)
|
c := state.Get("driverConfig").(*DriverConfig)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
|
||||||
// Skip export if requested
|
// Skip export if requested
|
||||||
if s.SkipExport {
|
if s.SkipExport {
|
||||||
@@ -72,57 +64,72 @@ func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multiste
|
|||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.RemoteType != "esx5" {
|
// load output path from state. If it doesn't exist, just use the local
|
||||||
ui.Say("Skipping export of virtual machine (export is allowed only for ESXi)...")
|
// outputdir.
|
||||||
return multistep.ActionContinue
|
exportOutputPath, ok := state.Get("export_output_path").(string)
|
||||||
|
if !ok || exportOutputPath == "" {
|
||||||
|
if *s.OutputDir != "" {
|
||||||
|
exportOutputPath = *s.OutputDir
|
||||||
|
} else {
|
||||||
|
exportOutputPath = s.VMName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ovftool := GetOVFTool()
|
err := os.MkdirAll(exportOutputPath, 0755)
|
||||||
if ovftool == "" {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error ovftool not found")
|
state.Put("error creating export directory", err)
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
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...")
|
ui.Say("Exporting virtual machine...")
|
||||||
var displayName string
|
var displayName string
|
||||||
if v, ok := state.GetOk("display_name"); ok {
|
if v, ok := state.GetOk("display_name"); ok {
|
||||||
displayName = v.(string)
|
displayName = v.(string)
|
||||||
}
|
}
|
||||||
ui_args, err := s.generateArgs(c, displayName, true)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
err := fmt.Errorf("Couldn't generate ovftool uri: %s", err)
|
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(ui_args, " ")))
|
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool,
|
||||||
var out bytes.Buffer
|
strings.Join(ui_args, " ")))
|
||||||
args, err := s.generateArgs(c, displayName, false)
|
// 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 {
|
if err != nil {
|
||||||
err := fmt.Errorf("Couldn't generate ovftool uri: %s", err)
|
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
cmd := exec.Command(ovftool, args...)
|
} else {
|
||||||
cmd.Stdout = &out
|
args, err = s.generateLocalExportArgs(exportOutputPath)
|
||||||
if err := cmd.Run(); err != nil {
|
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool,
|
||||||
err := fmt.Errorf("Error exporting virtual machine: %s\n%s\n", err, out.String())
|
strings.Join(ui_args, " ")))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
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
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,41 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStepExport_impl(t *testing.T) {
|
func TestStepExport_impl(t *testing.T) {
|
||||||
var _ multistep.Step = new(StepExport)
|
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)
|
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)
|
step := new(StepExport)
|
||||||
|
|
||||||
var config DriverConfig
|
step.SkipExport = true
|
||||||
config.RemoteType = "foo"
|
|
||||||
state.Put("driverConfig", &config)
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||||
t.Fatalf("bad action: %#v", action)
|
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")
|
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
|
// Cleanup
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepExport_wrongtype_impl(t *testing.T) {
|
func TestStepExport_localArgs(t *testing.T) {
|
||||||
testStepExport_wrongtype_impl(t, "foo")
|
// even though we aren't overriding the remote args and they are present,
|
||||||
testStepExport_wrongtype_impl(t, "")
|
// 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
|
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
|
// Setup the state bag
|
||||||
state := new(multistep.BasicStateBag)
|
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,
|
DoCleanup: b.config.DriverConfig.CleanUpRemoteCache,
|
||||||
Checksum: b.config.ISOChecksum,
|
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{},
|
&stepCreateVMX{},
|
||||||
&vmwcommon.StepConfigureVMX{
|
&vmwcommon.StepConfigureVMX{
|
||||||
CustomData: b.config.VMXData,
|
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,
|
SkipExport: b.config.SkipExport,
|
||||||
VMName: b.config.VMName,
|
VMName: b.config.VMName,
|
||||||
OVFToolOptions: b.config.OVFToolOptions,
|
OVFToolOptions: b.config.OVFToolOptions,
|
||||||
|
OutputDir: &b.config.OutputConfig.OutputDir,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
|||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
|
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
|
||||||
|
|
||||||
b = Builder{}
|
b = Builder{}
|
||||||
_, _, errs := b.Prepare(config)
|
_, _, errs := b.Prepare(config)
|
||||||
if errs == nil {
|
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) {
|
func TestBuilderPrepare_RemoteExport(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|||||||
@@ -33,55 +33,12 @@ type Config struct {
|
|||||||
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
||||||
vmwcommon.VMXConfig `mapstructure:",squash"`
|
vmwcommon.VMXConfig `mapstructure:",squash"`
|
||||||
vmwcommon.ExportConfig `mapstructure:",squash"`
|
vmwcommon.ExportConfig `mapstructure:",squash"`
|
||||||
// The size(s) of any additional
|
vmwcommon.DiskConfig `mapstructure:",squash"`
|
||||||
// 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"`
|
|
||||||
// The size of the hard disk for the VM in megabytes.
|
// The size of the hard disk for the VM in megabytes.
|
||||||
// The builder uses expandable, not fixed-size virtual hard disks, so the
|
// 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
|
// 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).
|
// is full. By default this is set to 40000 (about 40 GB).
|
||||||
DiskSize uint `mapstructure:"disk_size" required:"false"`
|
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
|
// The adapter type (or bus) that will be used
|
||||||
// by the cdrom device. This is chosen by default based on the disk adapter
|
// 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
|
// 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.FloppyConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.VNCConfig.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.ExportConfig.Prepare(&c.ctx)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)
|
||||||
if c.DiskName == "" {
|
|
||||||
c.DiskName = "disk"
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.DiskSize == 0 {
|
if c.DiskSize == 0 {
|
||||||
c.DiskSize = 40000
|
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 == "" {
|
if c.DiskTypeId == "" {
|
||||||
// Default is growable virtual disk split in 2GB files.
|
// Default is growable virtual disk split in 2GB files.
|
||||||
c.DiskTypeId = "1"
|
c.DiskTypeId = "1"
|
||||||
|
|
||||||
if c.RemoteType == "esx5" {
|
if c.RemoteType == "esx5" {
|
||||||
c.DiskTypeId = "zeroedthick"
|
c.DiskTypeId = "zeroedthick"
|
||||||
|
c.SkipCompaction = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,18 +176,19 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Format != "" {
|
if c.Format == "" {
|
||||||
if c.RemoteType != "esx5" {
|
if c.RemoteType == "" {
|
||||||
errs = packer.MultiErrorAppend(errs,
|
c.Format = "vmx"
|
||||||
fmt.Errorf("format is only valid when RemoteType=esx5"))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
c.Format = "ovf"
|
c.Format = "ovf"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") {
|
if c.RemoteType == "" && c.Format == "vmx" {
|
||||||
errs = packer.MultiErrorAppend(errs,
|
// if we're building locally and want a vmx, there's nothing to export.
|
||||||
fmt.Errorf("format must be one of ova, ovf, or vmx"))
|
// Set skip export flag here to keep the export step from attempting
|
||||||
|
// an unneded export
|
||||||
|
c.SkipExport = true
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.DriverConfig.Validate(c.SkipExport)
|
err = c.DriverConfig.Validate(c.SkipExport)
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ type FlatConfig struct {
|
|||||||
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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_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},
|
"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},
|
"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_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},
|
"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},
|
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
|
||||||
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
|
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.
|
// Set up the state.
|
||||||
state := new(multistep.BasicStateBag)
|
state := new(multistep.BasicStateBag)
|
||||||
@@ -74,6 +79,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
DoCleanup: true,
|
DoCleanup: true,
|
||||||
Checksum: "none",
|
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{
|
&StepCloneVMX{
|
||||||
Path: b.config.SourcePath,
|
Path: b.config.SourcePath,
|
||||||
OutputDir: &b.config.OutputDir,
|
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,
|
SkipExport: b.config.SkipExport,
|
||||||
VMName: b.config.VMName,
|
VMName: b.config.VMName,
|
||||||
OVFToolOptions: b.config.OVFToolOptions,
|
OVFToolOptions: b.config.OVFToolOptions,
|
||||||
|
OutputDir: &b.config.OutputConfig.OutputDir,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not have error: %s", err)
|
t.Fatalf("should not have error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||||
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type Config struct {
|
|||||||
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
||||||
vmwcommon.VMXConfig `mapstructure:",squash"`
|
vmwcommon.VMXConfig `mapstructure:",squash"`
|
||||||
vmwcommon.ExportConfig `mapstructure:",squash"`
|
vmwcommon.ExportConfig `mapstructure:",squash"`
|
||||||
|
vmwcommon.DiskConfig `mapstructure:",squash"`
|
||||||
// By default Packer creates a 'full' clone of the virtual machine
|
// By default Packer creates a 'full' clone of the virtual machine
|
||||||
// specified in source_path. The resultant virtual machine is fully
|
// specified in source_path. The resultant virtual machine is fully
|
||||||
// independant from the parent it was cloned from.
|
// 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.FloppyConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.VNCConfig.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.ExportConfig.Prepare(&c.ctx)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)
|
||||||
|
|
||||||
if c.RemoteType == "" {
|
if c.RemoteType == "" {
|
||||||
if c.SourcePath == "" {
|
if c.SourcePath == "" {
|
||||||
@@ -112,23 +114,33 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.DriverConfig.Validate(c.SkipExport)
|
if c.DiskTypeId == "" {
|
||||||
if err != nil {
|
// Default is growable virtual disk split in 2GB files.
|
||||||
errs = packer.MultiErrorAppend(errs, err)
|
c.DiskTypeId = "1"
|
||||||
|
|
||||||
|
if c.RemoteType == "esx5" {
|
||||||
|
c.DiskTypeId = "zeroedthick"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Format != "" {
|
if c.Format == "" {
|
||||||
if c.RemoteType != "esx5" {
|
if c.RemoteType == "" {
|
||||||
errs = packer.MultiErrorAppend(errs,
|
c.Format = "vmx"
|
||||||
fmt.Errorf("format is only valid when remote_type=esx5"))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
c.Format = "ovf"
|
c.Format = "ovf"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") {
|
if c.RemoteType == "" && c.Format == "vmx" {
|
||||||
errs = packer.MultiErrorAppend(errs,
|
// if we're building locally and want a vmx, there's nothing to export.
|
||||||
fmt.Errorf("format must be one of ova, ovf, or vmx"))
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnings
|
// Warnings
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ type FlatConfig struct {
|
|||||||
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export" hcl:"skip_export"`
|
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"`
|
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"`
|
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"`
|
Linked *bool `mapstructure:"linked" required:"false" cty:"linked" hcl:"linked"`
|
||||||
SourcePath *string `mapstructure:"source_path" required:"true" cty:"source_path" hcl:"source_path"`
|
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"`
|
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},
|
"skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false},
|
||||||
"keep_registered": &hcldec.AttrSpec{Name: "keep_registered", 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},
|
"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},
|
"linked": &hcldec.AttrSpec{Name: "linked", Type: cty.Bool, Required: false},
|
||||||
"source_path": &hcldec.AttrSpec{Name: "source_path", Type: cty.String, 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},
|
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -58,3 +58,120 @@ func TestNewConfig_sourcePath(t *testing.T) {
|
|||||||
warns, errs = (&Config{}).Prepare(cfg)
|
warns, errs = (&Config{}).Prepare(cfg)
|
||||||
testConfigOk(t, warns, errs)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
}
|
}
|
||||||
artifact := &common.Artifact{
|
artifact := &common.Artifact{
|
||||||
Name: b.config.VMName,
|
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")},
|
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||||
}
|
}
|
||||||
if b.config.Export != nil {
|
if b.config.Export != nil {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ type StepCloneVM struct {
|
|||||||
|
|
||||||
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
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)
|
vmPath := path.Join(s.Location.Folder, s.Location.VMName)
|
||||||
|
|
||||||
err := d.PreCleanVM(ui, vmPath, s.Force)
|
err := d.PreCleanVM(ui, vmPath, s.Force)
|
||||||
@@ -128,7 +128,7 @@ func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
|
|||||||
if st == nil {
|
if st == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
vm := st.(*driver.VirtualMachine)
|
vm := st.(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
ui.Say("Destroying VM...")
|
ui.Say("Destroying VM...")
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ func (c *CustomizeConfig) Prepare() []error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepCustomize) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
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)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
identity, err := s.identitySettings()
|
identity, err := s.identitySettings()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const BuilderId = "jetbrains.vsphere"
|
|||||||
type Artifact struct {
|
type Artifact struct {
|
||||||
Outconfig *OutputConfig
|
Outconfig *OutputConfig
|
||||||
Name string
|
Name string
|
||||||
VM *driver.VirtualMachine
|
VM *driver.VirtualMachineDriver
|
||||||
|
|
||||||
// StateData should store data such as GeneratedData
|
// StateData should store data such as GeneratedData
|
||||||
// to be shared with post-processors
|
// to be shared with post-processors
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -21,8 +21,8 @@ type LocationConfig struct {
|
|||||||
// the host is in a folder. For example `folder/host`. See the
|
// the host is in a folder. For example `folder/host`. See the
|
||||||
// `Specifying Clusters and Hosts` section above for more details.
|
// `Specifying Clusters and Hosts` section above for more details.
|
||||||
Host string `mapstructure:"host"`
|
Host string `mapstructure:"host"`
|
||||||
// VMWare resource pool. Defaults to the root resource pool of the
|
// VMWare resource pool. If not set, it will look for the root resource pool of the `host` or `cluster`.
|
||||||
// `host` or `cluster`.
|
// If a root resource is not found, it will then look for a default resource pool.
|
||||||
ResourcePool string `mapstructure:"resource_pool"`
|
ResourcePool string `mapstructure:"resource_pool"`
|
||||||
// VMWare datastore. Required if `host` is a cluster, or if `host` has
|
// VMWare datastore. Required if `host` is a cluster, or if `host` has
|
||||||
// multiple datastores.
|
// multiple datastores.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type StepBootCommand struct {
|
|||||||
func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
debug := state.Get("debug").(bool)
|
debug := state.Get("debug").(bool)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if s.Config.BootCommand == nil {
|
if s.Config.BootCommand == nil {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type StepConfigParams struct {
|
|||||||
|
|
||||||
func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
configParams := make(map[string]string)
|
configParams := make(map[string]string)
|
||||||
|
|
||||||
if s.Config.ConfigParams != nil {
|
if s.Config.ConfigParams != nil {
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ func (s *StepExport) Cleanup(multistep.StateBag) {
|
|||||||
|
|
||||||
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
ui.Message("Starting export...")
|
ui.Message("Starting export...")
|
||||||
lease, err := vm.Export()
|
lease, err := vm.Export()
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ type StepConfigureHardware struct {
|
|||||||
|
|
||||||
func (s *StepConfigureHardware) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepConfigureHardware) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(driver.VirtualMachine)
|
||||||
|
|
||||||
if *s.Config != (HardwareConfig{}) {
|
if *s.Config != (HardwareConfig{}) {
|
||||||
ui.Say("Customizing hardware...")
|
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 {
|
func (s *StepImportToContentLibrary) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if s.ContentLibConfig.Ovf {
|
if s.ContentLibConfig.Ovf {
|
||||||
@@ -142,7 +142,7 @@ func (s *StepImportToContentLibrary) Run(_ context.Context, state multistep.Stat
|
|||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachine) error {
|
func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachineDriver) error {
|
||||||
ovf := vcenter.OVF{
|
ovf := vcenter.OVF{
|
||||||
Spec: vcenter.CreateSpec{
|
Spec: vcenter.CreateSpec{
|
||||||
Name: s.ContentLibConfig.Name,
|
Name: s.ContentLibConfig.Name,
|
||||||
@@ -154,7 +154,7 @@ func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachine
|
|||||||
return vm.ImportOvfToContentLibrary(ovf)
|
return vm.ImportOvfToContentLibrary(ovf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepImportToContentLibrary) importVmTemplate(vm *driver.VirtualMachine) error {
|
func (s *StepImportToContentLibrary) importVmTemplate(vm *driver.VirtualMachineDriver) error {
|
||||||
template := vcenter.Template{
|
template := vcenter.Template{
|
||||||
Name: s.ContentLibConfig.Name,
|
Name: s.ContentLibConfig.Name,
|
||||||
Description: s.ContentLibConfig.Description,
|
Description: s.ContentLibConfig.Description,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type StepRun struct {
|
|||||||
|
|
||||||
func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if s.Config.BootOrder != "" {
|
if s.Config.BootOrder != "" {
|
||||||
ui.Say("Set boot order...")
|
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) {
|
func (s *StepRun) Cleanup(state multistep.StateBag) {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if s.Config.BootOrder == "" && s.SetOrder {
|
if s.Config.BootOrder == "" && s.SetOrder {
|
||||||
ui.Say("Clear boot order...")
|
ui.Say("Clear boot order...")
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ type StepShutdown struct {
|
|||||||
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
comm := state.Get("communicator").(packer.Communicator)
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if off, _ := vm.IsPoweredOff(); off {
|
if off, _ := vm.IsPoweredOff(); off {
|
||||||
// Probably power off initiated by last provisioner, though disable_shutdown is not set
|
// Probably power off initiated by last provisioner, though disable_shutdown is not set
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type StepCreateSnapshot struct {
|
|||||||
|
|
||||||
func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if s.CreateSnapshot {
|
if s.CreateSnapshot {
|
||||||
ui.Say("Creating snapshot...")
|
ui.Say("Creating snapshot...")
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) mult
|
|||||||
s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
|
s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
|
||||||
s.Comm.SSHClearAuthorizedKeys = true
|
s.Comm.SSHClearAuthorizedKeys = true
|
||||||
|
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
err = vm.AddPublicKeys(ctx, string(s.Comm.SSHPublicKey))
|
err = vm.AddPublicKeys(ctx, string(s.Comm.SSHPublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Put("error", fmt.Errorf("error saving temporary keypair in the vm: %s", err))
|
state.Put("error", fmt.Errorf("error saving temporary keypair in the vm: %s", err))
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type StepConvertToTemplate struct {
|
|||||||
|
|
||||||
func (s *StepConvertToTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepConvertToTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if s.ConvertToTemplate {
|
if s.ConvertToTemplate {
|
||||||
ui.Say("Convert VM into template...")
|
ui.Say("Convert VM into template...")
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (c *WaitIpConfig) GetIPNet() *net.IPNet {
|
|||||||
|
|
||||||
func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
var ip string
|
var ip string
|
||||||
var err error
|
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 prevIp = ""
|
||||||
var stopTime time.Time
|
var stopTime time.Time
|
||||||
var interval time.Duration
|
var interval time.Duration
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func RenderConfig(config map[string]interface{}) string {
|
|||||||
return string(j)
|
return string(j)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConn(t *testing.T) *driver.Driver {
|
func TestConn(t *testing.T) driver.Driver {
|
||||||
username := os.Getenv("VSPHERE_USERNAME")
|
username := os.Getenv("VSPHERE_USERNAME")
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = "root"
|
username = "root"
|
||||||
@@ -56,7 +56,7 @@ func TestConn(t *testing.T) *driver.Driver {
|
|||||||
return d
|
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]
|
artifactRaw := artifacts[0]
|
||||||
artifact, _ := artifactRaw.(*common.Artifact)
|
artifact, _ := artifactRaw.(*common.Artifact)
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package driver
|
|||||||
import "github.com/vmware/govmomi/object"
|
import "github.com/vmware/govmomi/object"
|
||||||
|
|
||||||
type Cluster struct {
|
type Cluster struct {
|
||||||
driver *Driver
|
driver *VCenterDriver
|
||||||
cluster *object.ClusterComputeResource
|
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)
|
c, err := d.finder.ClusterComputeResource(d.ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -13,20 +13,31 @@ import (
|
|||||||
"github.com/vmware/govmomi/vim25/types"
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Datastore struct {
|
type Datastore interface {
|
||||||
ds *object.Datastore
|
Info(params ...string) (*mo.Datastore, error)
|
||||||
driver *Driver
|
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 {
|
type DatastoreDriver struct {
|
||||||
return &Datastore{
|
ds *object.Datastore
|
||||||
|
driver *VCenterDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VCenterDriver) NewDatastore(ref *types.ManagedObjectReference) Datastore {
|
||||||
|
return &DatastoreDriver{
|
||||||
ds: object.NewDatastore(d.client.Client, *ref),
|
ds: object.NewDatastore(d.client.Client, *ref),
|
||||||
driver: d,
|
driver: d,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If name is an empty string, then resolve host's one
|
// 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 == "" {
|
if name == "" {
|
||||||
h, err := d.FindHost(host)
|
h, err := d.FindHost(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -55,13 +66,13 @@ func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Datastore{
|
return &DatastoreDriver{
|
||||||
ds: ds,
|
ds: ds,
|
||||||
driver: d,
|
driver: d,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) GetDatastoreName(id string) (string, error) {
|
func (d *VCenterDriver) GetDatastoreName(id string) (string, error) {
|
||||||
obj := types.ManagedObjectReference{
|
obj := types.ManagedObjectReference{
|
||||||
Type: "Datastore",
|
Type: "Datastore",
|
||||||
Value: id,
|
Value: id,
|
||||||
@@ -76,7 +87,7 @@ func (d *Driver) GetDatastoreName(id string) (string, error) {
|
|||||||
return me.Name, nil
|
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
|
var p []string
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
p = []string{"*"}
|
p = []string{"*"}
|
||||||
@@ -91,21 +102,25 @@ func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) {
|
|||||||
return &info, nil
|
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)
|
_, err := ds.ds.Stat(ds.driver.ctx, path)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *Datastore) Name() string {
|
func (ds *DatastoreDriver) Name() string {
|
||||||
return ds.ds.Name()
|
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)
|
return ds.ds.Path(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The file ID isn't available via the API, so we use DatastoreBrowser to search
|
// 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}
|
ref := types.ManagedObjectReference{Type: "Datastore", Value: datastoreID}
|
||||||
ds := object.NewDatastore(d.vimClient, ref)
|
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
|
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
|
p := soap.DefaultUpload
|
||||||
ctx := ds.driver.ctx
|
ctx := ds.driver.ctx
|
||||||
|
|
||||||
if set_host_for_datastore_uploads && host != "" {
|
if setHost && host != "" {
|
||||||
h, err := ds.driver.FindHost(host)
|
h, err := ds.driver.FindHost(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
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)
|
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -164,7 +179,7 @@ func (ds *Datastore) Delete(path string) error {
|
|||||||
return fm.Delete(ds.driver.ctx, path)
|
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)
|
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -193,8 +208,9 @@ func (d *DatastoreIsoPath) Validate() bool {
|
|||||||
// [datastore] /dir/subdir/file
|
// [datastore] /dir/subdir/file
|
||||||
// [datastore] dir/subdir/file
|
// [datastore] dir/subdir/file
|
||||||
// [] /dir/subdir/file
|
// [] /dir/subdir/file
|
||||||
// /dir/subdir/file or dir/subdir/file
|
// [data-store] /dir/subdir/file
|
||||||
matched, _ := regexp.MatchString(`^((\[\w*\])?\s*([^\[\]]+))$`, d.path)
|
// dir/subdir/file or dir/subdir/file
|
||||||
|
matched, _ := regexp.MatchString(`^\s*(\[[^\[\]\/]*\])?\s*[^\[\]]+\s*$`, d.path)
|
||||||
return matched
|
return matched
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +220,7 @@ func (d *DatastoreIsoPath) GetFilePath() string {
|
|||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
// removes datastore name from path
|
// removes datastore name from path
|
||||||
filePath = parts[1]
|
filePath = parts[1]
|
||||||
filePath = strings.TrimLeft(filePath, " ")
|
filePath = strings.TrimSpace(filePath)
|
||||||
}
|
}
|
||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -36,19 +36,54 @@ func TestDatastoreIsoPath(t *testing.T) {
|
|||||||
isoPath: "[datastore][] /dir/subdir/file",
|
isoPath: "[datastore][] /dir/subdir/file",
|
||||||
valid: false,
|
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}
|
dsIsoPath := &DatastoreIsoPath{path: c.isoPath}
|
||||||
if dsIsoPath.Validate() != c.valid {
|
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 {
|
if !c.valid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filePath := dsIsoPath.GetFilePath()
|
filePath := dsIsoPath.GetFilePath()
|
||||||
if filePath != c.filePath {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,46 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/vmware/govmomi"
|
"github.com/vmware/govmomi"
|
||||||
"github.com/vmware/govmomi/find"
|
"github.com/vmware/govmomi/find"
|
||||||
"github.com/vmware/govmomi/object"
|
"github.com/vmware/govmomi/object"
|
||||||
"github.com/vmware/govmomi/session"
|
"github.com/vmware/govmomi/session"
|
||||||
|
"github.com/vmware/govmomi/vapi/library"
|
||||||
"github.com/vmware/govmomi/vapi/rest"
|
"github.com/vmware/govmomi/vapi/rest"
|
||||||
"github.com/vmware/govmomi/vim25"
|
"github.com/vmware/govmomi/vim25"
|
||||||
"github.com/vmware/govmomi/vim25/soap"
|
"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
|
// context that controls the authenticated sessions used to run the VM commands
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
client *govmomi.Client
|
client *govmomi.Client
|
||||||
@@ -33,7 +63,7 @@ type ConnectConfig struct {
|
|||||||
Datacenter string
|
Datacenter string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDriver(config *ConnectConfig) (*Driver, error) {
|
func NewDriver(config *ConnectConfig) (Driver, error) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer))
|
vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer))
|
||||||
@@ -67,7 +97,7 @@ func NewDriver(config *ConnectConfig) (*Driver, error) {
|
|||||||
}
|
}
|
||||||
finder.SetDatacenter(datacenter)
|
finder.SetDatacenter(datacenter)
|
||||||
|
|
||||||
d := Driver{
|
d := &VCenterDriver{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
client: client,
|
client: client,
|
||||||
vimClient: vimClient,
|
vimClient: vimClient,
|
||||||
@@ -78,7 +108,7 @@ func NewDriver(config *ConnectConfig) (*Driver, error) {
|
|||||||
datacenter: datacenter,
|
datacenter: datacenter,
|
||||||
finder: finder,
|
finder: finder,
|
||||||
}
|
}
|
||||||
return &d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The rest.Client requires vCenter.
|
// The rest.Client requires vCenter.
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,17 +1,29 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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
|
// Defines whether acceptance tests should be run
|
||||||
const TestHostName = "esxi-1.vsphere65.test"
|
const TestHostName = "esxi-1.vsphere65.test"
|
||||||
|
|
||||||
func newTestDriver(t *testing.T) *Driver {
|
func newTestDriver(t *testing.T) Driver {
|
||||||
username := os.Getenv("VSPHERE_USERNAME")
|
username := os.Getenv("VSPHERE_USERNAME")
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = "root"
|
username = "root"
|
||||||
@@ -37,3 +49,58 @@ func newVMName() string {
|
|||||||
rand.Seed(time.Now().UTC().UnixNano())
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
return fmt.Sprintf("test-%v", rand.Intn(1000))
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,18 +12,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Folder struct {
|
type Folder struct {
|
||||||
driver *Driver
|
driver *VCenterDriver
|
||||||
folder *object.Folder
|
folder *object.Folder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) NewFolder(ref *types.ManagedObjectReference) *Folder {
|
func (d *VCenterDriver) NewFolder(ref *types.ManagedObjectReference) *Folder {
|
||||||
return &Folder{
|
return &Folder{
|
||||||
folder: object.NewFolder(d.client.Client, *ref),
|
folder: object.NewFolder(d.client.Client, *ref),
|
||||||
driver: d,
|
driver: d,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) FindFolder(name string) (*Folder, error) {
|
func (d *VCenterDriver) FindFolder(name string) (*Folder, error) {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
// create folders if they don't exist
|
// create folders if they don't exist
|
||||||
parent := ""
|
parent := ""
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Host struct {
|
type Host struct {
|
||||||
driver *Driver
|
driver *VCenterDriver
|
||||||
host *object.HostSystem
|
host *object.HostSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) NewHost(ref *types.ManagedObjectReference) *Host {
|
func (d *VCenterDriver) NewHost(ref *types.ManagedObjectReference) *Host {
|
||||||
return &Host{
|
return &Host{
|
||||||
host: object.NewHostSystem(d.client.Client, *ref),
|
host: object.NewHostSystem(d.client.Client, *ref),
|
||||||
driver: d,
|
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)
|
h, err := d.finder.HostSystem(d.ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Library struct {
|
type Library struct {
|
||||||
driver *Driver
|
driver *VCenterDriver
|
||||||
library *library.Library
|
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)
|
lm := library.NewManager(d.restClient.client)
|
||||||
l, err := lm.GetLibraryByName(d.ctx, name)
|
l, err := lm.GetLibraryByName(d.ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -26,7 +26,7 @@ func (d *Driver) FindContentLibraryByName(name string) (*Library, error) {
|
|||||||
}, nil
|
}, 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)
|
lm := library.NewManager(d.restClient.client)
|
||||||
items, err := lm.GetLibraryItems(d.ctx, libraryId)
|
items, err := lm.GetLibraryItems(d.ctx, libraryId)
|
||||||
if err != nil {
|
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)
|
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")
|
log.Printf("Check if ISO path is a Content Library path")
|
||||||
err := d.restClient.Login(d.ctx)
|
err := d.restClient.Login(d.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Network struct {
|
type Network struct {
|
||||||
driver *Driver
|
driver *VCenterDriver
|
||||||
network object.NetworkReference
|
network object.NetworkReference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) NewNetwork(ref *types.ManagedObjectReference) *Network {
|
func (d *VCenterDriver) NewNetwork(ref *types.ManagedObjectReference) *Network {
|
||||||
return &Network{
|
return &Network{
|
||||||
network: object.NewNetwork(d.client.Client, *ref),
|
network: object.NewNetwork(d.client.Client, *ref),
|
||||||
driver: d,
|
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)
|
n, err := d.finder.Network(d.ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -31,7 +31,7 @@ func (d *Driver) FindNetwork(name string) (*Network, error) {
|
|||||||
}, nil
|
}, 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)
|
ns, err := d.finder.NetworkList(d.ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package driver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/vmware/govmomi/find"
|
||||||
"github.com/vmware/govmomi/object"
|
"github.com/vmware/govmomi/object"
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
"github.com/vmware/govmomi/vim25/mo"
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
@@ -10,17 +12,17 @@ import (
|
|||||||
|
|
||||||
type ResourcePool struct {
|
type ResourcePool struct {
|
||||||
pool *object.ResourcePool
|
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{
|
return &ResourcePool{
|
||||||
pool: object.NewResourcePool(d.client.Client, *ref),
|
pool: object.NewResourcePool(d.client.Client, *ref),
|
||||||
driver: d,
|
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
|
var res string
|
||||||
if cluster != "" {
|
if cluster != "" {
|
||||||
res = cluster
|
res = cluster
|
||||||
@@ -28,10 +30,24 @@ func (d *Driver) FindResourcePool(cluster string, host string, name string) (*Re
|
|||||||
res = host
|
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 {
|
if err != nil {
|
||||||
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
dp = vapp.ResourcePool
|
||||||
|
} else if dperr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p = dp
|
||||||
|
}
|
||||||
|
|
||||||
return &ResourcePool{
|
return &ResourcePool{
|
||||||
pool: p,
|
pool: p,
|
||||||
driver: d,
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,9 +21,43 @@ import (
|
|||||||
"github.com/vmware/govmomi/vim25/types"
|
"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
|
vm *object.VirtualMachine
|
||||||
driver *Driver
|
driver *VCenterDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
type CloneConfig struct {
|
type CloneConfig struct {
|
||||||
@@ -87,25 +121,25 @@ type Disk struct {
|
|||||||
ControllerIndex int
|
ControllerIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) NewVM(ref *types.ManagedObjectReference) *VirtualMachine {
|
func (d *VCenterDriver) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
|
||||||
return &VirtualMachine{
|
return &VirtualMachineDriver{
|
||||||
vm: object.NewVirtualMachine(d.client.Client, *ref),
|
vm: object.NewVirtualMachine(d.client.Client, *ref),
|
||||||
driver: d,
|
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)
|
vm, err := d.finder.VirtualMachine(d.ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &VirtualMachine{
|
return &VirtualMachineDriver{
|
||||||
vm: vm,
|
vm: vm,
|
||||||
driver: d,
|
driver: d,
|
||||||
}, nil
|
}, 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)
|
vm, err := d.FindVM(vmPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*find.NotFoundError); !ok {
|
if _, ok := err.(*find.NotFoundError); !ok {
|
||||||
@@ -130,7 +164,7 @@ func (d *Driver) PreCleanVM(ui packer.Ui, vmPath string, force bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) {
|
func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) {
|
||||||
createSpec := types.VirtualMachineConfigSpec{
|
createSpec := types.VirtualMachineConfigSpec{
|
||||||
Name: config.Name,
|
Name: config.Name,
|
||||||
Annotation: config.Annotation,
|
Annotation: config.Annotation,
|
||||||
@@ -219,7 +253,7 @@ func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) {
|
|||||||
return d.NewVM(&vmRef), nil
|
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
|
var p []string
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
p = []string{"*"}
|
p = []string{"*"}
|
||||||
@@ -234,7 +268,7 @@ func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) {
|
|||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) {
|
func (vm *VirtualMachineDriver) Devices() (object.VirtualDeviceList, error) {
|
||||||
vmInfo, err := vm.Info("config.hardware.device")
|
vmInfo, err := vm.Info("config.hardware.device")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -243,7 +277,7 @@ func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) {
|
|||||||
return vmInfo.Config.Hardware.Device, nil
|
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)
|
folder, err := vm.driver.FindFolder(config.Folder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -262,7 +296,7 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
datastoreRef := datastore.ds.Reference()
|
datastoreRef := datastore.Reference()
|
||||||
relocateSpec.Datastore = &datastoreRef
|
relocateSpec.Datastore = &datastoreRef
|
||||||
|
|
||||||
var cloneSpec types.VirtualMachineCloneSpec
|
var cloneSpec types.VirtualMachineCloneSpec
|
||||||
@@ -350,7 +384,7 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt
|
|||||||
return created, nil
|
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 {
|
if len(newProps) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -398,7 +432,7 @@ func (vm *VirtualMachine) updateVAppConfig(ctx context.Context, newProps map[str
|
|||||||
}, nil
|
}, 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}
|
newProps := map[string]string{"public-keys": publicKeys}
|
||||||
config, err := vm.updateVAppConfig(ctx, newProps)
|
config, err := vm.updateVAppConfig(ctx, newProps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -415,7 +449,7 @@ func (vm *VirtualMachine) AddPublicKeys(ctx context.Context, publicKeys string)
|
|||||||
return err
|
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)
|
log.Printf("fetching properties for VM %q", vm.vm.InventoryPath)
|
||||||
var props mo.VirtualMachine
|
var props mo.VirtualMachine
|
||||||
if err := vm.vm.Properties(ctx, vm.vm.Reference(), nil, &props); err != nil {
|
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
|
return &props, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) Destroy() error {
|
func (vm *VirtualMachineDriver) Destroy() error {
|
||||||
task, err := vm.vm.Destroy(vm.driver.ctx)
|
task, err := vm.vm.Destroy(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -433,7 +467,7 @@ func (vm *VirtualMachine) Destroy() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
|
func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error {
|
||||||
var confSpec types.VirtualMachineConfigSpec
|
var confSpec types.VirtualMachineConfigSpec
|
||||||
confSpec.NumCPUs = config.CPUs
|
confSpec.NumCPUs = config.CPUs
|
||||||
confSpec.NumCoresPerSocket = config.CpuCores
|
confSpec.NumCoresPerSocket = config.CpuCores
|
||||||
@@ -524,7 +558,7 @@ func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
|
|||||||
return err
|
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)
|
task, err := vm.vm.Customize(vm.driver.ctx, spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -532,7 +566,7 @@ func (vm *VirtualMachine) Customize(spec types.CustomizationSpec) error {
|
|||||||
return task.Wait(vm.driver.ctx)
|
return task.Wait(vm.driver.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) ResizeDisk(diskSize int64) error {
|
func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) error {
|
||||||
var confSpec types.VirtualMachineConfigSpec
|
var confSpec types.VirtualMachineConfigSpec
|
||||||
|
|
||||||
devices, err := vm.vm.Device(vm.driver.ctx)
|
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")
|
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)
|
task, err := vm.vm.PowerOn(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -590,7 +624,7 @@ func (vm *VirtualMachine) PowerOn() error {
|
|||||||
return err
|
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)
|
netIP, err := vm.vm.WaitForNetIP(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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")
|
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)
|
state, err := vm.vm.PowerState(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -632,7 +666,7 @@ func (vm *VirtualMachine) PowerOff() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) IsPoweredOff() (bool, error) {
|
func (vm *VirtualMachineDriver) IsPoweredOff() (bool, error) {
|
||||||
state, err := vm.vm.PowerState(vm.driver.ctx)
|
state, err := vm.vm.PowerState(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -641,12 +675,12 @@ func (vm *VirtualMachine) IsPoweredOff() (bool, error) {
|
|||||||
return state == types.VirtualMachinePowerStatePoweredOff, nil
|
return state == types.VirtualMachinePowerStatePoweredOff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) StartShutdown() error {
|
func (vm *VirtualMachineDriver) StartShutdown() error {
|
||||||
err := vm.vm.ShutdownGuest(vm.driver.ctx)
|
err := vm.vm.ShutdownGuest(vm.driver.ctx)
|
||||||
return err
|
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)
|
shutdownTimer := time.After(timeout)
|
||||||
for {
|
for {
|
||||||
off, err := vm.IsPoweredOff()
|
off, err := vm.IsPoweredOff()
|
||||||
@@ -670,7 +704,7 @@ func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Dura
|
|||||||
return nil
|
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)
|
task, err := vm.vm.CreateSnapshot(vm.driver.ctx, name, "", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -679,11 +713,11 @@ func (vm *VirtualMachine) CreateSnapshot(name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) ConvertToTemplate() error {
|
func (vm *VirtualMachineDriver) ConvertToTemplate() error {
|
||||||
return vm.vm.MarkAsTemplate(vm.driver.ctx)
|
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)
|
err := vm.driver.restClient.Login(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -717,7 +751,7 @@ func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
|
|||||||
return vm.driver.restClient.Logout(vm.driver.ctx)
|
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)
|
err := vm.driver.restClient.Login(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -769,7 +803,7 @@ func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
template.VMHomeStorage.Datastore = d.ds.Reference().Value
|
template.VMHomeStorage.Datastore = d.Reference().Value
|
||||||
}
|
}
|
||||||
|
|
||||||
vcm := vcenter.NewManager(vm.driver.restClient.client)
|
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)
|
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")
|
vmInfo, err := vm.Info("name", "layoutEx.file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -789,14 +823,14 @@ func (vm *VirtualMachine) GetDir() (string, error) {
|
|||||||
|
|
||||||
vmxName := fmt.Sprintf("/%s.vmx", vmInfo.Name)
|
vmxName := fmt.Sprintf("/%s.vmx", vmInfo.Name)
|
||||||
for _, file := range vmInfo.LayoutEx.File {
|
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 RemoveDatastorePrefix(file.Name[:len(file.Name)-len(vmxName)]), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("cannot find '%s'", vmxName)
|
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 {
|
if len(config.Storage) == 0 {
|
||||||
return nil, errors.New("no storage devices have been defined")
|
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
|
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 {
|
if len(config.NICs) == 0 {
|
||||||
return nil, errors.New("no network adapters have been defined")
|
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
|
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 != "" {
|
if network != "" {
|
||||||
var err error
|
var err error
|
||||||
networks, err := d.FindNetworks(network)
|
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)
|
devices, err := vm.vm.Device(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -985,7 +1019,7 @@ func (vm *VirtualMachine) AddCdrom(controllerType string, datastoreIsoPath strin
|
|||||||
return vm.addDevice(cdrom)
|
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)
|
devices, err := vm.vm.Device(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1003,7 +1037,7 @@ func (vm *VirtualMachine) AddFloppy(imgPath string) error {
|
|||||||
return vm.addDevice(floppy)
|
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)
|
devices, err := vm.vm.Device(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1016,11 +1050,11 @@ func (vm *VirtualMachine) SetBootOrder(order []string) error {
|
|||||||
return vm.vm.SetBootOptions(vm.driver.ctx, &bootOptions)
|
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...)
|
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}
|
newDevices := object.VirtualDeviceList{device}
|
||||||
confSpec := types.VirtualMachineConfigSpec{}
|
confSpec := types.VirtualMachineConfigSpec{}
|
||||||
var err error
|
var err error
|
||||||
@@ -1038,7 +1072,7 @@ func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error {
|
|||||||
return err
|
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 confSpec types.VirtualMachineConfigSpec
|
||||||
|
|
||||||
var ov []types.BaseOptionValue
|
var ov []types.BaseOptionValue
|
||||||
@@ -1066,19 +1100,19 @@ func (vm *VirtualMachine) AddConfigParams(params map[string]string, info *types.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) Export() (*nfc.Lease, error) {
|
func (vm *VirtualMachineDriver) Export() (*nfc.Lease, error) {
|
||||||
return vm.vm.Export(vm.driver.ctx)
|
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)
|
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())
|
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
|
var mgr mo.OvfManager
|
||||||
err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.ctx, m.Reference(), nil, &mgr)
|
err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.ctx, m.Reference(), nil, &mgr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ var (
|
|||||||
ErrNoSataController = errors.New("no available SATA controller")
|
ErrNoSataController = errors.New("no available SATA controller")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (vm *VirtualMachine) AddSATAController() error {
|
func (vm *VirtualMachineDriver) AddSATAController() error {
|
||||||
sata := &types.VirtualAHCIController{}
|
sata := &types.VirtualAHCIController{}
|
||||||
return vm.addDevice(sata)
|
return vm.addDevice(sata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) FindSATAController() (*types.VirtualAHCIController, error) {
|
func (vm *VirtualMachineDriver) FindSATAController() (*types.VirtualAHCIController, error) {
|
||||||
l, err := vm.Devices()
|
l, err := vm.Devices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -29,7 +29,7 @@ func (vm *VirtualMachine) FindSATAController() (*types.VirtualAHCIController, er
|
|||||||
return c.(*types.VirtualAHCIController), nil
|
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()
|
l, err := vm.Devices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -52,7 +52,7 @@ func (vm *VirtualMachine) CreateCdrom(c *types.VirtualController) (*types.Virtua
|
|||||||
return device, nil
|
return device, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) RemoveCdroms() error {
|
func (vm *VirtualMachineDriver) RemoveCdroms() error {
|
||||||
devices, err := vm.Devices()
|
devices, err := vm.Devices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -69,7 +69,7 @@ func (vm *VirtualMachine) RemoveCdroms() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) EjectCdroms() error {
|
func (vm *VirtualMachineDriver) EjectCdroms() error {
|
||||||
devices, err := vm.Devices()
|
devices, err := vm.Devices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func TestVMAcc_clone(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
config *CloneConfig
|
config *CloneConfig
|
||||||
checkFunction func(*testing.T, *VirtualMachine, *CloneConfig)
|
checkFunction func(*testing.T, VirtualMachine, *CloneConfig)
|
||||||
}{
|
}{
|
||||||
{"Default", &CloneConfig{}, cloneDefaultCheck},
|
{"Default", &CloneConfig{}, cloneDefaultCheck},
|
||||||
{"LinkedClone", &CloneConfig{LinkedClone: true}, cloneLinkedCloneCheck},
|
{"LinkedClone", &CloneConfig{LinkedClone: true}, cloneLinkedCloneCheck},
|
||||||
@@ -53,8 +53,8 @@ func TestVMAcc_clone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
|
func cloneDefaultCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
|
||||||
d := vm.driver
|
d := vm.(*VirtualMachineDriver).driver
|
||||||
|
|
||||||
// Check that the clone can be found by its name
|
// Check that the clone can be found by its name
|
||||||
if _, err := d.FindVM(config.Name); err != nil {
|
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")
|
log.Printf("[DEBUG] Configuring the vm")
|
||||||
hwConfig := &HardwareConfig{
|
hwConfig := &HardwareConfig{
|
||||||
CPUs: 2,
|
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")
|
log.Printf("[DEBUG] Configuring the vm")
|
||||||
err := vm.Configure(&HardwareConfig{RAMReserveAll: true})
|
err := vm.Configure(&HardwareConfig{RAMReserveAll: true})
|
||||||
if err != nil {
|
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")
|
vmInfo, err := vm.Info("layoutEx.disk")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Cannot read VM properties: %v", err)
|
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")
|
vmInfo, err := vm.Info("parent")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Cannot read VM properties: %v", err)
|
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()
|
path, err := f.Path()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Cannot read folder name: %v", err)
|
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")
|
vmInfo, err := vm.Info("resourcePool")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Cannot read VM properties: %v", err)
|
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()
|
path, err := p.Path()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Cannot read resource pool name: %v", err)
|
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)
|
stopper := startVM(t, vm, config.Name)
|
||||||
defer stopper()
|
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)
|
stopper := startVM(t, vm, config.Name)
|
||||||
defer stopper()
|
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()
|
err := vm.ConvertToTemplate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to convert to template: %v", err)
|
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")
|
log.Printf("[DEBUG] Starting the vm")
|
||||||
if err := vm.PowerOn(); err != nil {
|
if err := vm.PowerOn(); err != nil {
|
||||||
t.Fatalf("Cannot start vm '%v': %v", vmName, err)
|
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")
|
log.Printf("[DEBUG] Deleting the VM")
|
||||||
if err := vm.Destroy(); err != nil {
|
if err := vm.Destroy(); err != nil {
|
||||||
t.Errorf("!!! ERROR DELETING VM '%v': %v!!!", vmName, err)
|
t.Errorf("!!! ERROR DELETING VM '%v': %v!!!", vmName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the clone is no longer exists
|
// 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)
|
t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", vmName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func TestVMAcc_create(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
config *CreateConfig
|
config *CreateConfig
|
||||||
checkFunction func(*testing.T, *VirtualMachine, *CreateConfig)
|
checkFunction func(*testing.T, VirtualMachine, *CreateConfig)
|
||||||
}{
|
}{
|
||||||
{"MinimalConfiguration", &CreateConfig{}, createDefaultCheck},
|
{"MinimalConfiguration", &CreateConfig{}, createDefaultCheck},
|
||||||
}
|
}
|
||||||
@@ -36,8 +36,8 @@ func TestVMAcc_create(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultCheck(t *testing.T, vm *VirtualMachine, config *CreateConfig) {
|
func createDefaultCheck(t *testing.T, vm VirtualMachine, config *CreateConfig) {
|
||||||
d := vm.driver
|
d := vm.(*VirtualMachineDriver).driver
|
||||||
|
|
||||||
// Check that the clone can be found by its name
|
// Check that the clone can be found by its name
|
||||||
if _, err := d.FindVM(config.Name); err != nil {
|
if _, err := d.FindVM(config.Name); err != nil {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type KeyInput struct {
|
|||||||
Shift bool
|
Shift bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) {
|
func (vm *VirtualMachineDriver) TypeOnKeyboard(input KeyInput) (int32, error) {
|
||||||
var spec types.UsbScanCodeSpec
|
var spec types.UsbScanCodeSpec
|
||||||
|
|
||||||
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
|
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -170,7 +170,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
|
|
||||||
artifact := &common.Artifact{
|
artifact := &common.Artifact{
|
||||||
Name: b.config.VMName,
|
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")},
|
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ func (c *CDRomConfig) Prepare() []error {
|
|||||||
|
|
||||||
func (s *StepAddCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepAddCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if s.Config.CdromType == "sata" {
|
if s.Config.CdromType == "sata" {
|
||||||
if _, err := vm.FindSATAController(); err == driver.ErrNoSataController {
|
if _, err := vm.FindSATAController(); err == driver.ErrNoSataController {
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ type StepAddFloppy struct {
|
|||||||
|
|
||||||
func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
d := state.Get("driver").(*driver.Driver)
|
d := state.Get("driver").(*driver.VCenterDriver)
|
||||||
|
|
||||||
if floppyPath, ok := state.GetOk("floppy_path"); ok {
|
if floppyPath, ok := state.GetOk("floppy_path"); ok {
|
||||||
ui.Say("Uploading created floppy image")
|
ui.Say("Uploading created floppy image")
|
||||||
@@ -90,7 +90,7 @@ func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
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 {
|
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
|
||||||
ui.Say("Deleting Floppy image ...")
|
ui.Say("Deleting Floppy image ...")
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ type StepCreateVM struct {
|
|||||||
|
|
||||||
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
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)
|
vmPath := path.Join(s.Location.Folder, s.Location.VMName)
|
||||||
|
|
||||||
err := d.PreCleanVM(ui, vmPath, s.Force)
|
err := d.PreCleanVM(ui, vmPath, s.Force)
|
||||||
@@ -287,7 +287,7 @@ func (s *StepCreateVM) Cleanup(state multistep.StateBag) {
|
|||||||
if st == nil {
|
if st == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
vm := st.(*driver.VirtualMachine)
|
vm := st.(driver.VirtualMachine)
|
||||||
|
|
||||||
ui.Say("Destroying VM...")
|
ui.Say("Destroying VM...")
|
||||||
err := vm.Destroy()
|
err := vm.Destroy()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ type StepRemoteUpload struct {
|
|||||||
|
|
||||||
func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
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 {
|
if path, ok := state.GetOk("iso_path"); ok {
|
||||||
filename := filepath.Base(path.(string))
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ type StepRemoveCDRom struct {
|
|||||||
|
|
||||||
func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
ui.Say("Eject CD-ROM drives...")
|
ui.Say("Eject CD-ROM drives...")
|
||||||
err := vm.EjectCdroms()
|
err := vm.EjectCdroms()
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ type StepRemoveFloppy struct {
|
|||||||
|
|
||||||
func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
d := state.Get("driver").(*driver.Driver)
|
d := state.Get("driver").(*driver.VCenterDriver)
|
||||||
|
|
||||||
ui.Say("Deleting Floppy drives...")
|
ui.Say("Deleting Floppy drives...")
|
||||||
devices, err := vm.Devices()
|
devices, err := vm.Devices()
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -50,7 +50,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||||||
// Run executes a yandex Packer build and returns a packer.Artifact
|
// Run executes a yandex Packer build and returns a packer.Artifact
|
||||||
// representing a Yandex.Cloud compute image.
|
// representing a Yandex.Cloud compute image.
|
||||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
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())
|
ctx = requestid.ContextWithClientTraceID(ctx, uuid.New().String())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+12
-53
@@ -16,11 +16,8 @@ import (
|
|||||||
"github.com/hashicorp/packer/helper/config"
|
"github.com/hashicorp/packer/helper/config"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"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 defaultGpuPlatformID = "gpu-standard-v1"
|
||||||
const defaultPlatformID = "standard-v1"
|
const defaultPlatformID = "standard-v1"
|
||||||
const defaultMaxRetries = 3
|
const defaultMaxRetries = 3
|
||||||
@@ -31,23 +28,15 @@ var reImageFamily = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$`)
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
Communicator communicator.Config `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.
|
// 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
|
// 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'.
|
// check options 'source_image_folder_id' and 'target_image_folder_id'.
|
||||||
FolderID string `mapstructure:"folder_id" required:"true"`
|
FolderID string `mapstructure:"folder_id" required:"true"`
|
||||||
// Path to file with Service Account key in json format. This
|
// Service account identifier to assign to instance.
|
||||||
// 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
|
|
||||||
ServiceAccountID string `mapstructure:"service_account_id" required:"false"`
|
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
|
// The name of the disk, if unset the instance name
|
||||||
// will be used.
|
// will be used.
|
||||||
DiskName string `mapstructure:"disk_name" required:"false"`
|
DiskName string `mapstructure:"disk_name" required:"false"`
|
||||||
@@ -59,8 +48,7 @@ type Config struct {
|
|||||||
ImageDescription string `mapstructure:"image_description" required:"false"`
|
ImageDescription string `mapstructure:"image_description" required:"false"`
|
||||||
// The family name of the resulting image.
|
// The family name of the resulting image.
|
||||||
ImageFamily string `mapstructure:"image_family" required:"false"`
|
ImageFamily string `mapstructure:"image_family" required:"false"`
|
||||||
// Key/value pair labels to
|
// Key/value pair labels to apply to the created image.
|
||||||
// apply to the created image.
|
|
||||||
ImageLabels map[string]string `mapstructure:"image_labels" required:"false"`
|
ImageLabels map[string]string `mapstructure:"image_labels" required:"false"`
|
||||||
// Minimum size of the disk that will be created from built image, specified in gigabytes.
|
// Minimum size of the disk that will be created from built image, specified in gigabytes.
|
||||||
// Should be more or equal to `disk_size_gb`.
|
// Should be more or equal to `disk_size_gb`.
|
||||||
@@ -78,16 +66,14 @@ type Config struct {
|
|||||||
InstanceMemory int `mapstructure:"instance_mem_gb" required:"false"`
|
InstanceMemory int `mapstructure:"instance_mem_gb" required:"false"`
|
||||||
// The name assigned to the instance.
|
// The name assigned to the instance.
|
||||||
InstanceName string `mapstructure:"instance_name" required:"false"`
|
InstanceName string `mapstructure:"instance_name" required:"false"`
|
||||||
// Key/value pair labels to apply to
|
// Key/value pair labels to apply to the launched instance.
|
||||||
// the launched instance.
|
|
||||||
Labels map[string]string `mapstructure:"labels" required:"false"`
|
Labels map[string]string `mapstructure:"labels" required:"false"`
|
||||||
// Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`.
|
// Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`.
|
||||||
PlatformID string `mapstructure:"platform_id" required:"false"`
|
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 applied to the launched instance.
|
||||||
Metadata map[string]string `mapstructure:"metadata" required:"false"`
|
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"`
|
MetadataFromFile map[string]string `mapstructure:"metadata_from_file"`
|
||||||
// Launch a preemptible instance. This defaults to `false`.
|
// Launch a preemptible instance. This defaults to `false`.
|
||||||
Preemptible bool `mapstructure:"preemptible"`
|
Preemptible bool `mapstructure:"preemptible"`
|
||||||
@@ -95,12 +81,11 @@ type Config struct {
|
|||||||
SerialLogFile string `mapstructure:"serial_log_file" required:"false"`
|
SerialLogFile string `mapstructure:"serial_log_file" required:"false"`
|
||||||
// The source image family to create the new image
|
// 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
|
// 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"`
|
SourceImageFamily string `mapstructure:"source_image_family" required:"true"`
|
||||||
// The ID of the folder containing the source image.
|
// The ID of the folder containing the source image.
|
||||||
SourceImageFolderID string `mapstructure:"source_image_folder_id" required:"false"`
|
SourceImageFolderID string `mapstructure:"source_image_folder_id" required:"false"`
|
||||||
// The source image ID to use to create the new image
|
// The source image ID to use to create the new image from.
|
||||||
// from.
|
|
||||||
SourceImageID string `mapstructure:"source_image_id" required:"false"`
|
SourceImageID string `mapstructure:"source_image_id" required:"false"`
|
||||||
// The source image name to use to create the new image
|
// The source image name to use to create the new image
|
||||||
// from. Name will be looked up in `source_image_folder_id`.
|
// 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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accumulate any errors
|
||||||
var errs *packer.MultiError
|
var errs *packer.MultiError
|
||||||
|
|
||||||
|
errs = packer.MultiErrorAppend(errs, c.AccessConfig.Prepare(&c.ctx)...)
|
||||||
|
|
||||||
if c.SerialLogFile != "" {
|
if c.SerialLogFile != "" {
|
||||||
if _, err := os.Stat(c.SerialLogFile); os.IsExist(err) {
|
if _, err := os.Stat(c.SerialLogFile); os.IsExist(err) {
|
||||||
errs = packer.MultiErrorAppend(errs,
|
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 == "" {
|
if c.Zone == "" {
|
||||||
c.Zone = defaultZone
|
c.Zone = defaultZone
|
||||||
}
|
}
|
||||||
@@ -248,35 +232,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
c.MaxRetries = defaultMaxRetries
|
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 == "" {
|
if c.FolderID == "" {
|
||||||
c.FolderID = os.Getenv("YC_FOLDER_ID")
|
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 == "" {
|
if c.FolderID == "" {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("a folder_id must be specified"))
|
errs, errors.New("a folder_id must be specified"))
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ type FlatConfig struct {
|
|||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
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"`
|
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||||
Endpoint *string `mapstructure:"endpoint" required:"false" cty:"endpoint" hcl:"endpoint"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
MetadataFromFile map[string]string `mapstructure:"metadata_from_file" cty:"metadata_from_file" hcl:"metadata_from_file"`
|
||||||
Preemptible *bool `mapstructure:"preemptible" cty:"preemptible" hcl:"preemptible"`
|
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_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},
|
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||||
"endpoint": &hcldec.AttrSpec{Name: "endpoint", Type: cty.String, 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_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},
|
"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_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_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},
|
"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},
|
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
||||||
"labels": &hcldec.AttrSpec{Name: "labels", Type: cty.Map(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},
|
"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": &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},
|
"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},
|
"preemptible": &hcldec.AttrSpec{Name: "preemptible", Type: cty.Bool, Required: false},
|
||||||
|
|||||||
+10
-10
@@ -33,27 +33,27 @@ type driverYC struct {
|
|||||||
ui packer.Ui
|
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...")
|
log.Printf("[INFO] Initialize Yandex.Cloud client...")
|
||||||
|
|
||||||
sdkConfig := ycsdk.Config{}
|
sdkConfig := ycsdk.Config{}
|
||||||
|
|
||||||
if config.Endpoint != "" {
|
if ac.Endpoint != "" {
|
||||||
sdkConfig.Endpoint = config.Endpoint
|
sdkConfig.Endpoint = ac.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case config.Token == "" && config.ServiceAccountKeyFile == "":
|
case ac.Token == "" && ac.ServiceAccountKeyFile == "":
|
||||||
log.Printf("[INFO] Use Instance Service Account for authentication")
|
log.Printf("[INFO] Use Instance Service Account for authentication")
|
||||||
sdkConfig.Credentials = ycsdk.InstanceServiceAccount()
|
sdkConfig.Credentials = ycsdk.InstanceServiceAccount()
|
||||||
|
|
||||||
case config.Token != "":
|
case ac.Token != "":
|
||||||
log.Printf("[INFO] Use OAuth token for authentication")
|
log.Printf("[INFO] Use OAuth token for authentication")
|
||||||
sdkConfig.Credentials = ycsdk.OAuthToken(config.Token)
|
sdkConfig.Credentials = ycsdk.OAuthToken(ac.Token)
|
||||||
|
|
||||||
case config.ServiceAccountKeyFile != "":
|
case ac.ServiceAccountKeyFile != "":
|
||||||
log.Printf("[INFO] Use Service Account key file %q for authentication", config.ServiceAccountKeyFile)
|
log.Printf("[INFO] Use Service Account key file %q for authentication", ac.ServiceAccountKeyFile)
|
||||||
key, err := iamkey.ReadFromJSONFile(config.ServiceAccountKeyFile)
|
key, err := iamkey.ReadFromJSONFile(ac.ServiceAccountKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) {
|
|||||||
requestIDInterceptor := requestid.Interceptor()
|
requestIDInterceptor := requestid.Interceptor()
|
||||||
|
|
||||||
retryInterceptor := retry.Interceptor(
|
retryInterceptor := retry.Interceptor(
|
||||||
retry.WithMax(config.MaxRetries),
|
retry.WithMax(ac.MaxRetries),
|
||||||
retry.WithCodes(codes.Unavailable),
|
retry.WithCodes(codes.Unavailable),
|
||||||
retry.WithAttemptHeader(true),
|
retry.WithAttemptHeader(true),
|
||||||
retry.WithBackoff(retry.BackoffExponentialWithJitter(defaultExponentialBackoffBase, defaultExponentialBackoffCap)))
|
retry.WithBackoff(retry.BackoffExponentialWithJitter(defaultExponentialBackoffBase, defaultExponentialBackoffCap)))
|
||||||
|
|||||||
@@ -130,3 +130,15 @@ func (va *InspectArgs) AddFlagSets(flags *flag.FlagSet) {
|
|||||||
type InspectArgs struct {
|
type InspectArgs struct {
|
||||||
MetaArgs
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/amazon/ebs"
|
||||||
"github.com/hashicorp/packer/builder/file"
|
"github.com/hashicorp/packer/builder/file"
|
||||||
"github.com/hashicorp/packer/builder/null"
|
"github.com/hashicorp/packer/builder/null"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
@@ -89,6 +90,8 @@ func TestHelperProcess(*testing.T) {
|
|||||||
os.Exit((&InspectCommand{Meta: commandMeta()}).Run(args))
|
os.Exit((&InspectCommand{Meta: commandMeta()}).Run(args))
|
||||||
case "build":
|
case "build":
|
||||||
os.Exit((&BuildCommand{Meta: commandMeta()}).Run(args))
|
os.Exit((&BuildCommand{Meta: commandMeta()}).Run(args))
|
||||||
|
case "hcl2_upgrade":
|
||||||
|
os.Exit((&HCL2UpgradeCommand{Meta: commandMeta()}).Run(args))
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
|
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
@@ -117,6 +120,7 @@ func getBareComponentFinder() packer.ComponentFinder {
|
|||||||
BuilderStore: packer.MapOfBuilder{
|
BuilderStore: packer.MapOfBuilder{
|
||||||
"file": func() (packer.Builder, error) { return &file.Builder{}, nil },
|
"file": func() (packer.Builder, error) { return &file.Builder{}, nil },
|
||||||
"null": func() (packer.Builder, error) { return &null.Builder{}, nil },
|
"null": func() (packer.Builder, error) { return &null.Builder{}, nil },
|
||||||
|
"amazon-ebs": func() (packer.Builder, error) { return &ebs.Builder{}, nil },
|
||||||
},
|
},
|
||||||
ProvisionerStore: packer.MapOfProvisioner{
|
ProvisionerStore: packer.MapOfProvisioner{
|
||||||
"shell-local": func() (packer.Provisioner, error) { return &shell_local.Provisioner{}, nil },
|
"shell-local": func() (packer.Provisioner, error) { return &shell_local.Provisioner{}, nil },
|
||||||
|
|||||||
@@ -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{}
|
||||||
|
}
|
||||||
@@ -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
@@ -44,11 +44,10 @@ func (c *InspectCommand) RunContext(ctx context.Context, cla *InspectArgs) int {
|
|||||||
if ret != 0 {
|
if ret != 0 {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
diags := packerStarter.Initialize()
|
|
||||||
ret = writeDiags(c.Ui, nil, diags)
|
// here we ignore init diags to allow unknown variables to be used
|
||||||
if ret != 0 {
|
_ = packerStarter.Initialize()
|
||||||
return ret
|
|
||||||
}
|
|
||||||
return packerStarter.InspectConfig(packer.InspectConfigOptions{
|
return packerStarter.InspectConfig(packer.InspectConfigOptions{
|
||||||
Ui: c.Ui,
|
Ui: c.Ui,
|
||||||
})
|
})
|
||||||
|
|||||||
+55
-4
@@ -15,11 +15,38 @@ func Test_commands(t *testing.T) {
|
|||||||
env []string
|
env []string
|
||||||
expected 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
|
{[]string{"inspect", "-var=fruit=banana", filepath.Join(testFixture("var-arg"), "fruit_builder.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode
|
||||||
|
|
||||||
> input-variables:
|
> input-variables:
|
||||||
|
|
||||||
var.fruit: "banana" [debug: {Type:cty.String,CmdValue:banana,VarfileValue:null,EnvValue:null,DefaultValue:null}]
|
var.fruit: "banana"
|
||||||
|
|
||||||
> local-variables:
|
> local-variables:
|
||||||
|
|
||||||
@@ -42,11 +69,13 @@ local.fruit: "banana"
|
|||||||
<no post-processor>
|
<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:
|
> 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:
|
> local-variables:
|
||||||
|
|
||||||
@@ -58,7 +87,9 @@ var.fruit: "peach" [debug: {Type:cty.String,CmdValue:peach,VarfileValue:null,Env
|
|||||||
|
|
||||||
> input-variables:
|
> 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:
|
> local-variables:
|
||||||
|
|
||||||
@@ -94,6 +125,26 @@ Use it at will.
|
|||||||
manifest
|
manifest
|
||||||
shell-local
|
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
@@ -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
|
// command implements. The exact behavior of FlagSet can be configured
|
||||||
// using the flags as the second parameter, for example to disable
|
// using the flags as the second parameter, for example to disable
|
||||||
// build settings on the commands that don't handle builds.
|
// 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)
|
f := flag.NewFlagSet(n, flag.ContinueOnError)
|
||||||
|
|
||||||
// Create an io.Writer that writes to our Ui properly for errors.
|
// 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
Reference in New Issue
Block a user