Merge remote-tracking branch 'origin/master' into azr_selectable_temp_keygen_type_gcp

This commit is contained in:
Adrien Delorme 2020-10-26 15:47:29 +01:00
commit f5e037e8b4
452 changed files with 168232 additions and 72858 deletions

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ Thumbs.db
/packer.exe /packer.exe
.project .project
cache cache
/.vscode/

View File

@ -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]

View File

@ -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

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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,

View File

@ -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

View File

@ -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...)

View File

@ -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{

View File

@ -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)

View File

@ -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",

View File

@ -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...)

View File

@ -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)
}
}

View File

@ -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{

View File

@ -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...)

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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")
}
}

View File

@ -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...)
} }

View File

@ -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
} }

View File

@ -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)

View File

@ -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...)

View File

@ -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...)

View File

@ -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.

View File

@ -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
}

View File

@ -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")

View 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 {

View File

@ -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{

View File

@ -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{

View File

@ -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...)

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -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 {

View File

@ -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")
}

View File

@ -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 {

View File

@ -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
} }

View File

@ -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())
}
}
}

View File

@ -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 {

View File

@ -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"),

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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",
}
}

View File

@ -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) {
}

View File

@ -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)
}
})
}
}

View File

@ -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)

View File

@ -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
} }

View File

@ -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)

View File

@ -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{

View File

@ -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)

View File

@ -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{

View File

@ -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{

View File

@ -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...)

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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)...)
} }

View File

@ -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) {
}

View File

@ -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)
}
}
}
}

View File

@ -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
} }

View File

@ -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)

View File

@ -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{}

View File

@ -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,
}, },

View File

@ -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)...)
} }

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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),

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -1,5 +1,5 @@
variable "ubuntu_1604_version" { variable "ubuntu_1604_version" {
default = "16.04.6" default = "16.04.7"
} }
locals { locals {

View File

@ -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",
} }
} }

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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