From f24bbefe5d5a64f8121c62759e4efa95c476fdff Mon Sep 17 00:00:00 2001 From: Itxaka Date: Fri, 2 Jul 2021 17:42:01 +0200 Subject: [PATCH] Add snapshot import to amazon-import This patch adds the possibility of importing and image to amazon by using the import snapshot api, which has lower requirements than the image import. It resuses the current post-process method but diverges once we need to import the image. The image import is untouched, but the new snapshot import does something similar to image. It uploads the artifact to s3, then imports it as snapshot and finally registers the snapshot as a new ami. Signed-off-by: Itxaka --- builder/common/state.go | 49 +++ post-processor/import/post-processor.go | 339 +++++++++++++----- .../import/post-processor.hcl2spec.go | 82 +++-- 3 files changed, 339 insertions(+), 131 deletions(-) diff --git a/builder/common/state.go b/builder/common/state.go index 48b5f0e05..bc57b982a 100644 --- a/builder/common/state.go +++ b/builder/common/state.go @@ -206,6 +206,18 @@ func (w *AWSPollingConfig) WaitUntilImageImported(ctx aws.Context, conn *ec2.EC2 return err } +func (w *AWSPollingConfig) WaitUntilSnapshotImported(ctx aws.Context, conn *ec2.EC2, taskID string) error { + importInput := ec2.DescribeImportSnapshotTasksInput{ + ImportTaskIds: []*string{&taskID}, + } + + err := WaitForSnapshotToBeImported(conn, + ctx, + &importInput, + w.getWaiterOptions()...) + return err +} + // Custom waiters using AWS's request.Waiter func WaitForVolumeToBeAttached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeVolumesInput, opts ...request.WaiterOption) error { @@ -307,6 +319,43 @@ func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeIm return w.WaitWithContext(ctx) } +func WaitForSnapshotToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportSnapshotTasksInput, opts ...request.WaiterOption) error { + w := request.Waiter{ + Name: "DescribeSnapshot", + MaxAttempts: 720, + Delay: request.ConstantWaiterDelay(5 * time.Second), + Acceptors: []request.WaiterAcceptor{ + { + State: request.SuccessWaiterState, + Matcher: request.PathAllWaiterMatch, + Argument: "ImportSnapshotTasks[].SnapshotTaskDetail.Status", + Expected: "completed", + }, + { + State: request.FailureWaiterState, + Matcher: request.PathAnyWaiterMatch, + Argument: "ImportSnapshotTasks[].SnapshotTaskDetail.Status", + Expected: "deleted", + }, + }, + Logger: c.Config.Logger, + NewRequest: func(opts []request.Option) (*request.Request, error) { + var inCpy *ec2.DescribeImportSnapshotTasksInput + if input != nil { + tmp := *input + inCpy = &tmp + } + req, _ := c.DescribeImportSnapshotTasksRequest(inCpy) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return req, nil + }, + } + w.ApplyOptions(opts...) + + return w.WaitWithContext(ctx) +} + // This helper function uses the environment variables AWS_TIMEOUT_SECONDS and // AWS_POLL_DELAY_SECONDS to generate waiter options that can be passed into any // request.Waiter function. These options will control how many times the waiter diff --git a/post-processor/import/post-processor.go b/post-processor/import/post-processor.go index 20ea991d5..080b5fd97 100644 --- a/post-processor/import/post-processor.go +++ b/post-processor/import/post-processor.go @@ -31,21 +31,23 @@ type Config struct { awscommon.AccessConfig `mapstructure:",squash"` // Variables specific to this post processor - S3Bucket string `mapstructure:"s3_bucket_name"` - S3Key string `mapstructure:"s3_key_name"` - S3Encryption string `mapstructure:"s3_encryption"` - S3EncryptionKey string `mapstructure:"s3_encryption_key"` - SkipClean bool `mapstructure:"skip_clean"` - Tags map[string]string `mapstructure:"tags"` - Name string `mapstructure:"ami_name"` - Description string `mapstructure:"ami_description"` - Users []string `mapstructure:"ami_users"` - Groups []string `mapstructure:"ami_groups"` - Encrypt bool `mapstructure:"ami_encrypt"` - KMSKey string `mapstructure:"ami_kms_key"` - LicenseType string `mapstructure:"license_type"` - RoleName string `mapstructure:"role_name"` - Format string `mapstructure:"format"` + S3Bucket string `mapstructure:"s3_bucket_name"` + S3Key string `mapstructure:"s3_key_name"` + S3Encryption string `mapstructure:"s3_encryption"` + S3EncryptionKey string `mapstructure:"s3_encryption_key"` + SkipClean bool `mapstructure:"skip_clean"` + Tags map[string]string `mapstructure:"tags"` + Name string `mapstructure:"ami_name"` + Description string `mapstructure:"ami_description"` + Users []string `mapstructure:"ami_users"` + Groups []string `mapstructure:"ami_groups"` + Encrypt bool `mapstructure:"ami_encrypt"` + KMSKey string `mapstructure:"ami_kms_key"` + LicenseType string `mapstructure:"license_type"` + RoleName string `mapstructure:"role_name"` + Format string `mapstructure:"format"` + FromSnapshot bool `mapstructure:"from_snapshot"` + FromSnapshotDeviceName string `mapstructure:"from_snapshot_device_name"` ctx interpolate.Context } @@ -81,6 +83,10 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { p.config.S3Key = "packer-import-{{timestamp}}." + p.config.Format } + if p.config.FromSnapshotDeviceName == "" { + p.config.FromSnapshotDeviceName = "/dev/sda" + } + errs := new(packersdk.MultiError) // Check and render s3_key_name @@ -111,6 +117,17 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { errs, fmt.Errorf("invalid format '%s'. Only 'ova', 'raw', 'vhd', 'vhdx', or 'vmdk' are allowed", p.config.Format)) } + // If importing to snapshot, only 3 formats are allowed + if p.config.FromSnapshot { + switch p.config.Format { + case "vhd", "vmdk", "raw": + default: + errs = packersdk.MultiErrorAppend( + errs, fmt.Errorf( + "invalid format '%s' for snapshot import. Only 'raw', 'vhd', or 'vmdk' are allowed", p.config.Format)) + } + } + if p.config.S3Encryption != "" && p.config.S3Encryption != "AES256" && p.config.S3Encryption != "aws:kms" { errs = packersdk.MultiErrorAppend( errs, fmt.Errorf("invalid s3 encryption format '%s'. Only 'AES256' and 'aws:kms' are allowed", p.config.S3Encryption)) @@ -210,90 +227,22 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa // Call EC2 image import process log.Printf("Calling EC2 to import from s3://%s/%s", p.config.S3Bucket, p.config.S3Key) + // Split into snapshot or image import + var createdami string ec2conn := ec2.New(session) - params := &ec2.ImportImageInput{ - Encrypted: &p.config.Encrypt, - DiskContainers: []*ec2.ImageDiskContainer{ - { - Format: &p.config.Format, - UserBucket: &ec2.UserBucket{ - S3Bucket: &p.config.S3Bucket, - S3Key: &p.config.S3Key, - }, - }, - }, - } - - if p.config.Encrypt && p.config.KMSKey != "" { - params.KmsKeyId = &p.config.KMSKey - } - - if p.config.RoleName != "" { - params.SetRoleName(p.config.RoleName) - } - if p.config.LicenseType != "" { - ui.Message(fmt.Sprintf("Setting license type to '%s'", p.config.LicenseType)) - params.LicenseType = &p.config.LicenseType - } - - var import_start *ec2.ImportImageOutput - err = retry.Config{ - Tries: 11, - RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear, - }.Run(ctx, func(ctx context.Context) error { - import_start, err = ec2conn.ImportImage(params) - return err - }) - - if err != nil { - return nil, false, false, fmt.Errorf("Failed to start import from s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err) + if p.config.FromSnapshot { + createdami, err = p.importSnapshot(ui, ctx, ec2conn) + } else { + createdami, err = p.importImage(ui, ctx, ec2conn) } - ui.Message(fmt.Sprintf("Started import of s3://%s/%s, task id %s", p.config.S3Bucket, p.config.S3Key, *import_start.ImportTaskId)) - - // Wait for import process to complete, this takes a while - ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *import_start.ImportTaskId)) - err = p.config.PollingConfig.WaitUntilImageImported(aws.BackgroundContext(), ec2conn, *import_start.ImportTaskId) if err != nil { - - // Retrieve the status message - import_result, err2 := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ - ImportTaskIds: []*string{ - import_start.ImportTaskId, - }, - }) - - statusMessage := "Error retrieving status message" - - if err2 == nil { - statusMessage = *import_result.ImportImageTasks[0].StatusMessage - } - return nil, false, false, fmt.Errorf("Import task %s failed with status message: %s, error: %s", *import_start.ImportTaskId, statusMessage, err) - } - - // Retrieve what the outcome was for the import task - import_result, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ - ImportTaskIds: []*string{ - import_start.ImportTaskId, - }, - }) - - if err != nil { - return nil, false, false, fmt.Errorf("Failed to find import task %s: %s", *import_start.ImportTaskId, err) - } - // Check it was actually completed - if *import_result.ImportImageTasks[0].Status != "completed" { - // The most useful error message is from the job itself - return nil, false, false, fmt.Errorf("Import task %s failed: %s", *import_start.ImportTaskId, *import_result.ImportImageTasks[0].StatusMessage) + return nil, false, false, err } - ui.Message(fmt.Sprintf("Import task %s complete", *import_start.ImportTaskId)) - - // Pull AMI ID out of the completed job - createdami := *import_result.ImportImageTasks[0].ImageId - - if p.config.Name != "" { + // Dont rename on snapshot as we set the name on creation + if p.config.Name != "" && !p.config.FromSnapshot { ui.Message(fmt.Sprintf("Starting rename of AMI (%s)", createdami)) @@ -466,3 +415,209 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa return artifact, false, false, nil } + +func (p *PostProcessor) importSnapshot(ui packersdk.Ui, ctx context.Context, ec2conn *ec2.EC2) (string, error) { + var err error + var err2 error + var importResult *ec2.DescribeImportSnapshotTasksOutput + + params := &ec2.ImportSnapshotInput{ + Encrypted: &p.config.Encrypt, + DiskContainer: &ec2.SnapshotDiskContainer{ + Format: &p.config.Format, + UserBucket: &ec2.UserBucket{ + S3Bucket: &p.config.S3Bucket, + S3Key: &p.config.S3Key, + }, + }, + } + + if p.config.Encrypt && p.config.KMSKey != "" { + params.KmsKeyId = &p.config.KMSKey + } + + if p.config.RoleName != "" { + params.SetRoleName(p.config.RoleName) + } + + var importStart *ec2.ImportSnapshotOutput + err = retry.Config{ + Tries: 11, + RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear, + }.Run(ctx, func(ctx context.Context) error { + importStart, err = ec2conn.ImportSnapshot(params) + return err + }) + + if err != nil { + return "", fmt.Errorf("Failed to start import from s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err) + } + + importTaskId := importStart.ImportTaskId + + ui.Message(fmt.Sprintf("Started import of s3://%s/%s, task id %s", p.config.S3Bucket, p.config.S3Key, *importTaskId)) + + // Wait for import process to complete, this takes a while + ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *importTaskId)) + + err = p.config.PollingConfig.WaitUntilSnapshotImported(aws.BackgroundContext(), ec2conn, *importTaskId) + if err != nil { + // Retrieve the status message + importResult, err2 = ec2conn.DescribeImportSnapshotTasks(&ec2.DescribeImportSnapshotTasksInput{ + ImportTaskIds: []*string{ + importTaskId, + }, + }) + + statusMessage := "Error retrieving status message" + + if err2 == nil { + statusMessage = *importResult.ImportSnapshotTasks[0].SnapshotTaskDetail.StatusMessage + } + return "", fmt.Errorf("Import task %s failed with status message: %s, error: %s", *importTaskId, statusMessage, err) + } + + // Retrieve what the outcome was for the import task + importResult, err = ec2conn.DescribeImportSnapshotTasks(&ec2.DescribeImportSnapshotTasksInput{ + ImportTaskIds: []*string{ + importTaskId, + }, + }) + + if err != nil { + return "", fmt.Errorf("Failed to find import task %s: %s", *importTaskId, err) + } + + snapshotId := importResult.ImportSnapshotTasks[0].SnapshotTaskDetail.SnapshotId + + // Check it was actually completed + if *importResult.ImportSnapshotTasks[0].SnapshotTaskDetail.Status != "completed" { + // The most useful error message is from the job itself + return "", fmt.Errorf("Import task %s failed: %s", *importTaskId, *importResult.ImportSnapshotTasks[0].SnapshotTaskDetail.StatusMessage) + } + + ui.Message(fmt.Sprintf("Import task %s complete", *importTaskId)) + + ebsDevice := ec2.EbsBlockDevice{ + SnapshotId: snapshotId, + } + + blockDevice := ec2.BlockDeviceMapping{ + DeviceName: &p.config.FromSnapshotDeviceName, + Ebs: &ebsDevice, + } + + var imageName string + + // Unfortunately when importing from snapshot we need to give a name to the ami so either get the name + // from the config or set a name based on the source s3 object + if p.config.Name != "" { + imageName = p.config.Name + } else { + imageName = fmt.Sprintf("packer-import-from-%s", p.config.S3Key) + } + + registerImageOutput, err := ec2conn.RegisterImage(&ec2.RegisterImageInput{ + BlockDeviceMappings: []*ec2.BlockDeviceMapping{ + &blockDevice, + }, + RootDeviceName: &p.config.FromSnapshotDeviceName, + Name: &imageName, + }) + + if err != nil { + return "", fmt.Errorf("Failed to register snapshot %s as AMI: %s", snapshotId, err) + } + + // Pull AMI ID out of the completed job + createdAmi := *registerImageOutput.ImageId + return createdAmi, err +} + +func (p *PostProcessor) importImage(ui packersdk.Ui, ctx context.Context, ec2conn *ec2.EC2) (string, error) { + var err error + var value string + params := &ec2.ImportImageInput{ + Encrypted: &p.config.Encrypt, + DiskContainers: []*ec2.ImageDiskContainer{ + { + Format: &p.config.Format, + UserBucket: &ec2.UserBucket{ + S3Bucket: &p.config.S3Bucket, + S3Key: &p.config.S3Key, + }, + }, + }, + } + + if p.config.Encrypt && p.config.KMSKey != "" { + params.KmsKeyId = &p.config.KMSKey + } + + if p.config.RoleName != "" { + params.SetRoleName(p.config.RoleName) + } + + if p.config.LicenseType != "" { + ui.Message(fmt.Sprintf("Setting license type to '%s'", p.config.LicenseType)) + params.LicenseType = &p.config.LicenseType + } + + var importStart *ec2.ImportImageOutput + err = retry.Config{ + Tries: 11, + RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear, + }.Run(ctx, func(ctx context.Context) error { + importStart, err = ec2conn.ImportImage(params) + return err + }) + + if err != nil { + return value, fmt.Errorf("Failed to start import from s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err) + } + + importTaskId := importStart.ImportTaskId + + ui.Message(fmt.Sprintf("Started import of s3://%s/%s, task id %s", p.config.S3Bucket, p.config.S3Key, *importTaskId)) + + // Wait for import process to complete, this takes a while + ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *importTaskId)) + err = p.config.PollingConfig.WaitUntilImageImported(aws.BackgroundContext(), ec2conn, *importTaskId) + if err != nil { + // Retrieve the status message + importResult, err2 := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ + ImportTaskIds: []*string{ + importTaskId, + }, + }) + + statusMessage := "Error retrieving status message" + + if err2 == nil { + statusMessage = *importResult.ImportImageTasks[0].StatusMessage + } + return value, fmt.Errorf("Import task %s failed with status message: %s, error: %s", importTaskId, statusMessage, err) + } + + // Retrieve what the outcome was for the import task + importResult, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ + ImportTaskIds: []*string{ + importTaskId, + }, + }) + + if err != nil { + return value, fmt.Errorf("Failed to find import task %s: %s", *importTaskId, err) + } + // Check it was actually completed + if *importResult.ImportImageTasks[0].Status != "completed" { + // The most useful error message is from the job itself + return value, fmt.Errorf("Import task %s failed: %s", *importTaskId, *importResult.ImportImageTasks[0].StatusMessage) + } + + ui.Message(fmt.Sprintf("Import task %s complete", *importTaskId)) + + // Pull AMI ID out of the completed job + createdAmi := *importResult.ImportImageTasks[0].ImageId + return createdAmi, err +} diff --git a/post-processor/import/post-processor.hcl2spec.go b/post-processor/import/post-processor.hcl2spec.go index d0bf007a4..80fe8214c 100644 --- a/post-processor/import/post-processor.hcl2spec.go +++ b/post-processor/import/post-processor.hcl2spec.go @@ -11,45 +11,47 @@ import ( // FlatConfig is an auto-generated flat version of Config. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatConfig struct { - PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` - PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` - PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` - 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"` - AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"` - AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"` - CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"` - CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"` - DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"` - InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"` - MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"` - MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code" hcl:"mfa_code"` - ProfileName *string `mapstructure:"profile" required:"false" cty:"profile" hcl:"profile"` - RawRegion *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"` - SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"` - SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"` - SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"` - Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"` - VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"` - PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"` - S3Bucket *string `mapstructure:"s3_bucket_name" cty:"s3_bucket_name" hcl:"s3_bucket_name"` - S3Key *string `mapstructure:"s3_key_name" cty:"s3_key_name" hcl:"s3_key_name"` - S3Encryption *string `mapstructure:"s3_encryption" cty:"s3_encryption" hcl:"s3_encryption"` - S3EncryptionKey *string `mapstructure:"s3_encryption_key" cty:"s3_encryption_key" hcl:"s3_encryption_key"` - SkipClean *bool `mapstructure:"skip_clean" cty:"skip_clean" hcl:"skip_clean"` - Tags map[string]string `mapstructure:"tags" cty:"tags" hcl:"tags"` - Name *string `mapstructure:"ami_name" cty:"ami_name" hcl:"ami_name"` - Description *string `mapstructure:"ami_description" cty:"ami_description" hcl:"ami_description"` - Users []string `mapstructure:"ami_users" cty:"ami_users" hcl:"ami_users"` - Groups []string `mapstructure:"ami_groups" cty:"ami_groups" hcl:"ami_groups"` - Encrypt *bool `mapstructure:"ami_encrypt" cty:"ami_encrypt" hcl:"ami_encrypt"` - KMSKey *string `mapstructure:"ami_kms_key" cty:"ami_kms_key" hcl:"ami_kms_key"` - LicenseType *string `mapstructure:"license_type" cty:"license_type" hcl:"license_type"` - RoleName *string `mapstructure:"role_name" cty:"role_name" hcl:"role_name"` - Format *string `mapstructure:"format" cty:"format" hcl:"format"` + 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"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + 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"` + AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"` + AssumeRole *common.FlatAssumeRoleConfig `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"` + CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2" hcl:"custom_endpoint_ec2"` + CredsFilename *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file" hcl:"shared_credentials_file"` + DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages" hcl:"decode_authorization_messages"` + InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"` + MaxRetries *int `mapstructure:"max_retries" required:"false" cty:"max_retries" hcl:"max_retries"` + MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code" hcl:"mfa_code"` + ProfileName *string `mapstructure:"profile" required:"false" cty:"profile" hcl:"profile"` + RawRegion *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"` + SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"` + SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check" hcl:"skip_metadata_api_check"` + SkipCredsValidation *bool `mapstructure:"skip_credential_validation" cty:"skip_credential_validation" hcl:"skip_credential_validation"` + Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"` + VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine" hcl:"vault_aws_engine"` + PollingConfig *common.FlatAWSPollingConfig `mapstructure:"aws_polling" required:"false" cty:"aws_polling" hcl:"aws_polling"` + S3Bucket *string `mapstructure:"s3_bucket_name" cty:"s3_bucket_name" hcl:"s3_bucket_name"` + S3Key *string `mapstructure:"s3_key_name" cty:"s3_key_name" hcl:"s3_key_name"` + S3Encryption *string `mapstructure:"s3_encryption" cty:"s3_encryption" hcl:"s3_encryption"` + S3EncryptionKey *string `mapstructure:"s3_encryption_key" cty:"s3_encryption_key" hcl:"s3_encryption_key"` + SkipClean *bool `mapstructure:"skip_clean" cty:"skip_clean" hcl:"skip_clean"` + Tags map[string]string `mapstructure:"tags" cty:"tags" hcl:"tags"` + Name *string `mapstructure:"ami_name" cty:"ami_name" hcl:"ami_name"` + Description *string `mapstructure:"ami_description" cty:"ami_description" hcl:"ami_description"` + Users []string `mapstructure:"ami_users" cty:"ami_users" hcl:"ami_users"` + Groups []string `mapstructure:"ami_groups" cty:"ami_groups" hcl:"ami_groups"` + Encrypt *bool `mapstructure:"ami_encrypt" cty:"ami_encrypt" hcl:"ami_encrypt"` + KMSKey *string `mapstructure:"ami_kms_key" cty:"ami_kms_key" hcl:"ami_kms_key"` + LicenseType *string `mapstructure:"license_type" cty:"license_type" hcl:"license_type"` + RoleName *string `mapstructure:"role_name" cty:"role_name" hcl:"role_name"` + Format *string `mapstructure:"format" cty:"format" hcl:"format"` + FromSnapshot *bool `mapstructure:"from_snapshot" cty:"from_snapshot" hcl:"from_snapshot"` + FromSnapshotDeviceName *string `mapstructure:"from_snapshot_device_name" cty:"from_snapshot_device_name" hcl:"from_snapshot_device_name"` } // FlatMapstructure returns a new FlatConfig. @@ -103,6 +105,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "license_type": &hcldec.AttrSpec{Name: "license_type", Type: cty.String, Required: false}, "role_name": &hcldec.AttrSpec{Name: "role_name", Type: cty.String, Required: false}, "format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false}, + "from_snapshot": &hcldec.AttrSpec{Name: "from_snapshot", Type: cty.Bool, Required: false}, + "from_snapshot_device_name": &hcldec.AttrSpec{Name: "from_snapshot_device_name", Type: cty.String, Required: false}, } return s }