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