Merge remote-tracking branch 'origin/master' into azr_selectable_temp_keygen_type_gcp
This commit is contained in:
commit
f5e037e8b4
|
@ -27,3 +27,4 @@ Thumbs.db
|
||||||
/packer.exe
|
/packer.exe
|
||||||
.project
|
.project
|
||||||
cache
|
cache
|
||||||
|
/.vscode/
|
||||||
|
|
|
@ -25,12 +25,17 @@
|
||||||
* builder/oracle-oci: New option to specify image compartment separate from
|
* builder/oracle-oci: New option to specify image compartment separate from
|
||||||
build compartment. [GH-10040]
|
build compartment. [GH-10040]
|
||||||
* builder/oracle-oci: New option to specify boot volume size. [GH-10017]
|
* builder/oracle-oci: New option to specify boot volume size. [GH-10017]
|
||||||
|
* builder/oracle: Add `base_image_filter` option as alternative to
|
||||||
|
`base_image_ocid` [GH-10116]
|
||||||
* builder/outscale: Migrate to new Outscale SDK. [GH-10056]
|
* builder/outscale: Migrate to new Outscale SDK. [GH-10056]
|
||||||
* builder/scaleway: Allow the user to use an image label (eg ubuntu_focal)
|
* builder/scaleway: Allow the user to use an image label (eg ubuntu_focal)
|
||||||
instead of a hardcoded UUID on the Scaleway builder. [GH-10061]
|
instead of a hardcoded UUID on the Scaleway builder. [GH-10061]
|
||||||
|
* core: Let user provide type of generated ssh key instead of always doing ssh-
|
||||||
|
rsa [GH-10101]
|
||||||
* hcl: Add build.name variable so users can access build name in addition to
|
* hcl: Add build.name variable so users can access build name in addition to
|
||||||
source name. [GH-10114]
|
source name. [GH-10114]
|
||||||
* hcl: Add consul_key function to HCL templates. [GH-10119]
|
* hcl: Add consul_key function to HCL templates. [GH-10119]
|
||||||
|
* hcl: Add HCL2 aws_secretsmanager function [GH-10124]
|
||||||
* hcl: Add packer.version variable to hcl configs so users can access the
|
* hcl: Add packer.version variable to hcl configs so users can access the
|
||||||
Packer release version. [GH-10117]
|
Packer release version. [GH-10117]
|
||||||
|
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -1,4 +1,5 @@
|
||||||
TEST?=$(shell go list ./...)
|
TEST?=$(shell go list ./...)
|
||||||
|
COUNT?=1
|
||||||
VET?=$(shell go list ./...)
|
VET?=$(shell go list ./...)
|
||||||
ACC_TEST_BUILDERS?=all
|
ACC_TEST_BUILDERS?=all
|
||||||
ACC_TEST_PROVISIONERS?=all
|
ACC_TEST_PROVISIONERS?=all
|
||||||
|
@ -136,7 +137,7 @@ generate-check: generate ## Check go code generation is on par
|
||||||
fi
|
fi
|
||||||
|
|
||||||
test: mode-check vet ## Run unit tests
|
test: mode-check vet ## Run unit tests
|
||||||
@go test $(TEST) $(TESTARGS) -timeout=3m
|
@go test -count $(COUNT) $(TEST) $(TESTARGS) -timeout=3m
|
||||||
|
|
||||||
# acctest runs provisioners acceptance tests
|
# acctest runs provisioners acceptance tests
|
||||||
provisioners-acctest: install-build-deps generate
|
provisioners-acctest: install-build-deps generate
|
||||||
|
@ -145,14 +146,14 @@ provisioners-acctest: install-build-deps generate
|
||||||
# testacc runs acceptance tests
|
# testacc runs acceptance tests
|
||||||
testacc: install-build-deps generate ## Run acceptance tests
|
testacc: install-build-deps generate ## Run acceptance tests
|
||||||
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
@echo "WARN: Acceptance tests will take a long time to run and may cost money. Ctrl-C if you want to cancel."
|
||||||
PACKER_ACC=1 go test -v $(TEST) $(TESTARGS) -timeout=45m
|
PACKER_ACC=1 go test -count $(COUNT) -v $(TEST) $(TESTARGS) -timeout=120m
|
||||||
|
|
||||||
testrace: mode-check vet ## Test with race detection enabled
|
testrace: mode-check vet ## Test with race detection enabled
|
||||||
@GO111MODULE=off go test -race $(TEST) $(TESTARGS) -timeout=3m -p=8
|
@GO111MODULE=off go test -count $(COUNT) -race $(TEST) $(TESTARGS) -timeout=3m -p=8
|
||||||
|
|
||||||
# Runs code coverage and open a html page with report
|
# Runs code coverage and open a html page with report
|
||||||
cover:
|
cover:
|
||||||
go test $(TEST) $(TESTARGS) -timeout=3m -coverprofile=coverage.out
|
go test -count $(COUNT) $(TEST) $(TESTARGS) -timeout=3m -coverprofile=coverage.out
|
||||||
go tool cover -html=coverage.out
|
go tool cover -html=coverage.out
|
||||||
rm coverage.out
|
rm coverage.out
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -194,6 +194,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -85,6 +85,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -84,6 +84,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
@ -333,6 +334,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
PollingConfig: b.config.PollingConfig,
|
PollingConfig: b.config.PollingConfig,
|
||||||
LaunchDevices: launchDevices,
|
LaunchDevices: launchDevices,
|
||||||
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
|
SnapshotOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||||
|
SnapshotTags: b.config.SnapshotTags,
|
||||||
|
Ctx: b.config.ctx,
|
||||||
},
|
},
|
||||||
&awscommon.StepDeregisterAMI{
|
&awscommon.StepDeregisterAMI{
|
||||||
AccessConfig: &b.config.AccessConfig,
|
AccessConfig: &b.config.AccessConfig,
|
||||||
|
|
|
@ -6,11 +6,13 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
multierror "github.com/hashicorp/go-multierror"
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepSnapshotVolumes creates snapshots of the created volumes.
|
// StepSnapshotVolumes creates snapshots of the created volumes.
|
||||||
|
@ -23,6 +25,8 @@ type StepSnapshotVolumes struct {
|
||||||
snapshotIds map[string]string
|
snapshotIds map[string]string
|
||||||
snapshotMutex sync.Mutex
|
snapshotMutex sync.Mutex
|
||||||
SnapshotOmitMap map[string]bool
|
SnapshotOmitMap map[string]bool
|
||||||
|
SnapshotTags map[string]string
|
||||||
|
Ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
|
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
|
||||||
|
@ -40,12 +44,34 @@ func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName str
|
||||||
return fmt.Errorf("Volume ID for device %s not found", deviceName)
|
return fmt.Errorf("Volume ID for device %s not found", deviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating snapshot tags")
|
||||||
|
snapshotTags, err := awscommon.TagMap(s.SnapshotTags).EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
snapshotTags.Report(ui)
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
|
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
|
||||||
description := fmt.Sprintf("Packer: %s", time.Now().String())
|
description := fmt.Sprintf("Packer: %s", time.Now().String())
|
||||||
|
|
||||||
|
// Collect tags for tagging on resource creation
|
||||||
|
var tagSpecs []*ec2.TagSpecification
|
||||||
|
|
||||||
|
if len(snapshotTags) > 0 {
|
||||||
|
snapTags := &ec2.TagSpecification{
|
||||||
|
ResourceType: aws.String("snapshot"),
|
||||||
|
Tags: snapshotTags,
|
||||||
|
}
|
||||||
|
|
||||||
|
tagSpecs = append(tagSpecs, snapTags)
|
||||||
|
}
|
||||||
|
|
||||||
createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{
|
createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{
|
||||||
VolumeId: &volumeId,
|
VolumeId: &volumeId,
|
||||||
Description: &description,
|
Description: &description,
|
||||||
|
TagSpecifications: tagSpecs,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -100,6 +100,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
SourceAMI: `{{ .SourceAMI }} `,
|
SourceAMI: `{{ .SourceAMI }} `,
|
||||||
}
|
}
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
}, raws...)
|
}, raws...)
|
||||||
|
|
|
@ -107,6 +107,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
|
|
||||||
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
b.config.ctx.Funcs = awscommon.TemplateFuncs
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -222,7 +222,6 @@ 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),
|
||||||
NewStepDeleteAdditionalDisks(azureClient, ui),
|
|
||||||
}
|
}
|
||||||
} else if b.config.OSType == constants.Target_Windows {
|
} else if b.config.OSType == constants.Target_Windows {
|
||||||
steps = []multistep.Step{
|
steps = []multistep.Step{
|
||||||
|
@ -263,7 +262,6 @@ 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),
|
||||||
NewStepDeleteAdditionalDisks(azureClient, ui),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
|
return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
|
||||||
|
|
|
@ -6,7 +6,6 @@ package arm
|
||||||
// * ARM_CLIENT_ID
|
// * ARM_CLIENT_ID
|
||||||
// * ARM_CLIENT_SECRET
|
// * ARM_CLIENT_SECRET
|
||||||
// * ARM_SUBSCRIPTION_ID
|
// * ARM_SUBSCRIPTION_ID
|
||||||
// * ARM_OBJECT_ID
|
|
||||||
// * ARM_STORAGE_ACCOUNT
|
// * ARM_STORAGE_ACCOUNT
|
||||||
//
|
//
|
||||||
// The subscription in question should have a resource group
|
// The subscription in question should have a resource group
|
||||||
|
@ -47,6 +46,14 @@ func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderAcc_ManagedDisk_Windows_Build_Resource_Group_Additional_Disk(t *testing.T) {
|
||||||
|
builderT.Test(t, builderT.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Builder: &Builder{},
|
||||||
|
Template: testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuilderAcc_ManagedDisk_Windows_DeviceLogin(t *testing.T) {
|
func TestBuilderAcc_ManagedDisk_Windows_DeviceLogin(t *testing.T) {
|
||||||
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
|
if os.Getenv(DeviceLoginAcceptanceTest) == "" {
|
||||||
t.Skip(fmt.Sprintf(
|
t.Skip(fmt.Sprintf(
|
||||||
|
@ -151,7 +158,7 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||||
|
|
||||||
"build_resource_group_name" : "packer-acceptance-test",
|
"build_resource_group_name" : "packer-acceptance-test",
|
||||||
"managed_image_resource_group_name": "packer-acceptance-test",
|
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||||
"managed_image_name": "testBuilderAccManagedDiskWindows-{{timestamp}}",
|
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroup-{{timestamp}}",
|
||||||
|
|
||||||
"os_type": "Windows",
|
"os_type": "Windows",
|
||||||
"image_publisher": "MicrosoftWindowsServer",
|
"image_publisher": "MicrosoftWindowsServer",
|
||||||
|
@ -170,6 +177,42 @@ const testBuilderAccManagedDiskWindowsBuildResourceGroup = `
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionalDisk = `
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||||
|
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||||
|
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}"
|
||||||
|
},
|
||||||
|
"builders": [{
|
||||||
|
"type": "test",
|
||||||
|
|
||||||
|
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||||
|
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||||
|
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||||
|
|
||||||
|
"build_resource_group_name" : "packer-acceptance-test",
|
||||||
|
"managed_image_resource_group_name": "packer-acceptance-test",
|
||||||
|
"managed_image_name": "testBuilderAccManagedDiskWindowsBuildResourceGroupAdditionDisk-{{timestamp}}",
|
||||||
|
|
||||||
|
"os_type": "Windows",
|
||||||
|
"image_publisher": "MicrosoftWindowsServer",
|
||||||
|
"image_offer": "WindowsServer",
|
||||||
|
"image_sku": "2012-R2-Datacenter",
|
||||||
|
|
||||||
|
"communicator": "winrm",
|
||||||
|
"winrm_use_ssl": "true",
|
||||||
|
"winrm_insecure": "true",
|
||||||
|
"winrm_timeout": "3m",
|
||||||
|
"winrm_username": "packer",
|
||||||
|
"async_resourcegroup_delete": "true",
|
||||||
|
|
||||||
|
"vm_size": "Standard_DS2_v2",
|
||||||
|
"disk_additional_size": [10,15]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const testBuilderAccManagedDiskWindowsDeviceLogin = `
|
const testBuilderAccManagedDiskWindowsDeviceLogin = `
|
||||||
{
|
{
|
||||||
"variables": {
|
"variables": {
|
||||||
|
@ -262,7 +305,6 @@ const testBuilderAccBlobWindows = `
|
||||||
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
"client_id": "{{env ` + "`ARM_CLIENT_ID`" + `}}",
|
||||||
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
"client_secret": "{{env ` + "`ARM_CLIENT_SECRET`" + `}}",
|
||||||
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
"subscription_id": "{{env ` + "`ARM_SUBSCRIPTION_ID`" + `}}",
|
||||||
"object_id": "{{env ` + "`ARM_OBJECT_ID`" + `}}",
|
|
||||||
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
"storage_account": "{{env ` + "`ARM_STORAGE_ACCOUNT`" + `}}"
|
||||||
},
|
},
|
||||||
"builders": [{
|
"builders": [{
|
||||||
|
@ -271,7 +313,6 @@ const testBuilderAccBlobWindows = `
|
||||||
"client_id": "{{user ` + "`client_id`" + `}}",
|
"client_id": "{{user ` + "`client_id`" + `}}",
|
||||||
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
"client_secret": "{{user ` + "`client_secret`" + `}}",
|
||||||
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
"subscription_id": "{{user ` + "`subscription_id`" + `}}",
|
||||||
"object_id": "{{user ` + "`object_id`" + `}}",
|
|
||||||
|
|
||||||
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
"storage_account": "{{user ` + "`storage_account`" + `}}",
|
||||||
"resource_group_name": "packer-acceptance-test",
|
"resource_group_name": "packer-acceptance-test",
|
||||||
|
|
|
@ -580,6 +580,7 @@ func (c *Config) createCertificate() (string, error) {
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
c.ctx.Funcs = azcommon.TemplateFuncs
|
c.ctx.Funcs = azcommon.TemplateFuncs
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
}, raws...)
|
}, raws...)
|
||||||
|
|
|
@ -6,8 +6,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/builder/azure/common/constants"
|
"github.com/hashicorp/packer/builder/azure/common/constants"
|
||||||
|
"github.com/hashicorp/packer/common/retry"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
@ -15,7 +17,7 @@ import (
|
||||||
type StepDeployTemplate struct {
|
type StepDeployTemplate struct {
|
||||||
client *AzureClient
|
client *AzureClient
|
||||||
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
|
deploy func(ctx context.Context, resourceGroupName string, deploymentName string) error
|
||||||
delete func(ctx context.Context, client *AzureClient, resourceType string, resourceName string, resourceGroupName string) error
|
delete func(ctx context.Context, deploymentName, resourceGroupName string) error
|
||||||
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
|
disk func(ctx context.Context, resourceGroupName string, computeName string) (string, string, error)
|
||||||
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
|
deleteDisk func(ctx context.Context, imageType string, imageName string, resourceGroupName string) error
|
||||||
say func(message string)
|
say func(message string)
|
||||||
|
@ -36,7 +38,7 @@ func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, de
|
||||||
}
|
}
|
||||||
|
|
||||||
step.deploy = step.deployTemplate
|
step.deploy = step.deployTemplate
|
||||||
step.delete = deleteResource
|
step.delete = step.deleteDeploymentResources
|
||||||
step.disk = step.getImageDetails
|
step.disk = step.getImageDetails
|
||||||
step.deleteDisk = step.deleteImage
|
step.deleteDisk = step.deleteImage
|
||||||
return step
|
return step
|
||||||
|
@ -62,8 +64,9 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
//Only clean up if this was an existing resource group and the resource group
|
// Only clean up if this is an existing resource group that has been verified to exist.
|
||||||
//is marked as created
|
// ArmIsResourceGroupCreated is set in step_create_resource_group to true, when Packer has verified that the resource group exists.
|
||||||
|
// ArmIsExistingResourceGroup is set to true when build_resource_group is set in the Packer configuration.
|
||||||
existingResourceGroup := state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
existingResourceGroup := state.Get(constants.ArmIsExistingResourceGroup).(bool)
|
||||||
resourceGroupCreated := state.Get(constants.ArmIsResourceGroupCreated).(bool)
|
resourceGroupCreated := state.Get(constants.ArmIsResourceGroupCreated).(bool)
|
||||||
if !existingResourceGroup || !resourceGroupCreated {
|
if !existingResourceGroup || !resourceGroupCreated {
|
||||||
|
@ -75,74 +78,12 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
|
||||||
|
|
||||||
deploymentName := s.name
|
deploymentName := s.name
|
||||||
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||||
|
err := s.deleteDeploymentResources(context.TODO(), deploymentName, resourceGroupName)
|
||||||
// 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 {
|
if err != nil {
|
||||||
ui.Error(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
|
s.reportIfError(err, resourceGroupName)
|
||||||
"Name: %s\n"+
|
|
||||||
"Error: %s", resourceGroupName, err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for deploymentOperations.NotDone() {
|
NewStepDeleteAdditionalDisks(s.client, ui).Run(context.TODO(), state)
|
||||||
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 {
|
||||||
|
@ -152,27 +93,31 @@ func (s *StepDeployTemplate) deployTemplate(ctx context.Context, resourceGroupNa
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := s.client.DeploymentsClient.CreateOrUpdate(ctx, resourceGroupName, deploymentName, *deployment)
|
f, err := s.client.DeploymentsClient.CreateOrUpdate(ctx, resourceGroupName, deploymentName, *deployment)
|
||||||
if err == nil {
|
|
||||||
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.say(s.client.LastError.Error())
|
s.say(s.client.LastError.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||||
|
if err == nil {
|
||||||
|
s.say(s.client.LastError.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep.StateBag) error {
|
func (s *StepDeployTemplate) deleteTemplate(ctx context.Context, state multistep.StateBag) error {
|
||||||
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
|
deploymentName := s.name
|
||||||
var deploymentName = s.name
|
resourceGroupName := state.Get(constants.ArmResourceGroupName).(string)
|
||||||
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)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return f.WaitForCompletionRef(ctx, s.client.DeploymentsClient.Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -186,10 +131,11 @@ func (s *StepDeployTemplate) getImageDetails(ctx context.Context, resourceGroupN
|
||||||
if vm.StorageProfile.OsDisk.Vhd != nil {
|
if vm.StorageProfile.OsDisk.Vhd != nil {
|
||||||
imageType = "image"
|
imageType = "image"
|
||||||
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
|
imageName = *vm.StorageProfile.OsDisk.Vhd.URI
|
||||||
} else {
|
return imageType, imageName, nil
|
||||||
|
}
|
||||||
|
|
||||||
imageType = "Microsoft.Compute/disks"
|
imageType = "Microsoft.Compute/disks"
|
||||||
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
imageName = *vm.StorageProfile.OsDisk.ManagedDisk.ID
|
||||||
}
|
|
||||||
|
|
||||||
return imageType, imageName, nil
|
return imageType, imageName, nil
|
||||||
}
|
}
|
||||||
|
@ -267,3 +213,58 @@ func (s *StepDeployTemplate) deleteImage(ctx context.Context, imageType string,
|
||||||
|
|
||||||
return blob.Delete(nil)
|
return blob.Delete(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StepDeployTemplate) deleteDeploymentResources(ctx context.Context, deploymentName, resourceGroupName string) error {
|
||||||
|
var maxResources int32 = 50
|
||||||
|
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))
|
||||||
|
|
||||||
|
err = 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 != nil {
|
||||||
|
s.reportIfError(err, resourceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deploymentOperations.Next(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDeployTemplate) 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -153,6 +153,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc()
|
b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc()
|
||||||
md := &mapstructure.Metadata{}
|
md := &mapstructure.Metadata{}
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderID,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -389,6 +389,7 @@ func newConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
var c Config
|
var c Config
|
||||||
c.ctx.Funcs = TemplateFuncs
|
c.ctx.Funcs = TemplateFuncs
|
||||||
err := config.Decode(&c, &config.DecodeOpts{
|
err := config.Decode(&c, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
}, raws...)
|
}, raws...)
|
||||||
|
|
|
@ -127,6 +127,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
var md mapstructure.Metadata
|
var md mapstructure.Metadata
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
Metadata: &md,
|
Metadata: &md,
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -291,6 +291,7 @@ type Config struct {
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
c.ctx.Funcs = TemplateFuncs
|
c.ctx.Funcs = TemplateFuncs
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -89,6 +89,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: hypervcommon.BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -82,6 +82,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: hypervcommon.BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -17,6 +17,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BUILDER_ID,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -3,6 +3,7 @@ package linode
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
@ -248,3 +249,43 @@ func TestBuilderPrepare_Label(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_StateTimeout(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test default
|
||||||
|
_, warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.StateTimeout != 5*time.Minute {
|
||||||
|
t.Errorf("invalid: %s", b.config.StateTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test set
|
||||||
|
config["state_timeout"] = "5m"
|
||||||
|
b = Builder{}
|
||||||
|
_, warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
config["state_timeout"] = "tubes"
|
||||||
|
b = Builder{}
|
||||||
|
_, warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
"github.com/hashicorp/packer/helper/communicator"
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
@ -34,6 +35,7 @@ type Config struct {
|
||||||
RootSSHKey string `mapstructure:"root_ssh_key"`
|
RootSSHKey string `mapstructure:"root_ssh_key"`
|
||||||
ImageLabel string `mapstructure:"image_label"`
|
ImageLabel string `mapstructure:"image_label"`
|
||||||
Description string `mapstructure:"image_description"`
|
Description string `mapstructure:"image_description"`
|
||||||
|
StateTimeout time.Duration `mapstructure:"state_timeout" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRandomRootPassword() (string, error) {
|
func createRandomRootPassword() (string, error) {
|
||||||
|
@ -94,6 +96,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.StateTimeout == 0 {
|
||||||
|
// Default to 5 minute timeouts waiting for state change
|
||||||
|
c.StateTimeout = 5 * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||||
errs = packer.MultiErrorAppend(errs, es...)
|
errs = packer.MultiErrorAppend(errs, es...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ type FlatConfig struct {
|
||||||
RootSSHKey *string `mapstructure:"root_ssh_key" cty:"root_ssh_key" hcl:"root_ssh_key"`
|
RootSSHKey *string `mapstructure:"root_ssh_key" cty:"root_ssh_key" hcl:"root_ssh_key"`
|
||||||
ImageLabel *string `mapstructure:"image_label" cty:"image_label" hcl:"image_label"`
|
ImageLabel *string `mapstructure:"image_label" cty:"image_label" hcl:"image_label"`
|
||||||
Description *string `mapstructure:"image_description" cty:"image_description" hcl:"image_description"`
|
Description *string `mapstructure:"image_description" cty:"image_description" hcl:"image_description"`
|
||||||
|
StateTimeout *string `mapstructure:"state_timeout" required:"false" cty:"state_timeout" hcl:"state_timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
// FlatMapstructure returns a new FlatConfig.
|
||||||
|
@ -157,6 +158,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"root_ssh_key": &hcldec.AttrSpec{Name: "root_ssh_key", Type: cty.String, Required: false},
|
"root_ssh_key": &hcldec.AttrSpec{Name: "root_ssh_key", Type: cty.String, Required: false},
|
||||||
"image_label": &hcldec.AttrSpec{Name: "image_label", Type: cty.String, Required: false},
|
"image_label": &hcldec.AttrSpec{Name: "image_label", Type: cty.String, Required: false},
|
||||||
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
|
||||||
|
"state_timeout": &hcldec.AttrSpec{Name: "state_timeout", Type: cty.String, Required: false},
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ type stepShutdownLinode struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepShutdownLinode) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepShutdownLinode) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
c := state.Get("config").(*Config)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
instance := state.Get("instance").(*linodego.Instance)
|
instance := state.Get("instance").(*linodego.Instance)
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ func (s *stepShutdownLinode) Run(ctx context.Context, state multistep.StateBag)
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.client.WaitForInstanceStatus(ctx, instance.ID, linodego.InstanceOffline, 120)
|
_, err := s.client.WaitForInstanceStatus(ctx, instance.ID, linodego.InstanceOffline, int(c.StateTimeout.Seconds()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.New("Error shutting down Linode: " + err.Error())
|
err = errors.New("Error shutting down Linode: " + err.Error())
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|
|
@ -21,6 +21,7 @@ type Config struct {
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{},
|
InterpolateFilter: &interpolate.RenderFilter{},
|
||||||
}, raws...)
|
}, raws...)
|
||||||
|
|
|
@ -40,6 +40,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
}, raws...)
|
}, raws...)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
|
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
|
||||||
|
|
||||||
package oci
|
package oci
|
||||||
|
|
||||||
|
@ -35,6 +35,16 @@ type CreateVNICDetails struct {
|
||||||
SubnetId *string `mapstructure:"subnet_id" required:"false"`
|
SubnetId *string `mapstructure:"subnet_id" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListImagesRequest struct {
|
||||||
|
// fields that can be specified under "base_image_filter"
|
||||||
|
CompartmentId *string `mapstructure:"compartment_id"`
|
||||||
|
DisplayName *string `mapstructure:"display_name"`
|
||||||
|
DisplayNameSearch *string `mapstructure:"display_name_search"`
|
||||||
|
OperatingSystem *string `mapstructure:"operating_system"`
|
||||||
|
OperatingSystemVersion *string `mapstructure:"operating_system_version"`
|
||||||
|
Shape *string `mapstructure:"shape"`
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
|
@ -70,11 +80,12 @@ type Config struct {
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
BaseImageID string `mapstructure:"base_image_ocid"`
|
BaseImageID string `mapstructure:"base_image_ocid"`
|
||||||
|
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
|
||||||
ImageName string `mapstructure:"image_name"`
|
ImageName string `mapstructure:"image_name"`
|
||||||
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
||||||
|
|
||||||
// Instance
|
// Instance
|
||||||
InstanceName string `mapstructure:"instance_name"`
|
InstanceName *string `mapstructure:"instance_name"`
|
||||||
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
||||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
||||||
Shape string `mapstructure:"shape"`
|
Shape string `mapstructure:"shape"`
|
||||||
|
@ -269,9 +280,17 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||||
errs, errors.New("'subnet_ocid' must be specified"))
|
errs, errors.New("'subnet_ocid' must be specified"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.BaseImageID == "" {
|
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("'base_image_ocid' must be specified"))
|
errs, errors.New("'base_image_ocid' or 'base_image_filter' must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BaseImageFilter.CompartmentId == nil {
|
||||||
|
c.BaseImageFilter.CompartmentId = &c.CompartmentID
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BaseImageFilter.Shape == nil {
|
||||||
|
c.BaseImageFilter.Shape = &c.Shape
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
|
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
|
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
|
||||||
package oci
|
package oci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -78,6 +78,7 @@ type FlatConfig struct {
|
||||||
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
|
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
|
||||||
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
|
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
|
||||||
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
|
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
|
||||||
|
BaseImageFilter *FlatListImagesRequest `mapstructure:"base_image_filter" cty:"base_image_filter" hcl:"base_image_filter"`
|
||||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
||||||
ImageCompartmentID *string `mapstructure:"image_compartment_ocid" cty:"image_compartment_ocid" hcl:"image_compartment_ocid"`
|
ImageCompartmentID *string `mapstructure:"image_compartment_ocid" cty:"image_compartment_ocid" hcl:"image_compartment_ocid"`
|
||||||
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
||||||
|
@ -175,6 +176,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
|
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
|
||||||
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
|
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
|
||||||
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
|
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
|
||||||
|
"base_image_filter": &hcldec.BlockSpec{TypeName: "base_image_filter", Nested: hcldec.ObjectSpec((*FlatListImagesRequest)(nil).HCL2Spec())},
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||||
"image_compartment_ocid": &hcldec.AttrSpec{Name: "image_compartment_ocid", Type: cty.String, Required: false},
|
"image_compartment_ocid": &hcldec.AttrSpec{Name: "image_compartment_ocid", 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},
|
||||||
|
@ -231,3 +233,36 @@ func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
||||||
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
type FlatListImagesRequest struct {
|
||||||
|
CompartmentId *string `mapstructure:"compartment_id" cty:"compartment_id" hcl:"compartment_id"`
|
||||||
|
DisplayName *string `mapstructure:"display_name" cty:"display_name" hcl:"display_name"`
|
||||||
|
DisplayNameSearch *string `mapstructure:"display_name_search" cty:"display_name_search" hcl:"display_name_search"`
|
||||||
|
OperatingSystem *string `mapstructure:"operating_system" cty:"operating_system" hcl:"operating_system"`
|
||||||
|
OperatingSystemVersion *string `mapstructure:"operating_system_version" cty:"operating_system_version" hcl:"operating_system_version"`
|
||||||
|
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatMapstructure returns a new FlatListImagesRequest.
|
||||||
|
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
||||||
|
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
func (*ListImagesRequest) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||||
|
return new(FlatListImagesRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HCL2Spec returns the hcl spec of a ListImagesRequest.
|
||||||
|
// This spec is used by HCL to read the fields of ListImagesRequest.
|
||||||
|
// The decoded values from this spec will then be applied to a FlatListImagesRequest.
|
||||||
|
func (*FlatListImagesRequest) HCL2Spec() map[string]hcldec.Spec {
|
||||||
|
s := map[string]hcldec.Spec{
|
||||||
|
"compartment_id": &hcldec.AttrSpec{Name: "compartment_id", Type: cty.String, Required: false},
|
||||||
|
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
|
||||||
|
"display_name_search": &hcldec.AttrSpec{Name: "display_name_search", Type: cty.String, Required: false},
|
||||||
|
"operating_system": &hcldec.AttrSpec{Name: "operating_system", Type: cty.String, Required: false},
|
||||||
|
"operating_system_version": &hcldec.AttrSpec{Name: "operating_system_version", Type: cty.String, Required: false},
|
||||||
|
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
|
@ -88,6 +88,36 @@ func TestConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("BaseImageFilterWithoutOCID", func(t *testing.T) {
|
||||||
|
raw := testConfig(cfgFile)
|
||||||
|
raw["base_image_ocid"] = ""
|
||||||
|
raw["base_image_filter"] = map[string]interface{}{
|
||||||
|
"display_name": "hello_world",
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
errs := c.Prepare(raw)
|
||||||
|
|
||||||
|
if errs != nil {
|
||||||
|
t.Fatalf("Unexpected error in configuration %+v", errs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BaseImageFilterDefault", func(t *testing.T) {
|
||||||
|
raw := testConfig(cfgFile)
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
errs := c.Prepare(raw)
|
||||||
|
if errs != nil {
|
||||||
|
t.Fatalf("Unexpected error in configuration %+v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *c.BaseImageFilter.Shape != raw["shape"] {
|
||||||
|
t.Fatalf("Default base_image_filter shape %v does not equal config shape %v",
|
||||||
|
*c.BaseImageFilter.Shape, raw["shape"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("NoAccessConfig", func(t *testing.T) {
|
t.Run("NoAccessConfig", func(t *testing.T) {
|
||||||
raw := testConfig(cfgFile)
|
raw := testConfig(cfgFile)
|
||||||
delete(raw, "access_cfg_file")
|
delete(raw, "access_cfg_file")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
core "github.com/oracle/oci-go-sdk/core"
|
core "github.com/oracle/oci-go-sdk/core"
|
||||||
|
@ -51,22 +52,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||||
metadata["user_data"] = d.cfg.UserData
|
metadata["user_data"] = d.cfg.UserData
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceDetails := core.LaunchInstanceDetails{
|
// Create VNIC details for instance
|
||||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
|
||||||
CompartmentId: &d.cfg.CompartmentID,
|
|
||||||
DefinedTags: d.cfg.InstanceDefinedTags,
|
|
||||||
FreeformTags: d.cfg.InstanceTags,
|
|
||||||
Shape: &d.cfg.Shape,
|
|
||||||
SubnetId: &d.cfg.SubnetID,
|
|
||||||
Metadata: metadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
// When empty, the default display name is used.
|
|
||||||
if d.cfg.InstanceName != "" {
|
|
||||||
instanceDetails.DisplayName = &d.cfg.InstanceName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass VNIC details, if specified, to the instance
|
|
||||||
CreateVnicDetails := core.CreateVnicDetails{
|
CreateVnicDetails := core.CreateVnicDetails{
|
||||||
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
||||||
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
||||||
|
@ -79,14 +65,69 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||||
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceDetails.CreateVnicDetails = &CreateVnicDetails
|
// Determine base image ID
|
||||||
|
var imageId *string
|
||||||
|
if d.cfg.BaseImageID != "" {
|
||||||
|
imageId = &d.cfg.BaseImageID
|
||||||
|
} else {
|
||||||
|
// Pull images and determine which image ID to use, if BaseImageId not specified
|
||||||
|
response, err := d.computeClient.ListImages(ctx, core.ListImagesRequest{
|
||||||
|
CompartmentId: d.cfg.BaseImageFilter.CompartmentId,
|
||||||
|
DisplayName: d.cfg.BaseImageFilter.DisplayName,
|
||||||
|
OperatingSystem: d.cfg.BaseImageFilter.OperatingSystem,
|
||||||
|
OperatingSystemVersion: d.cfg.BaseImageFilter.OperatingSystemVersion,
|
||||||
|
Shape: d.cfg.BaseImageFilter.Shape,
|
||||||
|
LifecycleState: "AVAILABLE",
|
||||||
|
SortBy: "TIMECREATED",
|
||||||
|
SortOrder: "DESC",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(response.Items) == 0 {
|
||||||
|
return "", errors.New("base_image_filter returned no images")
|
||||||
|
}
|
||||||
|
if d.cfg.BaseImageFilter.DisplayNameSearch != nil {
|
||||||
|
// Return most recent image that matches regex
|
||||||
|
imageNameRegex, err := regexp.Compile(*d.cfg.BaseImageFilter.DisplayNameSearch)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, image := range response.Items {
|
||||||
|
if imageNameRegex.MatchString(*image.DisplayName) {
|
||||||
|
imageId = image.Id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if imageId == nil {
|
||||||
|
return "", errors.New("No image matched display_name_search criteria")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no regex provided, simply return most recent image pulled
|
||||||
|
imageId = response.Items[0].Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create Source details which will be used to Launch Instance
|
// Create Source details which will be used to Launch Instance
|
||||||
instanceDetails.SourceDetails = core.InstanceSourceViaImageDetails{
|
InstanceSourceDetails := core.InstanceSourceViaImageDetails{
|
||||||
ImageId: &d.cfg.BaseImageID,
|
ImageId: imageId,
|
||||||
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
|
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build instance details
|
||||||
|
instanceDetails := core.LaunchInstanceDetails{
|
||||||
|
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
||||||
|
CompartmentId: &d.cfg.CompartmentID,
|
||||||
|
CreateVnicDetails: &CreateVnicDetails,
|
||||||
|
DefinedTags: d.cfg.InstanceDefinedTags,
|
||||||
|
DisplayName: d.cfg.InstanceName,
|
||||||
|
FreeformTags: d.cfg.InstanceTags,
|
||||||
|
Shape: &d.cfg.Shape,
|
||||||
|
SourceDetails: InstanceSourceDetails,
|
||||||
|
SubnetId: &d.cfg.SubnetID,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
|
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -45,6 +45,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
b.config.ctx.Funcs = osccommon.TemplateFuncs
|
b.config.ctx.Funcs = osccommon.TemplateFuncs
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -46,6 +46,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
b.config.ctx.Funcs = osccommon.TemplateFuncs
|
b.config.ctx.Funcs = osccommon.TemplateFuncs
|
||||||
|
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -53,6 +53,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
SourceOMI: `{{ .SourceOMI }} `,
|
SourceOMI: `{{ .SourceOMI }} `,
|
||||||
}
|
}
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
}, raws...)
|
}, raws...)
|
||||||
|
|
|
@ -66,6 +66,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
b.config.ctx.Funcs = osccommon.TemplateFuncs
|
b.config.ctx.Funcs = osccommon.TemplateFuncs
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -87,6 +87,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -51,6 +51,7 @@ type Config struct {
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: parallelscommon.BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -1,191 +1,5 @@
|
||||||
package proxmox
|
package proxmox
|
||||||
|
|
||||||
import (
|
import proxmoxiso "github.com/hashicorp/packer/builder/proxmox/iso"
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/Telmate/proxmox-api-go/proxmox"
|
type Builder = proxmoxiso.Builder
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/hashicorp/packer/common"
|
|
||||||
"github.com/hashicorp/packer/helper/communicator"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
|
||||||
"github.com/hashicorp/packer/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The unique id for the builder
|
|
||||||
const BuilderId = "proxmox.builder"
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config Config
|
|
||||||
runner multistep.Runner
|
|
||||||
proxmoxClient *proxmox.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builder implements packer.Builder
|
|
||||||
var _ packer.Builder = &Builder{}
|
|
||||||
|
|
||||||
var pluginVersion = "1.0.0"
|
|
||||||
|
|
||||||
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|
||||||
warnings, errs := b.config.Prepare(raws...)
|
|
||||||
if errs != nil {
|
|
||||||
return nil, warnings, errs
|
|
||||||
}
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadPathKey = "downloaded_iso_path"
|
|
||||||
|
|
||||||
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
|
||||||
var err error
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
InsecureSkipVerify: b.config.SkipCertValidation,
|
|
||||||
}
|
|
||||||
b.proxmoxClient, err = proxmox.NewClient(b.config.proxmoxURL.String(), nil, tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = b.proxmoxClient.Login(b.config.Username, b.config.Password, "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the state
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("proxmoxClient", b.proxmoxClient)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Build the steps
|
|
||||||
steps := []multistep.Step{
|
|
||||||
&common.StepDownload{
|
|
||||||
Checksum: b.config.ISOChecksum,
|
|
||||||
Description: "ISO",
|
|
||||||
Extension: b.config.TargetExtension,
|
|
||||||
ResultKey: downloadPathKey,
|
|
||||||
TargetPath: b.config.TargetPath,
|
|
||||||
Url: b.config.ISOUrls,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for idx := range b.config.AdditionalISOFiles {
|
|
||||||
steps = append(steps, &common.StepDownload{
|
|
||||||
Checksum: b.config.AdditionalISOFiles[idx].ISOChecksum,
|
|
||||||
Description: "additional ISO",
|
|
||||||
Extension: b.config.AdditionalISOFiles[idx].TargetExtension,
|
|
||||||
ResultKey: b.config.AdditionalISOFiles[idx].downloadPathKey,
|
|
||||||
TargetPath: b.config.AdditionalISOFiles[idx].downloadPathKey,
|
|
||||||
Url: b.config.AdditionalISOFiles[idx].ISOUrls,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
steps = append(steps,
|
|
||||||
&stepUploadISO{},
|
|
||||||
&stepUploadAdditionalISOs{},
|
|
||||||
&stepStartVM{},
|
|
||||||
&common.StepHTTPServer{
|
|
||||||
HTTPDir: b.config.HTTPDir,
|
|
||||||
HTTPPortMin: b.config.HTTPPortMin,
|
|
||||||
HTTPPortMax: b.config.HTTPPortMax,
|
|
||||||
HTTPAddress: b.config.HTTPAddress,
|
|
||||||
},
|
|
||||||
&stepTypeBootCommand{
|
|
||||||
BootConfig: b.config.BootConfig,
|
|
||||||
Ctx: b.config.ctx,
|
|
||||||
},
|
|
||||||
&communicator.StepConnect{
|
|
||||||
Config: &b.config.Comm,
|
|
||||||
Host: commHost(b.config.Comm.Host()),
|
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
|
||||||
},
|
|
||||||
&common.StepProvision{},
|
|
||||||
&common.StepCleanupTempKeys{
|
|
||||||
Comm: &b.config.Comm,
|
|
||||||
},
|
|
||||||
&stepConvertToTemplate{},
|
|
||||||
&stepFinalizeTemplateConfig{},
|
|
||||||
&stepSuccess{},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run the steps
|
|
||||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
|
||||||
b.runner.Run(ctx, state)
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
// If we were interrupted or cancelled, then just exit.
|
|
||||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
||||||
return nil, errors.New("build was cancelled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the template_id was set properly, otherwise we didn't progress through the last step
|
|
||||||
tplID, ok := state.Get("template_id").(int)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("template ID could not be determined")
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
templateID: tplID,
|
|
||||||
proxmoxClient: b.proxmoxClient,
|
|
||||||
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns ssh_host or winrm_host (see communicator.Config.Host) config
|
|
||||||
// parameter when set, otherwise gets the host IP from running VM
|
|
||||||
func commHost(host string) func(state multistep.StateBag) (string, error) {
|
|
||||||
if host != "" {
|
|
||||||
return func(state multistep.StateBag) (string, error) {
|
|
||||||
return host, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getVMIP
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads the first non-loopback interface's IP address from the VM.
|
|
||||||
// qemu-guest-agent package must be installed on the VM
|
|
||||||
func getVMIP(state multistep.StateBag) (string, error) {
|
|
||||||
client := state.Get("proxmoxClient").(*proxmox.Client)
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
vmRef := state.Get("vmRef").(*proxmox.VmRef)
|
|
||||||
|
|
||||||
ifs, err := client.GetVmAgentNetworkInterfaces(vmRef)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.VMInterface != "" {
|
|
||||||
for _, iface := range ifs {
|
|
||||||
if config.VMInterface != iface.Name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range iface.IPAddresses {
|
|
||||||
if addr.IsLoopback() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return addr.String(), nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("Interface %s only has loopback addresses", config.VMInterface)
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("Interface %s not found in VM", config.VMInterface)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, iface := range ifs {
|
|
||||||
for _, addr := range iface.IPAddresses {
|
|
||||||
if addr.IsLoopback() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return addr.String(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("Found no IP addresses on VM")
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package proxmoxclone
|
||||||
|
|
||||||
|
import (
|
||||||
|
proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox"
|
||||||
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The unique id for the builder
|
||||||
|
const BuilderID = "proxmox.clone"
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder implements packer.Builder
|
||||||
|
var _ packer.Builder = &Builder{}
|
||||||
|
|
||||||
|
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
|
return b.config.Prepare(raws...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("clone-config", &b.config)
|
||||||
|
|
||||||
|
preSteps := []multistep.Step{
|
||||||
|
&StepSshKeyPair{
|
||||||
|
Debug: b.config.PackerDebug,
|
||||||
|
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
postSteps := []multistep.Step{}
|
||||||
|
|
||||||
|
sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, preSteps, postSteps, &cloneVMCreator{})
|
||||||
|
return sb.Run(ctx, ui, hook, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
type cloneVMCreator struct{}
|
||||||
|
|
||||||
|
func (*cloneVMCreator) Create(vmRef *proxmoxapi.VmRef, config proxmoxapi.ConfigQemu, state multistep.StateBag) error {
|
||||||
|
client := state.Get("proxmoxClient").(*proxmoxapi.Client)
|
||||||
|
c := state.Get("clone-config").(*Config)
|
||||||
|
comm := state.Get("config").(*proxmox.Config).Comm
|
||||||
|
|
||||||
|
fullClone := 1
|
||||||
|
if c.FullClone.False() {
|
||||||
|
fullClone = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
config.FullClone = &fullClone
|
||||||
|
config.CIuser = comm.SSHUsername
|
||||||
|
config.Sshkeys = string(comm.SSHPublicKey)
|
||||||
|
sourceVmr, err := client.GetVmRefByName(c.CloneVM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = config.CloneVm(sourceVmr, vmRef, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = config.UpdateConfig(vmRef, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
//go:generate mapstructure-to-hcl2 -type Config
|
||||||
|
|
||||||
|
package proxmoxclone
|
||||||
|
|
||||||
|
import (
|
||||||
|
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
|
||||||
|
"github.com/hashicorp/packer/helper/config"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
proxmox.Config `mapstructure:",squash"`
|
||||||
|
|
||||||
|
CloneVM string `mapstructure:"clone_vm"`
|
||||||
|
FullClone config.Trilean `mapstructure:"full_clone" required:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
|
var errs *packer.MultiError
|
||||||
|
_, warnings, merrs := c.Config.Prepare(c, raws...)
|
||||||
|
if merrs != nil {
|
||||||
|
errs = packer.MultiErrorAppend(errs, merrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, warnings, errs
|
||||||
|
}
|
||||||
|
return nil, warnings, nil
|
||||||
|
}
|
|
@ -0,0 +1,211 @@
|
||||||
|
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||||
|
package proxmoxclone
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlatConfig is an auto-generated flat version of Config.
|
||||||
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
type FlatConfig struct {
|
||||||
|
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||||
|
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||||
|
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||||
|
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||||
|
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||||
|
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||||
|
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||||
|
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
|
||||||
|
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
|
||||||
|
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
|
||||||
|
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
|
||||||
|
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
|
||||||
|
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
|
||||||
|
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
|
||||||
|
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
|
||||||
|
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
|
||||||
|
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||||
|
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||||
|
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||||
|
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||||
|
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||||
|
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||||
|
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||||
|
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||||
|
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||||
|
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||||
|
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||||
|
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||||
|
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||||
|
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||||
|
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||||
|
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||||
|
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
||||||
|
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
||||||
|
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
||||||
|
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
||||||
|
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
||||||
|
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
||||||
|
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
||||||
|
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||||
|
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||||
|
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||||
|
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||||
|
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||||
|
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||||
|
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||||
|
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
||||||
|
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
||||||
|
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
||||||
|
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
||||||
|
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
||||||
|
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
||||||
|
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
||||||
|
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
||||||
|
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||||
|
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||||
|
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
||||||
|
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
||||||
|
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||||
|
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
||||||
|
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||||
|
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||||
|
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||||
|
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
|
||||||
|
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
|
||||||
|
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
|
||||||
|
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
|
||||||
|
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
|
||||||
|
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
|
||||||
|
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
|
||||||
|
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
|
||||||
|
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
|
||||||
|
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
|
||||||
|
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
|
||||||
|
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
|
||||||
|
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
|
||||||
|
VGA *proxmox.FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
|
||||||
|
NICs []proxmox.FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
|
||||||
|
Disks []proxmox.FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
|
||||||
|
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
|
||||||
|
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
|
||||||
|
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
|
||||||
|
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
|
||||||
|
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
|
||||||
|
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
|
||||||
|
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
|
||||||
|
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
|
||||||
|
AdditionalISOFiles []proxmox.FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
|
||||||
|
VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"`
|
||||||
|
CloneVM *string `mapstructure:"clone_vm" cty:"clone_vm" hcl:"clone_vm"`
|
||||||
|
FullClone *bool `mapstructure:"full_clone" required:"false" cty:"full_clone" hcl:"full_clone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatMapstructure returns a new FlatConfig.
|
||||||
|
// FlatConfig is an auto-generated flat version of Config.
|
||||||
|
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||||
|
return new(FlatConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HCL2Spec returns the hcl spec of a Config.
|
||||||
|
// This spec is used by HCL to read the fields of Config.
|
||||||
|
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||||
|
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
|
s := map[string]hcldec.Spec{
|
||||||
|
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||||
|
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||||
|
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||||
|
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||||
|
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||||
|
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||||
|
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||||
|
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
|
||||||
|
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
|
||||||
|
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
|
||||||
|
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
|
||||||
|
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
|
||||||
|
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
|
||||||
|
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
|
||||||
|
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||||
|
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
|
||||||
|
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||||
|
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||||
|
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||||
|
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||||
|
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||||
|
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||||
|
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||||
|
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||||
|
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||||
|
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||||
|
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||||
|
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||||
|
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||||
|
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||||
|
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||||
|
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||||
|
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||||
|
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||||
|
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||||
|
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||||
|
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||||
|
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||||
|
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||||
|
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||||
|
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
||||||
|
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||||
|
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||||
|
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||||
|
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||||
|
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||||
|
"proxmox_url": &hcldec.AttrSpec{Name: "proxmox_url", Type: cty.String, Required: false},
|
||||||
|
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
|
||||||
|
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
|
||||||
|
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
|
||||||
|
"node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false},
|
||||||
|
"pool": &hcldec.AttrSpec{Name: "pool", Type: cty.String, Required: false},
|
||||||
|
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
|
||||||
|
"vm_id": &hcldec.AttrSpec{Name: "vm_id", Type: cty.Number, Required: false},
|
||||||
|
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
|
||||||
|
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
|
||||||
|
"cpu_type": &hcldec.AttrSpec{Name: "cpu_type", Type: cty.String, Required: false},
|
||||||
|
"sockets": &hcldec.AttrSpec{Name: "sockets", Type: cty.Number, Required: false},
|
||||||
|
"os": &hcldec.AttrSpec{Name: "os", Type: cty.String, Required: false},
|
||||||
|
"vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())},
|
||||||
|
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatnicConfig)(nil).HCL2Spec())},
|
||||||
|
"disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())},
|
||||||
|
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
|
||||||
|
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
|
||||||
|
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
|
||||||
|
"disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false},
|
||||||
|
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
||||||
|
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false},
|
||||||
|
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
|
||||||
|
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
|
||||||
|
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatstorageConfig)(nil).HCL2Spec())},
|
||||||
|
"vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false},
|
||||||
|
"clone_vm": &hcldec.AttrSpec{Name: "clone_vm", Type: cty.String, Required: false},
|
||||||
|
"full_clone": &hcldec.AttrSpec{Name: "full_clone", Type: cty.Bool, Required: false},
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package proxmoxclone
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
common "github.com/hashicorp/packer/builder/proxmox/common"
|
||||||
|
"github.com/hashicorp/packer/common/uuid"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/helper/ssh"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StepSshKeyPair executes the business logic for setting the SSH key pair in
|
||||||
|
// the specified communicator.Config.
|
||||||
|
type StepSshKeyPair struct {
|
||||||
|
Debug bool
|
||||||
|
DebugKeyPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
c := state.Get("config").(*common.Config)
|
||||||
|
|
||||||
|
if c.Comm.SSHPassword != "" {
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Comm.SSHPrivateKeyFile != "" {
|
||||||
|
ui.Say("Using existing SSH private key for the communicator...")
|
||||||
|
privateKeyBytes, err := c.Comm.ReadSSHPrivateKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
kp, err := ssh.KeyPairFromPrivateKey(ssh.FromPrivateKeyConfig{
|
||||||
|
RawPrivateKeyPemBlock: privateKeyBytes,
|
||||||
|
Comment: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Comm.SSHPrivateKey = privateKeyBytes
|
||||||
|
c.Comm.SSHKeyPairName = kp.Comment
|
||||||
|
c.Comm.SSHTemporaryKeyPairName = kp.Comment
|
||||||
|
c.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Comm.SSHAgentAuth {
|
||||||
|
ui.Say("Using local SSH Agent to authenticate connections for the communicator...")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating ephemeral key pair for SSH communicator...")
|
||||||
|
|
||||||
|
kp, err := ssh.NewKeyPair(ssh.CreateKeyPairConfig{
|
||||||
|
Comment: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Comm.SSHKeyPairName = kp.Comment
|
||||||
|
c.Comm.SSHTemporaryKeyPairName = kp.Comment
|
||||||
|
c.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock
|
||||||
|
c.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
|
||||||
|
c.Comm.SSHClearAuthorizedKeys = true
|
||||||
|
|
||||||
|
ui.Say("Created ephemeral SSH key pair for communicator")
|
||||||
|
|
||||||
|
// If we're in debug mode, output the private key to the working
|
||||||
|
// directory.
|
||||||
|
if s.Debug {
|
||||||
|
ui.Message(fmt.Sprintf("Saving communicator private key for debug purposes: %s", s.DebugKeyPath))
|
||||||
|
f, err := os.OpenFile(s.DebugKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Write the key out
|
||||||
|
if _, err := f.Write(kp.PrivateKeyPemBlock); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepSshKeyPair) Cleanup(state multistep.StateBag) {
|
||||||
|
if s.Debug {
|
||||||
|
if err := os.Remove(s.DebugKeyPath); err != nil {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error removing debug key '%s': %s", s.DebugKeyPath, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Artifact struct {
|
type Artifact struct {
|
||||||
|
builderID string
|
||||||
templateID int
|
templateID int
|
||||||
proxmoxClient *proxmox.Client
|
proxmoxClient *proxmox.Client
|
||||||
|
|
||||||
|
@ -21,8 +22,8 @@ type Artifact struct {
|
||||||
// Artifact implements packer.Artifact
|
// Artifact implements packer.Artifact
|
||||||
var _ packer.Artifact = &Artifact{}
|
var _ packer.Artifact = &Artifact{}
|
||||||
|
|
||||||
func (*Artifact) BuilderId() string {
|
func (a *Artifact) BuilderId() string {
|
||||||
return BuilderId
|
return a.builderID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Artifact) Files() []string {
|
func (*Artifact) Files() []string {
|
|
@ -0,0 +1,167 @@
|
||||||
|
package proxmox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Telmate/proxmox-api-go/proxmox"
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSharedBuilder(id string, config Config, preSteps []multistep.Step, postSteps []multistep.Step, vmCreator ProxmoxVMCreator) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
id: id,
|
||||||
|
config: config,
|
||||||
|
preSteps: preSteps,
|
||||||
|
postSteps: postSteps,
|
||||||
|
vmCreator: vmCreator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
id string
|
||||||
|
config Config
|
||||||
|
preSteps []multistep.Step
|
||||||
|
postSteps []multistep.Step
|
||||||
|
runner multistep.Runner
|
||||||
|
proxmoxClient *proxmox.Client
|
||||||
|
vmCreator ProxmoxVMCreator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook, state multistep.StateBag) (packer.Artifact, error) {
|
||||||
|
var err error
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: b.config.SkipCertValidation,
|
||||||
|
}
|
||||||
|
b.proxmoxClient, err = proxmox.NewClient(b.config.proxmoxURL.String(), nil, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.proxmoxClient.Login(b.config.Username, b.config.Password, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the state
|
||||||
|
state.Put("config", &b.config)
|
||||||
|
state.Put("proxmoxClient", b.proxmoxClient)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
|
||||||
|
comm := &b.config.Comm
|
||||||
|
|
||||||
|
// Build the steps
|
||||||
|
coreSteps := []multistep.Step{
|
||||||
|
&stepStartVM{
|
||||||
|
vmCreator: b.vmCreator,
|
||||||
|
},
|
||||||
|
&common.StepHTTPServer{
|
||||||
|
HTTPDir: b.config.HTTPDir,
|
||||||
|
HTTPPortMin: b.config.HTTPPortMin,
|
||||||
|
HTTPPortMax: b.config.HTTPPortMax,
|
||||||
|
HTTPAddress: b.config.HTTPAddress,
|
||||||
|
},
|
||||||
|
&stepTypeBootCommand{
|
||||||
|
BootConfig: b.config.BootConfig,
|
||||||
|
Ctx: b.config.Ctx,
|
||||||
|
},
|
||||||
|
&communicator.StepConnect{
|
||||||
|
Config: comm,
|
||||||
|
Host: commHost((*comm).Host()),
|
||||||
|
SSHConfig: (*comm).SSHConfigFunc(),
|
||||||
|
},
|
||||||
|
&common.StepProvision{},
|
||||||
|
&common.StepCleanupTempKeys{
|
||||||
|
Comm: &b.config.Comm,
|
||||||
|
},
|
||||||
|
&stepConvertToTemplate{},
|
||||||
|
&stepFinalizeTemplateConfig{},
|
||||||
|
&stepSuccess{},
|
||||||
|
}
|
||||||
|
steps := append(b.preSteps, coreSteps...)
|
||||||
|
steps = append(steps, b.postSteps...)
|
||||||
|
// Run the steps
|
||||||
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
|
b.runner.Run(ctx, state)
|
||||||
|
// If there was an error, return that
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
// If we were interrupted or cancelled, then just exit.
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return nil, errors.New("build was cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the template_id was set properly, otherwise we didn't progress through the last step
|
||||||
|
tplID, ok := state.Get("template_id").(int)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("template ID could not be determined")
|
||||||
|
}
|
||||||
|
|
||||||
|
artifact := &Artifact{
|
||||||
|
builderID: b.id,
|
||||||
|
templateID: tplID,
|
||||||
|
proxmoxClient: b.proxmoxClient,
|
||||||
|
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifact, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns ssh_host or winrm_host (see communicator.Config.Host) config
|
||||||
|
// parameter when set, otherwise gets the host IP from running VM
|
||||||
|
func commHost(host string) func(state multistep.StateBag) (string, error) {
|
||||||
|
if host != "" {
|
||||||
|
return func(state multistep.StateBag) (string, error) {
|
||||||
|
return host, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getVMIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the first non-loopback interface's IP address from the VM.
|
||||||
|
// qemu-guest-agent package must be installed on the VM
|
||||||
|
func getVMIP(state multistep.StateBag) (string, error) {
|
||||||
|
client := state.Get("proxmoxClient").(*proxmox.Client)
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
vmRef := state.Get("vmRef").(*proxmox.VmRef)
|
||||||
|
|
||||||
|
ifs, err := client.GetVmAgentNetworkInterfaces(vmRef)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.VMInterface != "" {
|
||||||
|
for _, iface := range ifs {
|
||||||
|
if config.VMInterface != iface.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range iface.IPAddresses {
|
||||||
|
if addr.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return addr.String(), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Interface %s only has loopback addresses", config.VMInterface)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Interface %s not found in VM", config.VMInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifs {
|
||||||
|
for _, addr := range iface.IPAddresses {
|
||||||
|
if addr.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return addr.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Found no IP addresses on VM")
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -25,7 +24,6 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
common.HTTPConfig `mapstructure:",squash"`
|
common.HTTPConfig `mapstructure:",squash"`
|
||||||
common.ISOConfig `mapstructure:",squash"`
|
|
||||||
bootcommand.BootConfig `mapstructure:",squash"`
|
bootcommand.BootConfig `mapstructure:",squash"`
|
||||||
BootKeyInterval time.Duration `mapstructure:"boot_key_interval"`
|
BootKeyInterval time.Duration `mapstructure:"boot_key_interval"`
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
|
@ -49,8 +47,6 @@ type Config struct {
|
||||||
VGA vgaConfig `mapstructure:"vga"`
|
VGA vgaConfig `mapstructure:"vga"`
|
||||||
NICs []nicConfig `mapstructure:"network_adapters"`
|
NICs []nicConfig `mapstructure:"network_adapters"`
|
||||||
Disks []diskConfig `mapstructure:"disks"`
|
Disks []diskConfig `mapstructure:"disks"`
|
||||||
ISOFile string `mapstructure:"iso_file"`
|
|
||||||
ISOStoragePool string `mapstructure:"iso_storage_pool"`
|
|
||||||
Agent bool `mapstructure:"qemu_agent"`
|
Agent bool `mapstructure:"qemu_agent"`
|
||||||
SCSIController string `mapstructure:"scsi_controller"`
|
SCSIController string `mapstructure:"scsi_controller"`
|
||||||
Onboot bool `mapstructure:"onboot"`
|
Onboot bool `mapstructure:"onboot"`
|
||||||
|
@ -58,17 +54,24 @@ type Config struct {
|
||||||
|
|
||||||
TemplateName string `mapstructure:"template_name"`
|
TemplateName string `mapstructure:"template_name"`
|
||||||
TemplateDescription string `mapstructure:"template_description"`
|
TemplateDescription string `mapstructure:"template_description"`
|
||||||
UnmountISO bool `mapstructure:"unmount_iso"`
|
|
||||||
|
|
||||||
CloudInit bool `mapstructure:"cloud_init"`
|
CloudInit bool `mapstructure:"cloud_init"`
|
||||||
CloudInitStoragePool string `mapstructure:"cloud_init_storage_pool"`
|
CloudInitStoragePool string `mapstructure:"cloud_init_storage_pool"`
|
||||||
|
|
||||||
shouldUploadISO bool
|
|
||||||
|
|
||||||
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
|
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
|
||||||
VMInterface string `mapstructure:"vm_interface"`
|
VMInterface string `mapstructure:"vm_interface"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
Ctx interpolate.Context `mapstructure-to-hcl2:",skip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
type nicConfig struct {
|
type nicConfig struct {
|
||||||
|
@ -92,27 +95,18 @@ 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(upper interface{}, raws ...interface{}) ([]string, []string, error) {
|
||||||
// Agent defaults to true
|
// Agent defaults to true
|
||||||
c.Agent = true
|
c.Agent = true
|
||||||
// Do not add a cloud-init cdrom by default
|
// Do not add a cloud-init cdrom by default
|
||||||
c.CloudInit = false
|
c.CloudInit = false
|
||||||
|
|
||||||
var md mapstructure.Metadata
|
var md mapstructure.Metadata
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(upper, &config.DecodeOpts{
|
||||||
Metadata: &md,
|
Metadata: &md,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.Ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
Exclude: []string{
|
Exclude: []string{
|
||||||
"boot_command",
|
"boot_command",
|
||||||
|
@ -120,11 +114,13 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
},
|
},
|
||||||
}, raws...)
|
}, raws...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs *packer.MultiError
|
var errs *packer.MultiError
|
||||||
warnings := make([]string, 0)
|
var warnings []string
|
||||||
|
|
||||||
|
packer.LogSecretFilter.Set(c.Password)
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
if c.ProxmoxURLRaw == "" {
|
if c.ProxmoxURLRaw == "" {
|
||||||
|
@ -208,89 +204,14 @@ 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"
|
||||||
}
|
}
|
||||||
|
|
||||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.Ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.Ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.Ctx)...)
|
||||||
|
|
||||||
// Check ISO 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.ISOFile != "" {
|
|
||||||
c.shouldUploadISO = false
|
|
||||||
} else {
|
|
||||||
isoWarnings, isoErrors := c.ISOConfig.Prepare(&c.ctx)
|
|
||||||
errs = packer.MultiErrorAppend(errs, isoErrors...)
|
|
||||||
warnings = append(warnings, isoWarnings...)
|
|
||||||
c.shouldUploadISO = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c.ISOFile == "" && len(c.ISOConfig.ISOUrls) == 0) || (c.ISOFile != "" && len(c.ISOConfig.ISOUrls) != 0) {
|
|
||||||
errs = packer.MultiErrorAppend(errs, errors.New("either iso_file or iso_url, but not both, must be specified"))
|
|
||||||
}
|
|
||||||
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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required configurations that will display errors if not set
|
// Required configurations that will display errors if not set
|
||||||
if c.Username == "" {
|
if c.Username == "" {
|
||||||
|
@ -329,11 +250,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
return nil, errs
|
return nil, warnings, errs
|
||||||
}
|
}
|
||||||
|
return nil, warnings, nil
|
||||||
packer.LogSecretFilter.Set(c.Password)
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(haystack []string, needle string) bool {
|
func contains(haystack []string, needle string) bool {
|
|
@ -21,11 +21,6 @@ type FlatConfig struct {
|
||||||
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"`
|
||||||
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
|
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
|
||||||
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
|
|
||||||
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
|
|
||||||
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
|
|
||||||
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
|
|
||||||
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
|
|
||||||
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
|
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"`
|
||||||
|
@ -95,15 +90,12 @@ type FlatConfig struct {
|
||||||
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"`
|
|
||||||
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"`
|
|
||||||
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"`
|
AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
|
||||||
|
@ -134,11 +126,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
|
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
|
||||||
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
|
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
|
||||||
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
|
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
|
||||||
"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},
|
|
||||||
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
|
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
|
||||||
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
|
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
|
||||||
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||||
|
@ -208,15 +195,12 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*FlatvgaConfig)(nil).HCL2Spec())},
|
"vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*FlatvgaConfig)(nil).HCL2Spec())},
|
||||||
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatnicConfig)(nil).HCL2Spec())},
|
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatnicConfig)(nil).HCL2Spec())},
|
||||||
"disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*FlatdiskConfig)(nil).HCL2Spec())},
|
"disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*FlatdiskConfig)(nil).HCL2Spec())},
|
||||||
"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},
|
|
||||||
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
|
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
|
||||||
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
|
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
|
||||||
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
|
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
|
||||||
"disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false},
|
"disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false},
|
||||||
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
||||||
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false},
|
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, 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())},
|
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())},
|
||||||
|
@ -305,6 +289,8 @@ type FlatstorageConfig struct {
|
||||||
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"`
|
||||||
Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"`
|
Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"`
|
||||||
|
ShouldUploadISO *bool `cty:"should_upload_iso" hcl:"should_upload_iso"`
|
||||||
|
DownloadPathKey *string `cty:"download_path_key" hcl:"download_path_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatstorageConfig.
|
// FlatMapstructure returns a new FlatstorageConfig.
|
||||||
|
@ -328,6 +314,8 @@ func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"iso_file": &hcldec.AttrSpec{Name: "iso_file", 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},
|
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
|
||||||
"unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false},
|
"unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false},
|
||||||
|
"should_upload_iso": &hcldec.AttrSpec{Name: "should_upload_iso", Type: cty.Bool, Required: false},
|
||||||
|
"download_path_key": &hcldec.AttrSpec{Name: "download_path_key", Type: cty.String, Required: false},
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package proxmox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mandatoryConfig(t *testing.T) map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
|
||||||
|
"username": "apiuser@pve",
|
||||||
|
"password": "supersecret",
|
||||||
|
"node": "my-proxmox",
|
||||||
|
"ssh_username": "root",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiredParameters(t *testing.T) {
|
||||||
|
var c Config
|
||||||
|
_, _, err := c.Prepare(&c, make(map[string]interface{}))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected empty configuration to fail")
|
||||||
|
}
|
||||||
|
errs, ok := err.(*packer.MultiError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected errors to be packer.MultiError")
|
||||||
|
}
|
||||||
|
|
||||||
|
required := []string{"username", "password", "proxmox_url", "node", "ssh_username"}
|
||||||
|
for _, param := range required {
|
||||||
|
found := false
|
||||||
|
for _, err := range errs.Errors {
|
||||||
|
if strings.Contains(err.Error(), param) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected error about missing parameter %q", required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentSetToFalse(t *testing.T) {
|
||||||
|
cfg := mandatoryConfig(t)
|
||||||
|
cfg["qemu_agent"] = false
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
_, _, err := c.Prepare(&c, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Agent != false {
|
||||||
|
t.Errorf("Expected Agent to be false, got %t", c.Agent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPacketQueueSupportForNetworkAdapters(t *testing.T) {
|
||||||
|
drivertests := []struct {
|
||||||
|
expectedToFail bool
|
||||||
|
model string
|
||||||
|
}{
|
||||||
|
{expectedToFail: false, model: "virtio"},
|
||||||
|
{expectedToFail: true, model: "e1000"},
|
||||||
|
{expectedToFail: true, model: "e1000-82540em"},
|
||||||
|
{expectedToFail: true, model: "e1000-82544gc"},
|
||||||
|
{expectedToFail: true, model: "e1000-82545em"},
|
||||||
|
{expectedToFail: true, model: "i82551"},
|
||||||
|
{expectedToFail: true, model: "i82557b"},
|
||||||
|
{expectedToFail: true, model: "i82559er"},
|
||||||
|
{expectedToFail: true, model: "ne2k_isa"},
|
||||||
|
{expectedToFail: true, model: "ne2k_pci"},
|
||||||
|
{expectedToFail: true, model: "pcnet"},
|
||||||
|
{expectedToFail: true, model: "rtl8139"},
|
||||||
|
{expectedToFail: true, model: "vmxnet3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range drivertests {
|
||||||
|
device := make(map[string]interface{})
|
||||||
|
device["bridge"] = "vmbr0"
|
||||||
|
device["model"] = tt.model
|
||||||
|
device["packet_queues"] = 2
|
||||||
|
|
||||||
|
devices := make([]map[string]interface{}, 0)
|
||||||
|
devices = append(devices, device)
|
||||||
|
|
||||||
|
cfg := mandatoryConfig(t)
|
||||||
|
cfg["network_adapters"] = devices
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
_, _, err := c.Prepare(&c, cfg)
|
||||||
|
|
||||||
|
if tt.expectedToFail == true && err == nil {
|
||||||
|
t.Error("expected config preparation to fail, but no error occured")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expectedToFail == false && err != nil {
|
||||||
|
t.Errorf("expected config preparation to succeed, but %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,24 +38,6 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St
|
||||||
// set, we need to clear it
|
// set, we need to clear it
|
||||||
changes["description"] = c.TemplateDescription
|
changes["description"] = c.TemplateDescription
|
||||||
|
|
||||||
if c.UnmountISO {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if vmParams["ide2"] == nil || !strings.HasSuffix(vmParams["ide2"].(string), "media=cdrom") {
|
|
||||||
err := fmt.Errorf("Cannot eject ISO from cdrom drive, ide2 is not present, or not a cdrom media")
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
changes["ide2"] = "none,media=cdrom"
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.CloudInit {
|
if c.CloudInit {
|
||||||
vmParams, err := client.GetVmConfig(vmRef)
|
vmParams, err := client.GetVmConfig(vmRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,29 +75,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 {
|
|
@ -56,18 +56,15 @@ func TestTemplateFinalize(t *testing.T) {
|
||||||
builderConfig: &Config{
|
builderConfig: &Config{
|
||||||
TemplateName: "my-template",
|
TemplateName: "my-template",
|
||||||
TemplateDescription: "some-description",
|
TemplateDescription: "some-description",
|
||||||
UnmountISO: true,
|
|
||||||
},
|
},
|
||||||
initialVMConfig: map[string]interface{}{
|
initialVMConfig: map[string]interface{}{
|
||||||
"name": "dummy",
|
"name": "dummy",
|
||||||
"description": "Packer ephemeral build VM",
|
"description": "Packer ephemeral build VM",
|
||||||
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
|
||||||
},
|
},
|
||||||
expectCallSetConfig: true,
|
expectCallSetConfig: true,
|
||||||
expectedVMConfig: map[string]interface{}{
|
expectedVMConfig: map[string]interface{}{
|
||||||
"name": "my-template",
|
"name": "my-template",
|
||||||
"description": "some-description",
|
"description": "some-description",
|
||||||
"ide2": "none,media=cdrom",
|
|
||||||
},
|
},
|
||||||
expectedAction: multistep.ActionContinue,
|
expectedAction: multistep.ActionContinue,
|
||||||
},
|
},
|
||||||
|
@ -76,13 +73,11 @@ func TestTemplateFinalize(t *testing.T) {
|
||||||
builderConfig: &Config{
|
builderConfig: &Config{
|
||||||
TemplateName: "my-template",
|
TemplateName: "my-template",
|
||||||
TemplateDescription: "some-description",
|
TemplateDescription: "some-description",
|
||||||
UnmountISO: true,
|
|
||||||
CloudInit: true,
|
CloudInit: true,
|
||||||
},
|
},
|
||||||
initialVMConfig: map[string]interface{}{
|
initialVMConfig: map[string]interface{}{
|
||||||
"name": "dummy",
|
"name": "dummy",
|
||||||
"description": "Packer ephemeral build VM",
|
"description": "Packer ephemeral build VM",
|
||||||
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
|
||||||
"bootdisk": "virtio0",
|
"bootdisk": "virtio0",
|
||||||
"virtio0": "ceph01:base-223-disk-0,cache=unsafe,media=disk,size=32G",
|
"virtio0": "ceph01:base-223-disk-0,cache=unsafe,media=disk,size=32G",
|
||||||
},
|
},
|
||||||
|
@ -90,7 +85,6 @@ func TestTemplateFinalize(t *testing.T) {
|
||||||
expectedVMConfig: map[string]interface{}{
|
expectedVMConfig: map[string]interface{}{
|
||||||
"name": "my-template",
|
"name": "my-template",
|
||||||
"description": "some-description",
|
"description": "some-description",
|
||||||
"ide2": "none,media=cdrom",
|
|
||||||
"ide3": "ceph01:cloudinit",
|
"ide3": "ceph01:cloudinit",
|
||||||
},
|
},
|
||||||
expectedAction: multistep.ActionContinue,
|
expectedAction: multistep.ActionContinue,
|
||||||
|
@ -100,7 +94,6 @@ func TestTemplateFinalize(t *testing.T) {
|
||||||
builderConfig: &Config{
|
builderConfig: &Config{
|
||||||
TemplateName: "my-template",
|
TemplateName: "my-template",
|
||||||
TemplateDescription: "some-description",
|
TemplateDescription: "some-description",
|
||||||
UnmountISO: false,
|
|
||||||
CloudInit: true,
|
CloudInit: true,
|
||||||
},
|
},
|
||||||
initialVMConfig: map[string]interface{}{
|
initialVMConfig: map[string]interface{}{
|
||||||
|
@ -116,27 +109,12 @@ func TestTemplateFinalize(t *testing.T) {
|
||||||
expectCallSetConfig: false,
|
expectCallSetConfig: false,
|
||||||
expectedAction: multistep.ActionHalt,
|
expectedAction: multistep.ActionHalt,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "no cd-drive with unmount=true should returns halt",
|
|
||||||
builderConfig: &Config{
|
|
||||||
TemplateName: "my-template",
|
|
||||||
TemplateDescription: "some-description",
|
|
||||||
UnmountISO: true,
|
|
||||||
},
|
|
||||||
initialVMConfig: map[string]interface{}{
|
|
||||||
"name": "dummy",
|
|
||||||
"description": "Packer ephemeral build VM",
|
|
||||||
"ide1": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
|
||||||
},
|
|
||||||
expectCallSetConfig: false,
|
|
||||||
expectedAction: multistep.ActionHalt,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "GetVmConfig error should return halt",
|
name: "GetVmConfig error should return halt",
|
||||||
builderConfig: &Config{
|
builderConfig: &Config{
|
||||||
TemplateName: "my-template",
|
TemplateName: "my-template",
|
||||||
TemplateDescription: "some-description",
|
TemplateDescription: "some-description",
|
||||||
UnmountISO: true,
|
CloudInit: true,
|
||||||
},
|
},
|
||||||
getConfigErr: fmt.Errorf("some error"),
|
getConfigErr: fmt.Errorf("some error"),
|
||||||
expectCallSetConfig: false,
|
expectCallSetConfig: false,
|
||||||
|
@ -147,12 +125,10 @@ func TestTemplateFinalize(t *testing.T) {
|
||||||
builderConfig: &Config{
|
builderConfig: &Config{
|
||||||
TemplateName: "my-template",
|
TemplateName: "my-template",
|
||||||
TemplateDescription: "some-description",
|
TemplateDescription: "some-description",
|
||||||
UnmountISO: true,
|
|
||||||
},
|
},
|
||||||
initialVMConfig: map[string]interface{}{
|
initialVMConfig: map[string]interface{}{
|
||||||
"name": "dummy",
|
"name": "dummy",
|
||||||
"description": "Packer ephemeral build VM",
|
"description": "Packer ephemeral build VM",
|
||||||
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
|
||||||
},
|
},
|
||||||
expectCallSetConfig: true,
|
expectCallSetConfig: true,
|
||||||
setConfigErr: fmt.Errorf("some error"),
|
setConfigErr: fmt.Errorf("some error"),
|
|
@ -15,7 +15,13 @@ import (
|
||||||
//
|
//
|
||||||
// It sets the vmRef state which is used throughout the later steps to reference the VM
|
// It sets the vmRef state which is used throughout the later steps to reference the VM
|
||||||
// in API calls.
|
// in API calls.
|
||||||
type stepStartVM struct{}
|
type stepStartVM struct {
|
||||||
|
vmCreator ProxmoxVMCreator
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxmoxVMCreator interface {
|
||||||
|
Create(*proxmox.VmRef, proxmox.ConfigQemu, multistep.StateBag) error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
@ -32,8 +38,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
|
||||||
kvm = false
|
kvm = false
|
||||||
}
|
}
|
||||||
|
|
||||||
isoFile := state.Get("iso_file").(string)
|
|
||||||
|
|
||||||
ui.Say("Creating VM")
|
ui.Say("Creating VM")
|
||||||
config := proxmox.ConfigQemu{
|
config := proxmox.ConfigQemu{
|
||||||
Name: c.VMName,
|
Name: c.VMName,
|
||||||
|
@ -47,7 +51,6 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
|
||||||
QemuSockets: c.Sockets,
|
QemuSockets: c.Sockets,
|
||||||
QemuOs: c.OS,
|
QemuOs: c.OS,
|
||||||
QemuVga: generateProxmoxVga(c.VGA),
|
QemuVga: generateProxmoxVga(c.VGA),
|
||||||
QemuIso: isoFile,
|
|
||||||
QemuNetworks: generateProxmoxNetworkAdapters(c.NICs),
|
QemuNetworks: generateProxmoxNetworkAdapters(c.NICs),
|
||||||
QemuDisks: generateProxmoxDisks(c.Disks),
|
QemuDisks: generateProxmoxDisks(c.Disks),
|
||||||
Scsihw: c.SCSIController,
|
Scsihw: c.SCSIController,
|
||||||
|
@ -78,8 +81,9 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
|
||||||
vmRef.SetPool(c.Pool)
|
vmRef.SetPool(c.Pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := config.CreateVm(vmRef, client)
|
err := s.vmCreator.Create(vmRef, config, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating VM: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
|
@ -89,20 +93,9 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
|
||||||
state.Put("vmRef", vmRef)
|
state.Put("vmRef", vmRef)
|
||||||
// instance_id is the generic term used so that users can have access to the
|
// instance_id is the generic term used so that users can have access to the
|
||||||
// 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)
|
// Note that this is just the VMID, we do not keep the node, pool and other
|
||||||
|
// info available in the vmref type.
|
||||||
for idx := range c.AdditionalISOFiles {
|
state.Put("instance_id", vmRef.VmId())
|
||||||
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)
|
|
@ -136,7 +136,7 @@ func TestTypeBootCommand(t *testing.T) {
|
||||||
|
|
||||||
step := stepTypeBootCommand{
|
step := stepTypeBootCommand{
|
||||||
c.builderConfig.BootConfig,
|
c.builderConfig.BootConfig,
|
||||||
c.builderConfig.ctx,
|
c.builderConfig.Ctx,
|
||||||
}
|
}
|
||||||
action := step.Run(context.TODO(), state)
|
action := step.Run(context.TODO(), state)
|
||||||
step.Cleanup(state)
|
step.Cleanup(state)
|
|
@ -0,0 +1,76 @@
|
||||||
|
package proxmoxiso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox"
|
||||||
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The unique id for the builder
|
||||||
|
const BuilderID = "proxmox.iso"
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder implements packer.Builder
|
||||||
|
var _ packer.Builder = &Builder{}
|
||||||
|
|
||||||
|
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
|
return b.config.Prepare(raws...)
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadPathKey = "downloaded_iso_path"
|
||||||
|
|
||||||
|
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("iso-config", &b.config)
|
||||||
|
|
||||||
|
preSteps := []multistep.Step{
|
||||||
|
&common.StepDownload{
|
||||||
|
Checksum: b.config.ISOChecksum,
|
||||||
|
Description: "ISO",
|
||||||
|
Extension: b.config.TargetExtension,
|
||||||
|
ResultKey: downloadPathKey,
|
||||||
|
TargetPath: b.config.TargetPath,
|
||||||
|
Url: b.config.ISOUrls,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for idx := range b.config.AdditionalISOFiles {
|
||||||
|
preSteps = append(preSteps, &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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
preSteps = append(preSteps,
|
||||||
|
&stepUploadISO{},
|
||||||
|
&stepUploadAdditionalISOs{},
|
||||||
|
)
|
||||||
|
postSteps := []multistep.Step{
|
||||||
|
&stepFinalizeISOTemplate{},
|
||||||
|
}
|
||||||
|
|
||||||
|
sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, preSteps, postSteps, &isoVMCreator{})
|
||||||
|
return sb.Run(ctx, ui, hook, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
type isoVMCreator struct{}
|
||||||
|
|
||||||
|
func (*isoVMCreator) Create(vmRef *proxmoxapi.VmRef, config proxmoxapi.ConfigQemu, state multistep.StateBag) error {
|
||||||
|
isoFile := state.Get("iso_file").(string)
|
||||||
|
config.QemuIso = isoFile
|
||||||
|
|
||||||
|
client := state.Get("proxmoxClient").(*proxmoxapi.Client)
|
||||||
|
return config.CreateVm(vmRef, client)
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig
|
||||||
|
|
||||||
|
package proxmoxiso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
proxmox.Config `mapstructure:",squash"`
|
||||||
|
|
||||||
|
common.ISOConfig `mapstructure:",squash"`
|
||||||
|
ISOFile string `mapstructure:"iso_file"`
|
||||||
|
ISOStoragePool string `mapstructure:"iso_storage_pool"`
|
||||||
|
UnmountISO bool `mapstructure:"unmount_iso"`
|
||||||
|
shouldUploadISO bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
|
var errs *packer.MultiError
|
||||||
|
_, warnings, merrs := c.Config.Prepare(c, raws...)
|
||||||
|
if merrs != nil {
|
||||||
|
errs = packer.MultiErrorAppend(errs, merrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ISO 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.ISOFile != "" {
|
||||||
|
c.shouldUploadISO = false
|
||||||
|
} else {
|
||||||
|
isoWarnings, isoErrors := c.ISOConfig.Prepare(&c.Ctx)
|
||||||
|
errs = packer.MultiErrorAppend(errs, isoErrors...)
|
||||||
|
warnings = append(warnings, isoWarnings...)
|
||||||
|
c.shouldUploadISO = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.ISOFile == "" && len(c.ISOConfig.ISOUrls) == 0) || (c.ISOFile != "" && len(c.ISOConfig.ISOUrls) != 0) {
|
||||||
|
errs = packer.MultiErrorAppend(errs, errors.New("either iso_file or iso_url, but not both, must be specified"))
|
||||||
|
}
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, warnings, errs
|
||||||
|
}
|
||||||
|
return nil, warnings, nil
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig"; DO NOT EDIT.
|
||||||
|
package proxmoxiso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
proxmox "github.com/hashicorp/packer/builder/proxmox/common"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlatConfig is an auto-generated flat version of Config.
|
||||||
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
type FlatConfig struct {
|
||||||
|
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||||
|
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||||
|
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||||
|
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||||
|
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||||
|
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||||
|
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||||
|
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
|
||||||
|
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
|
||||||
|
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
|
||||||
|
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
|
||||||
|
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
|
||||||
|
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
|
||||||
|
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
|
||||||
|
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
|
||||||
|
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
|
||||||
|
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||||
|
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||||
|
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||||
|
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||||
|
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||||
|
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||||
|
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||||
|
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||||
|
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||||
|
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||||
|
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||||
|
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||||
|
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||||
|
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||||
|
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||||
|
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||||
|
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
||||||
|
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
||||||
|
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
||||||
|
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
||||||
|
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
||||||
|
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
||||||
|
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
||||||
|
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||||
|
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||||
|
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||||
|
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||||
|
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||||
|
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||||
|
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||||
|
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
||||||
|
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
||||||
|
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
||||||
|
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
||||||
|
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
||||||
|
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
||||||
|
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
||||||
|
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
||||||
|
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||||
|
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||||
|
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
||||||
|
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
||||||
|
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||||
|
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
||||||
|
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||||
|
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||||
|
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||||
|
ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"`
|
||||||
|
SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
|
||||||
|
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
|
||||||
|
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
|
||||||
|
Node *string `mapstructure:"node" cty:"node" hcl:"node"`
|
||||||
|
Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"`
|
||||||
|
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
|
||||||
|
VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"`
|
||||||
|
Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"`
|
||||||
|
Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"`
|
||||||
|
CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"`
|
||||||
|
Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"`
|
||||||
|
OS *string `mapstructure:"os" cty:"os" hcl:"os"`
|
||||||
|
VGA *proxmox.FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"`
|
||||||
|
NICs []proxmox.FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"`
|
||||||
|
Disks []proxmox.FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"`
|
||||||
|
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"`
|
||||||
|
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"`
|
||||||
|
Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"`
|
||||||
|
DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"`
|
||||||
|
TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"`
|
||||||
|
TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"`
|
||||||
|
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
|
||||||
|
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
|
||||||
|
AdditionalISOFiles []proxmox.FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
|
||||||
|
VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"`
|
||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatMapstructure returns a new FlatConfig.
|
||||||
|
// FlatConfig is an auto-generated flat version of Config.
|
||||||
|
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||||
|
return new(FlatConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HCL2Spec returns the hcl spec of a Config.
|
||||||
|
// This spec is used by HCL to read the fields of Config.
|
||||||
|
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||||
|
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
|
s := map[string]hcldec.Spec{
|
||||||
|
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||||
|
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||||
|
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||||
|
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||||
|
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||||
|
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||||
|
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||||
|
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
|
||||||
|
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
|
||||||
|
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
|
||||||
|
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
|
||||||
|
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
|
||||||
|
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
|
||||||
|
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
|
||||||
|
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||||
|
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
|
||||||
|
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||||
|
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||||
|
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||||
|
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||||
|
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||||
|
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||||
|
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||||
|
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||||
|
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||||
|
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||||
|
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||||
|
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||||
|
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||||
|
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||||
|
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||||
|
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||||
|
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||||
|
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||||
|
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||||
|
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||||
|
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||||
|
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||||
|
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||||
|
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||||
|
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||||
|
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||||
|
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
||||||
|
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||||
|
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||||
|
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||||
|
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||||
|
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||||
|
"proxmox_url": &hcldec.AttrSpec{Name: "proxmox_url", Type: cty.String, Required: false},
|
||||||
|
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
|
||||||
|
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
|
||||||
|
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
|
||||||
|
"node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false},
|
||||||
|
"pool": &hcldec.AttrSpec{Name: "pool", Type: cty.String, Required: false},
|
||||||
|
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
|
||||||
|
"vm_id": &hcldec.AttrSpec{Name: "vm_id", Type: cty.Number, Required: false},
|
||||||
|
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
|
||||||
|
"cores": &hcldec.AttrSpec{Name: "cores", Type: cty.Number, Required: false},
|
||||||
|
"cpu_type": &hcldec.AttrSpec{Name: "cpu_type", Type: cty.String, Required: false},
|
||||||
|
"sockets": &hcldec.AttrSpec{Name: "sockets", Type: cty.Number, Required: false},
|
||||||
|
"os": &hcldec.AttrSpec{Name: "os", Type: cty.String, Required: false},
|
||||||
|
"vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())},
|
||||||
|
"network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatnicConfig)(nil).HCL2Spec())},
|
||||||
|
"disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())},
|
||||||
|
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
|
||||||
|
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
|
||||||
|
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
|
||||||
|
"disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false},
|
||||||
|
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
||||||
|
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false},
|
||||||
|
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
|
||||||
|
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
|
||||||
|
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatstorageConfig)(nil).HCL2Spec())},
|
||||||
|
"vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false},
|
||||||
|
"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},
|
||||||
|
"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_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
|
@ -1,55 +1,17 @@
|
||||||
package proxmox
|
package proxmoxiso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/packer"
|
|
||||||
"github.com/hashicorp/packer/template"
|
"github.com/hashicorp/packer/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mandatoryConfig(t *testing.T) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
|
|
||||||
"username": "apiuser@pve",
|
|
||||||
"password": "supersecret",
|
|
||||||
"iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso",
|
|
||||||
"node": "my-proxmox",
|
|
||||||
"ssh_username": "root",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequiredParameters(t *testing.T) {
|
|
||||||
var c Config
|
|
||||||
_, err := c.Prepare(make(map[string]interface{}))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expected empty configuration to fail")
|
|
||||||
}
|
|
||||||
errs, ok := err.(*packer.MultiError)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("Expected errors to be packer.MultiError")
|
|
||||||
}
|
|
||||||
|
|
||||||
required := []string{"username", "password", "proxmox_url", "iso_file", "node", "ssh_username"}
|
|
||||||
for _, param := range required {
|
|
||||||
found := false
|
|
||||||
for _, err := range errs.Errors {
|
|
||||||
if strings.Contains(err.Error(), param) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Errorf("Expected error about missing parameter %q", required)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicExampleFromDocsIsValid(t *testing.T) {
|
func TestBasicExampleFromDocsIsValid(t *testing.T) {
|
||||||
const config = `{
|
const config = `{
|
||||||
"builders": [
|
"builders": [
|
||||||
{
|
{
|
||||||
"type": "proxmox",
|
"type": "proxmox-iso",
|
||||||
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
|
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
|
||||||
"insecure_skip_tls_verify": true,
|
"insecure_skip_tls_verify": true,
|
||||||
"username": "apiuser@pve",
|
"username": "apiuser@pve",
|
||||||
|
@ -93,9 +55,9 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b := &Builder{}
|
b := &Builder{}
|
||||||
_, warn, err := b.Prepare(tpl.Builders["proxmox"].Config)
|
_, _, err = b.Prepare(tpl.Builders["proxmox-iso"].Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err, warn)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The example config does not set a number of optional fields. Validate that:
|
// The example config does not set a number of optional fields. Validate that:
|
||||||
|
@ -154,7 +116,7 @@ func TestAgentSetToFalse(t *testing.T) {
|
||||||
cfg["qemu_agent"] = false
|
cfg["qemu_agent"] = false
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
warn, err := c.Prepare(cfg)
|
_, warn, err := c.Prepare(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err, warn)
|
t.Fatal(err, warn)
|
||||||
}
|
}
|
||||||
|
@ -197,7 +159,7 @@ func TestPacketQueueSupportForNetworkAdapters(t *testing.T) {
|
||||||
cfg["network_adapters"] = devices
|
cfg["network_adapters"] = devices
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
_, err := c.Prepare(cfg)
|
_, _, err := c.Prepare(cfg)
|
||||||
|
|
||||||
if tt.expectedToFail == true && err == nil {
|
if tt.expectedToFail == true && err == nil {
|
||||||
t.Error("expected config preparation to fail, but no error occured")
|
t.Error("expected config preparation to fail, but no error occured")
|
||||||
|
@ -246,7 +208,7 @@ func TestHardDiskControllerIOThreadSupport(t *testing.T) {
|
||||||
cfg["scsi_controller"] = tt.controller
|
cfg["scsi_controller"] = tt.controller
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
_, err := c.Prepare(cfg)
|
_, _, err := c.Prepare(cfg)
|
||||||
|
|
||||||
if tt.expectedToFail == true && err == nil {
|
if tt.expectedToFail == true && err == nil {
|
||||||
t.Error("expected config preparation to fail, but no error occured")
|
t.Error("expected config preparation to fail, but no error occured")
|
||||||
|
@ -257,3 +219,14 @@ func TestHardDiskControllerIOThreadSupport(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mandatoryConfig(t *testing.T) map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
|
||||||
|
"username": "apiuser@pve",
|
||||||
|
"password": "supersecret",
|
||||||
|
"node": "my-proxmox",
|
||||||
|
"ssh_username": "root",
|
||||||
|
"iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso",
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package proxmoxiso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Telmate/proxmox-api-go/proxmox"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stepFinalizeISOTemplate does any ISO-builder specific modifications after
|
||||||
|
// conversion to a template, and after the non-specific modifications in
|
||||||
|
// common.stepFinalizeTemplateConfig
|
||||||
|
type stepFinalizeISOTemplate struct{}
|
||||||
|
|
||||||
|
type templateFinalizer interface {
|
||||||
|
GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error)
|
||||||
|
SetVmConfig(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ templateFinalizer = &proxmox.Client{}
|
||||||
|
|
||||||
|
func (s *stepFinalizeISOTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
client := state.Get("proxmoxClient").(templateFinalizer)
|
||||||
|
c := state.Get("iso-config").(*Config)
|
||||||
|
vmRef := state.Get("vmRef").(*proxmox.VmRef)
|
||||||
|
|
||||||
|
changes := make(map[string]interface{})
|
||||||
|
|
||||||
|
if c.UnmountISO {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if vmParams["ide2"] == nil || !strings.HasSuffix(vmParams["ide2"].(string), "media=cdrom") {
|
||||||
|
err := fmt.Errorf("Cannot eject ISO from cdrom drive, ide2 is not present, or not a cdrom media")
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
changes["ide2"] = "none,media=cdrom"
|
||||||
|
}
|
||||||
|
if len(c.AdditionalISOFiles) > 0 {
|
||||||
|
vmParams, err := client.GetVmConfig(vmRef)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error fetching template config: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
for idx := range c.AdditionalISOFiles {
|
||||||
|
cdrom := c.AdditionalISOFiles[idx].Device
|
||||||
|
if c.AdditionalISOFiles[idx].Unmount {
|
||||||
|
if vmParams[cdrom] == nil || !strings.Contains(vmParams[cdrom].(string), "media=cdrom") {
|
||||||
|
err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present or not a cdrom media", cdrom)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
changes[cdrom] = "none,media=cdrom"
|
||||||
|
} else {
|
||||||
|
changes[cdrom] = c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(changes) > 0 {
|
||||||
|
_, err := client.SetVmConfig(vmRef, changes)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error updating template: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepFinalizeISOTemplate) Cleanup(state multistep.StateBag) {
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package proxmoxiso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Telmate/proxmox-api-go/proxmox"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type finalizerMock struct {
|
||||||
|
getConfig func() (map[string]interface{}, error)
|
||||||
|
setConfig func(map[string]interface{}) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m finalizerMock) GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error) {
|
||||||
|
return m.getConfig()
|
||||||
|
}
|
||||||
|
func (m finalizerMock) SetVmConfig(vmref *proxmox.VmRef, c map[string]interface{}) (interface{}, error) {
|
||||||
|
return m.setConfig(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ templateFinalizer = finalizerMock{}
|
||||||
|
|
||||||
|
func TestISOTemplateFinalize(t *testing.T) {
|
||||||
|
cs := []struct {
|
||||||
|
name string
|
||||||
|
builderConfig *Config
|
||||||
|
initialVMConfig map[string]interface{}
|
||||||
|
getConfigErr error
|
||||||
|
expectCallSetConfig bool
|
||||||
|
expectedVMConfig map[string]interface{}
|
||||||
|
setConfigErr error
|
||||||
|
expectedAction multistep.StepAction
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default config does nothing",
|
||||||
|
builderConfig: &Config{},
|
||||||
|
initialVMConfig: map[string]interface{}{
|
||||||
|
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
||||||
|
},
|
||||||
|
expectCallSetConfig: false,
|
||||||
|
expectedVMConfig: map[string]interface{}{
|
||||||
|
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
||||||
|
},
|
||||||
|
expectedAction: multistep.ActionContinue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should unmount when configured",
|
||||||
|
builderConfig: &Config{
|
||||||
|
UnmountISO: true,
|
||||||
|
},
|
||||||
|
initialVMConfig: map[string]interface{}{
|
||||||
|
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
||||||
|
},
|
||||||
|
expectCallSetConfig: true,
|
||||||
|
expectedVMConfig: map[string]interface{}{
|
||||||
|
"ide2": "none,media=cdrom",
|
||||||
|
},
|
||||||
|
expectedAction: multistep.ActionContinue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no cd-drive with unmount=true should returns halt",
|
||||||
|
builderConfig: &Config{
|
||||||
|
UnmountISO: true,
|
||||||
|
},
|
||||||
|
initialVMConfig: map[string]interface{}{
|
||||||
|
"ide1": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
||||||
|
},
|
||||||
|
expectCallSetConfig: false,
|
||||||
|
expectedAction: multistep.ActionHalt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GetVmConfig error should return halt",
|
||||||
|
builderConfig: &Config{
|
||||||
|
UnmountISO: true,
|
||||||
|
},
|
||||||
|
getConfigErr: fmt.Errorf("some error"),
|
||||||
|
expectCallSetConfig: false,
|
||||||
|
expectedAction: multistep.ActionHalt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetVmConfig error should return halt",
|
||||||
|
builderConfig: &Config{
|
||||||
|
UnmountISO: true,
|
||||||
|
},
|
||||||
|
initialVMConfig: map[string]interface{}{
|
||||||
|
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
|
||||||
|
},
|
||||||
|
expectCallSetConfig: true,
|
||||||
|
setConfigErr: fmt.Errorf("some error"),
|
||||||
|
expectedAction: multistep.ActionHalt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cs {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
finalizer := finalizerMock{
|
||||||
|
getConfig: func() (map[string]interface{}, error) {
|
||||||
|
return c.initialVMConfig, c.getConfigErr
|
||||||
|
},
|
||||||
|
setConfig: func(cfg map[string]interface{}) (string, error) {
|
||||||
|
if !c.expectCallSetConfig {
|
||||||
|
t.Error("Did not expect SetVmConfig to be called")
|
||||||
|
}
|
||||||
|
for key, val := range c.expectedVMConfig {
|
||||||
|
if cfg[key] != val {
|
||||||
|
t.Errorf("Expected %q to be %q, got %q", key, val, cfg[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", c.setConfigErr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("ui", packer.TestUi(t))
|
||||||
|
state.Put("iso-config", c.builderConfig)
|
||||||
|
state.Put("vmRef", proxmox.NewVmRef(1))
|
||||||
|
state.Put("proxmoxClient", finalizer)
|
||||||
|
|
||||||
|
step := stepFinalizeISOTemplate{}
|
||||||
|
action := step.Run(context.TODO(), state)
|
||||||
|
if action != c.expectedAction {
|
||||||
|
t.Errorf("Expected action to be %v, got %v", c.expectedAction, action)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
package proxmox
|
package proxmoxiso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
@ -16,24 +15,20 @@ import (
|
||||||
// to the VM
|
// to the VM
|
||||||
type stepUploadAdditionalISOs struct{}
|
type stepUploadAdditionalISOs 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 *stepUploadAdditionalISOs) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepUploadAdditionalISOs) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
client := state.Get("proxmoxClient").(uploader)
|
client := state.Get("proxmoxClient").(uploader)
|
||||||
c := state.Get("config").(*Config)
|
c := state.Get("iso-config").(*Config)
|
||||||
|
|
||||||
for idx := range c.AdditionalISOFiles {
|
for idx := range c.AdditionalISOFiles {
|
||||||
if !c.AdditionalISOFiles[idx].shouldUploadISO {
|
if !c.AdditionalISOFiles[idx].ShouldUploadISO {
|
||||||
state.Put("additional_iso_files", c.AdditionalISOFiles)
|
state.Put("additional_iso_files", c.AdditionalISOFiles)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
p := state.Get(c.AdditionalISOFiles[idx].downloadPathKey).(string)
|
p := state.Get(c.AdditionalISOFiles[idx].DownloadPathKey).(string)
|
||||||
if p == "" {
|
if p == "" {
|
||||||
err := fmt.Errorf("Path to downloaded ISO was empty")
|
err := fmt.Errorf("Path to downloaded ISO was empty")
|
||||||
state.Put("erroe", err)
|
state.Put("erroe", err)
|
|
@ -1,8 +1,9 @@
|
||||||
package proxmox
|
package proxmoxiso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
@ -14,12 +15,16 @@ 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 {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
client := state.Get("proxmoxClient").(uploader)
|
client := state.Get("proxmoxClient").(uploader)
|
||||||
c := state.Get("config").(*Config)
|
c := state.Get("iso-config").(*Config)
|
||||||
|
|
||||||
if !c.shouldUploadISO {
|
if !c.shouldUploadISO {
|
||||||
state.Put("iso_file", c.ISOFile)
|
state.Put("iso_file", c.ISOFile)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package proxmox
|
package proxmoxiso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -106,7 +106,7 @@ func TestUploadISO(t *testing.T) {
|
||||||
|
|
||||||
state := new(multistep.BasicStateBag)
|
state := new(multistep.BasicStateBag)
|
||||||
state.Put("ui", packer.TestUi(t))
|
state.Put("ui", packer.TestUi(t))
|
||||||
state.Put("config", c.builderConfig)
|
state.Put("iso-config", c.builderConfig)
|
||||||
state.Put(downloadPathKey, c.downloadPath)
|
state.Put(downloadPathKey, c.downloadPath)
|
||||||
state.Put("proxmoxClient", m)
|
state.Put("proxmoxClient", m)
|
||||||
|
|
|
@ -394,6 +394,7 @@ type Config struct {
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -282,7 +282,10 @@ func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config
|
||||||
if len(config.QemuArgs) > 0 {
|
if len(config.QemuArgs) > 0 {
|
||||||
s.ui.Say("Overriding default Qemu arguments with qemuargs template option...")
|
s.ui.Say("Overriding default Qemu arguments with qemuargs template option...")
|
||||||
|
|
||||||
commHostPort := state.Get("commHostPort").(int)
|
commHostPort := 0
|
||||||
|
if config.CommConfig.Comm.Type != "none" {
|
||||||
|
commHostPort = state.Get("commHostPort").(int)
|
||||||
|
}
|
||||||
httpIp := state.Get("http_ip").(string)
|
httpIp := state.Get("http_ip").(string)
|
||||||
httpPort := state.Get("http_port").(int)
|
httpPort := state.Get("http_port").(int)
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
var md mapstructure.Metadata
|
var md mapstructure.Metadata
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
Metadata: &md,
|
Metadata: &md,
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -35,6 +35,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -27,6 +27,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
errs := &multierror.Error{}
|
errs := &multierror.Error{}
|
||||||
|
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
}, raws...)
|
}, raws...)
|
||||||
|
|
|
@ -38,6 +38,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -154,6 +154,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -117,6 +117,7 @@ func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstruct
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
PluginType: vboxcommon.BuilderId, // "mitchellh.virtualbox"
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &b.config.ctx,
|
InterpolateContext: &b.config.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -82,6 +82,7 @@ type Config struct {
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: vboxcommon.BuilderId, // "mitchellh.virtualbox"
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -66,6 +66,7 @@ type Config struct {
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: vboxcommon.BuilderId, // "mitchellh.virtualbox"
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
|
|
@ -52,6 +52,7 @@ type Config struct {
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: common.BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
@ -64,7 +65,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// warnings := make([]string, 0)
|
||||||
errs := new(packer.MultiError)
|
errs := new(packer.MultiError)
|
||||||
|
|
||||||
errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
|
errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.CloneConfig.Prepare()...)
|
errs = packer.MultiErrorAppend(errs, c.CloneConfig.Prepare()...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
|
errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
|
||||||
|
@ -76,7 +79,12 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
|
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
|
|
||||||
|
_, shutdownErrs := c.ShutdownConfig.Prepare(c.Comm)
|
||||||
|
// shutdownWarnings, shutdownErrs := c.ShutdownConfig.Prepare(c.Comm)
|
||||||
|
// warnings = append(warnings, shutdownWarnings...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, shutdownErrs...)
|
||||||
|
|
||||||
if c.Export != nil {
|
if c.Export != nil {
|
||||||
errs = packer.MultiErrorAppend(errs, c.Export.Prepare(&c.ctx, &c.LocationConfig, &c.PackerConfig)...)
|
errs = packer.MultiErrorAppend(errs, c.Export.Prepare(&c.ctx, &c.LocationConfig, &c.PackerConfig)...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defining this interface ensures that we use the common step download, or the
|
||||||
|
// mock created to test this wrapper
|
||||||
|
type DownloadStep interface {
|
||||||
|
Run(context.Context, multistep.StateBag) multistep.StepAction
|
||||||
|
Cleanup(multistep.StateBag)
|
||||||
|
UseSourceToFindCacheTarget(source string) (*url.URL, string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VSphere has a specialized need -- before we waste time downloading an iso,
|
||||||
|
// we need to check whether that iso already exists on the remote datastore.
|
||||||
|
// if it does, we skip the download. This wrapping-step still uses the common
|
||||||
|
// StepDownload but only if the image isn't already present on the datastore.
|
||||||
|
type StepDownload struct {
|
||||||
|
DownloadStep DownloadStep
|
||||||
|
// These keys are VSphere-specific and used to check the remote datastore.
|
||||||
|
Url []string
|
||||||
|
ResultKey string
|
||||||
|
Datastore string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(driver.Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// Check whether iso is present on remote datastore.
|
||||||
|
ds, err := driver.FindDatastore(s.Datastore, s.Host)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("datastore doesn't exist: %v", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop over URLs to see if any are already present. If they are, store that
|
||||||
|
// one instate and continue
|
||||||
|
for _, source := range s.Url {
|
||||||
|
_, targetPath, err := s.DownloadStep.UseSourceToFindCacheTarget(source)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error getting target path: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
_, remotePath, _, _ := GetRemoteDirectoryAndPath(targetPath, ds)
|
||||||
|
|
||||||
|
if exists := ds.FileExists(remotePath); exists {
|
||||||
|
ui.Say(fmt.Sprintf("File %s already uploaded; continuing", targetPath))
|
||||||
|
state.Put(s.ResultKey, targetPath)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISO is not present on datastore, so we need to download, then upload it.
|
||||||
|
// Pass through to the common download step.
|
||||||
|
return s.DownloadStep.Run(ctx, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDownload) Cleanup(state multistep.StateBag) {
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
)
|
||||||
|
|
||||||
|
/// create mock step
|
||||||
|
type MockDownloadStep struct {
|
||||||
|
RunCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MockDownloadStep) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
s.RunCalled = true
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MockDownloadStep) Cleanup(state multistep.StateBag) {}
|
||||||
|
|
||||||
|
func (s *MockDownloadStep) UseSourceToFindCacheTarget(source string) (*url.URL, string, error) {
|
||||||
|
return nil, "sometarget", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// start tests
|
||||||
|
func downloadStepState(exists bool) *multistep.BasicStateBag {
|
||||||
|
state := basicStateBag(nil)
|
||||||
|
dsMock := &driver.DatastoreMock{
|
||||||
|
FileExistsReturn: exists,
|
||||||
|
}
|
||||||
|
driverMock := &driver.DriverMock{
|
||||||
|
DatastoreMock: dsMock,
|
||||||
|
}
|
||||||
|
state.Put("driver", driverMock)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepDownload_Run(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
filePresent bool
|
||||||
|
expectedAction multistep.StepAction
|
||||||
|
expectInternalStepCalled bool
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Remote iso present; download shouldn't be called",
|
||||||
|
filePresent: true,
|
||||||
|
expectedAction: multistep.ActionContinue,
|
||||||
|
expectInternalStepCalled: false,
|
||||||
|
errMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Remote iso not present; download should be called",
|
||||||
|
filePresent: false,
|
||||||
|
expectedAction: multistep.ActionContinue,
|
||||||
|
expectInternalStepCalled: true,
|
||||||
|
errMessage: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
internalStep := &MockDownloadStep{}
|
||||||
|
state := downloadStepState(tc.filePresent)
|
||||||
|
step := &StepDownload{
|
||||||
|
DownloadStep: internalStep,
|
||||||
|
Url: []string{"https://path/to/fake-url.iso"},
|
||||||
|
Datastore: "datastore-mock",
|
||||||
|
Host: "fake-host",
|
||||||
|
}
|
||||||
|
stepAction := step.Run(context.TODO(), state)
|
||||||
|
if stepAction != tc.expectedAction {
|
||||||
|
t.Fatalf("%s: Recieved wrong step action; step exists, should return early.", tc.name)
|
||||||
|
}
|
||||||
|
if tc.expectInternalStepCalled != internalStep.RunCalled {
|
||||||
|
if tc.expectInternalStepCalled {
|
||||||
|
t.Fatalf("%s: Expected internal download step to be called", tc.name)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("%s: Expected internal download step not to be called", tc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,24 +42,30 @@ func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetRemoteDirectoryAndPath(path string, ds driver.Datastore) (string, string, string, string) {
|
||||||
|
filename := filepath.Base(path)
|
||||||
|
remotePath := fmt.Sprintf("packer_cache/%s", filename)
|
||||||
|
remoteDirectory := fmt.Sprintf("[%s] packer_cache/", ds.Name())
|
||||||
|
fullRemotePath := fmt.Sprintf("%s/%s", remoteDirectory, filename)
|
||||||
|
|
||||||
|
return filename, remotePath, remoteDirectory, fullRemotePath
|
||||||
|
|
||||||
|
}
|
||||||
func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packer.Ui) (string, error) {
|
func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packer.Ui) (string, error) {
|
||||||
ds, err := d.FindDatastore(s.Datastore, s.Host)
|
ds, err := d.FindDatastore(s.Datastore, s.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("datastore doesn't exist: %v", err)
|
return "", fmt.Errorf("datastore doesn't exist: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := filepath.Base(path)
|
filename, remotePath, remoteDirectory, fullRemotePath := GetRemoteDirectoryAndPath(path, ds)
|
||||||
remotePath := fmt.Sprintf("packer_cache/%s", filename)
|
|
||||||
remoteDirectory := fmt.Sprintf("[%s] packer_cache/", ds.Name())
|
|
||||||
fullRemotePath := fmt.Sprintf("%s/%s", remoteDirectory, filename)
|
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Uploading %s to %s", filename, remotePath))
|
|
||||||
|
|
||||||
if exists := ds.FileExists(remotePath); exists == true {
|
if exists := ds.FileExists(remotePath); exists == true {
|
||||||
ui.Say(fmt.Sprintf("File %s already uploaded; continuing", filename))
|
ui.Say(fmt.Sprintf("File %s already exists; skipping upload.", fullRemotePath))
|
||||||
return fullRemotePath, nil
|
return fullRemotePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Uploading %s to %s", filename, remotePath))
|
||||||
|
|
||||||
if err := ds.MakeDirectory(remoteDirectory); err != nil {
|
if err := ds.MakeDirectory(remoteDirectory); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,34 +11,40 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/builder/vsphere/driver"
|
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||||
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShutdownConfig struct {
|
type ShutdownConfig struct {
|
||||||
// Specify a VM guest shutdown command. VMware guest tools are used by
|
// Specify a VM guest shutdown command. This command will be executed using
|
||||||
// default.
|
// the `communicator`. Otherwise the VMware guest tools are used to gracefully
|
||||||
|
// shutdown the VM guest.
|
||||||
Command string `mapstructure:"shutdown_command"`
|
Command string `mapstructure:"shutdown_command"`
|
||||||
// Amount of time to wait for graceful VM shutdown.
|
// Amount of time to wait for graceful VM shutdown.
|
||||||
// Defaults to 5m or five minutes.
|
// Defaults to 5m or five minutes.
|
||||||
|
// This will likely need to be modified if the `communicator` is 'none'.
|
||||||
Timeout time.Duration `mapstructure:"shutdown_timeout"`
|
Timeout time.Duration `mapstructure:"shutdown_timeout"`
|
||||||
// Packer normally halts the virtual machine after all provisioners have
|
// Packer normally halts the virtual machine after all provisioners have
|
||||||
// run when no `shutdown_command` is defined. If this is set to `true`, Packer
|
// run when no `shutdown_command` is defined. If this is set to `true`, Packer
|
||||||
// *will not* halt the virtual machine but will assume that you will send the stop
|
// *will not* halt the virtual machine but will assume that you will send the stop
|
||||||
// signal yourself through the preseed.cfg or your final provisioner.
|
// signal yourself through a preseed.cfg, a script or the final provisioner.
|
||||||
// Packer will wait for a default of five minutes until the virtual machine is shutdown.
|
// Packer will wait for a default of five minutes until the virtual machine is shutdown.
|
||||||
// The timeout can be changed using `shutdown_timeout` option.
|
// The timeout can be changed using `shutdown_timeout` option.
|
||||||
DisableShutdown bool `mapstructure:"disable_shutdown"`
|
DisableShutdown bool `mapstructure:"disable_shutdown"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ShutdownConfig) Prepare() []error {
|
func (c *ShutdownConfig) Prepare(comm communicator.Config) (warnings []string, errs []error) {
|
||||||
var errs []error
|
|
||||||
|
|
||||||
if c.Timeout == 0 {
|
if c.Timeout == 0 {
|
||||||
c.Timeout = 5 * time.Minute
|
c.Timeout = 5 * time.Minute
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
if comm.Type == "none" && c.Command != "" {
|
||||||
|
warnings = append(warnings, "The parameter `shutdown_command` is ignored as it requires a `communicator`.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type StepShutdown struct {
|
type StepShutdown struct {
|
||||||
|
@ -47,7 +53,6 @@ type StepShutdown struct {
|
||||||
|
|
||||||
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
comm := state.Get("communicator").(packer.Communicator)
|
|
||||||
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
vm := state.Get("vm").(*driver.VirtualMachineDriver)
|
||||||
|
|
||||||
if off, _ := vm.IsPoweredOff(); off {
|
if off, _ := vm.IsPoweredOff(); off {
|
||||||
|
@ -56,9 +61,17 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Config.DisableShutdown {
|
comm, _ := state.Get("communicator").(packer.Communicator)
|
||||||
|
if comm == nil {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("Please shutdown virtual machine within %s.", s.Config.Timeout)
|
||||||
|
ui.Message(msg)
|
||||||
|
|
||||||
|
} else if s.Config.DisableShutdown {
|
||||||
ui.Say("Automatic shutdown disabled. Please shutdown virtual machine.")
|
ui.Say("Automatic shutdown disabled. Please shutdown virtual machine.")
|
||||||
} else if s.Config.Command != "" {
|
} else if s.Config.Command != "" {
|
||||||
|
// Communicator is not needed unless shutdown_command is populated
|
||||||
|
|
||||||
ui.Say("Executing shutdown command...")
|
ui.Say("Executing shutdown command...")
|
||||||
log.Printf("Shutdown command: %s", s.Config.Command)
|
log.Printf("Shutdown command: %s", s.Config.Command)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,10 @@ import (
|
||||||
|
|
||||||
type DatastoreMock struct {
|
type DatastoreMock struct {
|
||||||
FileExistsCalled bool
|
FileExistsCalled bool
|
||||||
|
FileExistsReturn bool
|
||||||
|
|
||||||
|
NameReturn string
|
||||||
|
|
||||||
MakeDirectoryCalled bool
|
MakeDirectoryCalled bool
|
||||||
|
|
||||||
ResolvePathCalled bool
|
ResolvePathCalled bool
|
||||||
|
@ -30,12 +34,15 @@ func (ds *DatastoreMock) Info(params ...string) (*mo.Datastore, error) {
|
||||||
|
|
||||||
func (ds *DatastoreMock) FileExists(path string) bool {
|
func (ds *DatastoreMock) FileExists(path string) bool {
|
||||||
ds.FileExistsCalled = true
|
ds.FileExistsCalled = true
|
||||||
return false
|
return ds.FileExistsReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DatastoreMock) Name() string {
|
func (ds *DatastoreMock) Name() string {
|
||||||
|
if ds.NameReturn == "" {
|
||||||
return "datastore-mock"
|
return "datastore-mock"
|
||||||
}
|
}
|
||||||
|
return ds.NameReturn
|
||||||
|
}
|
||||||
|
|
||||||
func (ds *DatastoreMock) Reference() types.ManagedObjectReference {
|
func (ds *DatastoreMock) Reference() types.ManagedObjectReference {
|
||||||
return types.ManagedObjectReference{}
|
return types.ManagedObjectReference{}
|
||||||
|
|
|
@ -40,7 +40,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
&common.StepConnect{
|
&common.StepConnect{
|
||||||
Config: &b.config.ConnectConfig,
|
Config: &b.config.ConnectConfig,
|
||||||
},
|
},
|
||||||
&packerCommon.StepDownload{
|
&common.StepDownload{
|
||||||
|
DownloadStep: &packerCommon.StepDownload{
|
||||||
Checksum: b.config.ISOChecksum,
|
Checksum: b.config.ISOChecksum,
|
||||||
Description: "ISO",
|
Description: "ISO",
|
||||||
Extension: b.config.TargetExtension,
|
Extension: b.config.TargetExtension,
|
||||||
|
@ -48,6 +49,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
TargetPath: b.config.TargetPath,
|
TargetPath: b.config.TargetPath,
|
||||||
Url: b.config.ISOUrls,
|
Url: b.config.ISOUrls,
|
||||||
},
|
},
|
||||||
|
Url: b.config.ISOUrls,
|
||||||
|
ResultKey: "iso_path",
|
||||||
|
Datastore: b.config.Datastore,
|
||||||
|
Host: b.config.Host,
|
||||||
|
},
|
||||||
&packerCommon.StepCreateCD{
|
&packerCommon.StepCreateCD{
|
||||||
Files: b.config.CDConfig.CDFiles,
|
Files: b.config.CDConfig.CDFiles,
|
||||||
Label: b.config.CDConfig.CDLabel,
|
Label: b.config.CDConfig.CDLabel,
|
||||||
|
@ -71,10 +77,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
&common.StepConfigParams{
|
&common.StepConfigParams{
|
||||||
Config: &b.config.ConfigParamsConfig,
|
Config: &b.config.ConfigParamsConfig,
|
||||||
},
|
},
|
||||||
)
|
|
||||||
|
|
||||||
if b.config.Comm.Type != "none" {
|
|
||||||
steps = append(steps,
|
|
||||||
&packerCommon.StepCreateFloppy{
|
&packerCommon.StepCreateFloppy{
|
||||||
Files: b.config.FloppyFiles,
|
Files: b.config.FloppyFiles,
|
||||||
Directories: b.config.FloppyDirectories,
|
Directories: b.config.FloppyDirectories,
|
||||||
|
@ -105,6 +107,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
Ctx: b.config.ctx,
|
Ctx: b.config.ctx,
|
||||||
VMName: b.config.VMName,
|
VMName: b.config.VMName,
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if b.config.Comm.Type != "none" {
|
||||||
|
steps = append(steps,
|
||||||
&common.StepWaitForIp{
|
&common.StepWaitForIp{
|
||||||
Config: &b.config.WaitIpConfig,
|
Config: &b.config.WaitIpConfig,
|
||||||
},
|
},
|
||||||
|
@ -114,6 +120,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||||
},
|
},
|
||||||
&packerCommon.StepProvision{},
|
&packerCommon.StepProvision{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
steps = append(steps,
|
||||||
&common.StepShutdown{
|
&common.StepShutdown{
|
||||||
Config: &b.config.ShutdownConfig,
|
Config: &b.config.ShutdownConfig,
|
||||||
},
|
},
|
||||||
|
@ -121,10 +131,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
Datastore: b.config.Datastore,
|
Datastore: b.config.Datastore,
|
||||||
Host: b.config.Host,
|
Host: b.config.Host,
|
||||||
},
|
},
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
steps = append(steps,
|
|
||||||
&common.StepRemoveCDRom{
|
&common.StepRemoveCDRom{
|
||||||
Config: &b.config.RemoveCDRomConfig,
|
Config: &b.config.RemoveCDRomConfig,
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,6 +53,7 @@ type Config struct {
|
||||||
|
|
||||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
err := config.Decode(c, &config.DecodeOpts{
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
PluginType: common.BuilderId,
|
||||||
Interpolate: true,
|
Interpolate: true,
|
||||||
InterpolateContext: &c.ctx,
|
InterpolateContext: &c.ctx,
|
||||||
InterpolateFilter: &interpolate.RenderFilter{
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
@ -85,7 +86,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
|
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
|
|
||||||
|
shutdownWarnings, shutdownErrs := c.ShutdownConfig.Prepare(c.Comm)
|
||||||
|
warnings = append(warnings, shutdownWarnings...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, shutdownErrs...)
|
||||||
|
|
||||||
if c.Export != nil {
|
if c.Export != nil {
|
||||||
errs = packer.MultiErrorAppend(errs, c.Export.Prepare(&c.ctx, &c.LocationConfig, &c.PackerConfig)...)
|
errs = packer.MultiErrorAppend(errs, c.Export.Prepare(&c.ctx, &c.LocationConfig, &c.PackerConfig)...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,9 @@ type AccessConfig struct {
|
||||||
// is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable
|
// is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable
|
||||||
// `YC_SERVICE_ACCOUNT_KEY_FILE`.
|
// `YC_SERVICE_ACCOUNT_KEY_FILE`.
|
||||||
ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"`
|
ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"`
|
||||||
// OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set
|
// [OAuth token](https://cloud.yandex.com/docs/iam/concepts/authorization/oauth-token)
|
||||||
|
// or [IAM token](https://cloud.yandex.com/docs/iam/concepts/authorization/iam-token)
|
||||||
|
// to use to authenticate to Yandex.Cloud. Alternatively you may set
|
||||||
// value by environment variable `YC_TOKEN`.
|
// value by environment variable `YC_TOKEN`.
|
||||||
Token string `mapstructure:"token" required:"true"`
|
Token string `mapstructure:"token" required:"true"`
|
||||||
// The maximum number of times an API request is being executed.
|
// The maximum number of times an API request is being executed.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||||
|
@ -48,9 +49,13 @@ func NewDriverYC(ui packer.Ui, ac *AccessConfig) (Driver, error) {
|
||||||
sdkConfig.Credentials = ycsdk.InstanceServiceAccount()
|
sdkConfig.Credentials = ycsdk.InstanceServiceAccount()
|
||||||
|
|
||||||
case ac.Token != "":
|
case ac.Token != "":
|
||||||
|
if strings.HasPrefix(ac.Token, "t1.") && strings.Count(ac.Token, ".") == 2 {
|
||||||
|
log.Printf("[INFO] Use IAM token for authentication")
|
||||||
|
sdkConfig.Credentials = ycsdk.NewIAMTokenCredentials(ac.Token)
|
||||||
|
} else {
|
||||||
log.Printf("[INFO] Use OAuth token for authentication")
|
log.Printf("[INFO] Use OAuth token for authentication")
|
||||||
sdkConfig.Credentials = ycsdk.OAuthToken(ac.Token)
|
sdkConfig.Credentials = ycsdk.OAuthToken(ac.Token)
|
||||||
|
}
|
||||||
case ac.ServiceAccountKeyFile != "":
|
case ac.ServiceAccountKeyFile != "":
|
||||||
log.Printf("[INFO] Use Service Account key file %q for authentication", ac.ServiceAccountKeyFile)
|
log.Printf("[INFO] Use Service Account key file %q for authentication", ac.ServiceAccountKeyFile)
|
||||||
key, err := iamkey.ReadFromJSONFile(ac.ServiceAccountKeyFile)
|
key, err := iamkey.ReadFromJSONFile(ac.ServiceAccountKeyFile)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/format"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -11,20 +13,19 @@ import (
|
||||||
"github.com/hashicorp/packer/fix"
|
"github.com/hashicorp/packer/fix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deprecatedOptsTemplate = template.Must(template.New("deprecatedOptsTemplate").
|
var deprecatedOptsTemplate = template.Must(template.New("deprecatedOptsTemplate").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(`//<!-- Code generated by generate-fixer-deprecations; DO NOT EDIT MANUALLY -->
|
||||||
Parse(`//<!-- Code generated by generate-fixer-deprecations; DO NOT EDIT MANUALLY -->
|
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
var DeprecatedOptions = []string{
|
var DeprecatedOptions = map[string][]string{
|
||||||
{{- range .DeprecatedOpts}}
|
{{- range $key, $value := .DeprecatedOpts}}
|
||||||
"{{.}}",
|
"{{$key}}": []string{"{{ StringsJoin . "\", \"" }}"},
|
||||||
{{- end}}
|
{{- end}}
|
||||||
}
|
}
|
||||||
`))
|
`))
|
||||||
|
|
||||||
type executeOpts struct {
|
type executeOpts struct {
|
||||||
DeprecatedOpts []string
|
DeprecatedOpts map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -45,7 +46,7 @@ func main() {
|
||||||
packerDir := paths[0]
|
packerDir := paths[0]
|
||||||
|
|
||||||
// Load all deprecated options from all active fixers
|
// Load all deprecated options from all active fixers
|
||||||
allDeprecatedOpts := []string{}
|
allDeprecatedOpts := map[string][]string{}
|
||||||
for _, name := range fix.FixerOrder {
|
for _, name := range fix.FixerOrder {
|
||||||
fixer, ok := fix.Fixers[name]
|
fixer, ok := fix.Fixers[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -53,20 +54,38 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
deprecated := fixer.DeprecatedOptions()
|
deprecated := fixer.DeprecatedOptions()
|
||||||
allDeprecatedOpts = append(allDeprecatedOpts, deprecated...)
|
for k, v := range deprecated {
|
||||||
|
if allDeprecatedOpts[k] == nil {
|
||||||
|
allDeprecatedOpts[k] = v
|
||||||
|
} else {
|
||||||
|
allDeprecatedOpts[k] = append(allDeprecatedOpts[k], v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deprecated_path := filepath.Join(packerDir, "helper", "config",
|
deprecated_path := filepath.Join(packerDir, "helper", "config",
|
||||||
"deprecated_options.go")
|
"deprecated_options.go")
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
// execute template into buffer
|
||||||
|
deprecated := &executeOpts{DeprecatedOpts: allDeprecatedOpts}
|
||||||
|
err = deprecatedOptsTemplate.Execute(&buf, deprecated)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// we've written unformatted go code to the file. now we have to format it.
|
||||||
|
out, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
outputFile, err := os.Create(deprecated_path)
|
outputFile, err := os.Create(deprecated_path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
_, err = outputFile.Write(out)
|
||||||
defer outputFile.Close()
|
defer outputFile.Close()
|
||||||
|
|
||||||
deprecated := &executeOpts{DeprecatedOpts: allDeprecatedOpts}
|
|
||||||
err = deprecatedOptsTemplate.Execute(outputFile, deprecated)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v", err)
|
fmt.Printf("%v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -49,6 +49,8 @@ import (
|
||||||
parallelspvmbuilder "github.com/hashicorp/packer/builder/parallels/pvm"
|
parallelspvmbuilder "github.com/hashicorp/packer/builder/parallels/pvm"
|
||||||
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
|
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
|
||||||
proxmoxbuilder "github.com/hashicorp/packer/builder/proxmox"
|
proxmoxbuilder "github.com/hashicorp/packer/builder/proxmox"
|
||||||
|
proxmoxclonebuilder "github.com/hashicorp/packer/builder/proxmox/clone"
|
||||||
|
proxmoxisobuilder "github.com/hashicorp/packer/builder/proxmox/iso"
|
||||||
qemubuilder "github.com/hashicorp/packer/builder/qemu"
|
qemubuilder "github.com/hashicorp/packer/builder/qemu"
|
||||||
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
|
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
|
||||||
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
|
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
|
||||||
|
@ -146,6 +148,8 @@ var Builders = map[string]packer.Builder{
|
||||||
"parallels-pvm": new(parallelspvmbuilder.Builder),
|
"parallels-pvm": new(parallelspvmbuilder.Builder),
|
||||||
"profitbricks": new(profitbricksbuilder.Builder),
|
"profitbricks": new(profitbricksbuilder.Builder),
|
||||||
"proxmox": new(proxmoxbuilder.Builder),
|
"proxmox": new(proxmoxbuilder.Builder),
|
||||||
|
"proxmox-clone": new(proxmoxclonebuilder.Builder),
|
||||||
|
"proxmox-iso": new(proxmoxisobuilder.Builder),
|
||||||
"qemu": new(qemubuilder.Builder),
|
"qemu": new(qemubuilder.Builder),
|
||||||
"scaleway": new(scalewaybuilder.Builder),
|
"scaleway": new(scalewaybuilder.Builder),
|
||||||
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
||||||
|
|
|
@ -108,10 +108,10 @@ func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multis
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string) (string, error) {
|
func (s *StepDownload) UseSourceToFindCacheTarget(source string) (*url.URL, string, error) {
|
||||||
u, err := parseSourceURL(source)
|
u, err := parseSourceURL(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("url parse: %s", err)
|
return nil, "", fmt.Errorf("url parse: %s", err)
|
||||||
}
|
}
|
||||||
if checksum := u.Query().Get("checksum"); checksum != "" {
|
if checksum := u.Query().Get("checksum"); checksum != "" {
|
||||||
s.Checksum = checksum
|
s.Checksum = checksum
|
||||||
|
@ -142,7 +142,7 @@ func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string
|
||||||
}
|
}
|
||||||
targetPath, err = packer.CachePath(targetPath)
|
targetPath, err = packer.CachePath(targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("CachePath: %s", err)
|
return nil, "", fmt.Errorf("CachePath: %s", err)
|
||||||
}
|
}
|
||||||
} else if filepath.Ext(targetPath) == "" {
|
} else if filepath.Ext(targetPath) == "" {
|
||||||
// When an absolute path is provided
|
// When an absolute path is provided
|
||||||
|
@ -157,7 +157,14 @@ func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string
|
||||||
targetPath += ".iso"
|
targetPath += ".iso"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return u, targetPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string) (string, error) {
|
||||||
|
u, targetPath, err := s.UseSourceToFindCacheTarget(source)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
lockFile := targetPath + ".lock"
|
lockFile := targetPath + ".lock"
|
||||||
|
|
||||||
log.Printf("Acquiring lock for: %s (%s)", u.String(), lockFile)
|
log.Printf("Acquiring lock for: %s (%s)", u.String(), lockFile)
|
||||||
|
|
|
@ -58,6 +58,11 @@ EOF
|
||||||
boot_command = local.ubuntu_1604_boot_command
|
boot_command = local.ubuntu_1604_boot_command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source "source.vmware-vmx.base-ubuntu-amd64" {
|
||||||
|
name = "16.04"
|
||||||
|
source_path = "vmware_iso_ubuntu_1604_amd64/packer-base-ubuntu-amd64.vmx"
|
||||||
|
}
|
||||||
|
|
||||||
source "source.vmware-iso.base-ubuntu-amd64" {
|
source "source.vmware-iso.base-ubuntu-amd64" {
|
||||||
name = "18.04"
|
name = "18.04"
|
||||||
iso_url = local.iso_url_ubuntu_1804
|
iso_url = local.iso_url_ubuntu_1804
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
source "vmware-vmx" "base-ubuntu-amd64" {
|
||||||
|
headless = var.headless
|
||||||
|
boot_wait = "10s"
|
||||||
|
http_directory = local.http_directory
|
||||||
|
shutdown_command = "echo 'vagrant' | sudo -S shutdown -P now"
|
||||||
|
ssh_password = "vagrant"
|
||||||
|
ssh_port = 22
|
||||||
|
ssh_timeout = "10000s"
|
||||||
|
ssh_username = "vagrant"
|
||||||
|
tools_upload_flavor = "linux"
|
||||||
|
vmx_data = {
|
||||||
|
"cpuid.coresPerSocket" = "1"
|
||||||
|
"ethernet0.pciSlotNumber" = "32"
|
||||||
|
}
|
||||||
|
vmx_remove_ethernet_interfaces = true
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
variable "ubuntu_1604_version" {
|
variable "ubuntu_1604_version" {
|
||||||
default = "16.04.6"
|
default = "16.04.7"
|
||||||
}
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
|
|
|
@ -6,7 +6,7 @@ type Fixer interface {
|
||||||
// this fixer. It is used to generate a list of deprecated options that the
|
// this fixer. It is used to generate a list of deprecated options that the
|
||||||
// template parser checks against to warn users that they need to call
|
// template parser checks against to warn users that they need to call
|
||||||
// `packer fix` against their templates after upgrading.
|
// `packer fix` against their templates after upgrading.
|
||||||
DeprecatedOptions() []string
|
DeprecatedOptions() map[string][]string
|
||||||
|
|
||||||
// Fix takes a raw map structure input, potentially transforms it
|
// Fix takes a raw map structure input, potentially transforms it
|
||||||
// in some way, and returns the new, transformed structure. The
|
// in some way, and returns the new, transformed structure. The
|
||||||
|
@ -59,6 +59,7 @@ func init() {
|
||||||
"iso-checksum-type-and-url": new(FixerISOChecksumTypeAndURL),
|
"iso-checksum-type-and-url": new(FixerISOChecksumTypeAndURL),
|
||||||
"qemu-host-port": new(FixerQEMUHostPort),
|
"qemu-host-port": new(FixerQEMUHostPort),
|
||||||
"azure-exclude_from_latest": new(FixerAzureExcludeFromLatest),
|
"azure-exclude_from_latest": new(FixerAzureExcludeFromLatest),
|
||||||
|
"proxmox-type": new(FixerProxmoxType),
|
||||||
}
|
}
|
||||||
|
|
||||||
FixerOrder = []string{
|
FixerOrder = []string{
|
||||||
|
@ -95,5 +96,6 @@ func init() {
|
||||||
"iso-checksum-type-and-url",
|
"iso-checksum-type-and-url",
|
||||||
"qemu-host-port",
|
"qemu-host-port",
|
||||||
"azure-exclude_from_latest",
|
"azure-exclude_from_latest",
|
||||||
|
"proxmox-type",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,10 @@ import (
|
||||||
// with the clearer "ena_support". This disambiguates ena_support from sriov_support.
|
// with the clearer "ena_support". This disambiguates ena_support from sriov_support.
|
||||||
type FixerAmazonEnhancedNetworking struct{}
|
type FixerAmazonEnhancedNetworking struct{}
|
||||||
|
|
||||||
func (FixerAmazonEnhancedNetworking) DeprecatedOptions() []string {
|
func (FixerAmazonEnhancedNetworking) DeprecatedOptions() map[string][]string {
|
||||||
return []string{"enhanced_networking"}
|
return map[string][]string{
|
||||||
|
"*amazon*": []string{"enhanced_networking"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FixerAmazonEnhancedNetworking) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
func (FixerAmazonEnhancedNetworking) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
|
|
@ -12,8 +12,10 @@ import (
|
||||||
// true` with `"ssh_interface": "private_ip"`
|
// true` with `"ssh_interface": "private_ip"`
|
||||||
type FixerAmazonPrivateIP struct{}
|
type FixerAmazonPrivateIP struct{}
|
||||||
|
|
||||||
func (FixerAmazonPrivateIP) DeprecatedOptions() []string {
|
func (FixerAmazonPrivateIP) DeprecatedOptions() map[string][]string {
|
||||||
return []string{"ssh_private_ip"}
|
return map[string][]string{
|
||||||
|
"*amazon*": []string{"ssh_private_ip"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FixerAmazonPrivateIP) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
func (FixerAmazonPrivateIP) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
|
|
@ -10,8 +10,10 @@ import (
|
||||||
// template in a Amazon builder
|
// template in a Amazon builder
|
||||||
type FixerAmazonShutdownBehavior struct{}
|
type FixerAmazonShutdownBehavior struct{}
|
||||||
|
|
||||||
func (FixerAmazonShutdownBehavior) DeprecatedOptions() []string {
|
func (FixerAmazonShutdownBehavior) DeprecatedOptions() map[string][]string {
|
||||||
return []string{"shutdown_behaviour"}
|
return map[string][]string{
|
||||||
|
"*amazon*": []string{"shutdown_behaviour"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FixerAmazonShutdownBehavior) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
func (FixerAmazonShutdownBehavior) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
|
|
@ -8,8 +8,10 @@ import (
|
||||||
// from Amazon builder templates
|
// from Amazon builder templates
|
||||||
type FixerAmazonSpotPriceProductDeprecation struct{}
|
type FixerAmazonSpotPriceProductDeprecation struct{}
|
||||||
|
|
||||||
func (FixerAmazonSpotPriceProductDeprecation) DeprecatedOptions() []string {
|
func (FixerAmazonSpotPriceProductDeprecation) DeprecatedOptions() map[string][]string {
|
||||||
return []string{"spot_price_auto_product"}
|
return map[string][]string{
|
||||||
|
"*amazon*": []string{"spot_price_auto_product"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FixerAmazonSpotPriceProductDeprecation) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
func (FixerAmazonSpotPriceProductDeprecation) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
|
|
@ -8,8 +8,10 @@ import (
|
||||||
|
|
||||||
type FixerAmazonTemporarySecurityCIDRs struct{}
|
type FixerAmazonTemporarySecurityCIDRs struct{}
|
||||||
|
|
||||||
func (FixerAmazonTemporarySecurityCIDRs) DeprecatedOptions() []string {
|
func (FixerAmazonTemporarySecurityCIDRs) DeprecatedOptions() map[string][]string {
|
||||||
return []string{"temporary_security_group_source_cidr"}
|
return map[string][]string{
|
||||||
|
"*amazon*": []string{"temporary_security_group_source_cidr"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FixerAmazonTemporarySecurityCIDRs) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
func (FixerAmazonTemporarySecurityCIDRs) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
|
|
@ -10,8 +10,10 @@ import (
|
||||||
// template in an Azure builder
|
// template in an Azure builder
|
||||||
type FixerAzureExcludeFromLatest struct{}
|
type FixerAzureExcludeFromLatest struct{}
|
||||||
|
|
||||||
func (FixerAzureExcludeFromLatest) DeprecatedOptions() []string {
|
func (FixerAzureExcludeFromLatest) DeprecatedOptions() map[string][]string {
|
||||||
return []string{"exlude_from_latest"}
|
return map[string][]string{
|
||||||
|
"Azure*": []string{"exlude_from_latest"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FixerAzureExcludeFromLatest) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
func (FixerAzureExcludeFromLatest) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue