Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad7c2593ab |
@@ -8,7 +8,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Add track-internal
|
- name: Add track-internal
|
||||||
uses: andymckay/labeler@1.0.2
|
uses: andymckay/labeler@1.0.2
|
||||||
if: github.event.issue.pull_request == null
|
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.Github_Token }}
|
repo-token: ${{ secrets.Github_Token }}
|
||||||
add-labels: "track-internal"
|
add-labels: "track-internal"
|
||||||
|
|||||||
+1
-91
@@ -1,94 +1,4 @@
|
|||||||
## 1.6.3 (Upcoming)
|
## 1.6.2 (Upcoming)
|
||||||
|
|
||||||
## 1.6.2 (August 28, 2020)
|
|
||||||
|
|
||||||
### FEATURES:
|
|
||||||
* **New command** `hcl2_upgrade` is a JSON to HCL2 transpiler that allows users
|
|
||||||
to transform an existing JSON configuration template into its HCL2 template
|
|
||||||
equivalent. Please see [hcl2_upgrade command
|
|
||||||
docs](https://packer.io/docs/commands/hcl2_upgrade) for more details.
|
|
||||||
[GH-9659]
|
|
||||||
|
|
||||||
### IMPROVEMENTS:
|
|
||||||
* builder/amazon: Add all of the custom AWS template engines to `build`
|
|
||||||
template function for use by provisioners. [GH-9751]
|
|
||||||
* builder/amazon: Add aws_polling config option to override env variables.
|
|
||||||
[GH-9777]
|
|
||||||
* 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
|
|
||||||
progress bar belongs to. [GH-9780]
|
|
||||||
* core: Ui now pretty prints build durations. [GH-9749]
|
|
||||||
* core: When a build is cancelled, Packer will skip postprocessors gracefully
|
|
||||||
rather than failing them. [GH-9720]
|
|
||||||
* integrations/secretsmanager: Add support for plaintext non-key/pair secrets.
|
|
||||||
[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
|
|
||||||
SaltStack formulas. [GH-9726]
|
|
||||||
|
|
||||||
### BUG FIXES:
|
|
||||||
* build: Fix bug in code generator that caused generation to fail in nested
|
|
||||||
packer/packer dirs [GH-9728]
|
|
||||||
* build: Fix Makefile so that default target doesn't crash and creates dev
|
|
||||||
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
|
|
||||||
to hang. [GH-9708]
|
|
||||||
* builder/hyperv: Send boot command in small chunks to make it more stable.
|
|
||||||
[GH-9765]
|
|
||||||
* builder/scaleway: Fix config issue that made scaleway builder fail when used
|
|
||||||
with HCL2 config. [GH-9677]
|
|
||||||
* builder/vmware: Fully destroy vm if it was cancelled or errored. This will
|
|
||||||
make orphaned vms easier to destroy through vCenter. [GH-9782]
|
|
||||||
* builder/vsphere: Fix `alt`, `ctrl`, and `shift` keypresses in the
|
|
||||||
boot_command. [GH-9702] [GH-9739]
|
|
||||||
* builder/vsphere: Fix bug where Packer timed out if two interfaces were
|
|
||||||
defined but only one had an available IP. [GH-9748]
|
|
||||||
* builder/vsphere: Fix the configuration_parameters option so that it is always
|
|
||||||
applied, not just when the tool sync policy is set. [GH-9713]
|
|
||||||
* communicator: Fix `pause_before_connect` option to force a reconnect after
|
|
||||||
the pause. [GH-9772]
|
|
||||||
* core: Make `max_retries` provisioner option a string to allow variable
|
|
||||||
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)
|
## 1.6.1 (July 30, 2020)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -49,8 +49,8 @@
|
|||||||
/builder/proxmox/ @carlpett
|
/builder/proxmox/ @carlpett
|
||||||
/website/pages/docs/builders/proxmox* @carlpett
|
/website/pages/docs/builders/proxmox* @carlpett
|
||||||
|
|
||||||
/builder/scaleway/ @scaleway/devtools
|
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||||
/website/pages/docs/builders/scaleway* @scaleway/devtools
|
/website/pages/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||||
|
|
||||||
/builder/hcloud/ @LKaemmerling
|
/builder/hcloud/ @LKaemmerling
|
||||||
/website/pages/docs/builders/hcloud* @LKaemmerling
|
/website/pages/docs/builders/hcloud* @LKaemmerling
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export GOLDFLAGS
|
|||||||
.PHONY: bin checkversion ci ci-lint default install-build-deps install-gen-deps fmt fmt-docs fmt-examples generate install-lint-deps lint \
|
.PHONY: bin checkversion ci ci-lint default install-build-deps install-gen-deps fmt fmt-docs fmt-examples generate install-lint-deps lint \
|
||||||
releasebin test testacc testrace
|
releasebin test testacc testrace
|
||||||
|
|
||||||
default: install-build-deps install-gen-deps generate dev
|
default: install-build-deps install-gen-deps generate testrace dev releasebin package dev fmt fmt-check mode-check fmt-docs fmt-examples
|
||||||
|
|
||||||
ci: testrace ## Test in continuous integration
|
ci: testrace ## Test in continuous integration
|
||||||
|
|
||||||
|
|||||||
@@ -335,9 +335,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||||
generatedData := awscommon.GetGeneratedDataList()
|
generatedData := []string{"SourceAMIName", "Device", "MountPath"}
|
||||||
generatedData = append(generatedData, "Device", "MountPath")
|
|
||||||
|
|
||||||
return generatedData, warns, nil
|
return generatedData, warns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,15 +396,12 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
GeneratedData: generatedData,
|
GeneratedData: generatedData,
|
||||||
},
|
},
|
||||||
&StepCreateVolume{
|
&StepCreateVolume{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
RootVolumeType: b.config.RootVolumeType,
|
RootVolumeType: b.config.RootVolumeType,
|
||||||
RootVolumeSize: b.config.RootVolumeSize,
|
RootVolumeSize: b.config.RootVolumeSize,
|
||||||
RootVolumeTags: b.config.RootVolumeTags,
|
RootVolumeTags: b.config.RootVolumeTags,
|
||||||
Ctx: b.config.ctx,
|
Ctx: b.config.ctx,
|
||||||
},
|
},
|
||||||
&StepAttachVolume{
|
&StepAttachVolume{},
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
},
|
|
||||||
&StepEarlyUnflock{},
|
&StepEarlyUnflock{},
|
||||||
&chroot.StepPreMountCommands{
|
&chroot.StepPreMountCommands{
|
||||||
Commands: b.config.PreMountCommands,
|
Commands: b.config.PreMountCommands,
|
||||||
@@ -425,14 +420,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
&chroot.StepCopyFiles{
|
&chroot.StepCopyFiles{
|
||||||
Files: b.config.CopyFiles,
|
Files: b.config.CopyFiles,
|
||||||
},
|
},
|
||||||
&awscommon.StepSetGeneratedData{
|
|
||||||
GeneratedData: generatedData,
|
|
||||||
},
|
|
||||||
&chroot.StepChrootProvision{},
|
&chroot.StepChrootProvision{},
|
||||||
&chroot.StepEarlyCleanup{},
|
&chroot.StepEarlyCleanup{},
|
||||||
&StepSnapshot{
|
&StepSnapshot{},
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
},
|
|
||||||
&awscommon.StepDeregisterAMI{
|
&awscommon.StepDeregisterAMI{
|
||||||
AccessConfig: &b.config.AccessConfig,
|
AccessConfig: &b.config.AccessConfig,
|
||||||
ForceDeregister: b.config.AMIForceDeregister,
|
ForceDeregister: b.config.AMIForceDeregister,
|
||||||
@@ -445,7 +435,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||||
EnableAMIENASupport: b.config.AMIENASupport,
|
EnableAMIENASupport: b.config.AMIENASupport,
|
||||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
},
|
},
|
||||||
&awscommon.StepAMIRegionCopy{
|
&awscommon.StepAMIRegionCopy{
|
||||||
AccessConfig: &b.config.AccessConfig,
|
AccessConfig: &b.config.AccessConfig,
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ type FlatConfig struct {
|
|||||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
|
||||||
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" hcl2-schema-generator:"ami_block_device_mappings,direct" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
|
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" hcl2-schema-generator:"ami_block_device_mappings,direct" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
|
||||||
ChrootMounts [][]string `mapstructure:"chroot_mounts" required:"false" cty:"chroot_mounts" hcl:"chroot_mounts"`
|
ChrootMounts [][]string `mapstructure:"chroot_mounts" required:"false" cty:"chroot_mounts" hcl:"chroot_mounts"`
|
||||||
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper" hcl:"command_wrapper"`
|
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper" hcl:"command_wrapper"`
|
||||||
@@ -128,7 +127,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
|
||||||
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
|
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
|
||||||
"chroot_mounts": &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.List(cty.String)), Required: false},
|
"chroot_mounts": &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.List(cty.String)), Required: false},
|
||||||
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
|
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -227,25 +227,10 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
|||||||
if generatedData[0] != "SourceAMIName" {
|
if generatedData[0] != "SourceAMIName" {
|
||||||
t.Fatalf("Generated data should contain SourceAMIName")
|
t.Fatalf("Generated data should contain SourceAMIName")
|
||||||
}
|
}
|
||||||
if generatedData[1] != "BuildRegion" {
|
if generatedData[1] != "Device" {
|
||||||
t.Fatalf("Generated data should contain BuildRegion")
|
|
||||||
}
|
|
||||||
if generatedData[2] != "SourceAMI" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMI")
|
|
||||||
}
|
|
||||||
if generatedData[3] != "SourceAMICreationDate" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMICreationDate")
|
|
||||||
}
|
|
||||||
if generatedData[4] != "SourceAMIOwner" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwner")
|
|
||||||
}
|
|
||||||
if generatedData[5] != "SourceAMIOwnerName" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwnerName")
|
|
||||||
}
|
|
||||||
if generatedData[6] != "Device" {
|
|
||||||
t.Fatalf("Generated data should contain Device")
|
t.Fatalf("Generated data should contain Device")
|
||||||
}
|
}
|
||||||
if generatedData[7] != "MountPath" {
|
if generatedData[2] != "MountPath" {
|
||||||
t.Fatalf("Generated data should contain MountPath")
|
t.Fatalf("Generated data should contain MountPath")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ import (
|
|||||||
// device string - The location where the volume was attached.
|
// device string - The location where the volume was attached.
|
||||||
// attach_cleanup CleanupFunc
|
// attach_cleanup CleanupFunc
|
||||||
type StepAttachVolume struct {
|
type StepAttachVolume struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
attached bool
|
||||||
attached bool
|
volumeId string
|
||||||
volumeId string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
@@ -52,7 +51,7 @@ func (s *StepAttachVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
|||||||
s.volumeId = volumeId
|
s.volumeId = volumeId
|
||||||
|
|
||||||
// Wait for the volume to become attached
|
// Wait for the volume to become attached
|
||||||
err = s.PollingConfig.WaitUntilVolumeAttached(ctx, ec2conn, s.volumeId)
|
err = awscommon.WaitUntilVolumeAttached(ctx, ec2conn, s.volumeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
@@ -88,7 +87,7 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
|
|||||||
s.attached = false
|
s.attached = false
|
||||||
|
|
||||||
// Wait for the volume to detach
|
// Wait for the volume to detach
|
||||||
err = s.PollingConfig.WaitUntilVolumeDetached(aws.BackgroundContext(), ec2conn, s.volumeId)
|
err = awscommon.WaitUntilVolumeDetached(aws.BackgroundContext(), ec2conn, s.volumeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error waiting for volume: %s", err)
|
return fmt.Errorf("Error waiting for volume: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
// Produces:
|
// Produces:
|
||||||
// volume_id string - The ID of the created volume
|
// volume_id string - The ID of the created volume
|
||||||
type StepCreateVolume struct {
|
type StepCreateVolume struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
|
||||||
volumeId string
|
volumeId string
|
||||||
RootVolumeSize int64
|
RootVolumeSize int64
|
||||||
RootVolumeType string
|
RootVolumeType string
|
||||||
@@ -111,7 +110,7 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu
|
|||||||
log.Printf("Volume ID: %s", s.volumeId)
|
log.Printf("Volume ID: %s", s.volumeId)
|
||||||
|
|
||||||
// Wait for the volume to become ready
|
// Wait for the volume to become ready
|
||||||
err = s.PollingConfig.WaitUntilVolumeAvailable(ctx, ec2conn, s.volumeId)
|
err = awscommon.WaitUntilVolumeAvailable(ctx, ec2conn, s.volumeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
// StepRegisterAMI creates the AMI.
|
// StepRegisterAMI creates the AMI.
|
||||||
type StepRegisterAMI struct {
|
type StepRegisterAMI struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
|
||||||
RootVolumeSize int64
|
RootVolumeSize int64
|
||||||
EnableAMIENASupport confighelper.Trilean
|
EnableAMIENASupport confighelper.Trilean
|
||||||
EnableAMISriovNetSupport bool
|
EnableAMISriovNetSupport bool
|
||||||
@@ -82,7 +81,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
|||||||
state.Put("amis", amis)
|
state.Put("amis", amis)
|
||||||
|
|
||||||
ui.Say("Waiting for AMI to become ready...")
|
ui.Say("Waiting for AMI to become ready...")
|
||||||
if err := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
||||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import (
|
|||||||
// Produces:
|
// Produces:
|
||||||
// snapshot_id string - ID of the created snapshot
|
// snapshot_id string - ID of the created snapshot
|
||||||
type StepSnapshot struct {
|
type StepSnapshot struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
snapshotId string
|
||||||
snapshotId string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
@@ -44,7 +43,7 @@ func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
|
|||||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
|
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
|
||||||
|
|
||||||
// Wait for the snapshot to be ready
|
// Wait for the snapshot to be ready
|
||||||
err = s.PollingConfig.WaitUntilSnapshotDone(ctx, ec2conn, s.snapshotId)
|
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, s.snapshotId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|||||||
@@ -138,9 +138,6 @@ type AccessConfig struct {
|
|||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false"`
|
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false"`
|
||||||
// [Polling configuration](#polling-configuration) for the AWS waiter. Configures the waiter that checks
|
|
||||||
// resource state.
|
|
||||||
PollingConfig *AWSPollingConfig `mapstructure:"aws_polling" required:"false"`
|
|
||||||
|
|
||||||
getEC2Connection func() ec2iface.EC2API
|
getEC2Connection func() ec2iface.EC2API
|
||||||
}
|
}
|
||||||
@@ -218,6 +215,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
|||||||
if c.DecodeAuthZMessages {
|
if c.DecodeAuthZMessages {
|
||||||
DecodeAuthZMessages(c.session)
|
DecodeAuthZMessages(c.session)
|
||||||
}
|
}
|
||||||
|
LogEnvOverrideWarnings()
|
||||||
|
|
||||||
return c.session, nil
|
return c.session, nil
|
||||||
}
|
}
|
||||||
@@ -298,11 +296,6 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
|||||||
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
|
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.PollingConfig == nil {
|
|
||||||
c.PollingConfig = new(AWSPollingConfig)
|
|
||||||
}
|
|
||||||
c.PollingConfig.LogEnvOverrideWarnings()
|
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ func testAccessConfig() *AccessConfig {
|
|||||||
getEC2Connection: func() ec2iface.EC2API {
|
getEC2Connection: func() ec2iface.EC2API {
|
||||||
return &mockEC2Client{}
|
return &mockEC2Client{}
|
||||||
},
|
},
|
||||||
PollingConfig: new(AWSPollingConfig),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,32 +79,18 @@ type AMIConfig struct {
|
|||||||
// Default false.
|
// Default false.
|
||||||
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot" required:"false"`
|
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot" required:"false"`
|
||||||
// Whether or not to encrypt the resulting AMI when
|
// Whether or not to encrypt the resulting AMI when
|
||||||
// copying a provisioned instance to an AMI. By default, Packer will keep
|
// copying a provisioned instance to an AMI. By default, Packer will keep the
|
||||||
// the encryption setting to what it was in the source image. Setting false
|
// encryption setting to what it was in the source image. Setting false will
|
||||||
// will result in an unencrypted image, and true will result in an encrypted
|
// result in an unencrypted image, and true will result in an encrypted one.
|
||||||
// one.
|
|
||||||
//
|
|
||||||
// If you have used the `launch_block_device_mappings` to set an encryption
|
// If you have used the `launch_block_device_mappings` to set an encryption
|
||||||
// key and that key is the same as the one you want the image encrypted with
|
// key and that key is the same as the one you want the image encrypted with
|
||||||
// at the end, then you don't need to set this field; leaving it empty will
|
// at the end, then you don't need to set this field; leaving it empty will
|
||||||
// prevent an unnecessary extra copy step and save you some time.
|
// prevent an unnecessary extra copy step and save you some time.
|
||||||
AMIEncryptBootVolume config.Trilean `mapstructure:"encrypt_boot" required:"false"`
|
AMIEncryptBootVolume config.Trilean `mapstructure:"encrypt_boot" required:"false"`
|
||||||
// ID, alias or ARN of the KMS key to use for AMI encryption. This
|
// ID, alias or ARN of the KMS key to use for boot volume encryption. This
|
||||||
// only applies to the main `region` -- any regions the AMI gets copied to
|
// only applies to the main `region`, other regions where the AMI will be
|
||||||
// copied will be encrypted by the default EBS KMS key for that region,
|
// copied will be encrypted by the default EBS KMS key. For valid formats
|
||||||
// unless you set region-specific keys in AMIRegionKMSKeyIDs.
|
// see *KmsKeyId* in the [AWS API docs -
|
||||||
//
|
|
||||||
// Set this value if you select `encrypt_boot`, but don't want to use the
|
|
||||||
// region's default KMS key.
|
|
||||||
//
|
|
||||||
// If you have a custom kms key you'd like to apply to the launch volume,
|
|
||||||
// and are only building in one region, it is more efficient to leave this
|
|
||||||
// and `encrypt_boot` empty and to instead set the key id in the
|
|
||||||
// launch_block_device_mappings (you can find an example below). This saves
|
|
||||||
// potentially many minutes at the end of the build by preventing Packer
|
|
||||||
// from having to copy and re-encrypt the image at the end of the build.
|
|
||||||
//
|
|
||||||
// For valid formats see *KmsKeyId* in the [AWS API docs -
|
|
||||||
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
|
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html).
|
||||||
// This field is validated by Packer, when using an alias, you will have to
|
// This field is validated by Packer, when using an alias, you will have to
|
||||||
// prefix `kms_key_id` with `alias/`.
|
// prefix `kms_key_id` with `alias/`.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These will be attached when launching your instance. Your
|
// These will be attached when booting a new instance from your AMI. Your
|
||||||
// options here may vary depending on the type of VM you use.
|
// options here may vary depending on the type of VM you use.
|
||||||
//
|
//
|
||||||
// Example use case:
|
// Example use case:
|
||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
// JSON example:
|
// JSON example:
|
||||||
//
|
//
|
||||||
// ```json
|
// ```json
|
||||||
// launch_block_device_mappings: [
|
// ami_block_device_mappings: [
|
||||||
// {
|
// {
|
||||||
// "device_name": "/dev/sda1",
|
// "device_name": "/dev/sda1",
|
||||||
// "encrypted": true,
|
// "encrypted": true,
|
||||||
@@ -36,16 +36,13 @@ import (
|
|||||||
// HCL2 example:
|
// HCL2 example:
|
||||||
//
|
//
|
||||||
// ```hcl
|
// ```hcl
|
||||||
// launch_block_device_mappings {
|
// ami_block_device_mappings {
|
||||||
// device_name = "/dev/sda1"
|
// device_name = "/dev/sda1"
|
||||||
// encrypted = true
|
// encrypted = true
|
||||||
// kms_key_id = "1a2b3c4d-5e6f-1a2b-3c4d-5e6f1a2b3c4d"
|
// kms_key_id = "1a2b3c4d-5e6f-1a2b-3c4d-5e6f1a2b3c4d"
|
||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// Please note that the kms_key_id option in this example exists for
|
|
||||||
// launch_block_device_mappings but not ami_block_device_mappings.
|
|
||||||
//
|
|
||||||
// Documentation for Block Devices Mappings can be found here:
|
// Documentation for Block Devices Mappings can be found here:
|
||||||
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
|
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
|
||||||
//
|
//
|
||||||
@@ -83,14 +80,12 @@ type BlockDevice struct {
|
|||||||
// The size of the volume, in GiB. Required if not specifying a
|
// The size of the volume, in GiB. Required if not specifying a
|
||||||
// snapshot_id.
|
// snapshot_id.
|
||||||
VolumeSize int64 `mapstructure:"volume_size" required:"false"`
|
VolumeSize int64 `mapstructure:"volume_size" required:"false"`
|
||||||
// ID, alias or ARN of the KMS key to use for boot volume encryption.
|
// ID, alias or ARN of the KMS key to use for boot volume encryption. This
|
||||||
// This option exists for launch_block_device_mappings but not
|
// only applies to the main region, other regions where the AMI will be
|
||||||
// ami_block_device_mappings. The kms key id defined here only applies to
|
// copied will be encrypted by the default EBS KMS key. For valid formats
|
||||||
// the original build region; if the AMI gets copied to other regions, the
|
// see KmsKeyId in the [AWS API docs -
|
||||||
// volume in those regions will be encrypted by the default EBS KMS key.
|
|
||||||
// For valid formats see KmsKeyId in the [AWS API docs -
|
|
||||||
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html)
|
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html)
|
||||||
// This field is validated by Packer. When using an alias, you will have to
|
// This field is validated by Packer, when using an alias, you will have to
|
||||||
// prefix kms_key_id with alias/.
|
// prefix kms_key_id with alias/.
|
||||||
KmsKeyId string `mapstructure:"kms_key_id" required:"false"`
|
KmsKeyId string `mapstructure:"kms_key_id" required:"false"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,24 +40,6 @@ func extractBuildInfo(region string, state multistep.StateBag, generatedData *bu
|
|||||||
SourceAMIOwnerName: aws.StringValue(sourceAMI.ImageOwnerAlias),
|
SourceAMIOwnerName: aws.StringValue(sourceAMI.ImageOwnerAlias),
|
||||||
SourceAMITags: sourceAMITags,
|
SourceAMITags: sourceAMITags,
|
||||||
}
|
}
|
||||||
|
|
||||||
generatedData.Put("BuildRegion", buildInfoTemplate.BuildRegion)
|
|
||||||
generatedData.Put("SourceAMI", buildInfoTemplate.SourceAMI)
|
|
||||||
generatedData.Put("SourceAMICreationDate", buildInfoTemplate.SourceAMICreationDate)
|
|
||||||
generatedData.Put("SourceAMIName", buildInfoTemplate.SourceAMIName)
|
generatedData.Put("SourceAMIName", buildInfoTemplate.SourceAMIName)
|
||||||
generatedData.Put("SourceAMIOwner", buildInfoTemplate.SourceAMIOwner)
|
|
||||||
generatedData.Put("SourceAMIOwnerName", buildInfoTemplate.SourceAMIOwnerName)
|
|
||||||
|
|
||||||
return buildInfoTemplate
|
return buildInfoTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeneratedDataList() []string {
|
|
||||||
return []string{
|
|
||||||
"SourceAMIName",
|
|
||||||
"BuildRegion",
|
|
||||||
"SourceAMI",
|
|
||||||
"SourceAMICreationDate",
|
|
||||||
"SourceAMIOwner",
|
|
||||||
"SourceAMIOwnerName",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ type VpcFilterOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Statement struct {
|
type Statement struct {
|
||||||
Effect string `mapstructure:"Effect" required:"false"`
|
Effect string
|
||||||
Action []string `mapstructure:"Action" required:"false"`
|
Action []string
|
||||||
Resource []string `mapstructure:"Resource" required:"false"`
|
Resource []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PolicyDocument struct {
|
type PolicyDocument struct {
|
||||||
Version string `mapstructure:"Version" required:"false"`
|
Version string
|
||||||
Statement []Statement `mapstructure:"Statement" required:"false"`
|
Statement []Statement
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecurityGroupFilterOptions struct {
|
type SecurityGroupFilterOptions struct {
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
|
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatPolicyDocument struct {
|
type FlatPolicyDocument struct {
|
||||||
Version *string `mapstructure:"Version" required:"false" cty:"Version" hcl:"Version"`
|
Version *string `cty:"version" hcl:"version"`
|
||||||
Statement []FlatStatement `mapstructure:"Statement" required:"false" cty:"Statement" hcl:"Statement"`
|
Statement []FlatStatement `cty:"statement" hcl:"statement"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatPolicyDocument.
|
// FlatMapstructure returns a new FlatPolicyDocument.
|
||||||
@@ -55,8 +55,8 @@ func (*PolicyDocument) FlatMapstructure() interface{ HCL2Spec() map[string]hclde
|
|||||||
// The decoded values from this spec will then be applied to a FlatPolicyDocument.
|
// The decoded values from this spec will then be applied to a FlatPolicyDocument.
|
||||||
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
|
func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec {
|
||||||
s := map[string]hcldec.Spec{
|
s := map[string]hcldec.Spec{
|
||||||
"Version": &hcldec.AttrSpec{Name: "Version", Type: cty.String, Required: false},
|
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
||||||
"Statement": &hcldec.BlockListSpec{TypeName: "Statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
|
"statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())},
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -89,9 +89,9 @@ func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
// FlatStatement is an auto-generated flat version of Statement.
|
// FlatStatement is an auto-generated flat version of Statement.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatStatement struct {
|
type FlatStatement struct {
|
||||||
Effect *string `mapstructure:"Effect" required:"false" cty:"Effect" hcl:"Effect"`
|
Effect *string `cty:"effect" hcl:"effect"`
|
||||||
Action []string `mapstructure:"Action" required:"false" cty:"Action" hcl:"Action"`
|
Action []string `cty:"action" hcl:"action"`
|
||||||
Resource []string `mapstructure:"Resource" required:"false" cty:"Resource" hcl:"Resource"`
|
Resource []string `cty:"resource" hcl:"resource"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatStatement.
|
// FlatMapstructure returns a new FlatStatement.
|
||||||
@@ -106,9 +106,9 @@ func (*Statement) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spe
|
|||||||
// The decoded values from this spec will then be applied to a FlatStatement.
|
// The decoded values from this spec will then be applied to a FlatStatement.
|
||||||
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
|
func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec {
|
||||||
s := map[string]hcldec.Spec{
|
s := map[string]hcldec.Spec{
|
||||||
"Effect": &hcldec.AttrSpec{Name: "Effect", Type: cty.String, Required: false},
|
"effect": &hcldec.AttrSpec{Name: "effect", Type: cty.String, Required: false},
|
||||||
"Action": &hcldec.AttrSpec{Name: "Action", Type: cty.List(cty.String), Required: false},
|
"action": &hcldec.AttrSpec{Name: "action", Type: cty.List(cty.String), Required: false},
|
||||||
"Resource": &hcldec.AttrSpec{Name: "Resource", Type: cty.List(cty.String), Required: false},
|
"resource": &hcldec.AttrSpec{Name: "resource", Type: cty.List(cty.String), Required: false},
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:generate struct-markdown
|
|
||||||
//go:generate mapstructure-to-hcl2 -type AWSPollingConfig
|
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -40,45 +38,12 @@ type StateChangeConf struct {
|
|||||||
// Following are wrapper functions that use Packer's environment-variables to
|
// Following are wrapper functions that use Packer's environment-variables to
|
||||||
// determine retry logic, then call the AWS SDK's built-in waiters.
|
// determine retry logic, then call the AWS SDK's built-in waiters.
|
||||||
|
|
||||||
// Polling configuration for the AWS waiter. Configures the waiter for resources creation or actions like attaching
|
func WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.EC2API, imageId string) error {
|
||||||
// volumes or importing image.
|
|
||||||
// Usage example:
|
|
||||||
//
|
|
||||||
// In JSON:
|
|
||||||
// ```json
|
|
||||||
// "aws_polling" : {
|
|
||||||
// "delay_seconds": 30,
|
|
||||||
// "max_attempts": 50
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// In HCL2:
|
|
||||||
// ```hcl
|
|
||||||
// aws_polling {
|
|
||||||
// delay_seconds = 30
|
|
||||||
// max_attempts = 50
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
type AWSPollingConfig struct {
|
|
||||||
// Specifies the maximum number of attempts the waiter will check for resource state.
|
|
||||||
// This value can also be set via the AWS_MAX_ATTEMPTS.
|
|
||||||
// If both option and environment variable are set, the max_attempts will be considered over the AWS_MAX_ATTEMPTS.
|
|
||||||
// If none is set, defaults to AWS waiter default which is 40 max_attempts.
|
|
||||||
MaxAttempts int `mapstructure:"max_attempts" required:"false"`
|
|
||||||
// Specifies the delay in seconds between attempts to check the resource state.
|
|
||||||
// This value can also be set via the AWS_POLL_DELAY_SECONDS.
|
|
||||||
// If both option and environment variable are set, the delay_seconds will be considered over the AWS_POLL_DELAY_SECONDS.
|
|
||||||
// If none is set, defaults to AWS waiter default which is 15 seconds.
|
|
||||||
DelaySeconds int `mapstructure:"delay_seconds" required:"false"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.EC2API, imageId string) error {
|
|
||||||
imageInput := ec2.DescribeImagesInput{
|
imageInput := ec2.DescribeImagesInput{
|
||||||
ImageIds: []*string{&imageId},
|
ImageIds: []*string{&imageId},
|
||||||
}
|
}
|
||||||
|
|
||||||
waitOpts := w.getWaiterOptions()
|
waitOpts := getWaiterOptions()
|
||||||
if len(waitOpts) == 0 {
|
if len(waitOpts) == 0 {
|
||||||
// Bump this default to 30 minutes because the aws default
|
// Bump this default to 30 minutes because the aws default
|
||||||
// of ten minutes doesn't work for some of our long-running copies.
|
// of ten minutes doesn't work for some of our long-running copies.
|
||||||
@@ -101,7 +66,7 @@ func (w *AWSPollingConfig) WaitUntilAMIAvailable(ctx aws.Context, conn ec2iface.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilInstanceRunning(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
|
func WaitUntilInstanceRunning(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
|
||||||
|
|
||||||
instanceInput := ec2.DescribeInstancesInput{
|
instanceInput := ec2.DescribeInstancesInput{
|
||||||
InstanceIds: []*string{&instanceId},
|
InstanceIds: []*string{&instanceId},
|
||||||
@@ -110,11 +75,12 @@ func (w *AWSPollingConfig) WaitUntilInstanceRunning(ctx aws.Context, conn *ec2.E
|
|||||||
err := conn.WaitUntilInstanceRunningWithContext(
|
err := conn.WaitUntilInstanceRunningWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
&instanceInput,
|
&instanceInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilInstanceTerminated(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
|
func WaitUntilInstanceTerminated(ctx aws.Context, conn *ec2.EC2, instanceId string) error {
|
||||||
|
|
||||||
instanceInput := ec2.DescribeInstancesInput{
|
instanceInput := ec2.DescribeInstancesInput{
|
||||||
InstanceIds: []*string{&instanceId},
|
InstanceIds: []*string{&instanceId},
|
||||||
}
|
}
|
||||||
@@ -122,12 +88,12 @@ func (w *AWSPollingConfig) WaitUntilInstanceTerminated(ctx aws.Context, conn *ec
|
|||||||
err := conn.WaitUntilInstanceTerminatedWithContext(
|
err := conn.WaitUntilInstanceTerminatedWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
&instanceInput,
|
&instanceInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function works for both requesting and cancelling spot instances.
|
// This function works for both requesting and cancelling spot instances.
|
||||||
func (w *AWSPollingConfig) WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *ec2.EC2, spotRequestId string) error {
|
func WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *ec2.EC2, spotRequestId string) error {
|
||||||
spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{
|
spotRequestInput := ec2.DescribeSpotInstanceRequestsInput{
|
||||||
SpotInstanceRequestIds: []*string{&spotRequestId},
|
SpotInstanceRequestIds: []*string{&spotRequestId},
|
||||||
}
|
}
|
||||||
@@ -135,11 +101,11 @@ func (w *AWSPollingConfig) WaitUntilSpotRequestFulfilled(ctx aws.Context, conn *
|
|||||||
err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(
|
err := conn.WaitUntilSpotInstanceRequestFulfilledWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
&spotRequestInput,
|
&spotRequestInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
func WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
||||||
volumeInput := ec2.DescribeVolumesInput{
|
volumeInput := ec2.DescribeVolumesInput{
|
||||||
VolumeIds: []*string{&volumeId},
|
VolumeIds: []*string{&volumeId},
|
||||||
}
|
}
|
||||||
@@ -147,11 +113,11 @@ func (w *AWSPollingConfig) WaitUntilVolumeAvailable(ctx aws.Context, conn *ec2.E
|
|||||||
err := conn.WaitUntilVolumeAvailableWithContext(
|
err := conn.WaitUntilVolumeAvailableWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
&volumeInput,
|
&volumeInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2, snapshotID string) error {
|
func WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2, snapshotID string) error {
|
||||||
snapInput := ec2.DescribeSnapshotsInput{
|
snapInput := ec2.DescribeSnapshotsInput{
|
||||||
SnapshotIds: []*string{&snapshotID},
|
SnapshotIds: []*string{&snapshotID},
|
||||||
}
|
}
|
||||||
@@ -159,13 +125,13 @@ func (w *AWSPollingConfig) WaitUntilSnapshotDone(ctx aws.Context, conn *ec2.EC2,
|
|||||||
err := conn.WaitUntilSnapshotCompletedWithContext(
|
err := conn.WaitUntilSnapshotCompletedWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
&snapInput,
|
&snapInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrappers for our custom AWS waiters
|
// Wrappers for our custom AWS waiters
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
func WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
||||||
volumeInput := ec2.DescribeVolumesInput{
|
volumeInput := ec2.DescribeVolumesInput{
|
||||||
VolumeIds: []*string{&volumeId},
|
VolumeIds: []*string{&volumeId},
|
||||||
}
|
}
|
||||||
@@ -173,11 +139,11 @@ func (w *AWSPollingConfig) WaitUntilVolumeAttached(ctx aws.Context, conn *ec2.EC
|
|||||||
err := WaitForVolumeToBeAttached(conn,
|
err := WaitForVolumeToBeAttached(conn,
|
||||||
ctx,
|
ctx,
|
||||||
&volumeInput,
|
&volumeInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
func WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC2, volumeId string) error {
|
||||||
volumeInput := ec2.DescribeVolumesInput{
|
volumeInput := ec2.DescribeVolumesInput{
|
||||||
VolumeIds: []*string{&volumeId},
|
VolumeIds: []*string{&volumeId},
|
||||||
}
|
}
|
||||||
@@ -185,11 +151,11 @@ func (w *AWSPollingConfig) WaitUntilVolumeDetached(ctx aws.Context, conn *ec2.EC
|
|||||||
err := WaitForVolumeToBeDetached(conn,
|
err := WaitForVolumeToBeDetached(conn,
|
||||||
ctx,
|
ctx,
|
||||||
&volumeInput,
|
&volumeInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2, taskID string) error {
|
func WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2, taskID string) error {
|
||||||
importInput := ec2.DescribeImportImageTasksInput{
|
importInput := ec2.DescribeImportImageTasksInput{
|
||||||
ImportTaskIds: []*string{&taskID},
|
ImportTaskIds: []*string{&taskID},
|
||||||
}
|
}
|
||||||
@@ -197,7 +163,7 @@ func (w *AWSPollingConfig) WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2
|
|||||||
err := WaitForImageToBeImported(conn,
|
err := WaitForImageToBeImported(conn,
|
||||||
ctx,
|
ctx,
|
||||||
&importInput,
|
&importInput,
|
||||||
w.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,18 +298,8 @@ type overridableWaitVars struct {
|
|||||||
awsTimeoutSeconds envInfo
|
awsTimeoutSeconds envInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) getWaiterOptions() []request.WaiterOption {
|
func getWaiterOptions() []request.WaiterOption {
|
||||||
envOverrides := getEnvOverrides()
|
envOverrides := getEnvOverrides()
|
||||||
|
|
||||||
if w.MaxAttempts != 0 {
|
|
||||||
envOverrides.awsMaxAttempts.Val = w.MaxAttempts
|
|
||||||
envOverrides.awsMaxAttempts.overridden = true
|
|
||||||
}
|
|
||||||
if w.DelaySeconds != 0 {
|
|
||||||
envOverrides.awsPollDelaySeconds.Val = w.DelaySeconds
|
|
||||||
envOverrides.awsPollDelaySeconds.overridden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
waitOpts := applyEnvOverrides(envOverrides)
|
waitOpts := applyEnvOverrides(envOverrides)
|
||||||
return waitOpts
|
return waitOpts
|
||||||
}
|
}
|
||||||
@@ -377,38 +333,33 @@ func getEnvOverrides() overridableWaitVars {
|
|||||||
return envValues
|
return envValues
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AWSPollingConfig) LogEnvOverrideWarnings() {
|
func LogEnvOverrideWarnings() {
|
||||||
pollDelayEnv := os.Getenv("AWS_POLL_DELAY_SECONDS")
|
pollDelay := os.Getenv("AWS_POLL_DELAY_SECONDS")
|
||||||
timeoutSecondsEnv := os.Getenv("AWS_TIMEOUT_SECONDS")
|
timeoutSeconds := os.Getenv("AWS_TIMEOUT_SECONDS")
|
||||||
maxAttemptsEnv := os.Getenv("AWS_MAX_ATTEMPTS")
|
maxAttempts := os.Getenv("AWS_MAX_ATTEMPTS")
|
||||||
|
|
||||||
maxAttemptsIsSet := maxAttemptsEnv != "" || w.MaxAttempts != 0
|
if maxAttempts != "" && timeoutSeconds != "" {
|
||||||
timeoutSecondsIsSet := timeoutSecondsEnv != ""
|
|
||||||
pollDelayIsSet := pollDelayEnv != "" || w.DelaySeconds != 0
|
|
||||||
|
|
||||||
if maxAttemptsIsSet && timeoutSecondsIsSet {
|
|
||||||
warning := fmt.Sprintf("[WARNING] (aws): AWS_MAX_ATTEMPTS and " +
|
warning := fmt.Sprintf("[WARNING] (aws): AWS_MAX_ATTEMPTS and " +
|
||||||
"AWS_TIMEOUT_SECONDS are both set. Packer will use " +
|
"AWS_TIMEOUT_SECONDS are both set. Packer will use " +
|
||||||
"AWS_MAX_ATTEMPTS and discard AWS_TIMEOUT_SECONDS.")
|
"AWS_MAX_ATTEMPTS and discard AWS_TIMEOUT_SECONDS.")
|
||||||
if !pollDelayIsSet {
|
if pollDelay == "" {
|
||||||
warning = fmt.Sprintf("%s Since you have not set the poll delay, "+
|
warning = fmt.Sprintf("%s Since you have not set the poll delay, "+
|
||||||
"Packer will default to a 2-second delay.", warning)
|
"Packer will default to a 2-second delay.", warning)
|
||||||
}
|
}
|
||||||
log.Printf(warning)
|
log.Printf(warning)
|
||||||
} else if timeoutSecondsIsSet {
|
} else if timeoutSeconds != "" {
|
||||||
log.Printf("[WARNING] (aws): env var AWS_TIMEOUT_SECONDS is " +
|
log.Printf("[WARNING] (aws): env var AWS_TIMEOUT_SECONDS is " +
|
||||||
"deprecated in favor of AWS_MAX_ATTEMPTS env or aws_polling_max_attempts config option. " +
|
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
|
||||||
"If you have not explicitly set AWS_POLL_DELAY_SECONDS env or aws_polling_delay_seconds config option, " +
|
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
|
||||||
"we are defaulting to a poll delay of 2 seconds, regardless of the AWS waiter's default.")
|
"poll delay of 2 seconds, regardless of the AWS waiter's default.")
|
||||||
}
|
}
|
||||||
if !maxAttemptsIsSet && !timeoutSecondsIsSet && !pollDelayIsSet {
|
if maxAttempts == "" && timeoutSeconds == "" && pollDelay == "" {
|
||||||
log.Printf("[INFO] (aws): No AWS timeout and polling overrides have been set. " +
|
log.Printf("[INFO] (aws): No AWS timeout and polling overrides have been set. " +
|
||||||
"Packer will default to waiter-specific delays and timeouts. If you would " +
|
"Packer will default to waiter-specific delays and timeouts. If you would " +
|
||||||
"like to customize the length of time between retries and max " +
|
"like to customize the length of time between retries and max " +
|
||||||
"number of retries you may do so by setting the environment " +
|
"number of retries you may do so by setting the environment " +
|
||||||
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS or the " +
|
"variables AWS_POLL_DELAY_SECONDS and AWS_MAX_ATTEMPTS to your " +
|
||||||
"configuration options aws_polling_delay_seconds and aws_polling_max_attempts " +
|
"desired values.")
|
||||||
"to your desired values.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type AWSPollingConfig"; DO NOT EDIT.
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatAWSPollingConfig is an auto-generated flat version of AWSPollingConfig.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatAWSPollingConfig struct {
|
|
||||||
MaxAttempts *int `mapstructure:"max_attempts" required:"false" cty:"max_attempts" hcl:"max_attempts"`
|
|
||||||
DelaySeconds *int `mapstructure:"delay_seconds" required:"false" cty:"delay_seconds" hcl:"delay_seconds"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatAWSPollingConfig.
|
|
||||||
// FlatAWSPollingConfig is an auto-generated flat version of AWSPollingConfig.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*AWSPollingConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatAWSPollingConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a AWSPollingConfig.
|
|
||||||
// This spec is used by HCL to read the fields of AWSPollingConfig.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatAWSPollingConfig.
|
|
||||||
func (*FlatAWSPollingConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"max_attempts": &hcldec.AttrSpec{Name: "max_attempts", Type: cty.Number, Required: false},
|
|
||||||
"delay_seconds": &hcldec.AttrSpec{Name: "delay_seconds", Type: cty.Number, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
@@ -209,7 +209,7 @@ func (s *StepAMIRegionCopy) amiRegionCopy(ctx context.Context, state multistep.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the image to become ready
|
// Wait for the image to become ready
|
||||||
if err := s.AccessConfig.PollingConfig.WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
|
if err := WaitUntilAMIAvailable(ctx, regionconn, *resp.ImageId); err != nil {
|
||||||
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
|
return "", snapshotIds, fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s",
|
||||||
*resp.ImageId, target, err)
|
*resp.ImageId, target, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StepRunSourceInstance struct {
|
type StepRunSourceInstance struct {
|
||||||
PollingConfig *AWSPollingConfig
|
|
||||||
AssociatePublicIpAddress bool
|
AssociatePublicIpAddress bool
|
||||||
LaunchMappings EC2BlockDeviceMappingsBuilder
|
LaunchMappings EC2BlockDeviceMappingsBuilder
|
||||||
Comm *communicator.Config
|
Comm *communicator.Config
|
||||||
@@ -235,7 +234,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
|||||||
InstanceIds: []*string{aws.String(instanceId)},
|
InstanceIds: []*string{aws.String(instanceId)},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.PollingConfig.WaitUntilInstanceRunning(ctx, ec2conn, instanceId); err != nil {
|
if err := WaitUntilInstanceRunning(ctx, ec2conn, instanceId); err != nil {
|
||||||
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
|
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
@@ -365,7 +364,7 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.PollingConfig.WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
|
if err := WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/request"
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/packer/common/random"
|
"github.com/hashicorp/packer/common/random"
|
||||||
"github.com/hashicorp/packer/common/retry"
|
"github.com/hashicorp/packer/common/retry"
|
||||||
@@ -25,7 +23,6 @@ type EC2BlockDeviceMappingsBuilder interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StepRunSpotInstance struct {
|
type StepRunSpotInstance struct {
|
||||||
PollingConfig *AWSPollingConfig
|
|
||||||
AssociatePublicIpAddress bool
|
AssociatePublicIpAddress bool
|
||||||
LaunchMappings EC2BlockDeviceMappingsBuilder
|
LaunchMappings EC2BlockDeviceMappingsBuilder
|
||||||
BlockDurationMinutes int64
|
BlockDurationMinutes int64
|
||||||
@@ -280,39 +277,23 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
|||||||
Type: aws.String("instant"),
|
Type: aws.String("instant"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var createOutput *ec2.CreateFleetOutput
|
|
||||||
|
|
||||||
err = retry.Config{
|
|
||||||
Tries: 11,
|
|
||||||
ShouldRetry: func(err error) bool {
|
|
||||||
if strings.Contains(err.Error(), "Invalid IAM Instance Profile name") {
|
|
||||||
// eventual consistency of the profile. PutRolePolicy &
|
|
||||||
// AddRoleToInstanceProfile are eventually consistent and once
|
|
||||||
// we can wait on those operations, this can be removed.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return request.IsErrorRetryable(err)
|
|
||||||
},
|
|
||||||
RetryDelay: (&retry.Backoff{InitialBackoff: 500 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
|
||||||
}.Run(ctx, func(ctx context.Context) error {
|
|
||||||
createOutput, err = ec2conn.CreateFleet(createFleetInput)
|
|
||||||
|
|
||||||
if err == nil && createOutput.Errors != nil {
|
|
||||||
err = fmt.Errorf("errors: %v", createOutput.Errors)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("create request failed %v", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create the request for the spot instance.
|
// Create the request for the spot instance.
|
||||||
|
req, createOutput := ec2conn.CreateFleetRequest(createFleetInput)
|
||||||
|
ui.Message(fmt.Sprintf("Sending spot request (%s)...", req.RequestID))
|
||||||
|
// Actually send the spot connection request.
|
||||||
|
err = req.Send()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if createOutput.FleetId != nil {
|
if createOutput.FleetId != nil {
|
||||||
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
|
err = fmt.Errorf("Error waiting for fleet request (%s): %s", *createOutput.FleetId, err)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Error waiting for fleet request: %s", err)
|
err = fmt.Errorf("Error waiting for fleet request: %s", err)
|
||||||
}
|
}
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(createOutput.Instances) == 0 {
|
||||||
// We can end up with errors because one of the allowed availability
|
// We can end up with errors because one of the allowed availability
|
||||||
// zones doesn't have one of the allowed instance types; as long as
|
// zones doesn't have one of the allowed instance types; as long as
|
||||||
// an instance is launched, these errors aren't important.
|
// an instance is launched, these errors aren't important.
|
||||||
@@ -326,9 +307,6 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
|||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceId = *createOutput.Instances[0].InstanceIds[0]
|
instanceId = *createOutput.Instances[0].InstanceIds[0]
|
||||||
@@ -488,7 +466,7 @@ func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.PollingConfig.WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
|
if err := WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil {
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ func tStateSpot() multistep.StateBag {
|
|||||||
|
|
||||||
func getBasicStep() *StepRunSpotInstance {
|
func getBasicStep() *StepRunSpotInstance {
|
||||||
stepRunSpotInstance := StepRunSpotInstance{
|
stepRunSpotInstance := StepRunSpotInstance{
|
||||||
PollingConfig: new(AWSPollingConfig),
|
|
||||||
AssociatePublicIpAddress: false,
|
AssociatePublicIpAddress: false,
|
||||||
LaunchMappings: BlockDevices{},
|
LaunchMappings: BlockDevices{},
|
||||||
BlockDurationMinutes: 0,
|
BlockDurationMinutes: 0,
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
|
||||||
"github.com/hashicorp/packer/builder"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
|
||||||
)
|
|
||||||
|
|
||||||
// &awscommon.StepSetGeneratedData{
|
|
||||||
// GeneratedData: generatedData,
|
|
||||||
// },
|
|
||||||
|
|
||||||
type StepSetGeneratedData struct {
|
|
||||||
GeneratedData *builder.GeneratedData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepSetGeneratedData) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
|
||||||
|
|
||||||
extractBuildInfo(*ec2conn.Config.Region, state, s.GeneratedData)
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepSetGeneratedData) Cleanup(state multistep.StateBag) {
|
|
||||||
// No cleanup...
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StepStopEBSBackedInstance struct {
|
type StepStopEBSBackedInstance struct {
|
||||||
PollingConfig *AWSPollingConfig
|
|
||||||
Skip bool
|
Skip bool
|
||||||
DisableStopInstance bool
|
DisableStopInstance bool
|
||||||
}
|
}
|
||||||
@@ -75,7 +74,7 @@ func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.Sta
|
|||||||
&ec2.DescribeInstancesInput{
|
&ec2.DescribeInstancesInput{
|
||||||
InstanceIds: []*string{instance.InstanceId},
|
InstanceIds: []*string{instance.InstanceId},
|
||||||
},
|
},
|
||||||
s.PollingConfig.getWaiterOptions()...)
|
getWaiterOptions()...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
|
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||||||
|
|
||||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||||
|
|
||||||
generatedData := awscommon.GetGeneratedDataList()
|
generatedData := []string{"SourceAMIName"}
|
||||||
return generatedData, warns, nil
|
return generatedData, warns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +169,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
|
|
||||||
if b.config.IsSpotInstance() {
|
if b.config.IsSpotInstance() {
|
||||||
instanceStep = &awscommon.StepRunSpotInstance{
|
instanceStep = &awscommon.StepRunSpotInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.LaunchMappings,
|
LaunchMappings: b.config.LaunchMappings,
|
||||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||||
@@ -192,7 +191,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
instanceStep = &awscommon.StepRunSourceInstance{
|
instanceStep = &awscommon.StepRunSourceInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.LaunchMappings,
|
LaunchMappings: b.config.LaunchMappings,
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
@@ -286,15 +284,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
),
|
),
|
||||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||||
},
|
},
|
||||||
&awscommon.StepSetGeneratedData{
|
|
||||||
GeneratedData: generatedData,
|
|
||||||
},
|
|
||||||
&common.StepProvision{},
|
&common.StepProvision{},
|
||||||
&common.StepCleanupTempKeys{
|
&common.StepCleanupTempKeys{
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
},
|
},
|
||||||
&awscommon.StepStopEBSBackedInstance{
|
&awscommon.StepStopEBSBackedInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
Skip: b.config.IsSpotInstance(),
|
Skip: b.config.IsSpotInstance(),
|
||||||
DisableStopInstance: b.config.DisableStopInstance,
|
DisableStopInstance: b.config.DisableStopInstance,
|
||||||
},
|
},
|
||||||
@@ -311,7 +305,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
},
|
},
|
||||||
&stepCreateAMI{
|
&stepCreateAMI{
|
||||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
},
|
},
|
||||||
&awscommon.StepAMIRegionCopy{
|
&awscommon.StepAMIRegionCopy{
|
||||||
AccessConfig: &b.config.AccessConfig,
|
AccessConfig: &b.config.AccessConfig,
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ type FlatConfig struct {
|
|||||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
|
||||||
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name" hcl:"ami_name"`
|
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name" hcl:"ami_name"`
|
||||||
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description" hcl:"ami_description"`
|
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description" hcl:"ami_description"`
|
||||||
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type" hcl:"ami_virtualization_type"`
|
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type" hcl:"ami_virtualization_type"`
|
||||||
@@ -172,7 +171,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
|
||||||
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
|
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
|
||||||
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
|
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
|
||||||
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
|
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -144,25 +144,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
|||||||
if len(generatedData) == 0 {
|
if len(generatedData) == 0 {
|
||||||
t.Fatalf("Generated data should not be empty")
|
t.Fatalf("Generated data should not be empty")
|
||||||
}
|
}
|
||||||
if len(generatedData) == 0 {
|
|
||||||
t.Fatalf("Generated data should not be empty")
|
|
||||||
}
|
|
||||||
if generatedData[0] != "SourceAMIName" {
|
if generatedData[0] != "SourceAMIName" {
|
||||||
t.Fatalf("Generated data should contain SourceAMIName")
|
t.Fatalf("Generated data should contain SourceAMIName")
|
||||||
}
|
}
|
||||||
if generatedData[1] != "BuildRegion" {
|
|
||||||
t.Fatalf("Generated data should contain BuildRegion")
|
|
||||||
}
|
|
||||||
if generatedData[2] != "SourceAMI" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMI")
|
|
||||||
}
|
|
||||||
if generatedData[3] != "SourceAMICreationDate" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMICreationDate")
|
|
||||||
}
|
|
||||||
if generatedData[4] != "SourceAMIOwner" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwner")
|
|
||||||
}
|
|
||||||
if generatedData[5] != "SourceAMIOwnerName" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwnerName")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type stepCreateAMI struct {
|
type stepCreateAMI struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
|
||||||
image *ec2.Image
|
image *ec2.Image
|
||||||
AMISkipBuildRegion bool
|
AMISkipBuildRegion bool
|
||||||
}
|
}
|
||||||
@@ -86,23 +85,18 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
|
|||||||
|
|
||||||
// Wait for the image to become ready
|
// Wait for the image to become ready
|
||||||
ui.Say("Waiting for AMI to become ready...")
|
ui.Say("Waiting for AMI to become ready...")
|
||||||
if waitErr := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *createResp.ImageId); waitErr != nil {
|
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *createResp.ImageId); err != nil {
|
||||||
// waitErr should get bubbled up if the issue is a wait timeout
|
log.Printf("Error waiting for AMI: %s", err)
|
||||||
err := fmt.Errorf("Error waiting for AMI: %s", waitErr)
|
|
||||||
imResp, imerr := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{createResp.ImageId}})
|
imResp, imerr := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{createResp.ImageId}})
|
||||||
if imerr != nil {
|
if imerr != nil {
|
||||||
// If there's a failure describing images, bubble that error up too, but don't erase the waitErr.
|
log.Printf("Unable to determine reason waiting for AMI failed: %s", err)
|
||||||
log.Printf("DescribeImages call was unable to determine reason waiting for AMI failed: %s", imerr)
|
err = fmt.Errorf("Unknown error waiting for AMI; %s", err)
|
||||||
err = fmt.Errorf("Unknown error waiting for AMI; %s. DescribeImages returned an error: %s", waitErr, imerr)
|
|
||||||
}
|
}
|
||||||
if imResp != nil && len(imResp.Images) > 0 {
|
if imResp != nil && len(imResp.Images) > 0 {
|
||||||
// Finally, if there's a stateReason, store that with the wait err
|
|
||||||
image := imResp.Images[0]
|
image := imResp.Images[0]
|
||||||
if image != nil {
|
if image != nil {
|
||||||
stateReason := image.StateReason
|
stateReason := image.StateReason
|
||||||
if stateReason != nil {
|
err = fmt.Errorf("Error waiting for AMI. Reason: %s", stateReason)
|
||||||
err = fmt.Errorf("Error waiting for AMI: %s. DescribeImages returned the state reason: %s", waitErr, stateReason)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||||||
|
|
||||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||||
|
|
||||||
generatedData := awscommon.GetGeneratedDataList()
|
generatedData := []string{"SourceAMIName"}
|
||||||
return generatedData, warns, nil
|
return generatedData, warns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +192,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
|
|
||||||
if b.config.IsSpotInstance() {
|
if b.config.IsSpotInstance() {
|
||||||
instanceStep = &awscommon.StepRunSpotInstance{
|
instanceStep = &awscommon.StepRunSpotInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.LaunchMappings,
|
LaunchMappings: b.config.LaunchMappings,
|
||||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||||
@@ -214,7 +213,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
instanceStep = &awscommon.StepRunSourceInstance{
|
instanceStep = &awscommon.StepRunSourceInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.LaunchMappings,
|
LaunchMappings: b.config.LaunchMappings,
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
@@ -240,12 +238,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
// Build the steps
|
// Build the steps
|
||||||
steps := []multistep.Step{
|
steps := []multistep.Step{
|
||||||
&awscommon.StepPreValidate{
|
&awscommon.StepPreValidate{
|
||||||
DestAmiName: b.config.AMIName,
|
DestAmiName: b.config.AMIName,
|
||||||
ForceDeregister: b.config.AMIForceDeregister,
|
ForceDeregister: b.config.AMIForceDeregister,
|
||||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
VpcId: b.config.VpcId,
|
||||||
VpcId: b.config.VpcId,
|
SubnetId: b.config.SubnetId,
|
||||||
SubnetId: b.config.SubnetId,
|
HasSubnetFilter: !b.config.SubnetFilter.Empty(),
|
||||||
HasSubnetFilter: !b.config.SubnetFilter.Empty(),
|
|
||||||
},
|
},
|
||||||
&awscommon.StepSourceAMIInfo{
|
&awscommon.StepSourceAMIInfo{
|
||||||
SourceAmi: b.config.SourceAmi,
|
SourceAmi: b.config.SourceAmi,
|
||||||
@@ -310,15 +307,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
),
|
),
|
||||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||||
},
|
},
|
||||||
&awscommon.StepSetGeneratedData{
|
|
||||||
GeneratedData: generatedData,
|
|
||||||
},
|
|
||||||
&common.StepProvision{},
|
&common.StepProvision{},
|
||||||
&common.StepCleanupTempKeys{
|
&common.StepCleanupTempKeys{
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
},
|
},
|
||||||
&awscommon.StepStopEBSBackedInstance{
|
&awscommon.StepStopEBSBackedInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
Skip: b.config.IsSpotInstance(),
|
Skip: b.config.IsSpotInstance(),
|
||||||
DisableStopInstance: b.config.DisableStopInstance,
|
DisableStopInstance: b.config.DisableStopInstance,
|
||||||
},
|
},
|
||||||
@@ -328,7 +321,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
EnableAMIENASupport: b.config.AMIENASupport,
|
EnableAMIENASupport: b.config.AMIENASupport,
|
||||||
},
|
},
|
||||||
&StepSnapshotVolumes{
|
&StepSnapshotVolumes{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
LaunchDevices: launchDevices,
|
LaunchDevices: launchDevices,
|
||||||
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
|
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||||
},
|
},
|
||||||
@@ -348,17 +340,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
Architecture: b.config.Architecture,
|
Architecture: b.config.Architecture,
|
||||||
LaunchOmitMap: b.config.LaunchMappings.GetOmissions(),
|
LaunchOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
},
|
},
|
||||||
&awscommon.StepAMIRegionCopy{
|
&awscommon.StepAMIRegionCopy{
|
||||||
AccessConfig: &b.config.AccessConfig,
|
AccessConfig: &b.config.AccessConfig,
|
||||||
Regions: b.config.AMIRegions,
|
Regions: b.config.AMIRegions,
|
||||||
AMIKmsKeyId: b.config.AMIKmsKeyId,
|
AMIKmsKeyId: b.config.AMIKmsKeyId,
|
||||||
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
|
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
|
||||||
EncryptBootVolume: b.config.AMIEncryptBootVolume,
|
EncryptBootVolume: b.config.AMIEncryptBootVolume,
|
||||||
Name: b.config.AMIName,
|
Name: b.config.AMIName,
|
||||||
OriginalRegion: *ec2conn.Config.Region,
|
OriginalRegion: *ec2conn.Config.Region,
|
||||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
|
||||||
},
|
},
|
||||||
&awscommon.StepModifyAMIAttributes{
|
&awscommon.StepModifyAMIAttributes{
|
||||||
Description: b.config.AMIDescription,
|
Description: b.config.AMIDescription,
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ type FlatConfig struct {
|
|||||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
|
||||||
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"`
|
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"`
|
||||||
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
|
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
|
||||||
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes" hcl:"block_duration_minutes"`
|
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes" hcl:"block_duration_minutes"`
|
||||||
@@ -216,7 +215,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
|
||||||
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
|
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
|
||||||
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
|
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
|
||||||
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
|
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
|
||||||
|
|||||||
@@ -86,25 +86,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
|||||||
if len(generatedData) == 0 {
|
if len(generatedData) == 0 {
|
||||||
t.Fatalf("Generated data should not be empty")
|
t.Fatalf("Generated data should not be empty")
|
||||||
}
|
}
|
||||||
if len(generatedData) == 0 {
|
|
||||||
t.Fatalf("Generated data should not be empty")
|
|
||||||
}
|
|
||||||
if generatedData[0] != "SourceAMIName" {
|
if generatedData[0] != "SourceAMIName" {
|
||||||
t.Fatalf("Generated data should contain SourceAMIName")
|
t.Fatalf("Generated data should contain SourceAMIName")
|
||||||
}
|
}
|
||||||
if generatedData[1] != "BuildRegion" {
|
|
||||||
t.Fatalf("Generated data should contain BuildRegion")
|
|
||||||
}
|
|
||||||
if generatedData[2] != "SourceAMI" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMI")
|
|
||||||
}
|
|
||||||
if generatedData[3] != "SourceAMICreationDate" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMICreationDate")
|
|
||||||
}
|
|
||||||
if generatedData[4] != "SourceAMIOwner" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwner")
|
|
||||||
}
|
|
||||||
if generatedData[5] != "SourceAMIOwnerName" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwnerName")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
// StepRegisterAMI creates the AMI.
|
// StepRegisterAMI creates the AMI.
|
||||||
type StepRegisterAMI struct {
|
type StepRegisterAMI struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
|
||||||
RootDevice RootBlockDevice
|
RootDevice RootBlockDevice
|
||||||
AMIDevices []*ec2.BlockDeviceMapping
|
AMIDevices []*ec2.BlockDeviceMapping
|
||||||
LaunchDevices []*ec2.BlockDeviceMapping
|
LaunchDevices []*ec2.BlockDeviceMapping
|
||||||
@@ -87,7 +86,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
|||||||
|
|
||||||
// Wait for the image to become ready
|
// Wait for the image to become ready
|
||||||
ui.Say("Waiting for AMI to become ready...")
|
ui.Say("Waiting for AMI to become ready...")
|
||||||
if err := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
||||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/hashicorp/packer/builder/amazon/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const sourceDeviceName = "/dev/xvdf"
|
const sourceDeviceName = "/dev/xvdf"
|
||||||
@@ -24,7 +23,6 @@ func newStepRegisterAMI(amiDevices, launchDevices []*ec2.BlockDeviceMapping) *St
|
|||||||
},
|
},
|
||||||
AMIDevices: amiDevices,
|
AMIDevices: amiDevices,
|
||||||
LaunchDevices: launchDevices,
|
LaunchDevices: launchDevices,
|
||||||
PollingConfig: new(common.AWSPollingConfig),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
// Produces:
|
// Produces:
|
||||||
// snapshot_ids map[string]string - IDs of the created snapshots
|
// snapshot_ids map[string]string - IDs of the created snapshots
|
||||||
type StepSnapshotVolumes struct {
|
type StepSnapshotVolumes struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
|
||||||
LaunchDevices []*ec2.BlockDeviceMapping
|
LaunchDevices []*ec2.BlockDeviceMapping
|
||||||
snapshotIds map[string]string
|
snapshotIds map[string]string
|
||||||
snapshotMutex sync.Mutex
|
snapshotMutex sync.Mutex
|
||||||
@@ -57,7 +56,7 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
|
|||||||
s.snapshotMutex.Unlock()
|
s.snapshotMutex.Unlock()
|
||||||
|
|
||||||
// Wait for snapshot to be created
|
// Wait for snapshot to be created
|
||||||
err = s.PollingConfig.WaitUntilSnapshotDone(ctx, ec2conn, *createSnapResp.SnapshotId)
|
err = awscommon.WaitUntilSnapshotDone(ctx, ec2conn, *createSnapResp.SnapshotId)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/aws/aws-sdk-go/service/iam"
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/packer/builder"
|
|
||||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
"github.com/hashicorp/packer/hcl2template"
|
"github.com/hashicorp/packer/hcl2template"
|
||||||
@@ -147,7 +146,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||||||
|
|
||||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||||
|
|
||||||
generatedData := awscommon.GetGeneratedDataList()
|
generatedData := []string{"SourceAMIName"}
|
||||||
return generatedData, warns, nil
|
return generatedData, warns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,13 +166,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
state.Put("iam", iam)
|
state.Put("iam", iam)
|
||||||
state.Put("hook", hook)
|
state.Put("hook", hook)
|
||||||
state.Put("ui", ui)
|
state.Put("ui", ui)
|
||||||
generatedData := &builder.GeneratedData{State: state}
|
|
||||||
|
|
||||||
var instanceStep multistep.Step
|
var instanceStep multistep.Step
|
||||||
|
|
||||||
if b.config.IsSpotInstance() {
|
if b.config.IsSpotInstance() {
|
||||||
instanceStep = &awscommon.StepRunSpotInstance{
|
instanceStep = &awscommon.StepRunSpotInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.launchBlockDevices,
|
LaunchMappings: b.config.launchBlockDevices,
|
||||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||||
@@ -195,7 +192,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
instanceStep = &awscommon.StepRunSourceInstance{
|
instanceStep = &awscommon.StepRunSourceInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.launchBlockDevices,
|
LaunchMappings: b.config.launchBlockDevices,
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
@@ -280,15 +276,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
),
|
),
|
||||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||||
},
|
},
|
||||||
&awscommon.StepSetGeneratedData{
|
|
||||||
GeneratedData: generatedData,
|
|
||||||
},
|
|
||||||
&common.StepProvision{},
|
&common.StepProvision{},
|
||||||
&common.StepCleanupTempKeys{
|
&common.StepCleanupTempKeys{
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
},
|
},
|
||||||
&awscommon.StepStopEBSBackedInstance{
|
&awscommon.StepStopEBSBackedInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
Skip: b.config.IsSpotInstance(),
|
Skip: b.config.IsSpotInstance(),
|
||||||
DisableStopInstance: b.config.DisableStopInstance,
|
DisableStopInstance: b.config.DisableStopInstance,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ type FlatConfig struct {
|
|||||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
|
||||||
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"`
|
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"`
|
||||||
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
|
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
|
||||||
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes" hcl:"block_duration_minutes"`
|
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes" hcl:"block_duration_minutes"`
|
||||||
@@ -196,7 +195,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
|
||||||
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
|
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
|
||||||
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
|
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
|
||||||
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
|
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
|
||||||
|
|||||||
@@ -105,25 +105,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
|||||||
if len(generatedData) == 0 {
|
if len(generatedData) == 0 {
|
||||||
t.Fatalf("Generated data should not be empty")
|
t.Fatalf("Generated data should not be empty")
|
||||||
}
|
}
|
||||||
if len(generatedData) == 0 {
|
|
||||||
t.Fatalf("Generated data should not be empty")
|
|
||||||
}
|
|
||||||
if generatedData[0] != "SourceAMIName" {
|
if generatedData[0] != "SourceAMIName" {
|
||||||
t.Fatalf("Generated data should contain SourceAMIName")
|
t.Fatalf("Generated data should contain SourceAMIName")
|
||||||
}
|
}
|
||||||
if generatedData[1] != "BuildRegion" {
|
|
||||||
t.Fatalf("Generated data should contain BuildRegion")
|
|
||||||
}
|
|
||||||
if generatedData[2] != "SourceAMI" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMI")
|
|
||||||
}
|
|
||||||
if generatedData[3] != "SourceAMICreationDate" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMICreationDate")
|
|
||||||
}
|
|
||||||
if generatedData[4] != "SourceAMIOwner" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwner")
|
|
||||||
}
|
|
||||||
if generatedData[5] != "SourceAMIOwnerName" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwnerName")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||||||
}
|
}
|
||||||
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
|
||||||
|
|
||||||
generatedData := awscommon.GetGeneratedDataList()
|
generatedData := []string{"SourceAMIName"}
|
||||||
return generatedData, warns, nil
|
return generatedData, warns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +255,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
|
|
||||||
if b.config.IsSpotInstance() {
|
if b.config.IsSpotInstance() {
|
||||||
instanceStep = &awscommon.StepRunSpotInstance{
|
instanceStep = &awscommon.StepRunSpotInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.LaunchMappings,
|
LaunchMappings: b.config.LaunchMappings,
|
||||||
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
BlockDurationMinutes: b.config.BlockDurationMinutes,
|
||||||
@@ -274,7 +273,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
instanceStep = &awscommon.StepRunSourceInstance{
|
instanceStep = &awscommon.StepRunSourceInstance{
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||||
LaunchMappings: b.config.LaunchMappings,
|
LaunchMappings: b.config.LaunchMappings,
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
@@ -362,9 +360,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
),
|
),
|
||||||
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
|
||||||
},
|
},
|
||||||
&awscommon.StepSetGeneratedData{
|
|
||||||
GeneratedData: generatedData,
|
|
||||||
},
|
|
||||||
&common.StepProvision{},
|
&common.StepProvision{},
|
||||||
&common.StepCleanupTempKeys{
|
&common.StepCleanupTempKeys{
|
||||||
Comm: &b.config.RunConfig.Comm,
|
Comm: &b.config.RunConfig.Comm,
|
||||||
@@ -387,7 +382,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||||
EnableAMIENASupport: b.config.AMIENASupport,
|
EnableAMIENASupport: b.config.AMIENASupport,
|
||||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||||
PollingConfig: b.config.PollingConfig,
|
|
||||||
},
|
},
|
||||||
&awscommon.StepAMIRegionCopy{
|
&awscommon.StepAMIRegionCopy{
|
||||||
AccessConfig: &b.config.AccessConfig,
|
AccessConfig: &b.config.AccessConfig,
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ type FlatConfig struct {
|
|||||||
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"`
|
||||||
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"`
|
||||||
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"`
|
||||||
PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"`
|
|
||||||
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name" hcl:"ami_name"`
|
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name" hcl:"ami_name"`
|
||||||
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description" hcl:"ami_description"`
|
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description" hcl:"ami_description"`
|
||||||
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type" hcl:"ami_virtualization_type"`
|
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type" hcl:"ami_virtualization_type"`
|
||||||
@@ -178,7 +177,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
|
||||||
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
|
||||||
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
|
||||||
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
|
|
||||||
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
|
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
|
||||||
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
|
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
|
||||||
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
|
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -319,25 +319,7 @@ func TestBuilderPrepare_ReturnGeneratedData(t *testing.T) {
|
|||||||
if len(generatedData) == 0 {
|
if len(generatedData) == 0 {
|
||||||
t.Fatalf("Generated data should not be empty")
|
t.Fatalf("Generated data should not be empty")
|
||||||
}
|
}
|
||||||
if len(generatedData) == 0 {
|
|
||||||
t.Fatalf("Generated data should not be empty")
|
|
||||||
}
|
|
||||||
if generatedData[0] != "SourceAMIName" {
|
if generatedData[0] != "SourceAMIName" {
|
||||||
t.Fatalf("Generated data should contain SourceAMIName")
|
t.Fatalf("Generated data should contain SourceAMIName")
|
||||||
}
|
}
|
||||||
if generatedData[1] != "BuildRegion" {
|
|
||||||
t.Fatalf("Generated data should contain BuildRegion")
|
|
||||||
}
|
|
||||||
if generatedData[2] != "SourceAMI" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMI")
|
|
||||||
}
|
|
||||||
if generatedData[3] != "SourceAMICreationDate" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMICreationDate")
|
|
||||||
}
|
|
||||||
if generatedData[4] != "SourceAMIOwner" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwner")
|
|
||||||
}
|
|
||||||
if generatedData[5] != "SourceAMIOwnerName" {
|
|
||||||
t.Fatalf("Generated data should contain SourceAMIOwnerName")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StepRegisterAMI struct {
|
type StepRegisterAMI struct {
|
||||||
PollingConfig *awscommon.AWSPollingConfig
|
|
||||||
EnableAMIENASupport confighelper.Trilean
|
EnableAMIENASupport confighelper.Trilean
|
||||||
EnableAMISriovNetSupport bool
|
EnableAMISriovNetSupport bool
|
||||||
AMISkipBuildRegion bool
|
AMISkipBuildRegion bool
|
||||||
@@ -81,7 +80,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
|||||||
|
|
||||||
// Wait for the image to become ready
|
// Wait for the image to become ready
|
||||||
ui.Say("Waiting for AMI to become ready...")
|
ui.Say("Waiting for AMI to become ready...")
|
||||||
if err := s.PollingConfig.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
if err := awscommon.WaitUntilAMIAvailable(ctx, ec2conn, *registerResp.ImageId); err != nil {
|
||||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
|
|||||||
@@ -221,6 +221,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
||||||
NewStepCaptureImage(azureClient, ui),
|
NewStepCaptureImage(azureClient, ui),
|
||||||
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
||||||
|
NewStepDeleteResourceGroup(azureClient, ui),
|
||||||
|
NewStepDeleteOSDisk(azureClient, ui),
|
||||||
NewStepDeleteAdditionalDisks(azureClient, ui),
|
NewStepDeleteAdditionalDisks(azureClient, ui),
|
||||||
}
|
}
|
||||||
} else if b.config.OSType == constants.Target_Windows {
|
} else if b.config.OSType == constants.Target_Windows {
|
||||||
@@ -262,6 +264,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
NewStepSnapshotDataDisks(azureClient, ui, &b.config),
|
||||||
NewStepCaptureImage(azureClient, ui),
|
NewStepCaptureImage(azureClient, ui),
|
||||||
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
NewStepPublishToSharedImageGallery(azureClient, ui, &b.config),
|
||||||
|
NewStepDeleteResourceGroup(azureClient, ui),
|
||||||
|
NewStepDeleteOSDisk(azureClient, ui),
|
||||||
NewStepDeleteAdditionalDisks(azureClient, ui),
|
NewStepDeleteAdditionalDisks(azureClient, ui),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -103,8 +103,7 @@ type Config struct {
|
|||||||
// Authentication via OAUTH
|
// Authentication via OAUTH
|
||||||
ClientConfig client.Config `mapstructure:",squash"`
|
ClientConfig client.Config `mapstructure:",squash"`
|
||||||
|
|
||||||
// A list of one or more fully-qualified resource IDs of user assigned
|
// If set with one or more resource ids of user assigned managed identities, they will be configured on the VM.
|
||||||
// managed identities to be configured on the VM.
|
|
||||||
// See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token)
|
// See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token)
|
||||||
// for how to acquire tokens within the VM.
|
// for how to acquire tokens within the VM.
|
||||||
// To assign a user assigned managed identity to a VM, the provided account or service principal must have [Managed Identity Operator](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#managed-identity-operator)
|
// To assign a user assigned managed identity to a VM, the provided account or service principal must have [Managed Identity Operator](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#managed-identity-operator)
|
||||||
@@ -788,10 +787,6 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
|
|||||||
for _, rid := range c.UserAssignedManagedIdentities {
|
for _, rid := range c.UserAssignedManagedIdentities {
|
||||||
r, err := client.ParseResourceID(rid)
|
r, err := client.ParseResourceID(rid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error parsing resource ID from `user_assigned_managed_identities`; please make sure"+
|
|
||||||
" that this value follows the full resource id format: "+
|
|
||||||
"/subscriptions/<SUBSCRIPTON_ID>/resourcegroups/<RESOURCE_GROUP>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<USER_ASSIGNED_IDENTITY_NAME>.\n"+
|
|
||||||
" Original error: %s", err)
|
|
||||||
errs = packer.MultiErrorAppend(errs, err)
|
errs = packer.MultiErrorAppend(errs, err)
|
||||||
} else {
|
} else {
|
||||||
if !strings.EqualFold(r.Provider, "Microsoft.ManagedIdentity") {
|
if !strings.EqualFold(r.Provider, "Microsoft.ManagedIdentity") {
|
||||||
|
|||||||
@@ -111,30 +111,27 @@ func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) {
|
|||||||
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
|
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
|
||||||
ui.Say("\nThe resource group was not created by Packer, not deleting ...")
|
ui.Say("\nThe resource group was not created by Packer, not deleting ...")
|
||||||
return
|
return
|
||||||
}
|
} else {
|
||||||
|
ui.Say("\nCleanup requested, deleting resource group ...")
|
||||||
|
|
||||||
ctx := context.TODO()
|
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
ctx := context.TODO()
|
||||||
if exists, err := s.exists(ctx, resourceGroupName); !exists || err != nil {
|
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
|
||||||
return
|
if err == nil {
|
||||||
}
|
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||||
|
s.say(fmt.Sprintf("\n Not waiting for Resource Group delete as requested by user. Resource Group Name is %s", resourceGroupName))
|
||||||
ui.Say("\nCleanup requested, deleting resource group ...")
|
} else {
|
||||||
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
|
err = f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
|
||||||
if err == nil {
|
}
|
||||||
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
}
|
||||||
s.say(fmt.Sprintf("\n Not waiting for Resource Group delete as requested by user. Resource Group Name is %s", resourceGroupName))
|
if err != nil {
|
||||||
} else {
|
ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+
|
||||||
err = f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
|
"Name: %s\n"+
|
||||||
|
"Error: %s", resourceGroupName, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||||
|
ui.Say("Resource group has been deleted.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+
|
|
||||||
"Name: %s\n"+
|
|
||||||
"Error: %s", resourceGroupName, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
|
||||||
ui.Say("Resource group has been deleted.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,8 @@ func NewStepDeleteAdditionalDisks(client *AzureClient, ui packer.Ui) *StepDelete
|
|||||||
|
|
||||||
func (s *StepDeleteAdditionalDisk) deleteBlob(storageContainerName string, blobName string) error {
|
func (s *StepDeleteAdditionalDisk) deleteBlob(storageContainerName string, blobName string) error {
|
||||||
blob := s.client.BlobStorageClient.GetContainerReference(storageContainerName).GetBlobReference(blobName)
|
blob := s.client.BlobStorageClient.GetContainerReference(storageContainerName).GetBlobReference(blobName)
|
||||||
_, err := blob.BreakLease(nil)
|
err := blob.Delete(nil)
|
||||||
if err != nil && !strings.Contains(err.Error(), "LeaseNotPresentWithLeaseOperation") {
|
|
||||||
s.say(s.client.LastError.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = blob.Delete(nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.say(s.client.LastError.Error())
|
s.say(s.client.LastError.Error())
|
||||||
}
|
}
|
||||||
@@ -85,26 +80,25 @@ func (s *StepDeleteAdditionalDisk) Run(ctx context.Context, state multistep.Stat
|
|||||||
s.say("Failed to delete the managed Additional Disk!")
|
s.say("Failed to delete the managed Additional Disk!")
|
||||||
return processStepResult(err, s.error, state)
|
return processStepResult(err, s.error, state)
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(additionaldisk)
|
|
||||||
if err != nil {
|
|
||||||
s.say("Failed to parse the Additional Disk's VHD URI!")
|
|
||||||
return processStepResult(err, s.error, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
xs := strings.Split(u.Path, "/")
|
|
||||||
if len(xs) < 3 {
|
|
||||||
err = errors.New("Failed to parse Additional Disk's VHD URI!")
|
|
||||||
} else {
|
} else {
|
||||||
var storageAccountName = xs[1]
|
u, err := url.Parse(additionaldisk)
|
||||||
var blobName = strings.Join(xs[2:], "/")
|
if err != nil {
|
||||||
|
s.say("Failed to parse the Additional Disk's VHD URI!")
|
||||||
|
return processStepResult(err, s.error, state)
|
||||||
|
}
|
||||||
|
|
||||||
err = s.delete(storageAccountName, blobName)
|
xs := strings.Split(u.Path, "/")
|
||||||
}
|
if len(xs) < 3 {
|
||||||
if err != nil {
|
err = errors.New("Failed to parse Additional Disk's VHD URI!")
|
||||||
return processStepResult(err, s.error, state)
|
} else {
|
||||||
|
var storageAccountName = xs[1]
|
||||||
|
var blobName = strings.Join(xs[2:], "/")
|
||||||
|
|
||||||
|
err = s.delete(storageAccountName, blobName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return processStepResult(err, s.error, state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package arm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StepDeleteOSDisk struct {
|
||||||
|
client *AzureClient
|
||||||
|
delete func(string, string) error
|
||||||
|
deleteManaged func(context.Context, string, string) error
|
||||||
|
say func(message string)
|
||||||
|
error func(e error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStepDeleteOSDisk(client *AzureClient, ui packer.Ui) *StepDeleteOSDisk {
|
||||||
|
var step = &StepDeleteOSDisk{
|
||||||
|
client: client,
|
||||||
|
say: func(message string) { ui.Say(message) },
|
||||||
|
error: func(e error) { ui.Error(e.Error()) },
|
||||||
|
}
|
||||||
|
|
||||||
|
step.delete = step.deleteBlob
|
||||||
|
step.deleteManaged = step.deleteManagedDisk
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeleteOSDisk) deleteBlob(storageContainerName string, blobName string) error {
|
||||||
|
blob := s.client.BlobStorageClient.GetContainerReference(storageContainerName).GetBlobReference(blobName)
|
||||||
|
err := blob.Delete(nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.say(s.client.LastError.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeleteOSDisk) deleteManagedDisk(ctx context.Context, resourceGroupName string, imageName string) error {
|
||||||
|
xs := strings.Split(imageName, "/")
|
||||||
|
diskName := xs[len(xs)-1]
|
||||||
|
f, err := s.client.DisksClient.Delete(ctx, resourceGroupName, diskName)
|
||||||
|
if err == nil {
|
||||||
|
err = f.WaitForCompletionRef(ctx, s.client.DisksClient.Client)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeleteOSDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
s.say("Deleting the temporary OS disk ...")
|
||||||
|
|
||||||
|
var osDisk = state.Get(constants.ArmOSDiskVhd).(string)
|
||||||
|
var isManagedDisk = state.Get(constants.ArmIsManagedImage).(bool)
|
||||||
|
var isExistingResourceGroup = state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
||||||
|
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||||
|
|
||||||
|
if isManagedDisk && !isExistingResourceGroup {
|
||||||
|
s.say(fmt.Sprintf(" -> OS Disk : skipping, managed disk was used..."))
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if isManagedDisk {
|
||||||
|
err = s.deleteManaged(ctx, resourceGroupName, osDisk)
|
||||||
|
if err != nil {
|
||||||
|
s.say("Failed to delete the managed OS Disk!")
|
||||||
|
return processStepResult(err, s.error, state)
|
||||||
|
}
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
u, err := url.Parse(osDisk)
|
||||||
|
if err != nil {
|
||||||
|
s.say("Failed to parse the OS Disk's VHD URI!")
|
||||||
|
return processStepResult(err, s.error, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
xs := strings.Split(u.Path, "/")
|
||||||
|
if len(xs) < 3 {
|
||||||
|
err = errors.New("Failed to parse OS Disk's VHD URI!")
|
||||||
|
} else {
|
||||||
|
var storageAccountName = xs[1]
|
||||||
|
var blobName = strings.Join(xs[2:], "/")
|
||||||
|
|
||||||
|
err = s.delete(storageAccountName, blobName)
|
||||||
|
}
|
||||||
|
return processStepResult(err, s.error, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StepDeleteOSDisk) Cleanup(multistep.StateBag) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package arm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldFailIfGetFails(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(string, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||||
|
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionHalt {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||||
|
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldPassIfGetPasses(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(string, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionContinue {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||||
|
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldTakeStepArgumentsFromStateBag(t *testing.T) {
|
||||||
|
var actualStorageContainerName string
|
||||||
|
var actualBlobName string
|
||||||
|
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(storageContainerName string, blobName string) error {
|
||||||
|
actualStorageContainerName = storageContainerName
|
||||||
|
actualBlobName = blobName
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/images/pkrvm_os.vhd")
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
|
||||||
|
if result != multistep.ActionContinue {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualStorageContainerName != "images" {
|
||||||
|
t.Fatalf("Expected the storage container name to be 'images', but found '%s'.", actualStorageContainerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualBlobName != "pkrvm_os.vhd" {
|
||||||
|
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldHandleComplexStorageContainerNames(t *testing.T) {
|
||||||
|
var actualStorageContainerName string
|
||||||
|
var actualBlobName string
|
||||||
|
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(storageContainerName string, blobName string) error {
|
||||||
|
actualStorageContainerName = storageContainerName
|
||||||
|
actualBlobName = blobName
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://storage.blob.core.windows.net/abc/def/pkrvm_os.vhd")
|
||||||
|
testSubject.Run(context.Background(), stateBag)
|
||||||
|
|
||||||
|
if actualStorageContainerName != "abc" {
|
||||||
|
t.Fatalf("Expected the storage container name to be 'abc/def', but found '%s'.", actualStorageContainerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualBlobName != "def/pkrvm_os.vhd" {
|
||||||
|
t.Fatalf("Expected the blob name to be 'pkrvm_os.vhd', but found '%s'.", actualBlobName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldFailIfVHDNameCannotBeURLParsed(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(string, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid URL per https://golang.org/src/net/url/url_test.go
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteOSDisk("http://[fe80::1%en0]/")
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionHalt {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionHalt', but got '%v'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||||
|
t.Fatalf("Expected the step to not stateBag['%s'], but it was.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestStepDeleteOSDiskShouldFailIfVHDNameIsTooShort(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(string, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteOSDisk("storage.blob.core.windows.net/abc")
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionHalt {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||||
|
t.Fatalf("Expected the step to not stateBag['%s'], but it was.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldPassIfManagedDiskInTempResourceGroup(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(string, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := new(multistep.BasicStateBag)
|
||||||
|
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
|
||||||
|
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||||
|
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||||
|
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionContinue {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||||
|
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldFailIfManagedDiskInExistingResourceGroupFailsToDelete(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(string, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
deleteManaged: func(context.Context, string, string) error { return errors.New("UNIT TEST FAIL!") },
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := new(multistep.BasicStateBag)
|
||||||
|
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
|
||||||
|
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||||
|
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||||
|
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionHalt {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||||
|
t.Fatalf("Expected the step to not stateBag['%s'], but it was.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteOSDiskShouldFailIfManagedDiskInExistingResourceGroupIsDeleted(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteOSDisk{
|
||||||
|
delete: func(string, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
deleteManaged: func(context.Context, string, string) error { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := new(multistep.BasicStateBag)
|
||||||
|
stateBag.Put(constants.ArmOSDiskVhd, "subscriptions/123-456-789/resourceGroups/existingresourcegroup/providers/Microsoft.Compute/disks/osdisk")
|
||||||
|
stateBag.Put(constants.ArmIsManagedImage, true)
|
||||||
|
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
|
||||||
|
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionContinue {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||||
|
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTestStateBagStepDeleteOSDisk(osDiskVhd string) multistep.StateBag {
|
||||||
|
stateBag := new(multistep.BasicStateBag)
|
||||||
|
stateBag.Put(constants.ArmOSDiskVhd, osDiskVhd)
|
||||||
|
stateBag.Put(constants.ArmIsManagedImage, false)
|
||||||
|
stateBag.Put(constants.ArmIsExistingResourceGroup, false)
|
||||||
|
stateBag.Put(constants.ArmResourceGroupName, "testgroup")
|
||||||
|
|
||||||
|
return stateBag
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package arm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||||
|
"github.com/hashicorp/packer/common/retry"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxResourcesToDelete = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
type StepDeleteResourceGroup struct {
|
||||||
|
client *AzureClient
|
||||||
|
delete func(ctx context.Context, state multistep.StateBag, resourceGroupName string) error
|
||||||
|
say func(message string)
|
||||||
|
error func(e error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStepDeleteResourceGroup(client *AzureClient, ui packer.Ui) *StepDeleteResourceGroup {
|
||||||
|
var step = &StepDeleteResourceGroup{
|
||||||
|
client: client,
|
||||||
|
say: func(message string) { ui.Say(message) },
|
||||||
|
error: func(e error) { ui.Error(e.Error()) },
|
||||||
|
}
|
||||||
|
|
||||||
|
step.delete = step.deleteResourceGroup
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeleteResourceGroup) deleteResourceGroup(ctx context.Context, state multistep.StateBag, resourceGroupName string) error {
|
||||||
|
var err error
|
||||||
|
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
|
||||||
|
s.say("\nThe resource group was not created by Packer, only deleting individual resources ...")
|
||||||
|
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
|
||||||
|
err = s.deleteDeploymentResources(ctx, deploymentName, resourceGroupName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyVaultDeploymentName, ok := state.GetOk(constants.ArmKeyVaultDeploymentName); ok {
|
||||||
|
// Only delete if custom keyvault was not provided.
|
||||||
|
if exists := state.Get(constants.ArmIsExistingKeyVault).(bool); !exists {
|
||||||
|
s.say("\n Deleting the keyvault deployment because it was created by Packer...")
|
||||||
|
err = s.deleteDeploymentResources(ctx, keyVaultDeploymentName.(string), resourceGroupName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
s.say("\nThe resource group was created by Packer, deleting ...")
|
||||||
|
f, err := s.client.GroupsClient.Delete(ctx, resourceGroupName)
|
||||||
|
if err == nil {
|
||||||
|
if state.Get(constants.ArmAsyncResourceGroupDelete).(bool) {
|
||||||
|
// No need to wait for the completion for delete if request is Accepted
|
||||||
|
s.say(fmt.Sprintf("\nResource Group is being deleted, not waiting for deletion due to config. Resource Group Name '%s'", resourceGroupName))
|
||||||
|
} else {
|
||||||
|
f.WaitForCompletionRef(ctx, s.client.GroupsClient.Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.say(s.client.LastError.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
|
||||||
|
maxResources := int32(maxResourcesToDelete)
|
||||||
|
|
||||||
|
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(ctx, resourceGroupName, deploymentName, &maxResources)
|
||||||
|
if err != nil {
|
||||||
|
s.reportIfError(err, resourceGroupName)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for deploymentOperations.NotDone() {
|
||||||
|
deploymentOperation := deploymentOperations.Value()
|
||||||
|
// Sometimes an empty operation is added to the list by Azure
|
||||||
|
if deploymentOperation.Properties.TargetResource == nil {
|
||||||
|
deploymentOperations.Next()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
|
||||||
|
resourceType := *deploymentOperation.Properties.TargetResource.ResourceType
|
||||||
|
|
||||||
|
s.say(fmt.Sprintf(" -> %s : '%s'",
|
||||||
|
resourceType,
|
||||||
|
resourceName))
|
||||||
|
|
||||||
|
retry.Config{
|
||||||
|
Tries: 10,
|
||||||
|
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
|
||||||
|
}.Run(ctx, func(ctx context.Context) error {
|
||||||
|
err := deleteResource(ctx, s.client,
|
||||||
|
resourceType,
|
||||||
|
resourceName,
|
||||||
|
resourceGroupName)
|
||||||
|
if err != nil {
|
||||||
|
s.reportIfError(err, resourceName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err = deploymentOperations.Next(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeleteResourceGroup) reportIfError(err error, resourceName string) {
|
||||||
|
if err != nil {
|
||||||
|
s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||||
|
"Name: %s\n"+
|
||||||
|
"Error: %s", resourceName, err.Error()))
|
||||||
|
s.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeleteResourceGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
s.say("Deleting resource group ...")
|
||||||
|
|
||||||
|
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||||
|
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||||
|
|
||||||
|
err := s.delete(ctx, state, resourceGroupName)
|
||||||
|
if err != nil {
|
||||||
|
state.Put(constants.Error, err)
|
||||||
|
s.error(err)
|
||||||
|
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Put(constants.ArmIsResourceGroupCreated, false)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StepDeleteResourceGroup) Cleanup(multistep.StateBag) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package arm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepDeleteResourceGroupShouldFailIfDeleteFails(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteResourceGroup{
|
||||||
|
delete: func(context.Context, multistep.StateBag, string) error { return fmt.Errorf("!! Unit Test FAIL !!") },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionHalt {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == false {
|
||||||
|
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteResourceGroupShouldPassIfDeletePasses(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteResourceGroup{
|
||||||
|
delete: func(context.Context, multistep.StateBag, string) error { return nil },
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
|
||||||
|
|
||||||
|
var result = testSubject.Run(context.Background(), stateBag)
|
||||||
|
if result != multistep.ActionContinue {
|
||||||
|
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBag.GetOk(constants.Error); ok == true {
|
||||||
|
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDeleteResourceGroupShouldDeleteStateBagArmResourceGroupCreated(t *testing.T) {
|
||||||
|
var testSubject = &StepDeleteResourceGroup{
|
||||||
|
delete: func(context.Context, multistep.StateBag, string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
say: func(message string) {},
|
||||||
|
error: func(e error) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBag := DeleteTestStateBagStepDeleteResourceGroup()
|
||||||
|
testSubject.Run(context.Background(), stateBag)
|
||||||
|
|
||||||
|
value, ok := stateBag.GetOk(constants.ArmIsResourceGroupCreated)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected the resource bag value arm.IsResourceGroupCreated to exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.(bool) {
|
||||||
|
t.Fatalf("Expected arm.IsResourceGroupCreated to be false, but got %q", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTestStateBagStepDeleteResourceGroup() multistep.StateBag {
|
||||||
|
stateBag := new(multistep.BasicStateBag)
|
||||||
|
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
|
||||||
|
stateBag.Put(constants.ArmIsResourceGroupCreated, "Unit Test: IsResourceGroupCreated")
|
||||||
|
|
||||||
|
return stateBag
|
||||||
|
}
|
||||||
@@ -42,109 +42,6 @@ func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, de
|
|||||||
return step
|
return step
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
s.say("Deploying deployment template ...")
|
|
||||||
|
|
||||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
|
||||||
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
|
||||||
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name))
|
|
||||||
|
|
||||||
return processStepResult(
|
|
||||||
s.deploy(ctx, resourceGroupName, s.name),
|
|
||||||
s.error, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
|
||||||
defer func() {
|
|
||||||
err := s.deleteTemplate(context.Background(), state)
|
|
||||||
if err != nil {
|
|
||||||
s.say(s.client.LastError.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
//Only clean up if this was an existing resource group and the resource group
|
|
||||||
//is marked as created
|
|
||||||
existingResourceGroup := state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
|
||||||
resourceGroupCreated := state.Get(constants.ArmIsResourceGroupCreated).(bool)
|
|
||||||
if !existingResourceGroup || !resourceGroupCreated {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
|
||||||
ui.Say("\nThe resource group was not created by Packer, deleting individual resources ...")
|
|
||||||
|
|
||||||
deploymentName := s.name
|
|
||||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
|
||||||
|
|
||||||
// Get image disk details before deleting the image; otherwise we won't be able to
|
|
||||||
// delete the disk as the image request will return a 404
|
|
||||||
computeName := state.Get(constants.ArmComputeName).(string)
|
|
||||||
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
|
|
||||||
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "ResourceNotFound") {
|
|
||||||
ui.Error(fmt.Sprintf("Could not retrieve OS Image details: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(" -> Deployment Resources within: " + deploymentName)
|
|
||||||
if deploymentName != "" {
|
|
||||||
maxResources := int32(50)
|
|
||||||
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(context.TODO(), resourceGroupName, deploymentName, &maxResources)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
|
||||||
"Name: %s\n"+
|
|
||||||
"Error: %s", resourceGroupName, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
for deploymentOperations.NotDone() {
|
|
||||||
deploymentOperation := deploymentOperations.Value()
|
|
||||||
// Sometimes an empty operation is added to the list by Azure
|
|
||||||
if deploymentOperation.Properties.TargetResource == nil {
|
|
||||||
if err := deploymentOperations.Next(); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error moving to to next deployment operation ...\n\n"+
|
|
||||||
"Name: %s\n"+
|
|
||||||
"Error: %s", resourceGroupName, err))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf(" -> %s : '%s'",
|
|
||||||
*deploymentOperation.Properties.TargetResource.ResourceType,
|
|
||||||
*deploymentOperation.Properties.TargetResource.ResourceName))
|
|
||||||
|
|
||||||
err = s.delete(context.TODO(), s.client,
|
|
||||||
*deploymentOperation.Properties.TargetResource.ResourceType,
|
|
||||||
*deploymentOperation.Properties.TargetResource.ResourceName,
|
|
||||||
resourceGroupName)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
|
||||||
"Name: %s\n"+
|
|
||||||
"Error: %s", *deploymentOperation.Properties.TargetResource.ResourceName, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = deploymentOperations.Next(); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
|
||||||
"Name: %s\n"+
|
|
||||||
"Error: %s", resourceGroupName, err))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The disk is not defined as an operation in the template so it has to be deleted separately
|
|
||||||
if imageType == "" && imageName == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf(" -> %s : '%s'", imageType, imageName))
|
|
||||||
err = s.deleteDisk(context.TODO(), imageType, imageName, resourceGroupName)
|
|
||||||
if err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
|
||||||
"Name: %s\n"+
|
|
||||||
"Error: %s", imageName, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupName string, deploymentName string) error {
|
||||||
deployment, err := s.factory(s.config)
|
deployment, err := s.factory(s.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -165,32 +62,48 @@ func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep
|
|||||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||||
var deploymentName = s.name
|
var deploymentName = s.name
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Removing the created Deployment object: '%s'", deploymentName))
|
ui.Say(fmt.Sprintf("Removing the created Deployment object: '%s'", deploymentName))
|
||||||
|
|
||||||
f, err := s.client.DeploymentsClient.Delete(ctx, resourceGroupName, deploymentName)
|
f, err := s.client.DeploymentsClient.Delete(ctx, resourceGroupName, deploymentName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.say(s.client.LastError.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StepDeployTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
s.say("Deploying deployment template ...")
|
||||||
|
|
||||||
|
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||||
|
|
||||||
|
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
|
||||||
|
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name))
|
||||||
|
|
||||||
|
return processStepResult(
|
||||||
|
s.deploy(ctx, resourceGroupName, s.name),
|
||||||
|
s.error, state)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupName string, computeName string) (string, string, error) {
|
func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupName string, computeName string) (string, string, error) {
|
||||||
//We can't depend on constants.ArmOSDiskVhd being set
|
//We can't depend on constants.ArmOSDiskVhd being set
|
||||||
var imageName, imageType string
|
var imageName string
|
||||||
|
var imageType string
|
||||||
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
vm, err := s.client.VirtualMachinesClient.Get(ctx, resourceGroupName, computeName, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return imageName, imageType, err
|
return imageName, imageType, err
|
||||||
}
|
|
||||||
|
|
||||||
if vm.StorageProfile.OsDisk.Vhd != nil {
|
|
||||||
imageType = "image"
|
|
||||||
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
|
|
||||||
} else {
|
} else {
|
||||||
imageType = "Microsoft.Compute/disks"
|
if vm.StorageProfile.OsDisk.Vhd != nil {
|
||||||
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
imageType = "image"
|
||||||
|
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
|
||||||
|
} else {
|
||||||
|
imageType = "Microsoft.Compute/disks"
|
||||||
|
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageType, imageName, nil
|
return imageType, imageName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +158,6 @@ func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string,
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// VHD image
|
// VHD image
|
||||||
u, err := url.Parse(imageName)
|
u, err := url.Parse(imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -259,11 +171,75 @@ func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string,
|
|||||||
var blobName = strings.Join(xs[2:], "/")
|
var blobName = strings.Join(xs[2:], "/")
|
||||||
|
|
||||||
blob := s.client.BlobStorageClient.GetContainerReference(storageAccountName).GetBlobReference(blobName)
|
blob := s.client.BlobStorageClient.GetContainerReference(storageAccountName).GetBlobReference(blobName)
|
||||||
_, err = blob.BreakLease(nil)
|
err = blob.Delete(nil)
|
||||||
if err != nil && !strings.Contains(err.Error(), "LeaseNotPresentWithLeaseOperation") {
|
return err
|
||||||
s.say(s.client.LastError.Error())
|
}
|
||||||
return err
|
|
||||||
|
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||||
|
defer s.deleteTemplate(context.Background(), state)
|
||||||
|
|
||||||
|
//Only clean up if this was an existing resource group and the resource group
|
||||||
|
//is marked as created
|
||||||
|
var existingResourceGroup = state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
||||||
|
var resourceGroupCreated = state.Get(constants.ArmIsResourceGroupCreated).(bool)
|
||||||
|
if !existingResourceGroup || !resourceGroupCreated {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Say("\nThe resource group was not created by Packer, deleting individual resources ...")
|
||||||
|
|
||||||
|
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
||||||
|
var computeName = state.Get(constants.ArmComputeName).(string)
|
||||||
|
var deploymentName = s.name
|
||||||
|
imageType, imageName, err := s.disk(context.TODO(), resourceGroupName, computeName)
|
||||||
|
if err != nil {
|
||||||
|
ui.Error("Could not retrieve OS Image details")
|
||||||
}
|
}
|
||||||
|
|
||||||
return blob.Delete(nil)
|
ui.Say(" -> Deployment Resources within: " + deploymentName)
|
||||||
|
if deploymentName != "" {
|
||||||
|
maxResources := int32(50)
|
||||||
|
deploymentOperations, err := s.client.DeploymentOperationsClient.ListComplete(context.TODO(), resourceGroupName, deploymentName, &maxResources)
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
||||||
|
"Name: %s\n"+
|
||||||
|
"Error: %s", resourceGroupName, err))
|
||||||
|
}
|
||||||
|
for deploymentOperations.NotDone() {
|
||||||
|
deploymentOperation := deploymentOperations.Value()
|
||||||
|
// Sometimes an empty operation is added to the list by Azure
|
||||||
|
if deploymentOperation.Properties.TargetResource == nil {
|
||||||
|
deploymentOperations.Next()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ui.Say(fmt.Sprintf(" -> %s : '%s'",
|
||||||
|
*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||||
|
*deploymentOperation.Properties.TargetResource.ResourceName))
|
||||||
|
err = s.delete(context.TODO(), s.client,
|
||||||
|
*deploymentOperation.Properties.TargetResource.ResourceType,
|
||||||
|
*deploymentOperation.Properties.TargetResource.ResourceName,
|
||||||
|
resourceGroupName)
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||||
|
"Name: %s\n"+
|
||||||
|
"Error: %s", *deploymentOperation.Properties.TargetResource.ResourceName, err))
|
||||||
|
}
|
||||||
|
if err = deploymentOperations.Next(); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
||||||
|
"Name: %s\n"+
|
||||||
|
"Error: %s", resourceGroupName, err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The disk is not defined as an operation in the template so has to be
|
||||||
|
// deleted separately
|
||||||
|
ui.Say(fmt.Sprintf(" -> %s : '%s'", imageType, imageName))
|
||||||
|
err = s.deleteDisk(context.TODO(), imageType, imageName, resourceGroupName)
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
|
||||||
|
"Name: %s\n"+
|
||||||
|
"Error: %s", imageName, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,11 +391,8 @@ func checkHyperVGeneration(s string) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||||
switch runtime.GOOS {
|
if runtime.GOOS != "linux" {
|
||||||
case "linux", "freebsd":
|
return nil, errors.New("the azure-chroot builder only works on Linux environments")
|
||||||
break
|
|
||||||
default:
|
|
||||||
return nil, errors.New("the azure-chroot builder only works on Linux and FreeBSD environments")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := b.config.ClientConfig.FillParameters()
|
err := b.config.ClientConfig.FillParameters()
|
||||||
|
|||||||
@@ -3,18 +3,24 @@ package chroot
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
|
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute"
|
||||||
"github.com/Azure/go-autorest/autorest/azure"
|
"github.com/Azure/go-autorest/autorest/azure"
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
"github.com/hashicorp/packer/builder/azure/common/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiskAttacher interface {
|
type DiskAttacher interface {
|
||||||
AttachDisk(ctx context.Context, disk string) (lun int32, err error)
|
AttachDisk(ctx context.Context, disk string) (lun int32, err error)
|
||||||
|
DiskPathForLun(lun int32) string
|
||||||
WaitForDevice(ctx context.Context, i int32) (device string, err error)
|
WaitForDevice(ctx context.Context, i int32) (device string, err error)
|
||||||
DetachDisk(ctx context.Context, disk string) (err error)
|
DetachDisk(ctx context.Context, disk string) (err error)
|
||||||
WaitForDetach(ctx context.Context, diskID string) error
|
WaitForDetach(ctx context.Context, diskID string) error
|
||||||
@@ -32,7 +38,31 @@ type diskAttacher struct {
|
|||||||
vm *client.ComputeInfo // store info about this VM so that we don't have to ask metadata service on every call
|
vm *client.ComputeInfo // store info about this VM so that we don't have to ask metadata service on every call
|
||||||
}
|
}
|
||||||
|
|
||||||
var DiskNotFoundError = errors.New("Disk not found")
|
func (diskAttacher) DiskPathForLun(lun int32) string {
|
||||||
|
return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
||||||
|
path := da.DiskPathForLun(lun)
|
||||||
|
|
||||||
|
for {
|
||||||
|
link, err := os.Readlink(path)
|
||||||
|
if err == nil {
|
||||||
|
return filepath.Abs("/dev/disk/azure/scsi1/" + link)
|
||||||
|
} else if err != os.ErrNotExist {
|
||||||
|
if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
// continue
|
||||||
|
case <-ctx.Done():
|
||||||
|
return "", ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
|
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
|
||||||
log.Println("Fetching list of disks currently attached to VM")
|
log.Println("Fetching list of disks currently attached to VM")
|
||||||
@@ -81,6 +111,8 @@ func (da *diskAttacher) WaitForDetach(ctx context.Context, diskID string) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DiskNotFoundError = errors.New("Disk not found")
|
||||||
|
|
||||||
func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) {
|
func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) {
|
||||||
dataDisks, err := da.getDisks(ctx)
|
dataDisks, err := da.getDisks(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package chroot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
|
||||||
// This builder will always be running in Azure, where data disks show up
|
|
||||||
// on scbus5 target 0. The camcontrol command always outputs LUNs in
|
|
||||||
// unpadded hexadecimal format.
|
|
||||||
regexStr := fmt.Sprintf(`at scbus5 target 0 lun %x \(.*?da([\d]+)`, lun)
|
|
||||||
devRegex, err := regexp.Compile(regexStr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
cmd := exec.Command("camcontrol", "devlist")
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
outString := out.String()
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(outString))
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
// Check if this is the correct bus, target, and LUN.
|
|
||||||
if matches := devRegex.FindStringSubmatch(line); matches != nil {
|
|
||||||
// If this function immediately returns, devfs won't have
|
|
||||||
// created the device yet.
|
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
return fmt.Sprintf("/dev/da%s", matches[1]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = scanner.Err(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
// continue
|
|
||||||
case <-ctx.Done():
|
|
||||||
return "", ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package chroot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func diskPathForLun(lun int32) string {
|
|
||||||
return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
|
||||||
path := diskPathForLun(lun)
|
|
||||||
|
|
||||||
for {
|
|
||||||
link, err := os.Readlink(path)
|
|
||||||
if err == nil {
|
|
||||||
return filepath.Abs("/dev/disk/azure/scsi1/" + link)
|
|
||||||
} else if err != os.ErrNotExist {
|
|
||||||
if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
// continue
|
|
||||||
case <-ctx.Done():
|
|
||||||
return "", ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// +build !linux,!freebsd
|
|
||||||
|
|
||||||
package chroot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
|
||||||
panic("The azure-chroot builder does not work on this platform.")
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
@@ -63,13 +62,7 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
|
|||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
var deviceMount string
|
deviceMount := fmt.Sprintf("%s%s", device, s.MountPartition)
|
||||||
switch runtime.GOOS {
|
|
||||||
case "freebsd":
|
|
||||||
deviceMount = fmt.Sprintf("%sp%s", device, s.MountPartition)
|
|
||||||
default:
|
|
||||||
deviceMount = fmt.Sprintf("%s%s", device, s.MountPartition)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("deviceMount", deviceMount)
|
state.Put("deviceMount", deviceMount)
|
||||||
|
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
package chroot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStepMountDevice_Run(t *testing.T) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux", "freebsd":
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
t.Skip("Unsupported operating system")
|
|
||||||
}
|
|
||||||
mountPath, err := ioutil.TempDir("", "stepmountdevicetest")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unable to create a temporary directory: %q", err)
|
|
||||||
}
|
|
||||||
step := &StepMountDevice{
|
|
||||||
MountOptions: []string{"foo"},
|
|
||||||
MountPartition: "42",
|
|
||||||
MountPath: mountPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
var gotCommand string
|
|
||||||
var wrapper common.CommandWrapper
|
|
||||||
wrapper = func(ran string) (string, error) {
|
|
||||||
gotCommand = ran
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("wrappedCommand", wrapper)
|
|
||||||
state.Put("device", "/dev/quux")
|
|
||||||
|
|
||||||
ui, getErrs := testUI()
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
var config Config
|
|
||||||
state.Put("config", &config)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
got := step.Run(ctx, state)
|
|
||||||
if got != multistep.ActionContinue {
|
|
||||||
t.Errorf("Expected 'continue', but got '%v'", got)
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedMountDevice string
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "freebsd":
|
|
||||||
expectedMountDevice = "/dev/quuxp42"
|
|
||||||
default: // currently just Linux
|
|
||||||
expectedMountDevice = "/dev/quux42"
|
|
||||||
}
|
|
||||||
expectedCommand := fmt.Sprintf("mount -o foo %s %s", expectedMountDevice, mountPath)
|
|
||||||
if gotCommand != expectedCommand {
|
|
||||||
t.Errorf("Expected '%v', but got '%v'", expectedCommand, gotCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Remove(mountPath)
|
|
||||||
_ = getErrs
|
|
||||||
}
|
|
||||||
@@ -55,7 +55,7 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
|
|||||||
scanCodesToSendString := strings.Join(codes, " ")
|
scanCodesToSendString := strings.Join(codes, " ")
|
||||||
return driver.TypeScanCodes(vmName, scanCodesToSendString)
|
return driver.TypeScanCodes(vmName, scanCodesToSendString)
|
||||||
}
|
}
|
||||||
d := bootcommand.NewPCXTDriver(sendCodes, 32, s.GroupInterval)
|
d := bootcommand.NewPCXTDriver(sendCodes, -1, s.GroupInterval)
|
||||||
|
|
||||||
ui.Say("Typing the boot command...")
|
ui.Say("Typing the boot command...")
|
||||||
command, err := interpolate.Render(s.BootCommand, &s.Ctx)
|
command, err := interpolate.Render(s.BootCommand, &s.Ctx)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
errs, err)
|
errs, err)
|
||||||
}
|
}
|
||||||
for _, dc := range dcs {
|
for _, dc := range dcs {
|
||||||
if strings.EqualFold(dc.CountryCode, c.DataCenterName) {
|
if strings.ToLower(dc.CountryCode) == strings.ToLower(c.DataCenterName) {
|
||||||
c.DataCenterId = dc.Id
|
c.DataCenterId = dc.Id
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,7 @@ type Config struct {
|
|||||||
ImageName string `mapstructure:"image_name"`
|
ImageName string `mapstructure:"image_name"`
|
||||||
|
|
||||||
// Instance
|
// Instance
|
||||||
InstanceName string `mapstructure:"instance_name"`
|
InstanceName string `mapstructure:"instance_name"`
|
||||||
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
|
||||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
|
||||||
|
|
||||||
// Metadata optionally contains custom metadata key/value pairs provided in the
|
// Metadata optionally contains custom metadata key/value pairs provided in the
|
||||||
// configuration. While this can be used to set metadata["user_data"] the explicit
|
// configuration. While this can be used to set metadata["user_data"] the explicit
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ type FlatConfig struct {
|
|||||||
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
||||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
||||||
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
||||||
InstanceTags map[string]string `mapstructure:"instance_tags" cty:"instance_tags" hcl:"instance_tags"`
|
|
||||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags" cty:"instance_defined_tags" hcl:"instance_defined_tags"`
|
|
||||||
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
|
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
|
||||||
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
|
||||||
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
|
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
|
||||||
@@ -171,8 +169,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
||||||
"instance_tags": &hcldec.AttrSpec{Name: "instance_tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"instance_defined_tags": &hcldec.AttrSpec{Name: "instance_defined_tags", Type: cty.Map(cty.String), Required: false},
|
|
||||||
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
|
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false},
|
||||||
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
|
||||||
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
|||||||
instanceDetails := core.LaunchInstanceDetails{
|
instanceDetails := core.LaunchInstanceDetails{
|
||||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
||||||
CompartmentId: &d.cfg.CompartmentID,
|
CompartmentId: &d.cfg.CompartmentID,
|
||||||
DefinedTags: d.cfg.InstanceDefinedTags,
|
|
||||||
FreeformTags: d.cfg.InstanceTags,
|
|
||||||
ImageId: &d.cfg.BaseImageID,
|
ImageId: &d.cfg.BaseImageID,
|
||||||
Shape: &d.cfg.Shape,
|
Shape: &d.cfg.Shape,
|
||||||
SubnetId: &d.cfg.SubnetID,
|
SubnetId: &d.cfg.SubnetID,
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui
|
|||||||
if i != "" {
|
if i != "" {
|
||||||
alias = i
|
alias = i
|
||||||
}
|
}
|
||||||
if alias != "" && strings.EqualFold(alias, imageAlias) {
|
if alias != "" && strings.ToLower(alias) == strings.ToLower(imageAlias) {
|
||||||
return alias
|
return alias
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,21 +71,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
ResultKey: downloadPathKey,
|
ResultKey: downloadPathKey,
|
||||||
TargetPath: b.config.TargetPath,
|
TargetPath: b.config.TargetPath,
|
||||||
Url: b.config.ISOUrls,
|
Url: b.config.ISOUrls,
|
||||||
}}
|
},
|
||||||
|
|
||||||
for idx := range b.config.AdditionalISOFiles {
|
|
||||||
steps = append(steps, &common.StepDownload{
|
|
||||||
Checksum: b.config.AdditionalISOFiles[idx].ISOChecksum,
|
|
||||||
Description: "additional ISO",
|
|
||||||
Extension: b.config.AdditionalISOFiles[idx].TargetExtension,
|
|
||||||
ResultKey: b.config.AdditionalISOFiles[idx].downloadPathKey,
|
|
||||||
TargetPath: b.config.AdditionalISOFiles[idx].downloadPathKey,
|
|
||||||
Url: b.config.AdditionalISOFiles[idx].ISOUrls,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
steps = append(steps,
|
|
||||||
&stepUploadISO{},
|
&stepUploadISO{},
|
||||||
&stepUploadAdditionalISOs{},
|
|
||||||
&stepStartVM{},
|
&stepStartVM{},
|
||||||
&common.StepHTTPServer{
|
&common.StepHTTPServer{
|
||||||
HTTPDir: b.config.HTTPDir,
|
HTTPDir: b.config.HTTPDir,
|
||||||
@@ -109,8 +96,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
&stepConvertToTemplate{},
|
&stepConvertToTemplate{},
|
||||||
&stepFinalizeTemplateConfig{},
|
&stepFinalizeTemplateConfig{},
|
||||||
&stepSuccess{},
|
&stepSuccess{},
|
||||||
)
|
}
|
||||||
|
|
||||||
// Run the steps
|
// Run the steps
|
||||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner.Run(ctx, state)
|
b.runner.Run(ctx, state)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig
|
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig
|
||||||
|
|
||||||
package proxmox
|
package proxmox
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -65,8 +64,6 @@ type Config struct {
|
|||||||
|
|
||||||
shouldUploadISO bool
|
shouldUploadISO bool
|
||||||
|
|
||||||
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
|
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,15 +87,6 @@ type vgaConfig struct {
|
|||||||
Type string `mapstructure:"type"`
|
Type string `mapstructure:"type"`
|
||||||
Memory int `mapstructure:"memory"`
|
Memory int `mapstructure:"memory"`
|
||||||
}
|
}
|
||||||
type storageConfig struct {
|
|
||||||
common.ISOConfig `mapstructure:",squash"`
|
|
||||||
Device string `mapstructure:"device"`
|
|
||||||
ISOFile string `mapstructure:"iso_file"`
|
|
||||||
ISOStoragePool string `mapstructure:"iso_storage_pool"`
|
|
||||||
Unmount bool `mapstructure:"unmount"`
|
|
||||||
shouldUploadISO bool
|
|
||||||
downloadPathKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
// Agent defaults to true
|
// Agent defaults to true
|
||||||
@@ -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))
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("disk format must be specified for pool type %q", c.Disks[idx].StoragePoolType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for idx := range c.AdditionalISOFiles {
|
|
||||||
// Check AdditionalISO config
|
|
||||||
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
|
|
||||||
// (possibly to a local file) to an ISO file that will be downloaded and
|
|
||||||
// then uploaded to Proxmox.
|
|
||||||
if c.AdditionalISOFiles[idx].ISOFile != "" {
|
|
||||||
c.AdditionalISOFiles[idx].shouldUploadISO = false
|
|
||||||
} else {
|
|
||||||
c.AdditionalISOFiles[idx].downloadPathKey = "downloaded_additional_iso_path_" + strconv.Itoa(idx)
|
|
||||||
isoWarnings, isoErrors := c.AdditionalISOFiles[idx].ISOConfig.Prepare(&c.ctx)
|
|
||||||
errs = packer.MultiErrorAppend(errs, isoErrors...)
|
|
||||||
warnings = append(warnings, isoWarnings...)
|
|
||||||
c.AdditionalISOFiles[idx].shouldUploadISO = true
|
|
||||||
}
|
|
||||||
if c.AdditionalISOFiles[idx].Device == "" {
|
|
||||||
log.Printf("AdditionalISOFile %d Device not set, using default 'ide3'", idx)
|
|
||||||
c.AdditionalISOFiles[idx].Device = "ide3"
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "ide") {
|
|
||||||
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[3:])
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[3:]))
|
|
||||||
}
|
|
||||||
if busnumber == 2 {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus 2 is used by boot ISO"))
|
|
||||||
}
|
|
||||||
if busnumber > 3 {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("IDE bus index can't be higher than 3"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "sata") {
|
|
||||||
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
|
|
||||||
}
|
|
||||||
if busnumber > 5 {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SATA bus index can't be higher than 5"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.AdditionalISOFiles[idx].Device, "scsi") {
|
|
||||||
busnumber, err := strconv.Atoi(c.AdditionalISOFiles[idx].Device[4:])
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("%s is not a valid bus index", c.AdditionalISOFiles[idx].Device[4:]))
|
|
||||||
}
|
|
||||||
if busnumber > 30 {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("SCSI bus index can't be higher than 30"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (c.AdditionalISOFiles[idx].ISOFile == "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) == 0) || (c.AdditionalISOFiles[idx].ISOFile != "" && len(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls) != 0) {
|
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("either iso_file or iso_url, but not both, must be specified for AdditionalISO file %s", c.AdditionalISOFiles[idx].Device))
|
|
||||||
}
|
|
||||||
if len(c.ISOConfig.ISOUrls) != 0 && c.ISOStoragePool == "" {
|
|
||||||
errs = packer.MultiErrorAppend(errs, errors.New("when specifying iso_url, iso_storage_pool must also be specified"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.SCSIController == "" {
|
if c.SCSIController == "" {
|
||||||
log.Printf("SCSI controller not set, using default 'lsi'")
|
log.Printf("SCSI controller not set, using default 'lsi'")
|
||||||
c.SCSIController = "lsi"
|
c.SCSIController = "lsi"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig"; DO NOT EDIT.
|
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig"; DO NOT EDIT.
|
||||||
package proxmox
|
package proxmox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -9,101 +9,100 @@ import (
|
|||||||
// FlatConfig is an auto-generated flat version of Config.
|
// FlatConfig is an auto-generated flat version of Config.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatConfig struct {
|
type FlatConfig struct {
|
||||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
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"`
|
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"`
|
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
|
||||||
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
|
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"`
|
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"`
|
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"`
|
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"`
|
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
|
||||||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
|
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"`
|
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
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"`
|
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
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"`
|
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"`
|
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
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"`
|
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"`
|
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||||
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
|
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"`
|
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"`
|
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
|
||||||
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
|
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
|
||||||
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
|
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
|
||||||
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
|
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
|
||||||
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
|
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
|
||||||
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
|
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
|
||||||
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
|
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
|
||||||
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
|
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
|
||||||
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
|
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
|
||||||
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
|
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
|
||||||
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
|
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
|
||||||
VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
|
VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
|
||||||
NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
|
NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
|
||||||
Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
|
Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
|
||||||
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
|
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"`
|
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"`
|
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
|
||||||
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
|
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
|
||||||
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
|
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
|
||||||
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
|
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
|
||||||
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
|
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
|
||||||
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
|
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
|
||||||
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
|
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
|
||||||
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
|
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
|
||||||
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
|
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
|
||||||
AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
// FlatMapstructure returns a new FlatConfig.
|
||||||
@@ -212,7 +211,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
|
"unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
|
||||||
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
|
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
|
||||||
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
|
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
|
||||||
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())},
|
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -283,45 +281,6 @@ func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatstorageConfig is an auto-generated flat version of storageConfig.
|
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
type FlatstorageConfig struct {
|
|
||||||
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
|
|
||||||
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
|
|
||||||
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
|
|
||||||
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
|
|
||||||
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
|
|
||||||
Device *string `mapstructure:"device" cty:"device" hcl:"device"`
|
|
||||||
ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"`
|
|
||||||
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"`
|
|
||||||
Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatstorageConfig.
|
|
||||||
// FlatstorageConfig is an auto-generated flat version of storageConfig.
|
|
||||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
|
||||||
func (*storageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
|
||||||
return new(FlatstorageConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCL2Spec returns the hcl spec of a storageConfig.
|
|
||||||
// This spec is used by HCL to read the fields of storageConfig.
|
|
||||||
// The decoded values from this spec will then be applied to a FlatstorageConfig.
|
|
||||||
func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec {
|
|
||||||
s := map[string]hcldec.Spec{
|
|
||||||
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
|
|
||||||
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
|
|
||||||
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
|
|
||||||
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
|
|
||||||
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false},
|
|
||||||
"device": &hcldec.AttrSpec{Name: "device", Type: cty.String, Required: false},
|
|
||||||
"iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false},
|
|
||||||
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
|
|
||||||
"unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false},
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatvgaConfig is an auto-generated flat version of vgaConfig.
|
// FlatvgaConfig is an auto-generated flat version of vgaConfig.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatvgaConfig struct {
|
type FlatvgaConfig struct {
|
||||||
|
|||||||
@@ -93,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 {
|
if len(changes) > 0 {
|
||||||
_, err := client.SetVmConfig(vmRef, changes)
|
_, err := client.SetVmConfig(vmRef, changes)
|
||||||
if err != nil {
|
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.
|
// instance id inside of the provisioners, used in step_provision.
|
||||||
state.Put("instance_id", vmRef)
|
state.Put("instance_id", vmRef)
|
||||||
|
|
||||||
for idx := range c.AdditionalISOFiles {
|
|
||||||
params := map[string]interface{}{
|
|
||||||
c.AdditionalISOFiles[idx].Device: c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom",
|
|
||||||
}
|
|
||||||
_, err = client.SetVmConfig(vmRef, params)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error configuring VM: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Starting VM")
|
ui.Say("Starting VM")
|
||||||
_, err = client.StartVm(vmRef)
|
_, err = client.StartVm(vmRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -14,6 +15,10 @@ import (
|
|||||||
// stepUploadISO uploads an ISO file to Proxmox so we can boot from it
|
// stepUploadISO uploads an ISO file to Proxmox so we can boot from it
|
||||||
type stepUploadISO struct{}
|
type stepUploadISO struct{}
|
||||||
|
|
||||||
|
type uploader interface {
|
||||||
|
Upload(node string, storage string, contentType string, filename string, file io.Reader) error
|
||||||
|
}
|
||||||
|
|
||||||
var _ uploader = &proxmox.Client{}
|
var _ uploader = &proxmox.Client{}
|
||||||
|
|
||||||
func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
@@ -53,6 +58,7 @@ func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multi
|
|||||||
|
|
||||||
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename)
|
isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename)
|
||||||
state.Put("iso_file", isoStoragePath)
|
state.Put("iso_file", isoStoragePath)
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.BootType == "" {
|
if c.BootType == "" {
|
||||||
c.BootType = "local"
|
c.BootType = "bootscript"
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs *packer.MultiError
|
var errs *packer.MultiError
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ type ExportConfig struct {
|
|||||||
// This can be useful for passing product information to include in the
|
// This can be useful for passing product information to include in the
|
||||||
// resulting appliance file. Packer JSON configuration file example:
|
// resulting appliance file. Packer JSON configuration file example:
|
||||||
//
|
//
|
||||||
// In JSON:
|
|
||||||
// ```json
|
// ```json
|
||||||
// {
|
// {
|
||||||
// "type": "virtualbox-iso",
|
// "type": "virtualbox-iso",
|
||||||
@@ -32,19 +31,6 @@ type ExportConfig struct {
|
|||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// In HCL2:
|
|
||||||
// ```hcl
|
|
||||||
// source "virtualbox-iso" "basic-example" {
|
|
||||||
// export_opts = [
|
|
||||||
// "--manifest",
|
|
||||||
// "--vsys", "0",
|
|
||||||
// "--description", "{{user `vm_description`}}",
|
|
||||||
// "--version", "{{user `vm_version`}}"
|
|
||||||
// ]
|
|
||||||
// format = "ova"
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// A VirtualBox [VM
|
// A VirtualBox [VM
|
||||||
// description](https://www.virtualbox.org/manual/ch09.html#vboxmanage-export-ovf)
|
// description](https://www.virtualbox.org/manual/ch09.html#vboxmanage-export-ovf)
|
||||||
// may contain arbitrary strings; the GUI interprets HTML formatting. However,
|
// may contain arbitrary strings; the GUI interprets HTML formatting. However,
|
||||||
|
|||||||
@@ -6,40 +6,17 @@ import (
|
|||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
|
|
||||||
//In order to perform extra customization of the virtual machine, a template can
|
|
||||||
//define extra calls to `VBoxManage` to perform.
|
|
||||||
//[VBoxManage](https://www.virtualbox.org/manual/ch09.html) is the command-line
|
|
||||||
//interface to VirtualBox where you can completely control VirtualBox. It can be
|
|
||||||
//used to do things such as set RAM, CPUs, etc.
|
|
||||||
type VBoxManageConfig struct {
|
type VBoxManageConfig struct {
|
||||||
// Custom `VBoxManage` commands to execute in order to further customize
|
// Custom `VBoxManage` commands to execute in order to further customize
|
||||||
// the virtual machine being created. The example shown below sets the memory and number of CPUs
|
// the virtual machine being created. The value of this is an array of
|
||||||
// within the virtual machine:
|
// commands to execute. The commands are executed in the order defined in
|
||||||
//
|
// the template. For each command, the command is defined itself as an
|
||||||
// In JSON:
|
// array of strings, where each string represents a single argument on the
|
||||||
// ```json
|
// command-line to `VBoxManage` (but excluding `VBoxManage` itself). Each
|
||||||
// "vboxmanage": [
|
// arg is treated as a [configuration
|
||||||
// ["modifyvm", "{{.Name}}", "--memory", "1024"],
|
// template](/docs/templates/engine), where the `Name` variable is
|
||||||
// ["modifyvm", "{{.Name}}", "--cpus", "2"]
|
// replaced with the VM name. More details on how to use `VBoxManage` are
|
||||||
// ]
|
// below.
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// In HCL2:
|
|
||||||
// ```hcl
|
|
||||||
// vboxmanage = [
|
|
||||||
// ["modifyvm", "{{.Name}}", "--memory", "1024"],
|
|
||||||
// ["modifyvm", "{{.Name}}", "--cpus", "2"],
|
|
||||||
// ]
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// The value of `vboxmanage` is an array of commands to execute. These commands are
|
|
||||||
// executed in the order defined. So in the above example, the memory will be set
|
|
||||||
// followed by the CPUs.
|
|
||||||
// Each command itself is an array of strings, where each string is an argument to
|
|
||||||
// `VBoxManage`. Each argument is treated as a [configuration
|
|
||||||
// template](/docs/templates/engine). The only available
|
|
||||||
// variable is `Name` which is replaced with the unique name of the VM, which is
|
|
||||||
// required for many VBoxManage calls.
|
|
||||||
VBoxManage [][]string `mapstructure:"vboxmanage" required:"false"`
|
VBoxManage [][]string `mapstructure:"vboxmanage" required:"false"`
|
||||||
// Identical to vboxmanage,
|
// Identical to vboxmanage,
|
||||||
// except that it is run after the virtual machine is shutdown, and before the
|
// except that it is run after the virtual machine is shutdown, and before the
|
||||||
|
|||||||
@@ -89,20 +89,12 @@ type Config struct {
|
|||||||
// pack](https://www.virtualbox.org/wiki/Downloads#VirtualBox6.0.14OracleVMVirtualBoxExtensionPack)
|
// pack](https://www.virtualbox.org/wiki/Downloads#VirtualBox6.0.14OracleVMVirtualBoxExtensionPack)
|
||||||
// and you will need to enable EFI mode for nvme to work, ex:
|
// and you will need to enable EFI mode for nvme to work, ex:
|
||||||
//
|
//
|
||||||
// In JSON:
|
|
||||||
// ```json
|
// ```json
|
||||||
// "vboxmanage": [
|
// "vboxmanage": [
|
||||||
// [ "modifyvm", "{{.Name}}", "--firmware", "EFI" ],
|
// [ "modifyvm", "{{.Name}}", "--firmware", "EFI" ],
|
||||||
// ]
|
// ]
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// In HCL2:
|
|
||||||
// ```hcl
|
|
||||||
// vboxmanage = [
|
|
||||||
// [ "modifyvm", "{{.Name}}", "--firmware", "EFI" ],
|
|
||||||
// ]
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
HardDriveInterface string `mapstructure:"hard_drive_interface" required:"false"`
|
HardDriveInterface string `mapstructure:"hard_drive_interface" required:"false"`
|
||||||
// The number of ports available on any SATA controller created, defaults
|
// The number of ports available on any SATA controller created, defaults
|
||||||
// to 1. VirtualBox supports up to 30 ports on a maximum of 1 SATA
|
// to 1. VirtualBox supports up to 30 ports on a maximum of 1 SATA
|
||||||
|
|||||||
@@ -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
|
// Get the host ip address for the vm
|
||||||
HostIP(multistep.StateBag) (string, error)
|
HostIP(multistep.StateBag) (string, error)
|
||||||
|
|
||||||
// Export the vm to ovf or ova format using ovftool
|
|
||||||
Export([]string) error
|
|
||||||
|
|
||||||
// OvfTool
|
|
||||||
VerifyOvfTool(bool, bool) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDriver returns a new driver implementation for this operating
|
// NewDriver returns a new driver implementation for this operating
|
||||||
@@ -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)
|
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
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
@@ -78,13 +84,58 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *DriverConfig) Validate(SkipExport bool) 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.RemoteType != "" && c.RemotePassword == "" {
|
// check that password is valid by sending a dummy ovftool command
|
||||||
return fmt.Errorf("exporting the vm from esxi with ovftool requires " +
|
// now, so that we don't fail for a simple mistake after a long
|
||||||
"that you set a value for remote_password")
|
// 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
|
return nil
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -156,7 +154,7 @@ func (d *ESX5Driver) Stop(vmxPathLocal string) error {
|
|||||||
|
|
||||||
func (d *ESX5Driver) Register(vmxPathLocal string) error {
|
func (d *ESX5Driver) Register(vmxPathLocal string) error {
|
||||||
vmxPath := filepath.ToSlash(filepath.Join(d.outputDir, filepath.Base(vmxPathLocal)))
|
vmxPath := filepath.ToSlash(filepath.Join(d.outputDir, filepath.Base(vmxPathLocal)))
|
||||||
if err := d.upload(vmxPath, vmxPathLocal, nil); err != nil {
|
if err := d.upload(vmxPath, vmxPathLocal); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, err := d.run(nil, "vim-cmd", "solo/registervm", strconv.Quote(vmxPath))
|
r, err := d.run(nil, "vim-cmd", "solo/registervm", strconv.Quote(vmxPath))
|
||||||
@@ -187,7 +185,7 @@ func (d *ESX5Driver) IsDestroyed() (bool, error) {
|
|||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) UploadISO(localPath string, checksum string, ui packer.Ui) (string, error) {
|
func (d *ESX5Driver) UploadISO(localPath string, checksum string) (string, error) {
|
||||||
finalPath := d.CachePath(localPath)
|
finalPath := d.CachePath(localPath)
|
||||||
if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil {
|
if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -200,7 +198,7 @@ func (d *ESX5Driver) UploadISO(localPath string, checksum string, ui packer.Ui)
|
|||||||
}
|
}
|
||||||
log.Println("Initial checksum did not match, uploading.")
|
log.Println("Initial checksum did not match, uploading.")
|
||||||
|
|
||||||
if err := d.upload(finalPath, localPath, ui); err != nil {
|
if err := d.upload(finalPath, localPath); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,68 +263,6 @@ func (d *ESX5Driver) Verify() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) VerifyOvfTool(SkipExport, skipValidateCredentials bool) error {
|
|
||||||
err := d.base.VerifyOvfTool(SkipExport, skipValidateCredentials)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Verifying that ovftool credentials are valid...")
|
|
||||||
// check that password is valid by sending a dummy ovftool command
|
|
||||||
// now, so that we don't fail for a simple mistake after a long
|
|
||||||
// build
|
|
||||||
ovftool := GetOVFTool()
|
|
||||||
|
|
||||||
if skipValidateCredentials {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.Password == "" {
|
|
||||||
return fmt.Errorf("exporting the vm from esxi with ovftool requires " +
|
|
||||||
"that you set a value for remote_password")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the uri of the host, with embedded credentials
|
|
||||||
ovftool_uri := fmt.Sprintf("vi://%s", d.Host)
|
|
||||||
u, err := url.Parse(ovftool_uri)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Couldn't generate uri for ovftool: %s", err)
|
|
||||||
}
|
|
||||||
u.User = url.UserPassword(d.Username, d.Password)
|
|
||||||
|
|
||||||
ovfToolArgs := []string{"--noSSLVerify", "--verifyOnly", u.String()}
|
|
||||||
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmdCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
cmd := exec.CommandContext(cmdCtx, ovftool, ovfToolArgs...)
|
|
||||||
cmd.Stdout = &out
|
|
||||||
|
|
||||||
// Need to manually close stdin or else the ofvtool call will hang
|
|
||||||
// forever in a situation where the user has provided an invalid
|
|
||||||
// password or username
|
|
||||||
stdin, _ := cmd.StdinPipe()
|
|
||||||
defer stdin.Close()
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
outString := out.String()
|
|
||||||
// The command *should* fail with this error, if it
|
|
||||||
// authenticates properly.
|
|
||||||
if !strings.Contains(outString, "Found wrong kind of object") {
|
|
||||||
err := fmt.Errorf("ovftool validation error: %s; %s",
|
|
||||||
err, outString)
|
|
||||||
if strings.Contains(outString,
|
|
||||||
"Enter login information for source") {
|
|
||||||
err = fmt.Errorf("The username or password you " +
|
|
||||||
"provided to ovftool is invalid.")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
|
func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
|
||||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -731,26 +667,12 @@ func (d *ESX5Driver) mkdir(path string) error {
|
|||||||
return d.sh("mkdir", "-p", strconv.Quote(path))
|
return d.sh("mkdir", "-p", strconv.Quote(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) upload(dst, src string, ui packer.Ui) error {
|
func (d *ESX5Driver) upload(dst, src string) error {
|
||||||
// Get size so we can set up progress tracker
|
|
||||||
info, err := os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(src)
|
f, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
if ui != nil {
|
|
||||||
pf := ui.TrackProgress(filepath.Base(src), 0, info.Size(), f)
|
|
||||||
defer pf.Close()
|
|
||||||
|
|
||||||
return d.comm.Upload(dst, pf, &info)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.comm.Upload(dst, f, nil)
|
return d.comm.Upload(dst, f, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,10 +685,6 @@ func (d *ESX5Driver) Download(src, dst string) error {
|
|||||||
return d.comm.Download(d.datastorePath(src), file)
|
return d.comm.Download(d.datastorePath(src), file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) Export(args []string) error {
|
|
||||||
return d.base.Export(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyChecksum checks that file on the esxi instance matches hash
|
// VerifyChecksum checks that file on the esxi instance matches hash
|
||||||
func (d *ESX5Driver) VerifyChecksum(hash string, file string) bool {
|
func (d *ESX5Driver) VerifyChecksum(hash string, file string) bool {
|
||||||
if hash == "none" {
|
if hash == "none" {
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ type DriverMock struct {
|
|||||||
CreateDiskTypeId string
|
CreateDiskTypeId string
|
||||||
CreateDiskErr error
|
CreateDiskErr error
|
||||||
|
|
||||||
ExportCalled bool
|
|
||||||
ExportArgs []string
|
|
||||||
|
|
||||||
IsRunningCalled bool
|
IsRunningCalled bool
|
||||||
IsRunningPath string
|
IsRunningPath string
|
||||||
IsRunningResult bool
|
IsRunningResult bool
|
||||||
@@ -95,8 +92,6 @@ type DriverMock struct {
|
|||||||
|
|
||||||
VerifyCalled bool
|
VerifyCalled bool
|
||||||
VerifyErr error
|
VerifyErr error
|
||||||
|
|
||||||
VerifyOvftoolCalled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkMapperMock struct {
|
type NetworkMapperMock struct {
|
||||||
@@ -259,12 +254,6 @@ func (d *DriverMock) Verify() error {
|
|||||||
return d.VerifyErr
|
return d.VerifyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DriverMock) Export(args []string) error {
|
|
||||||
d.ExportCalled = true
|
|
||||||
d.ExportArgs = args
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DriverMock) GetVmwareDriver() VmwareDriver {
|
func (d *DriverMock) GetVmwareDriver() VmwareDriver {
|
||||||
var state VmwareDriver
|
var state VmwareDriver
|
||||||
state.DhcpLeasesPath = func(string) string {
|
state.DhcpLeasesPath = func(string) string {
|
||||||
@@ -281,7 +270,3 @@ func (d *DriverMock) GetVmwareDriver() VmwareDriver {
|
|||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DriverMock) VerifyOvfTool(_ bool, _ bool) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1196,7 +1196,7 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) {
|
|||||||
switch entry.id[0].(type) {
|
switch entry.id[0].(type) {
|
||||||
case pDeclarationHost:
|
case pDeclarationHost:
|
||||||
id := entry.id[0].(pDeclarationHost)
|
id := entry.id[0].(pDeclarationHost)
|
||||||
if strings.EqualFold(id.name, host) {
|
if strings.ToLower(id.name) == strings.ToLower(host) {
|
||||||
result = append(result, entry)
|
result = append(result, entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1237,7 +1237,7 @@ func (e NetworkMap) NameIntoDevices(name string) ([]string, error) {
|
|||||||
var devices []string
|
var devices []string
|
||||||
|
|
||||||
for _, val := range e {
|
for _, val := range e {
|
||||||
if strings.EqualFold(val["name"], name) {
|
if strings.ToLower(val["name"]) == strings.ToLower(name) {
|
||||||
devices = append(devices, val["device"])
|
devices = append(devices, val["device"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1250,7 +1250,7 @@ func (e NetworkMap) NameIntoDevices(name string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
func (e NetworkMap) DeviceIntoName(device string) (string, error) {
|
func (e NetworkMap) DeviceIntoName(device string) (string, error) {
|
||||||
for _, val := range e {
|
for _, val := range e {
|
||||||
if strings.EqualFold(val["device"], device) {
|
if strings.ToLower(val["device"]) == strings.ToLower(device) {
|
||||||
return val["name"], nil
|
return val["name"], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,30 +10,33 @@ import (
|
|||||||
|
|
||||||
type ExportConfig struct {
|
type ExportConfig struct {
|
||||||
// Either "ovf", "ova" or "vmx", this specifies the output
|
// Either "ovf", "ova" or "vmx", this specifies the output
|
||||||
// format of the exported virtual machine. This defaults to "ovf" for
|
// format of the exported virtual machine. This defaults to "ovf".
|
||||||
// remote (esx) builds, and "vmx" for local builds.
|
// Before using this option, you need to install ovftool. This option
|
||||||
// Before using this option, you need to install ovftool.
|
// currently only works when option remote_type is set to "esx5".
|
||||||
// Since ovftool is only capable of password based authentication
|
// Since ovftool is only capable of password based authentication
|
||||||
// remote_password must be set when exporting the VM from a remote instance.
|
// remote_password must be set when exporting the VM.
|
||||||
// If you are building locally, Packer will create a vmx and then
|
|
||||||
// export that vm to an ovf or ova. Packer will not delete the vmx and vmdk
|
|
||||||
// files; this is left up to the user if you don't want to keep those
|
|
||||||
// files.
|
|
||||||
Format string `mapstructure:"format" required:"false"`
|
Format string `mapstructure:"format" required:"false"`
|
||||||
// Extra options to pass to ovftool during export. Each item in the array
|
// Extra options to pass to ovftool during export. Each item in the array
|
||||||
// is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`,
|
// is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`,
|
||||||
// and `--targetType` are used by Packer for remote exports, and should not
|
// and `--targetType` are reserved, and should not be passed to this
|
||||||
// be passed to this argument. For ovf/ova exports from local builds, Packer
|
// argument. Currently, exporting the build VM (with ovftool) is only
|
||||||
// does not automatically set any ovftool options.
|
// 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"`
|
OVFToolOptions []string `mapstructure:"ovftool_options" required:"false"`
|
||||||
// Defaults to `false`. When true, Packer will not export the VM. This can
|
// Defaults to `false`. When enabled, Packer will not export the VM. Useful
|
||||||
// be useful if the build output is not the resultant image, but created
|
// if the build output is not the resultant image, but created inside the
|
||||||
// inside the VM.
|
// 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"`
|
SkipExport bool `mapstructure:"skip_export" required:"false"`
|
||||||
// Set this to true if you would like to keep a remotely-built
|
// Set this to true if you would like to keep
|
||||||
// VM registered with the remote ESXi server. If you do not need to export
|
// 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 unnecessarily
|
// the vm, then also set skip_export: true in order to avoid an unnecessary
|
||||||
// using ovftool to export the vm. Defaults to false.
|
// step of using ovftool to export the vm. Defaults to false.
|
||||||
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
|
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
|
||||||
// VMware-created disks are defragmented and
|
// VMware-created disks are defragmented and
|
||||||
// compacted at the end of the build process using vmware-vdiskmanager or
|
// compacted at the end of the build process using vmware-vdiskmanager or
|
||||||
@@ -53,6 +56,5 @@ func (c *ExportConfig) Prepare(ctx *interpolate.Context) []error {
|
|||||||
errs, fmt.Errorf("format must be one of ova, ovf, or vmx"))
|
errs, fmt.Errorf("format must be one of ova, ovf, or vmx"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ type HWConfig struct {
|
|||||||
// machine](https://kb.vmware.com/s/article/1001805) for desktop VMware
|
// machine](https://kb.vmware.com/s/article/1001805) for desktop VMware
|
||||||
// clients. For ESXi, refer to the proper ESXi documentation.
|
// clients. For ESXi, refer to the proper ESXi documentation.
|
||||||
NetworkAdapterType string `mapstructure:"network_adapter_type" required:"false"`
|
NetworkAdapterType string `mapstructure:"network_adapter_type" required:"false"`
|
||||||
// The custom name of the network. Sets the vmx value "ethernet0.networkName"
|
|
||||||
NetworkName string `mapstructure:"network_name" required:"false"`
|
|
||||||
// Specify whether to enable VMware's virtual soundcard device when
|
// Specify whether to enable VMware's virtual soundcard device when
|
||||||
// building the VM. Defaults to false.
|
// building the VM. Defaults to false.
|
||||||
Sound bool `mapstructure:"sound" required:"false"`
|
Sound bool `mapstructure:"sound" required:"false"`
|
||||||
|
|||||||
@@ -10,39 +10,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type OutputConfig struct {
|
type OutputConfig struct {
|
||||||
// This is the path on your local machine (the one running Packer) to the
|
// This is the path to the directory where the
|
||||||
// directory where the resulting virtual machine will be created.
|
// resulting virtual machine will be created. This may be relative or absolute.
|
||||||
// This may be relative or absolute. If relative, the path is relative to
|
// If relative, the path is relative to the working directory when packer
|
||||||
// the working directory when packer is executed.
|
// is executed. This directory must not exist or be empty prior to running
|
||||||
//
|
// the builder. By default this is output-BUILDNAME where "BUILDNAME" is the
|
||||||
// If you are running a remote esx build, the output_dir is the path on your
|
// name of the build.
|
||||||
// local machine (the machine running Packer) to which Packer will export
|
|
||||||
// the vm if you have `"skip_export": false`. If you want to manage the
|
|
||||||
// virtual machine's path on the remote datastore, use `remote_output_dir`.
|
|
||||||
//
|
|
||||||
// This directory must not exist or be empty prior to running
|
|
||||||
// the builder.
|
|
||||||
//
|
|
||||||
// By default this is output-BUILDNAME where "BUILDNAME" is the name of the
|
|
||||||
// build.
|
|
||||||
OutputDir string `mapstructure:"output_directory" required:"false"`
|
OutputDir string `mapstructure:"output_directory" required:"false"`
|
||||||
// This is the directoy on your remote esx host where you will save your
|
|
||||||
// vm, relative to your remote_datastore.
|
|
||||||
//
|
|
||||||
// This option's default value is your `vm_name`, and the final path of your
|
|
||||||
// vm will be vmfs/volumes/$remote_datastore/$vm_name/$vm_name.vmx where
|
|
||||||
// `$remote_datastore` and `$vm_name` match their corresponding template
|
|
||||||
// options
|
|
||||||
//
|
|
||||||
// For example, setting `"remote_output_directory": "path/to/subdir`
|
|
||||||
// will create a directory `/vmfs/volumes/remote_datastore/path/to/subdir`.
|
|
||||||
//
|
|
||||||
// Packer will not create the remote datastore for you; it must already
|
|
||||||
// exist. However, Packer will create all directories defined in the option
|
|
||||||
// that do not currently exist.
|
|
||||||
//
|
|
||||||
// This option will be ignored unless you are building on a remote esx host.
|
|
||||||
RemoteOutputDir string `mapstructure:"remote_output_directory" required:"false"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error {
|
func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error {
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RemoteDriver interface {
|
type RemoteDriver interface {
|
||||||
Driver
|
Driver
|
||||||
|
|
||||||
// UploadISO uploads a local ISO to the remote side and returns the
|
// UploadISO uploads a local ISO to the remote side and returns the
|
||||||
// new path that should be used in the VMX along with an error if it
|
// new path that should be used in the VMX along with an error if it
|
||||||
// exists.
|
// exists.
|
||||||
UploadISO(path string, checksum string, ui packer.Ui) (string, error)
|
UploadISO(path string, checksum string) (string, error)
|
||||||
|
|
||||||
// RemoveCache deletes localPath from the remote cache.
|
// RemoveCache deletes localPath from the remote cache.
|
||||||
RemoveCache(localPath string) error
|
RemoveCache(localPath string) error
|
||||||
@@ -28,7 +24,7 @@ type RemoteDriver interface {
|
|||||||
IsDestroyed() (bool, error)
|
IsDestroyed() (bool, error)
|
||||||
|
|
||||||
// Uploads a local file to remote side.
|
// Uploads a local file to remote side.
|
||||||
upload(dst, src string, ui packer.Ui) error
|
upload(dst, src string) error
|
||||||
|
|
||||||
// Download a remote file to a local file.
|
// Download a remote file to a local file.
|
||||||
Download(src, dst string) error
|
Download(src, dst string) error
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/packer/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RemoteDriverMock struct {
|
type RemoteDriverMock struct {
|
||||||
DriverMock
|
DriverMock
|
||||||
|
|
||||||
@@ -33,14 +29,10 @@ type RemoteDriverMock struct {
|
|||||||
RemovedCachePath string
|
RemovedCachePath string
|
||||||
CacheRemoved bool
|
CacheRemoved bool
|
||||||
|
|
||||||
ReturnValDirExists bool
|
|
||||||
|
|
||||||
ReloadVMErr error
|
ReloadVMErr error
|
||||||
|
|
||||||
outputDir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RemoteDriverMock) UploadISO(path string, checksum string, ui packer.Ui) (string, error) {
|
func (d *RemoteDriverMock) UploadISO(path string, checksum string) (string, error) {
|
||||||
d.UploadISOCalled = true
|
d.UploadISOCalled = true
|
||||||
d.UploadISOPath = path
|
d.UploadISOPath = path
|
||||||
return d.UploadISOResult, d.UploadISOErr
|
return d.UploadISOResult, d.UploadISOErr
|
||||||
@@ -68,7 +60,7 @@ func (d *RemoteDriverMock) IsDestroyed() (bool, error) {
|
|||||||
return d.IsDestroyedResult, d.IsDestroyedErr
|
return d.IsDestroyedResult, d.IsDestroyedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RemoteDriverMock) upload(dst, src string, ui packer.Ui) error {
|
func (d *RemoteDriverMock) upload(dst, src string) error {
|
||||||
return d.UploadErr
|
return d.UploadErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,33 +77,3 @@ func (d *RemoteDriverMock) RemoveCache(localPath string) error {
|
|||||||
func (d *RemoteDriverMock) ReloadVM() error {
|
func (d *RemoteDriverMock) ReloadVM() error {
|
||||||
return d.ReloadVMErr
|
return d.ReloadVMErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// the following functions satisfy the Outputdir interface
|
|
||||||
|
|
||||||
func (d *RemoteDriverMock) DirExists() (bool, error) {
|
|
||||||
return d.ReturnValDirExists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *RemoteDriverMock) ListFiles() ([]string, error) {
|
|
||||||
return []string{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *RemoteDriverMock) MkdirAll() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *RemoteDriverMock) Remove(string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *RemoteDriverMock) RemoveAll() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *RemoteDriverMock) SetOutputDir(s string) {
|
|
||||||
d.outputDir = s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *RemoteDriverMock) String() string {
|
|
||||||
return d.outputDir
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
@@ -13,15 +15,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// This step exports a VM built on ESXi using ovftool
|
// This step exports a VM built on ESXi using ovftool
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// display_name string
|
||||||
type StepExport struct {
|
type StepExport struct {
|
||||||
Format string
|
Format string
|
||||||
SkipExport bool
|
SkipExport bool
|
||||||
VMName string
|
VMName string
|
||||||
OVFToolOptions []string
|
OVFToolOptions []string
|
||||||
OutputDir *string
|
OutputDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (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)
|
ovftool_uri := fmt.Sprintf("vi://%s/%s", c.RemoteHost, displayName)
|
||||||
u, err := url.Parse(ovftool_uri)
|
u, err := url.Parse(ovftool_uri)
|
||||||
@@ -40,15 +57,7 @@ func (s *StepExport) generateRemoteExportArgs(c *DriverConfig, displayName strin
|
|||||||
"--skipManifestCheck",
|
"--skipManifestCheck",
|
||||||
"-tt=" + s.Format,
|
"-tt=" + s.Format,
|
||||||
u.String(),
|
u.String(),
|
||||||
filepath.Join(exportOutputPath, s.VMName+"."+s.Format),
|
s.OutputDir,
|
||||||
}
|
|
||||||
return append(s.OVFToolOptions, args...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepExport) generateLocalExportArgs(exportOutputPath string) ([]string, error) {
|
|
||||||
args := []string{
|
|
||||||
filepath.Join(exportOutputPath, s.VMName+".vmx"),
|
|
||||||
filepath.Join(exportOutputPath, s.VMName+"."+s.Format),
|
|
||||||
}
|
}
|
||||||
return append(s.OVFToolOptions, args...), nil
|
return append(s.OVFToolOptions, args...), nil
|
||||||
}
|
}
|
||||||
@@ -56,7 +65,6 @@ func (s *StepExport) generateLocalExportArgs(exportOutputPath string) ([]string,
|
|||||||
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
c := state.Get("driverConfig").(*DriverConfig)
|
c := state.Get("driverConfig").(*DriverConfig)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
driver := state.Get("driver").(Driver)
|
|
||||||
|
|
||||||
// Skip export if requested
|
// Skip export if requested
|
||||||
if s.SkipExport {
|
if s.SkipExport {
|
||||||
@@ -64,72 +72,57 @@ func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multiste
|
|||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
// load output path from state. If it doesn't exist, just use the local
|
if c.RemoteType != "esx5" {
|
||||||
// outputdir.
|
ui.Say("Skipping export of virtual machine (export is allowed only for ESXi)...")
|
||||||
exportOutputPath, ok := state.Get("export_output_path").(string)
|
return multistep.ActionContinue
|
||||||
if !ok || exportOutputPath == "" {
|
|
||||||
if *s.OutputDir != "" {
|
|
||||||
exportOutputPath = *s.OutputDir
|
|
||||||
} else {
|
|
||||||
exportOutputPath = s.VMName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.MkdirAll(exportOutputPath, 0755)
|
ovftool := GetOVFTool()
|
||||||
if err != nil {
|
if ovftool == "" {
|
||||||
state.Put("error creating export directory", err)
|
err := fmt.Errorf("Error ovftool not found")
|
||||||
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export the VM
|
||||||
|
if s.OutputDir == "" {
|
||||||
|
s.OutputDir = s.VMName + "." + s.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
os.MkdirAll(s.OutputDir, 0755)
|
||||||
|
|
||||||
ui.Say("Exporting virtual machine...")
|
ui.Say("Exporting virtual machine...")
|
||||||
var displayName string
|
var displayName string
|
||||||
if v, ok := state.GetOk("display_name"); ok {
|
if v, ok := state.GetOk("display_name"); ok {
|
||||||
displayName = v.(string)
|
displayName = v.(string)
|
||||||
}
|
}
|
||||||
|
ui_args, err := s.generateArgs(c, displayName, true)
|
||||||
var args, ui_args []string
|
|
||||||
|
|
||||||
ovftool := GetOVFTool()
|
|
||||||
if c.RemoteType == "esx5" {
|
|
||||||
// Generate arguments for the ovftool command, but obfuscating the
|
|
||||||
// password that we can log the command to the UI for debugging.
|
|
||||||
ui_args, err := s.generateRemoteExportArgs(c, displayName, true, exportOutputPath)
|
|
||||||
if err != nil {
|
|
||||||
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, " ")))
|
|
||||||
}
|
|
||||||
if err != nil {
|
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)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := driver.Export(args); err != nil {
|
ui.Message(out.String())
|
||||||
err := fmt.Errorf("Error performing ovftool export: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,41 +2,22 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStepExport_impl(t *testing.T) {
|
func TestStepExport_impl(t *testing.T) {
|
||||||
var _ multistep.Step = new(StepExport)
|
var _ multistep.Step = new(StepExport)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringPointer(s string) *string {
|
func testStepExport_wrongtype_impl(t *testing.T, remoteType string) {
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func remoteExportTestState(t *testing.T) multistep.StateBag {
|
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
driverConfig := &DriverConfig{
|
|
||||||
RemoteHost: "123.45.67.8",
|
|
||||||
RemotePassword: "password",
|
|
||||||
RemoteUser: "user",
|
|
||||||
RemoteType: "esx5",
|
|
||||||
}
|
|
||||||
state.Put("driverConfig", driverConfig)
|
|
||||||
state.Put("display_name", "vm_name")
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStepExport_ReturnIfSkip(t *testing.T) {
|
|
||||||
state := testState(t)
|
|
||||||
driverConfig := &DriverConfig{}
|
|
||||||
state.Put("driverConfig", driverConfig)
|
|
||||||
step := new(StepExport)
|
step := new(StepExport)
|
||||||
|
|
||||||
step.SkipExport = true
|
var config DriverConfig
|
||||||
|
config.RemoteType = "foo"
|
||||||
|
state.Put("driverConfig", &config)
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||||
t.Fatalf("bad action: %#v", action)
|
t.Fatalf("bad action: %#v", action)
|
||||||
@@ -45,189 +26,11 @@ func TestStepExport_ReturnIfSkip(t *testing.T) {
|
|||||||
t.Fatal("should NOT have error")
|
t.Fatal("should NOT have error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We told step to skip so it should not have reached the driver's Export
|
|
||||||
// func.
|
|
||||||
d := state.Get("driver").(*DriverMock)
|
|
||||||
if d.ExportCalled {
|
|
||||||
t.Fatal("Should not have called the driver export func")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepExport_localArgs(t *testing.T) {
|
func TestStepExport_wrongtype_impl(t *testing.T) {
|
||||||
// even though we aren't overriding the remote args and they are present,
|
testStepExport_wrongtype_impl(t, "foo")
|
||||||
// test shouldn't use them since remoteType is not set to esx.
|
testStepExport_wrongtype_impl(t, "")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,62 +16,13 @@ import (
|
|||||||
type StepOutputDir struct {
|
type StepOutputDir struct {
|
||||||
Force bool
|
Force bool
|
||||||
|
|
||||||
OutputConfig *OutputConfig
|
|
||||||
VMName string
|
|
||||||
|
|
||||||
RemoteType string
|
|
||||||
|
|
||||||
success bool
|
success bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepOutputDir) SetOutputAndExportDirs(state multistep.StateBag) OutputDir {
|
|
||||||
driver := state.Get("driver")
|
|
||||||
|
|
||||||
// Hold on to your pants. The output configuration is a little more complex
|
|
||||||
// than you'd expect because of all the moving parts between local and
|
|
||||||
// remote output, and exports, and legacy behavior.
|
|
||||||
var dir OutputDir
|
|
||||||
switch d := driver.(type) {
|
|
||||||
case OutputDir:
|
|
||||||
// The driver fulfils the OutputDir interface so that it can create
|
|
||||||
// output files on the remote instance.
|
|
||||||
dir = d
|
|
||||||
default:
|
|
||||||
// The driver will be running the build and creating the output
|
|
||||||
// directory locally
|
|
||||||
dir = new(LocalOutputDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If remote type is esx, we need to track both the output dir on the remote
|
|
||||||
// instance and the output dir locally. exportOutputPath is where we track
|
|
||||||
// the local output dir.
|
|
||||||
exportOutputPath := s.OutputConfig.OutputDir
|
|
||||||
|
|
||||||
if s.RemoteType != "" {
|
|
||||||
if s.OutputConfig.RemoteOutputDir != "" {
|
|
||||||
// User set the remote output dir.
|
|
||||||
s.OutputConfig.OutputDir = s.OutputConfig.RemoteOutputDir
|
|
||||||
} else {
|
|
||||||
// Default output dir to vm name. On remote esx instance, this will
|
|
||||||
// become something like /vmfs/volumes/mydatastore/vmname/vmname.vmx
|
|
||||||
s.OutputConfig.OutputDir = s.VMName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remember, this one's either the output from a local build, or the remote
|
|
||||||
// output from a remote build. Not the local export path for a remote build.
|
|
||||||
dir.SetOutputDir(s.OutputConfig.OutputDir)
|
|
||||||
|
|
||||||
// Set dir in the state for use in file cleanup and artifact
|
|
||||||
state.Put("dir", dir)
|
|
||||||
state.Put("export_output_path", exportOutputPath)
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
dir := state.Get("dir").(OutputDir)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
ui.Say("Configuring output and export directories...")
|
|
||||||
|
|
||||||
dir := s.SetOutputAndExportDirs(state)
|
|
||||||
exists, err := dir.DirExists()
|
exists, err := dir.DirExists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
@@ -80,7 +31,7 @@ func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multi
|
|||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
if s.Force {
|
if s.Force {
|
||||||
ui.Message("Deleting previous output directory...")
|
ui.Say("Deleting previous output directory...")
|
||||||
dir.RemoveAll()
|
dir.RemoveAll()
|
||||||
} else {
|
} else {
|
||||||
state.Put("error", fmt.Errorf(
|
state.Put("error", fmt.Errorf(
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ import (
|
|||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testOutputDir(t *testing.T) string {
|
func testOutputDir(t *testing.T) *LocalOutputDir {
|
||||||
td, err := ioutil.TempDir("", "packer")
|
td, err := ioutil.TempDir("", "packer")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
os.RemoveAll(td)
|
os.RemoveAll(td)
|
||||||
|
|
||||||
return td
|
result := new(LocalOutputDir)
|
||||||
|
result.SetOutputDir(td)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepOutputDir_impl(t *testing.T) {
|
func TestStepOutputDir_impl(t *testing.T) {
|
||||||
@@ -25,20 +27,12 @@ func TestStepOutputDir_impl(t *testing.T) {
|
|||||||
|
|
||||||
func TestStepOutputDir(t *testing.T) {
|
func TestStepOutputDir(t *testing.T) {
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
driver := new(DriverMock)
|
step := new(StepOutputDir)
|
||||||
state.Put("driver", driver)
|
|
||||||
|
|
||||||
td := testOutputDir(t)
|
dir := testOutputDir(t)
|
||||||
outconfig := &OutputConfig{
|
|
||||||
OutputDir: td,
|
|
||||||
}
|
|
||||||
|
|
||||||
step := &StepOutputDir{
|
|
||||||
OutputConfig: outconfig,
|
|
||||||
VMName: "testVM",
|
|
||||||
}
|
|
||||||
// Delete the test output directory when done
|
// Delete the test output directory when done
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(dir.dir)
|
||||||
|
state.Put("dir", dir)
|
||||||
|
|
||||||
// Test the run
|
// Test the run
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||||
@@ -47,37 +41,29 @@ func TestStepOutputDir(t *testing.T) {
|
|||||||
if _, ok := state.GetOk("error"); ok {
|
if _, ok := state.GetOk("error"); ok {
|
||||||
t.Fatal("should NOT have error")
|
t.Fatal("should NOT have error")
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(td); err != nil {
|
if _, err := os.Stat(dir.dir); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the cleanup
|
// Test the cleanup
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
||||||
if _, err := os.Stat(td); err != nil {
|
if _, err := os.Stat(dir.dir); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepOutputDir_existsNoForce(t *testing.T) {
|
func TestStepOutputDir_existsNoForce(t *testing.T) {
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
|
step := new(StepOutputDir)
|
||||||
|
|
||||||
td := testOutputDir(t)
|
dir := testOutputDir(t)
|
||||||
outconfig := &OutputConfig{
|
state.Put("dir", dir)
|
||||||
OutputDir: td,
|
|
||||||
}
|
|
||||||
|
|
||||||
step := &StepOutputDir{
|
|
||||||
OutputConfig: outconfig,
|
|
||||||
VMName: "testVM",
|
|
||||||
}
|
|
||||||
// Delete the test output directory when done
|
|
||||||
defer os.RemoveAll(td)
|
|
||||||
|
|
||||||
// Make sure the dir exists
|
// Make sure the dir exists
|
||||||
if err := os.MkdirAll(td, 0755); err != nil {
|
if err := os.MkdirAll(dir.dir, 0755); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(dir.dir)
|
||||||
|
|
||||||
// Test the run
|
// Test the run
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||||
@@ -89,33 +75,24 @@ func TestStepOutputDir_existsNoForce(t *testing.T) {
|
|||||||
|
|
||||||
// Test the cleanup
|
// Test the cleanup
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
||||||
if _, err := os.Stat(td); err != nil {
|
if _, err := os.Stat(dir.dir); err != nil {
|
||||||
t.Fatal("should not delete dir")
|
t.Fatal("should not delete dir")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepOutputDir_existsForce(t *testing.T) {
|
func TestStepOutputDir_existsForce(t *testing.T) {
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
|
step := new(StepOutputDir)
|
||||||
td := testOutputDir(t)
|
|
||||||
outconfig := &OutputConfig{
|
|
||||||
OutputDir: td,
|
|
||||||
}
|
|
||||||
|
|
||||||
step := &StepOutputDir{
|
|
||||||
OutputConfig: outconfig,
|
|
||||||
VMName: "testVM",
|
|
||||||
}
|
|
||||||
step.Force = true
|
step.Force = true
|
||||||
|
|
||||||
// Delete the test output directory when done
|
dir := testOutputDir(t)
|
||||||
defer os.RemoveAll(td)
|
state.Put("dir", dir)
|
||||||
|
|
||||||
// Make sure the dir exists
|
// Make sure the dir exists
|
||||||
if err := os.MkdirAll(td, 0755); err != nil {
|
if err := os.MkdirAll(dir.dir, 0755); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(dir.dir)
|
||||||
|
|
||||||
// Test the run
|
// Test the run
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||||
@@ -124,22 +101,17 @@ func TestStepOutputDir_existsForce(t *testing.T) {
|
|||||||
if _, ok := state.GetOk("error"); ok {
|
if _, ok := state.GetOk("error"); ok {
|
||||||
t.Fatal("should NOT have error")
|
t.Fatal("should NOT have error")
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(td); err != nil {
|
if _, err := os.Stat(dir.dir); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepOutputDir_cancel(t *testing.T) {
|
func TestStepOutputDir_cancel(t *testing.T) {
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
td := testOutputDir(t)
|
step := new(StepOutputDir)
|
||||||
outconfig := &OutputConfig{
|
|
||||||
OutputDir: td,
|
|
||||||
}
|
|
||||||
|
|
||||||
step := &StepOutputDir{
|
dir := testOutputDir(t)
|
||||||
OutputConfig: outconfig,
|
state.Put("dir", dir)
|
||||||
VMName: "testVM",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the run
|
// Test the run
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||||
@@ -148,29 +120,24 @@ func TestStepOutputDir_cancel(t *testing.T) {
|
|||||||
if _, ok := state.GetOk("error"); ok {
|
if _, ok := state.GetOk("error"); ok {
|
||||||
t.Fatal("should NOT have error")
|
t.Fatal("should NOT have error")
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(td); err != nil {
|
if _, err := os.Stat(dir.dir); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test cancel/halt
|
// Test cancel/halt
|
||||||
state.Put(multistep.StateCancelled, true)
|
state.Put(multistep.StateCancelled, true)
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
||||||
if _, err := os.Stat(td); err == nil {
|
if _, err := os.Stat(dir.dir); err == nil {
|
||||||
t.Fatal("directory should not exist")
|
t.Fatal("directory should not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepOutputDir_halt(t *testing.T) {
|
func TestStepOutputDir_halt(t *testing.T) {
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
td := testOutputDir(t)
|
step := new(StepOutputDir)
|
||||||
outconfig := &OutputConfig{
|
|
||||||
OutputDir: td,
|
|
||||||
}
|
|
||||||
|
|
||||||
step := &StepOutputDir{
|
dir := testOutputDir(t)
|
||||||
OutputConfig: outconfig,
|
state.Put("dir", dir)
|
||||||
VMName: "testVM",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the run
|
// Test the run
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||||
@@ -179,46 +146,14 @@ func TestStepOutputDir_halt(t *testing.T) {
|
|||||||
if _, ok := state.GetOk("error"); ok {
|
if _, ok := state.GetOk("error"); ok {
|
||||||
t.Fatal("should NOT have error")
|
t.Fatal("should NOT have error")
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(td); err != nil {
|
if _, err := os.Stat(dir.dir); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test cancel/halt
|
// Test cancel/halt
|
||||||
state.Put(multistep.StateHalted, true)
|
state.Put(multistep.StateHalted, true)
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
||||||
if _, err := os.Stat(td); err == nil {
|
if _, err := os.Stat(dir.dir); err == nil {
|
||||||
t.Fatal("directory should not exist")
|
t.Fatal("directory should not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepOutputDir_Remote(t *testing.T) {
|
|
||||||
// Tests remote driver
|
|
||||||
state := testState(t)
|
|
||||||
driver := new(RemoteDriverMock)
|
|
||||||
state.Put("driver", driver)
|
|
||||||
|
|
||||||
td := testOutputDir(t)
|
|
||||||
outconfig := &OutputConfig{
|
|
||||||
OutputDir: td,
|
|
||||||
RemoteOutputDir: "remote_path",
|
|
||||||
}
|
|
||||||
|
|
||||||
step := &StepOutputDir{
|
|
||||||
OutputConfig: outconfig,
|
|
||||||
VMName: "testVM",
|
|
||||||
RemoteType: "esx5",
|
|
||||||
}
|
|
||||||
// Delete the test output directory when done
|
|
||||||
defer os.RemoveAll(td)
|
|
||||||
|
|
||||||
// Test the run
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't pre-create the output path for export but we do set it in state.
|
|
||||||
exportOutputPath := state.Get("export_output_path").(string)
|
|
||||||
if exportOutputPath != td {
|
|
||||||
t.Fatalf("err: should have set export_output_path!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if remoteDriver, ok := driver.(RemoteDriver); ok {
|
if remoteDriver, ok := driver.(RemoteDriver); ok {
|
||||||
if s.SkipExport && !cancelled && !halted {
|
if s.SkipExport {
|
||||||
ui.Say("Unregistering virtual machine...")
|
ui.Say("Unregistering virtual machine...")
|
||||||
if err := remoteDriver.Unregister(s.registeredPath); err != nil {
|
if err := remoteDriver.Unregister(s.registeredPath); err != nil {
|
||||||
ui.Error(fmt.Sprintf("Error unregistering VM: %s", err))
|
ui.Error(fmt.Sprintf("Error unregistering VM: %s", err))
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (s *StepRemoteUpload) Run(ctx context.Context, state multistep.StateBag) mu
|
|||||||
|
|
||||||
ui.Say(s.Message)
|
ui.Say(s.Message)
|
||||||
log.Printf("Remote uploading: %s", path)
|
log.Printf("Remote uploading: %s", path)
|
||||||
newPath, err := remote.UploadISO(path, s.Checksum, ui)
|
newPath, err := remote.UploadISO(path, s.Checksum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error uploading file: %s", err)
|
err := fmt.Errorf("Error uploading file: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|||||||
@@ -12,20 +12,8 @@ import (
|
|||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testLocalOutputDir(t *testing.T) *LocalOutputDir {
|
|
||||||
td, err := ioutil.TempDir("", "packer")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
os.RemoveAll(td)
|
|
||||||
|
|
||||||
result := new(LocalOutputDir)
|
|
||||||
result.SetOutputDir(td)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStepShutdownState(t *testing.T) multistep.StateBag {
|
func testStepShutdownState(t *testing.T) multistep.StateBag {
|
||||||
dir := testLocalOutputDir(t)
|
dir := testOutputDir(t)
|
||||||
if err := dir.MkdirAll(); err != nil {
|
if err := dir.MkdirAll(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (c *StepUploadVMX) Run(ctx context.Context, state multistep.StateBag) multi
|
|||||||
remoteDriver, ok := driver.(RemoteDriver)
|
remoteDriver, ok := driver.(RemoteDriver)
|
||||||
if ok {
|
if ok {
|
||||||
remoteVmxPath := filepath.ToSlash(filepath.Join(fmt.Sprintf("%s", remoteDriver), filepath.Base(vmxPath)))
|
remoteVmxPath := filepath.ToSlash(filepath.Join(fmt.Sprintf("%s", remoteDriver), filepath.Base(vmxPath)))
|
||||||
if err := remoteDriver.upload(remoteVmxPath, vmxPath, nil); err != nil {
|
if err := remoteDriver.upload(remoteVmxPath, vmxPath); err != nil {
|
||||||
state.Put("error", fmt.Errorf("Error writing VMX: %s", err))
|
state.Put("error", fmt.Errorf("Error writing VMX: %s", err))
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,16 +35,30 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
|
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
|
||||||
}
|
}
|
||||||
// Before we get deep into the build, make sure ovftool is present and
|
|
||||||
// credentials are valid, if we're going to use ovftool.
|
// Determine the output dir implementation
|
||||||
if err := driver.VerifyOvfTool(b.config.SkipExport, b.config.SkipValidateCredentials); err != nil {
|
var dir vmwcommon.OutputDir
|
||||||
return nil, err
|
switch d := driver.(type) {
|
||||||
|
case vmwcommon.OutputDir:
|
||||||
|
dir = d
|
||||||
|
default:
|
||||||
|
dir = new(vmwcommon.LocalOutputDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The OutputDir will track remote esxi output; exportOutputPath preserves
|
||||||
|
// the path to the output on the machine running Packer.
|
||||||
|
exportOutputPath := b.config.OutputDir
|
||||||
|
|
||||||
|
if b.config.RemoteType != "" {
|
||||||
|
b.config.OutputDir = b.config.VMName
|
||||||
|
}
|
||||||
|
dir.SetOutputDir(b.config.OutputDir)
|
||||||
|
|
||||||
// Setup the state bag
|
// Setup the state bag
|
||||||
state := new(multistep.BasicStateBag)
|
state := new(multistep.BasicStateBag)
|
||||||
state.Put("config", &b.config)
|
state.Put("config", &b.config)
|
||||||
state.Put("debug", b.config.PackerDebug)
|
state.Put("debug", b.config.PackerDebug)
|
||||||
|
state.Put("dir", dir)
|
||||||
state.Put("driver", driver)
|
state.Put("driver", driver)
|
||||||
state.Put("hook", hook)
|
state.Put("hook", hook)
|
||||||
state.Put("ui", ui)
|
state.Put("ui", ui)
|
||||||
@@ -66,10 +80,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
Url: b.config.ISOUrls,
|
Url: b.config.ISOUrls,
|
||||||
},
|
},
|
||||||
&vmwcommon.StepOutputDir{
|
&vmwcommon.StepOutputDir{
|
||||||
Force: b.config.PackerForce,
|
Force: b.config.PackerForce,
|
||||||
OutputConfig: &b.config.OutputConfig,
|
|
||||||
RemoteType: b.config.RemoteType,
|
|
||||||
VMName: b.config.VMName,
|
|
||||||
},
|
},
|
||||||
&common.StepCreateFloppy{
|
&common.StepCreateFloppy{
|
||||||
Files: b.config.FloppyConfig.FloppyFiles,
|
Files: b.config.FloppyConfig.FloppyFiles,
|
||||||
@@ -88,15 +99,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
DoCleanup: b.config.DriverConfig.CleanUpRemoteCache,
|
DoCleanup: b.config.DriverConfig.CleanUpRemoteCache,
|
||||||
Checksum: b.config.ISOChecksum,
|
Checksum: b.config.ISOChecksum,
|
||||||
},
|
},
|
||||||
&vmwcommon.StepCreateDisks{
|
&stepCreateDisk{},
|
||||||
OutputDir: &b.config.OutputDir,
|
|
||||||
CreateMainDisk: true,
|
|
||||||
DiskName: b.config.DiskName,
|
|
||||||
MainDiskSize: b.config.DiskSize,
|
|
||||||
AdditionalDiskSize: b.config.AdditionalDiskSize,
|
|
||||||
DiskAdapterType: b.config.DiskAdapterType,
|
|
||||||
DiskTypeId: b.config.DiskTypeId,
|
|
||||||
},
|
|
||||||
&stepCreateVMX{},
|
&stepCreateVMX{},
|
||||||
&vmwcommon.StepConfigureVMX{
|
&vmwcommon.StepConfigureVMX{
|
||||||
CustomData: b.config.VMXData,
|
CustomData: b.config.VMXData,
|
||||||
@@ -176,7 +179,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
SkipExport: b.config.SkipExport,
|
SkipExport: b.config.SkipExport,
|
||||||
VMName: b.config.VMName,
|
VMName: b.config.VMName,
|
||||||
OVFToolOptions: b.config.OVFToolOptions,
|
OVFToolOptions: b.config.OVFToolOptions,
|
||||||
OutputDir: &b.config.OutputConfig.OutputDir,
|
OutputDir: exportOutputPath,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +202,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile the artifact list
|
// Compile the artifact list
|
||||||
exportOutputPath := state.Get("export_output_path").(string) // set in StepOutputDir
|
|
||||||
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
|
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
|
||||||
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
|
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
|||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
|
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
|
||||||
|
|
||||||
b = Builder{}
|
b = Builder{}
|
||||||
_, _, errs := b.Prepare(config)
|
_, _, errs := b.Prepare(config)
|
||||||
if errs == nil {
|
if errs == nil {
|
||||||
@@ -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) {
|
func TestBuilderPrepare_RemoteExport(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|||||||
@@ -33,12 +33,55 @@ type Config struct {
|
|||||||
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
||||||
vmwcommon.VMXConfig `mapstructure:",squash"`
|
vmwcommon.VMXConfig `mapstructure:",squash"`
|
||||||
vmwcommon.ExportConfig `mapstructure:",squash"`
|
vmwcommon.ExportConfig `mapstructure:",squash"`
|
||||||
vmwcommon.DiskConfig `mapstructure:",squash"`
|
// 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 size of the hard disk for the VM in megabytes.
|
||||||
// The builder uses expandable, not fixed-size virtual hard disks, so the
|
// The builder uses expandable, not fixed-size virtual hard disks, so the
|
||||||
// actual file representing the disk will not use the full size unless it
|
// actual file representing the disk will not use the full size unless it
|
||||||
// is full. By default this is set to 40000 (about 40 GB).
|
// is full. By default this is set to 40000 (about 40 GB).
|
||||||
DiskSize uint `mapstructure:"disk_size" required:"false"`
|
DiskSize uint `mapstructure:"disk_size" required:"false"`
|
||||||
|
// The type of VMware virtual disk to create. This
|
||||||
|
// option is for advanced usage.
|
||||||
|
//
|
||||||
|
// For desktop VMware clients:
|
||||||
|
//
|
||||||
|
// Type ID | Description
|
||||||
|
// ------- | ---
|
||||||
|
// `0` | Growable virtual disk contained in a single file (monolithic sparse).
|
||||||
|
// `1` | Growable virtual disk split into 2GB files (split sparse).
|
||||||
|
// `2` | Preallocated virtual disk contained in a single file (monolithic flat).
|
||||||
|
// `3` | Preallocated virtual disk split into 2GB files (split flat).
|
||||||
|
// `4` | Preallocated virtual disk compatible with ESX server (VMFS flat).
|
||||||
|
// `5` | Compressed disk optimized for streaming.
|
||||||
|
//
|
||||||
|
// The default is `1`.
|
||||||
|
//
|
||||||
|
// For ESXi, this defaults to `zeroedthick`. The available options for ESXi
|
||||||
|
// are: `zeroedthick`, `eagerzeroedthick`, `thin`. `rdm:dev`, `rdmp:dev`,
|
||||||
|
// `2gbsparse` are not supported. Due to default disk compaction, when using
|
||||||
|
// `zeroedthick` or `eagerzeroedthick` set `skip_compaction` to `true`.
|
||||||
|
//
|
||||||
|
// For more information, please consult the [Virtual Disk Manager User's
|
||||||
|
// Guide](https://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop
|
||||||
|
// VMware clients. For ESXi, refer to the proper ESXi documentation.
|
||||||
|
DiskTypeId string `mapstructure:"disk_type_id" required:"false"`
|
||||||
// The adapter type (or bus) that will be used
|
// The adapter type (or bus) that will be used
|
||||||
// by the cdrom device. This is chosen by default based on the disk adapter
|
// by the cdrom device. This is chosen by default based on the disk adapter
|
||||||
// type. VMware tends to lean towards ide for the cdrom device unless
|
// type. VMware tends to lean towards ide for the cdrom device unless
|
||||||
@@ -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.FloppyConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)
|
|
||||||
|
if c.DiskName == "" {
|
||||||
|
c.DiskName = "disk"
|
||||||
|
}
|
||||||
|
|
||||||
if c.DiskSize == 0 {
|
if c.DiskSize == 0 {
|
||||||
c.DiskSize = 40000
|
c.DiskSize = 40000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.DiskAdapterType == "" {
|
||||||
|
// Default is lsilogic
|
||||||
|
c.DiskAdapterType = "lsilogic"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.SkipCompaction {
|
||||||
|
if c.RemoteType == "esx5" {
|
||||||
|
if c.DiskTypeId == "" {
|
||||||
|
c.SkipCompaction = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.DiskTypeId == "" {
|
if c.DiskTypeId == "" {
|
||||||
// Default is growable virtual disk split in 2GB files.
|
// Default is growable virtual disk split in 2GB files.
|
||||||
c.DiskTypeId = "1"
|
c.DiskTypeId = "1"
|
||||||
|
|
||||||
if c.RemoteType == "esx5" {
|
if c.RemoteType == "esx5" {
|
||||||
c.DiskTypeId = "zeroedthick"
|
c.DiskTypeId = "zeroedthick"
|
||||||
c.SkipCompaction = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,19 +234,18 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Format == "" {
|
if c.Format != "" {
|
||||||
if c.RemoteType == "" {
|
if c.RemoteType != "esx5" {
|
||||||
c.Format = "vmx"
|
errs = packer.MultiErrorAppend(errs,
|
||||||
} else {
|
fmt.Errorf("format is only valid when RemoteType=esx5"))
|
||||||
c.Format = "ovf"
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
c.Format = "ovf"
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.RemoteType == "" && c.Format == "vmx" {
|
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") {
|
||||||
// if we're building locally and want a vmx, there's nothing to export.
|
errs = packer.MultiErrorAppend(errs,
|
||||||
// Set skip export flag here to keep the export step from attempting
|
fmt.Errorf("format must be one of ova, ovf, or vmx"))
|
||||||
// an unneded export
|
|
||||||
c.SkipExport = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.DriverConfig.Validate(c.SkipExport)
|
err = c.DriverConfig.Validate(c.SkipExport)
|
||||||
|
|||||||
@@ -50,13 +50,11 @@ type FlatConfig struct {
|
|||||||
CoreCount *int `mapstructure:"cores" required:"false" cty:"cores" hcl:"cores"`
|
CoreCount *int `mapstructure:"cores" required:"false" cty:"cores" hcl:"cores"`
|
||||||
Network *string `mapstructure:"network" required:"false" cty:"network" hcl:"network"`
|
Network *string `mapstructure:"network" required:"false" cty:"network" hcl:"network"`
|
||||||
NetworkAdapterType *string `mapstructure:"network_adapter_type" required:"false" cty:"network_adapter_type" hcl:"network_adapter_type"`
|
NetworkAdapterType *string `mapstructure:"network_adapter_type" required:"false" cty:"network_adapter_type" hcl:"network_adapter_type"`
|
||||||
NetworkName *string `mapstructure:"network_name" required:"false" cty:"network_name" hcl:"network_name"`
|
|
||||||
Sound *bool `mapstructure:"sound" required:"false" cty:"sound" hcl:"sound"`
|
Sound *bool `mapstructure:"sound" required:"false" cty:"sound" hcl:"sound"`
|
||||||
USB *bool `mapstructure:"usb" required:"false" cty:"usb" hcl:"usb"`
|
USB *bool `mapstructure:"usb" required:"false" cty:"usb" hcl:"usb"`
|
||||||
Serial *string `mapstructure:"serial" required:"false" cty:"serial" hcl:"serial"`
|
Serial *string `mapstructure:"serial" required:"false" cty:"serial" hcl:"serial"`
|
||||||
Parallel *string `mapstructure:"parallel" required:"false" cty:"parallel" hcl:"parallel"`
|
Parallel *string `mapstructure:"parallel" required:"false" cty:"parallel" hcl:"parallel"`
|
||||||
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
|
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
|
||||||
RemoteOutputDir *string `mapstructure:"remote_output_directory" required:"false" cty:"remote_output_directory" hcl:"remote_output_directory"`
|
|
||||||
Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"`
|
Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"`
|
||||||
VNCBindAddress *string `mapstructure:"vnc_bind_address" required:"false" cty:"vnc_bind_address" hcl:"vnc_bind_address"`
|
VNCBindAddress *string `mapstructure:"vnc_bind_address" required:"false" cty:"vnc_bind_address" hcl:"vnc_bind_address"`
|
||||||
VNCPortMin *int `mapstructure:"vnc_port_min" required:"false" cty:"vnc_port_min" hcl:"vnc_port_min"`
|
VNCPortMin *int `mapstructure:"vnc_port_min" required:"false" cty:"vnc_port_min" hcl:"vnc_port_min"`
|
||||||
@@ -126,8 +124,8 @@ type FlatConfig struct {
|
|||||||
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
|
||||||
DiskAdapterType *string `mapstructure:"disk_adapter_type" required:"false" cty:"disk_adapter_type" hcl:"disk_adapter_type"`
|
DiskAdapterType *string `mapstructure:"disk_adapter_type" required:"false" cty:"disk_adapter_type" hcl:"disk_adapter_type"`
|
||||||
DiskName *string `mapstructure:"vmdk_name" required:"false" cty:"vmdk_name" hcl:"vmdk_name"`
|
DiskName *string `mapstructure:"vmdk_name" required:"false" cty:"vmdk_name" hcl:"vmdk_name"`
|
||||||
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"`
|
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"`
|
CdromAdapterType *string `mapstructure:"cdrom_adapter_type" required:"false" cty:"cdrom_adapter_type" hcl:"cdrom_adapter_type"`
|
||||||
GuestOSType *string `mapstructure:"guest_os_type" required:"false" cty:"guest_os_type" hcl:"guest_os_type"`
|
GuestOSType *string `mapstructure:"guest_os_type" required:"false" cty:"guest_os_type" hcl:"guest_os_type"`
|
||||||
Version *string `mapstructure:"version" required:"false" cty:"version" hcl:"version"`
|
Version *string `mapstructure:"version" required:"false" cty:"version" hcl:"version"`
|
||||||
@@ -189,13 +187,11 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
|
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
|
||||||
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
||||||
"network_adapter_type": &hcldec.AttrSpec{Name: "network_adapter_type", Type: cty.String, Required: false},
|
"network_adapter_type": &hcldec.AttrSpec{Name: "network_adapter_type", Type: cty.String, Required: false},
|
||||||
"network_name": &hcldec.AttrSpec{Name: "network_name", Type: cty.String, Required: false},
|
|
||||||
"sound": &hcldec.AttrSpec{Name: "sound", Type: cty.Bool, Required: false},
|
"sound": &hcldec.AttrSpec{Name: "sound", Type: cty.Bool, Required: false},
|
||||||
"usb": &hcldec.AttrSpec{Name: "usb", Type: cty.Bool, Required: false},
|
"usb": &hcldec.AttrSpec{Name: "usb", Type: cty.Bool, Required: false},
|
||||||
"serial": &hcldec.AttrSpec{Name: "serial", Type: cty.String, Required: false},
|
"serial": &hcldec.AttrSpec{Name: "serial", Type: cty.String, Required: false},
|
||||||
"parallel": &hcldec.AttrSpec{Name: "parallel", Type: cty.String, Required: false},
|
"parallel": &hcldec.AttrSpec{Name: "parallel", Type: cty.String, Required: false},
|
||||||
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
|
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
|
||||||
"remote_output_directory": &hcldec.AttrSpec{Name: "remote_output_directory", Type: cty.String, Required: false},
|
|
||||||
"headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false},
|
"headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false},
|
||||||
"vnc_bind_address": &hcldec.AttrSpec{Name: "vnc_bind_address", Type: cty.String, Required: false},
|
"vnc_bind_address": &hcldec.AttrSpec{Name: "vnc_bind_address", Type: cty.String, Required: false},
|
||||||
"vnc_port_min": &hcldec.AttrSpec{Name: "vnc_port_min", Type: cty.Number, Required: false},
|
"vnc_port_min": &hcldec.AttrSpec{Name: "vnc_port_min", Type: cty.Number, Required: false},
|
||||||
@@ -265,8 +261,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||||||
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
|
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
|
||||||
"disk_adapter_type": &hcldec.AttrSpec{Name: "disk_adapter_type", Type: cty.String, Required: false},
|
"disk_adapter_type": &hcldec.AttrSpec{Name: "disk_adapter_type", Type: cty.String, Required: false},
|
||||||
"vmdk_name": &hcldec.AttrSpec{Name: "vmdk_name", Type: cty.String, Required: false},
|
"vmdk_name": &hcldec.AttrSpec{Name: "vmdk_name", Type: cty.String, Required: false},
|
||||||
"disk_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_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},
|
"cdrom_adapter_type": &hcldec.AttrSpec{Name: "cdrom_adapter_type", Type: cty.String, Required: false},
|
||||||
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
|
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
|
||||||
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user