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