Merge branch 'master' of github.com:hashicorp/packer into google-impersonation

This commit is contained in:
upodroid 2020-10-01 23:19:15 +01:00
commit b1c74f9df0
184 changed files with 23281 additions and 2579 deletions

View File

@ -7,11 +7,11 @@ version: 2.1
executors:
golang:
docker:
- image: circleci/golang:1.13
- image: circleci/golang:1.15
resource_class: medium+
darwin:
macos:
xcode: "9.0"
xcode: "12.0.0"
commands:
install-go-run-tests-unix:
@ -67,7 +67,7 @@ jobs:
steps:
- install-go-run-tests-unix:
GOOS: darwin
GOVERSION: "1.13"
GOVERSION: "1.15"
- codecov/upload:
file: coverage.txt
test-windows:
@ -76,7 +76,7 @@ jobs:
shell: bash.exe
steps:
- install-go-run-tests-windows:
GOVERSION: "1.13"
GOVERSION: "1.15"
- codecov/upload:
file: coverage.txt
check-lint:

View File

@ -13,5 +13,6 @@ coverage:
project: off
patch: off
ignore: # ignore hcl2spec generated code for coverage
- "**/*.hcl2spec.go"
ignore: # ignore hcl2spec generated code for coverage and mocks
- "**/*.hcl2spec.go"
- "**/*_mock.go"

View File

@ -1,13 +1,37 @@
## 1.6.3 (Upcoming)
## 1.6.5 (Upcoming)
## 1.6.4 (September 30, 2020)
### BUG FIXES:
* builder/amazon: Fix authentication issue when using instance profiles or
assumed roles for loading session-derived credentials. [GH-10007]
* builder/azure: Fix crash when using `azure_tag` or `azure_tags` configuration
options. [GH-10014]
* builder/qemu: Ensure `qemu_img_args` are honored during the disk convert
step. [GH-10001]
## 1.6.3 (September 25, 2020)
### IMPROVEMENTS:
* builder/amazon: Add `pause_before_ssm` option to pause for some time before
establishing a Session Manager session; defaults to 10s. [GH-9988]
* builder/amazon: Implement assume_role option that matches Terraform behavior.
[GH-9981]
* builder/azure: Support publishing to a Shared Image Gallery with a different
subscription id [GH-9875]
* builder/openstack: Add `external_source_image_url` and
`external_source_image_format` to support building images from external
source URLs. [GH-9992]
* builder/openstack: Include API requests and responses as part of the debug
log output. [GH-9972]
* builder/oracle-oci: Add `create_vnic_details` option for launch details.
[GH-9856]
* builder/oracle-oci: Allow freeform and defined tags to be added instance.
* builder/oracle-oci: Allow freeform and defined tags to be added to an instance.
[GH-9802]
* builder/proxmox: Add ability to specify interfaces for http_directroy and VM.
* builder/proxmox: Add `io_thread` option for supporting io threads when using
a `virtio-scsi-single` controller with a `scsi` or `virtio` disk type.
[GH-9969]
* builder/proxmox: Add ability to specify interfaces for http_directory and VM.
[GH-9874]
* builder/proxmox: Allow the mounting of multiple ISOs via the `cd_drive`
option. [GH-9653]
@ -16,17 +40,22 @@
to qemu-img [GH-9956]
* builder/qemu: Add `skip_resize_disk` option to skip the resizing of QCOW2
images. [GH-9896] [GH-9860]
* builder/qemu: Skip qemu-img convert on MacOS to prevent the creation
of corrupt images [QEMU
#1776920](https://bugs.launchpad.net/qemu/+bug/1776920)[GH-9949]
* builder/qemu: Skip qemu-img convert on MacOS to prevent the creation of
corrupt images [QEMU
#1776920](https://bugs.launchpad.net/qemu/+bug/1776920) [GH-9949]
* builder/scaleway: Change default boottype to local. [GH-9853]
* builder/scaleway: Update scaleway to use non-deprecated sdk. [GH-9902]
* builder/vmware: Add `vnc_over_websocket` to allow the sending of a
`boot_command` to hosts running ESXi 6.7 and above. [GH-9938]
* builder/vmware: Allow user to set vmware tools source path. [GH-9983]
* builder/vsphere-clone: Add ability to set `mac_address` [GH-9930]
* builder/vsphere-clone: Add floppy_files, cd_files, and iso_paths options.
[GH-9963]
* builder/vsphere-iso: Add NVMe controller support. [GH-9880]
* builder/vsphere: Look for a default resource pool when root resource pool is
not found. [GH-9809]
* core: Add support for running cygwin/msys2 based cd/iso creation tool
[GH-9954]
* core: New `cd_files` option to mount iso for modern OSes which don't support
floppies. [GH-9796] [GH-9919] [GH-9928] [GH-9932] [GH-9941]
* HCL2: When the type of a variable is not known evaluate setting as a literal
@ -41,6 +70,9 @@
error was creating multiple spot instances. [GH-9946]
* builder/amazon-ebssurrogate: Fix issue where builder defaults to AWS managed
key even when custom `kms_key_id` is set. [GH-9959]
* builder/amazon: Update ssm_driver log polling logic to prevent infinite loops
when SSM driver is terminated outside of Packer. [GH-9991]
* builder/azure: Fix crash when using HCL2 configs. [GH-9984] [GH-9985]
* builder/qemu: Fix hardcoded lowerbound causing negative ports [GH-9905]
* builder/qemu: Skip compaction when backing file is used. [GH-9918]
* builder/scaleway: Add pre validate step to prevent the creation of multiple
@ -50,7 +82,9 @@
* builder/vsphere: Fix overly strict iso_path validation regex. [GH-9855]
* command/console: Prevent failure when there are unknown vars. [GH-9864]
* command/inspect: Allow unset variables in HCL2 and JSON. [GH-9832]
* core: use $APPDATA over $HOME on Windows hosts when determining homedir.
* core: Prevent the UI progressbar from hanging and crashing when there is no
TTY available. [GH-9974]
* core: Use $APPDATA over $HOME on Windows hosts when determining homedir.
[GH-9830]
* post-processor/digitalocean-import: Fix crash caused by empty artifact.Files
slice. [GH-9857]

View File

@ -41,7 +41,9 @@ type FlatConfig struct {
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users" hcl:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups" hcl:"snapshot_groups"`
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"`
@ -50,6 +52,7 @@ type FlatConfig struct {
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"`
@ -117,7 +120,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@ -126,6 +131,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},

View File

@ -1,5 +1,5 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions,AssumeRoleConfig
package common
@ -11,15 +11,67 @@ import (
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
awsbase "github.com/hashicorp/aws-sdk-go-base"
cleanhttp "github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer/template/interpolate"
vaultapi "github.com/hashicorp/vault/api"
)
// AssumeRoleConfig lets users set configuration options for assuming a special
// role when executing Packer.
//
// Usage example:
//
// HCL config example:
//
// ```HCL
// source "example" "amazon-ebs"{
// assume_role {
// role_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
// session_name = "SESSION_NAME"
// external_id = "EXTERNAL_ID"
// }
// }
// ```
//
// JSON config example:
//
// ```json
// builder{
// "type": "amazon-ebs",
// "assume_role": {
// "role_arn" : "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME",
// "session_name": "SESSION_NAME",
// "external_id" : "EXTERNAL_ID"
// }
// }
// ```
type AssumeRoleConfig struct {
// Amazon Resource Name (ARN) of the IAM Role to assume.
AssumeRoleARN string `mapstructure:"role_arn" required:"false"`
// Number of seconds to restrict the assume role session duration.
AssumeRoleDurationSeconds int `mapstructure:"duration_seconds" required:"false"`
// The external ID to use when assuming the role. If omitted, no external
// ID is passed to the AssumeRole call.
AssumeRoleExternalID string `mapstructure:"external_id" required:"false"`
// IAM Policy JSON describing further restricting permissions for the IAM
// Role being assumed.
AssumeRolePolicy string `mapstructure:"policy" required:"false"`
// Set of Amazon Resource Names (ARNs) of IAM Policies describing further
// restricting permissions for the IAM Role being
AssumeRolePolicyARNs []string `mapstructure:"policy_arns" required:"false"`
// Session name to use when assuming the role.
AssumeRoleSessionName string `mapstructure:"session_name" required:"false"`
// Map of assume role session tags.
AssumeRoleTags map[string]string `mapstructure:"tags" required:"false"`
// Set of assume role session tag keys to pass to any subsequent sessions.
AssumeRoleTransitiveTagKeys []string `mapstructure:"transitive_tag_keys" required:"false"`
}
type VaultAWSEngineOptions struct {
Name string `mapstructure:"name"`
RoleARN string `mapstructure:"role_arn"`
@ -48,10 +100,17 @@ type AccessConfig struct {
// is not required if you are using `use_vault_aws_engine` for
// authentication instead.
AccessKey string `mapstructure:"access_key" required:"true"`
// If provided with a role ARN, Packer will attempt to assume this role
// using the supplied credentials. See
// [AssumeRoleConfig](#assume-role-configuration) below for more
// details on all of the options available, and for a usage example.
AssumeRole AssumeRoleConfig `mapstructure:"assume_role" required:"false"`
// This option is useful if you use a cloud
// provider whose API is compatible with aws EC2. Specify another endpoint
// like this https://ec2.custom.endpoint.com.
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2" required:"false"`
// Path to a credentials file to load credentials from
CredsFilename string `mapstructure:"shared_credentials_file" required:"false"`
// Enable automatic decoding of any encoded authorization (error) messages
// using the `sts:DecodeAuthorizationMessage` API. Note: requires that the
// effective user/role have permissions to `sts:DecodeAuthorizationMessage`
@ -86,6 +145,8 @@ type AccessConfig struct {
// validation of the ami_regions configuration option. Default false.
SkipValidation bool `mapstructure:"skip_region_validation" required:"false"`
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
// Set to true if you want to skip validating AWS credentials before runtime.
SkipCredsValidation bool `mapstructure:"skip_credential_validation"`
// The access token to use. This is different from the
// access key and secret key. If you're not sure what this is, then you
// probably don't need it. This will also be read from the AWS_SESSION_TOKEN
@ -152,16 +213,13 @@ func (c *AccessConfig) Session() (*session.Session, error) {
return c.session, nil
}
// Create new AWS config
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
if c.MaxRetries > 0 {
config = config.WithMaxRetries(c.MaxRetries)
}
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
config.WithCredentials(staticCreds)
}
// Set AWS config defaults.
if c.RawRegion != "" {
config = config.WithRegion(c.RawRegion)
}
@ -179,6 +237,16 @@ func (c *AccessConfig) Session() (*session.Session, error) {
}
transport.Proxy = http.ProxyFromEnvironment
// Figure out which possible credential providers are valid; test that we
// can get credentials via the selected providers, and set the providers in
// the config.
creds, err := c.GetCredentials(config)
if err != nil {
return nil, err
}
config.WithCredentials(creds)
// Create session options based on our AWS config
opts := session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: *config,
@ -204,9 +272,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
cp, err := c.session.Config.Credentials.Get()
if IsAWSErr(err, "NoCredentialProviders", "") {
return nil, fmt.Errorf("No valid credential sources found for AWS Builder. " +
"Please see https://www.packer.io/docs/builders/amazon#specifying-amazon-credentials " +
"for more information on providing credentials for the AWS Builder.")
return nil, c.NewNoValidCredentialSourcesError(err)
}
if err != nil {
@ -237,6 +303,42 @@ func (c *AccessConfig) IsChinaCloud() bool {
return strings.HasPrefix(c.SessionRegion(), "cn-")
}
// GetCredentials gets credentials from the environment, shared credentials,
// the session (which may include a credential process), or ECS/EC2 metadata
// endpoints. GetCredentials also validates the credentials and the ability to
// assume a role or will return an error if unsuccessful.
func (c *AccessConfig) GetCredentials(config *aws.Config) (*awsCredentials.Credentials, error) {
// Reload values into the config used by the Packer-Terraform shared SDK
awsbaseConfig := &awsbase.Config{
AccessKey: c.AccessKey,
AssumeRoleARN: c.AssumeRole.AssumeRoleARN,
AssumeRoleDurationSeconds: c.AssumeRole.AssumeRoleDurationSeconds,
AssumeRoleExternalID: c.AssumeRole.AssumeRoleExternalID,
AssumeRolePolicy: c.AssumeRole.AssumeRolePolicy,
AssumeRolePolicyARNs: c.AssumeRole.AssumeRolePolicyARNs,
AssumeRoleSessionName: c.AssumeRole.AssumeRoleSessionName,
AssumeRoleTags: c.AssumeRole.AssumeRoleTags,
AssumeRoleTransitiveTagKeys: c.AssumeRole.AssumeRoleTransitiveTagKeys,
CredsFilename: c.CredsFilename,
DebugLogging: false,
// TODO: implement for Packer
// IamEndpoint: c.Endpoints["iam"],
Insecure: c.InsecureSkipTLSVerify,
MaxRetries: c.MaxRetries,
Profile: c.ProfileName,
Region: c.RawRegion,
SecretKey: c.SecretKey,
SkipCredsValidation: c.SkipCredsValidation,
SkipMetadataApiCheck: c.SkipMetadataApiCheck,
// TODO: implement for Packer
// SkipRequestingAccountId: c.SkipRequestingAccountId,
// StsEndpoint: c.Endpoints["sts"],
Token: c.Token,
}
return awsbase.GetCredentials(awsbaseConfig)
}
func (c *AccessConfig) GetCredsFromVault() error {
// const EnvVaultAddress = "VAULT_ADDR"
// const EnvVaultToken = "VAULT_TOKEN"
@ -306,6 +408,13 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
return errs
}
func (c *AccessConfig) NewNoValidCredentialSourcesError(err error) error {
return fmt.Errorf("No valid credential sources found for AWS Builder. "+
"Please see https://www.packer.io/docs/builders/amazon#authentication "+
"for more information on providing credentials for the AWS Builder. "+
"Error: %w", err)
}
func (c *AccessConfig) NewEC2Connection() (ec2iface.EC2API, error) {
if c.getEC2Connection != nil {
return c.getEC2Connection(), nil

View File

@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions,AssumeRoleConfig"; DO NOT EDIT.
package common
import (
@ -6,6 +6,43 @@ import (
"github.com/zclconf/go-cty/cty"
)
// FlatAssumeRoleConfig is an auto-generated flat version of AssumeRoleConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatAssumeRoleConfig struct {
AssumeRoleARN *string `mapstructure:"role_arn" required:"false" cty:"role_arn" hcl:"role_arn"`
AssumeRoleDurationSeconds *int `mapstructure:"duration_seconds" required:"false" cty:"duration_seconds" hcl:"duration_seconds"`
AssumeRoleExternalID *string `mapstructure:"external_id" required:"false" cty:"external_id" hcl:"external_id"`
AssumeRolePolicy *string `mapstructure:"policy" required:"false" cty:"policy" hcl:"policy"`
AssumeRolePolicyARNs []string `mapstructure:"policy_arns" required:"false" cty:"policy_arns" hcl:"policy_arns"`
AssumeRoleSessionName *string `mapstructure:"session_name" required:"false" cty:"session_name" hcl:"session_name"`
AssumeRoleTags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
AssumeRoleTransitiveTagKeys []string `mapstructure:"transitive_tag_keys" required:"false" cty:"transitive_tag_keys" hcl:"transitive_tag_keys"`
}
// FlatMapstructure returns a new FlatAssumeRoleConfig.
// FlatAssumeRoleConfig is an auto-generated flat version of AssumeRoleConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*AssumeRoleConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatAssumeRoleConfig)
}
// HCL2Spec returns the hcl spec of a AssumeRoleConfig.
// This spec is used by HCL to read the fields of AssumeRoleConfig.
// The decoded values from this spec will then be applied to a FlatAssumeRoleConfig.
func (*FlatAssumeRoleConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"role_arn": &hcldec.AttrSpec{Name: "role_arn", Type: cty.String, Required: false},
"duration_seconds": &hcldec.AttrSpec{Name: "duration_seconds", Type: cty.Number, Required: false},
"external_id": &hcldec.AttrSpec{Name: "external_id", Type: cty.String, Required: false},
"policy": &hcldec.AttrSpec{Name: "policy", Type: cty.String, Required: false},
"policy_arns": &hcldec.AttrSpec{Name: "policy_arns", Type: cty.List(cty.String), Required: false},
"session_name": &hcldec.AttrSpec{Name: "session_name", Type: cty.String, Required: false},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
"transitive_tag_keys": &hcldec.AttrSpec{Name: "transitive_tag_keys", Type: cty.List(cty.String), Required: false},
}
return s
}
// FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatVaultAWSEngineOptions struct {

View File

@ -473,6 +473,13 @@ type RunConfig struct {
// terminating the tunnel it will automatically terminate itself after 20 minutes of inactivity.
SSHInterface string `mapstructure:"ssh_interface"`
// The time to wait before establishing the Session Manager session.
// The value of this should be a duration. Examples are
// `5s` and `1m30s` which will cause Packer to wait five seconds and one
// minute 30 seconds, respectively. If no set, defaults to 10 seconds.
// This option is useful when the remote port takes longer to become available.
PauseBeforeSSM time.Duration `mapstructure:"pause_before_ssm"`
// Which port to connect the local end of the session tunnel to. If
// left blank, Packer will choose a port for you from available ports.
// This option is only used when `ssh_interface` is set `session_manager`.
@ -535,6 +542,10 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
msg := fmt.Errorf(`no iam_instance_profile defined; session_manager connectivity requires a valid instance profile with AmazonSSMManagedInstanceCore permissions. Alternatively a temporary_iam_instance_profile_policy_document can be used.`)
errs = append(errs, msg)
}
if c.PauseBeforeSSM == 0 {
c.PauseBeforeSSM = 10 * time.Second
}
}
if c.Comm.SSHKeyPairName != "" {

View File

@ -46,7 +46,7 @@ func NewSSMDriver(config SSMDriverConfig) *SSMDriver {
// not wish to manage the session manually calling StopSession on a instance of this driver will terminate the active session
// created from calling StartSession.
func (d *SSMDriver) StartSession(ctx context.Context, input ssm.StartSessionInput) (*ssm.StartSessionOutput, error) {
log.Printf("Starting PortForwarding session to instance %q with following params %v", aws.StringValue(input.Target), input.Parameters)
log.Printf("Starting PortForwarding session to instance %q", aws.StringValue(input.Target))
var output *ssm.StartSessionOutput
err := retry.Config{
@ -110,15 +110,30 @@ func (d *SSMDriver) openTunnelForSession(ctx context.Context) error {
select {
case <-ctx.Done():
return
case output := <-stderrCh:
case output, ok := <-stderrCh:
if !ok {
stderrCh = nil
break
}
if output != "" {
log.Printf("[ERROR] %s: %s", prefix, output)
}
case output := <-stdoutCh:
case output, ok := <-stdoutCh:
if !ok {
stdoutCh = nil
break
}
if output != "" {
log.Printf("[DEBUG] %s: %s", prefix, output)
}
}
if stdoutCh == nil && stderrCh == nil {
log.Printf("[DEBUG] %s: %s", prefix, "active session has been terminated; stopping all log polling processes.")
return
}
}
}(ctx, sessionManagerPluginName)

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
@ -21,6 +22,7 @@ type StepCreateSSMTunnel struct {
RemotePortNumber int
SSMAgentEnabled bool
instanceId string
PauseBeforeSSM time.Duration
driver *SSMDriver
}
@ -32,6 +34,17 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionContinue
}
// Wait for the remote port to become available
if s.PauseBeforeSSM > 0 {
ui.Say(fmt.Sprintf("Waiting %s for establishing the SSM session...", s.PauseBeforeSSM))
select {
case <-time.After(s.PauseBeforeSSM):
break
case <-ctx.Done():
return multistep.ActionHalt
}
}
// Configure local port number
if err := s.ConfigureLocalHostPort(ctx); err != nil {
err := fmt.Errorf("error finding an available port to initiate a session tunnel: %s", err)

View File

@ -269,6 +269,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),

View File

@ -19,7 +19,9 @@ type FlatConfig struct {
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"`
@ -29,6 +31,7 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
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"`
@ -132,6 +135,7 @@ type FlatConfig struct {
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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings" hcl:"launch_block_device_mappings"`
@ -160,7 +164,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@ -170,6 +176,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@ -273,6 +280,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},

View File

@ -293,6 +293,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),

View File

@ -62,7 +62,9 @@ type FlatConfig struct {
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"`
@ -72,6 +74,7 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
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"`
@ -154,6 +157,7 @@ type FlatConfig struct {
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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name" hcl:"ami_name"`
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description" hcl:"ami_description"`
@ -204,7 +208,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@ -214,6 +220,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@ -296,6 +303,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},

View File

@ -263,6 +263,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),

View File

@ -64,7 +64,9 @@ type FlatConfig struct {
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"`
@ -74,6 +76,7 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
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"`
@ -156,6 +159,7 @@ type FlatConfig struct {
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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support" hcl:"ena_support"`
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support" hcl:"sriov_support"`
@ -184,7 +188,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@ -194,6 +200,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@ -276,6 +283,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},

View File

@ -343,6 +343,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepCreateSSMTunnel{
AWSSession: session,
Region: *ec2conn.Config.Region,
PauseBeforeSSM: b.config.PauseBeforeSSM,
LocalPortNumber: b.config.SessionManagerPort,
RemotePortNumber: b.config.Comm.Port(),
SSMAgentEnabled: b.config.SSMAgentEnabled(),

View File

@ -19,7 +19,9 @@ type FlatConfig struct {
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"`
@ -29,6 +31,7 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
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"`
@ -132,6 +135,7 @@ type FlatConfig struct {
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"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings" hcl:"launch_block_device_mappings"`
@ -166,7 +170,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@ -176,6 +182,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},
@ -279,6 +286,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},

View File

@ -390,7 +390,7 @@ func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resou
func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
stateBag.Put(constants.ArmTags, b.config.AzureTags)
stateBag.Put(constants.ArmTags, packerAzureCommon.MapToAzureTags(b.config.AzureTags))
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)

View File

@ -223,7 +223,11 @@ const testBuilderAccManagedDiskLinux = `
"image_sku": "16.04-LTS",
"location": "South Central US",
"vm_size": "Standard_DS2_v2"
"vm_size": "Standard_DS2_v2",
"azure_tags": {
"env": "testing",
"builder": "packer"
}
}]
}
`

View File

@ -33,3 +33,35 @@ func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) {
}
}
}
func TestStateBagShouldPoluateExpectedTags(t *testing.T) {
var testSubject Builder
expectedTags := map[string]string{
"env": "test",
"builder": "packer",
}
armConfig := getArmBuilderConfiguration()
armConfig["azure_tags"] = expectedTags
_, _, err := testSubject.Prepare(armConfig, getPackerConfiguration())
if err != nil {
t.Fatalf("failed to prepare: %s", err)
}
tags, ok := testSubject.stateBag.Get(constants.ArmTags).(map[string]*string)
if !ok {
t.Errorf("Expected the builder's state bag to contain tags of type %T, but didn't.", testSubject.config.AzureTags)
}
if len(tags) != len(expectedTags) {
t.Errorf("expect tags from state to be the same length as tags from config")
}
for k, v := range tags {
if expectedTags[k] != *v {
t.Errorf("expect tag value of %s to be %s, but got %s", k, expectedTags[k], *v)
}
}
}

View File

@ -288,7 +288,7 @@ type Config struct {
// Group, VM, NIC, VNET, Public IP, KeyVault, etc. The user can define up
// to 15 tags. Tag names cannot exceed 512 characters, and tag values
// cannot exceed 256 characters.
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false"`
AzureTags map[string]string `mapstructure:"azure_tags" required:"false"`
// Same as [`azure_tags`](#azure_tags) but defined as a singular repeatable block
// containing a `name` and a `value` field. In HCL2 mode the
// [`dynamic_block`](/docs/configuration/from-1.5/expressions#dynamic-blocks)
@ -513,7 +513,7 @@ func (c *Config) toImageParameters() *compute.Image {
},
},
Location: to.StringPtr(c.Location),
Tags: c.AzureTags,
Tags: azcommon.MapToAzureTags(c.AzureTags),
}
}
@ -596,10 +596,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
// copy singular blocks
for _, kv := range c.AzureTag {
v := kv.Value
c.AzureTags[kv.Name] = &v
}
c.AzureTag.CopyOn(&c.AzureTags)
err = c.ClientConfig.SetDefaultValues()
if err != nil {
@ -807,8 +804,8 @@ func assertTagProperties(c *Config, errs *packer.MultiError) {
if len(k) > 512 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 512 character limit", k, len(k)))
}
if len(*v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", *v, len(*v)))
if len(v) > 256 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("the tag name %q exceeds (%d) the 256 character limit", v, len(v)))
}
}
}
@ -1063,13 +1060,13 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("if either plan_name, plan_product, plan_publisher, or plan_promotion_code are defined then plan_name, plan_product, and plan_publisher must be defined"))
} else {
if c.AzureTags == nil {
c.AzureTags = make(map[string]*string)
c.AzureTags = make(map[string]string)
}
c.AzureTags["PlanInfo"] = &c.PlanInfo.PlanName
c.AzureTags["PlanProduct"] = &c.PlanInfo.PlanProduct
c.AzureTags["PlanPublisher"] = &c.PlanInfo.PlanPublisher
c.AzureTags["PlanPromotionCode"] = &c.PlanInfo.PlanPromotionCode
c.AzureTags["PlanInfo"] = c.PlanInfo.PlanName
c.AzureTags["PlanProduct"] = c.PlanInfo.PlanProduct
c.AzureTags["PlanPublisher"] = c.PlanInfo.PlanPublisher
c.AzureTags["PlanPromotionCode"] = c.PlanInfo.PlanPromotionCode
}
}

View File

@ -49,7 +49,7 @@ type FlatConfig struct {
ManagedImageOSDiskSnapshotName *string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false" cty:"managed_image_os_disk_snapshot_name" hcl:"managed_image_os_disk_snapshot_name"`
ManagedImageDataDiskSnapshotPrefix *string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false" cty:"managed_image_data_disk_snapshot_prefix" hcl:"managed_image_data_disk_snapshot_prefix"`
ManagedImageZoneResilient *bool `mapstructure:"managed_image_zone_resilient" required:"false" cty:"managed_image_zone_resilient" hcl:"managed_image_zone_resilient"`
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false" cty:"azure_tags" hcl:"azure_tags"`
AzureTags map[string]string `mapstructure:"azure_tags" required:"false" cty:"azure_tags" hcl:"azure_tags"`
AzureTag []hcl2template.FlatNameValue `mapstructure:"azure_tag" required:"false" cty:"azure_tag" hcl:"azure_tag"`
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name" hcl:"resource_group_name"`
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account" hcl:"storage_account"`

View File

@ -6,7 +6,9 @@ import (
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/hcl2template"
)
// List of configuration parameters that are required by the ARM builder.
@ -910,32 +912,55 @@ func TestConfigShouldAcceptTags(t *testing.T) {
},
}
var c Config
c := Config{
AzureTag: hcl2template.NameValues{
{Name: "tag03", Value: "value03"},
},
}
_, err := c.Prepare(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if len(c.AzureTags) != 2 {
t.Fatalf("expected to find 2 tags, but got %d", len(c.AzureTags))
if diff := cmp.Diff(c.AzureTags, map[string]string{
"tag01": "value01",
"tag02": "value02",
"tag03": "value03",
}); diff != "" {
t.Fatalf("unexpected azure tags: %s", diff)
}
}
func TestConfigShouldAcceptTag(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"communicator": "none",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}
if _, ok := c.AzureTags["tag01"]; !ok {
t.Error("expected to find key=\"tag01\", but did not")
c := Config{
AzureTag: hcl2template.NameValues{
{Name: "tag03", Value: "value03"},
},
}
if _, ok := c.AzureTags["tag02"]; !ok {
t.Error("expected to find key=\"tag02\", but did not")
_, err := c.Prepare(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
value := c.AzureTags["tag01"]
if *value != "value01" {
t.Errorf("expected AzureTags[\"tag01\"] to have value \"value01\", but got %q", *value)
}
value = c.AzureTags["tag02"]
if *value != "value02" {
t.Errorf("expected AzureTags[\"tag02\"] to have value \"value02\", but got %q", *value)
if diff := cmp.Diff(c.AzureTags, map[string]string{
"tag03": "value03",
}); diff != "" {
t.Fatalf("unexpected azure tags: %s", diff)
}
}
@ -2047,8 +2072,8 @@ func TestConfig_PrepareProvidedWinRMPassword(t *testing.T) {
}
}
func getArmBuilderConfiguration() map[string]string {
m := make(map[string]string)
func getArmBuilderConfiguration() map[string]interface{} {
m := make(map[string]interface{})
for _, v := range requiredConfigValues {
m[v] = "ignored00"
}

View File

@ -61,7 +61,13 @@ func (s *StepCreateResourceGroup) Run(ctx context.Context, state multistep.State
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var location = state.Get(constants.ArmLocation).(string)
var tags = state.Get(constants.ArmTags).(map[string]*string)
tags, ok := state.Get(constants.ArmTags).(map[string]*string)
if !ok {
err := fmt.Errorf("failed to extract tags from state bag")
state.Put(constants.Error, err)
s.error(err)
return multistep.ActionHalt
}
exists, err := s.exists(ctx, resourceGroupName)
if err != nil {

View File

@ -220,3 +220,32 @@ func createTestExistingStateBagStepCreateResourceGroup() multistep.StateBag {
stateBag.Put(constants.ArmTags, tags)
return stateBag
}
func TestStepCreateResourceGroupShouldFailIfTagsFailCast(t *testing.T) {
stateBag := new(multistep.BasicStateBag)
stateBag.Put(constants.ArmLocation, "Unit Test: Location")
stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName")
stateBag.Put(constants.ArmIsExistingResourceGroup, true)
value := "Unit Test: Tags"
tags := map[string]string{
"tag01": value,
}
stateBag.Put(constants.ArmTags, tags)
var testSubject = &StepCreateResourceGroup{
create: func(context.Context, string, string, map[string]*string) error { return nil },
say: func(message string) {},
error: func(e error) {},
exists: func(context.Context, string) (bool, error) { return false, nil },
}
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk(constants.Error); ok == false {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}

View File

@ -0,0 +1,10 @@
package common
func MapToAzureTags(in map[string]string) map[string]*string {
res := map[string]*string{}
for k := range in {
v := in[k]
res[k] = &v
}
return res
}

View File

@ -25,16 +25,16 @@ type Parameters struct {
/////////////////////////////////////////////////
// Template > Resource
type Resource struct {
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Plan *Plan `json:"plan,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]*string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
Identity *Identity `json:"identity,omitempty"`
ApiVersion *string `json:"apiVersion"`
Name *string `json:"name"`
Type *string `json:"type"`
Location *string `json:"location,omitempty"`
DependsOn *[]string `json:"dependsOn,omitempty"`
Plan *Plan `json:"plan,omitempty"`
Properties *Properties `json:"properties,omitempty"`
Tags *map[string]string `json:"tags,omitempty"`
Resources *[]Resource `json:"resources,omitempty"`
Identity *Identity `json:"identity,omitempty"`
}
type Plan struct {

View File

@ -374,7 +374,7 @@ func (s *TemplateBuilder) SetNetworkSecurityGroup(ipAddresses []string, port int
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
func (s *TemplateBuilder) SetTags(tags *map[string]string) error {
if tags == nil || len(*tags) == 0 {
return nil
}

View File

@ -3,16 +3,20 @@
package openstack
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/utils/openstack/clientconfig"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
@ -236,6 +240,15 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
return nil
}
func (c *AccessConfig) enableDebug(ui packer.Ui) {
c.osClient.HTTPClient = http.Client{
Transport: &DebugRoundTripper{
ui: ui,
rt: c.osClient.HTTPClient.Transport,
},
}
}
func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) {
return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{
Region: c.Region,
@ -273,3 +286,49 @@ func (c *AccessConfig) getEndpointType() gophercloud.Availability {
}
return gophercloud.AvailabilityPublic
}
type DebugRoundTripper struct {
ui packer.Ui
rt http.RoundTripper
numReauthAttempts int
}
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (drt *DebugRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
defer func() {
if request.Body != nil {
request.Body.Close()
}
}()
var response *http.Response
var err error
response, err = drt.rt.RoundTrip(request)
if response == nil {
return nil, err
}
if response.StatusCode == http.StatusUnauthorized {
if drt.numReauthAttempts == 3 {
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
}
drt.numReauthAttempts++
}
drt.DebugMessage(fmt.Sprintf("Request %s %s %d", request.Method, request.URL, response.StatusCode))
if response.StatusCode >= 400 {
buf := bytes.NewBuffer([]byte{})
body, _ := ioutil.ReadAll(io.TeeReader(response.Body, buf))
drt.DebugMessage(fmt.Sprintf("Response Error: %+v\n", string(body)))
bufWithClose := ioutil.NopCloser(buf)
response.Body = bufWithClose
}
return response, err
}
func (drt *DebugRoundTripper) DebugMessage(message string) {
drt.ui.Message(fmt.Sprintf("[DEBUG] %s", message))
}

View File

@ -71,6 +71,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
if b.config.PackerDebug {
b.config.enableDebug(ui)
}
computeClient, err := b.config.computeV2Client()
if err != nil {
return nil, fmt.Errorf("Error initializing compute client: %s", err)
@ -98,11 +102,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName),
},
&StepSourceImageInfo{
SourceImage: b.config.RunConfig.SourceImage,
SourceImageName: b.config.RunConfig.SourceImageName,
SourceImageOpts: b.config.RunConfig.sourceImageOpts,
SourceMostRecent: b.config.SourceImageFilters.MostRecent,
SourceProperties: b.config.SourceImageFilters.Filters.Properties,
SourceImage: b.config.RunConfig.SourceImage,
SourceImageName: b.config.RunConfig.SourceImageName,
ExternalSourceImageURL: b.config.RunConfig.ExternalSourceImageURL,
ExternalSourceImageFormat: b.config.RunConfig.ExternalSourceImageFormat,
SourceImageOpts: b.config.RunConfig.sourceImageOpts,
SourceMostRecent: b.config.SourceImageFilters.MostRecent,
SourceProperties: b.config.SourceImageFilters.Filters.Properties,
},
&StepDiscoverNetwork{
Networks: b.config.Networks,

View File

@ -95,6 +95,8 @@ type FlatConfig struct {
SSHIPVersion *string `mapstructure:"ssh_ip_version" required:"false" cty:"ssh_ip_version" hcl:"ssh_ip_version"`
SourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image" hcl:"source_image"`
SourceImageName *string `mapstructure:"source_image_name" required:"true" cty:"source_image_name" hcl:"source_image_name"`
ExternalSourceImageURL *string `mapstructure:"external_source_image_url" required:"true" cty:"external_source_image_url" hcl:"external_source_image_url"`
ExternalSourceImageFormat *string `mapstructure:"external_source_image_format" required:"false" cty:"external_source_image_format" hcl:"external_source_image_format"`
SourceImageFilters *FlatImageFilter `mapstructure:"source_image_filter" required:"true" cty:"source_image_filter" hcl:"source_image_filter"`
Flavor *string `mapstructure:"flavor" required:"true" cty:"flavor" hcl:"flavor"`
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone" hcl:"availability_zone"`
@ -220,6 +222,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_ip_version": &hcldec.AttrSpec{Name: "ssh_ip_version", Type: cty.String, Required: false},
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
"source_image_name": &hcldec.AttrSpec{Name: "source_image_name", Type: cty.String, Required: false},
"external_source_image_url": &hcldec.AttrSpec{Name: "external_source_image_url", Type: cty.String, Required: false},
"external_source_image_format": &hcldec.AttrSpec{Name: "external_source_image_format", Type: cty.String, Required: false},
"source_image_filter": &hcldec.BlockSpec{TypeName: "source_image_filter", Nested: hcldec.ObjectSpec((*FlatImageFilter)(nil).HCL2Spec())},
"flavor": &hcldec.AttrSpec{Name: "flavor", Type: cty.String, Required: false},
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},

View File

@ -33,6 +33,11 @@ type RunConfig struct {
// The name of the base image to use. This is an alternative way of
// providing source_image and only either of them can be specified.
SourceImageName string `mapstructure:"source_image_name" required:"true"`
// The URL of an external base image to use. This is an alternative way of
// providing source_image and only either of them can be specified.
ExternalSourceImageURL string `mapstructure:"external_source_image_url" required:"true"`
// The format of the external source image to use, e.g. qcow2, raw.
ExternalSourceImageFormat string `mapstructure:"external_source_image_format" required:"false"`
// Filters used to populate filter options. Example:
//
// ```json
@ -247,10 +252,19 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if c.SourceImage == "" && c.SourceImageName == "" && c.SourceImageFilters.Filters.Empty() {
errs = append(errs, errors.New("Either a source_image, a source_image_name, or source_image_filter must be specified"))
} else if len(c.SourceImage) > 0 && len(c.SourceImageName) > 0 {
errs = append(errs, errors.New("Only a source_image or a source_image_name can be specified, not both."))
hasOnlySourceImage := len(c.SourceImage) > 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0
hasOnlySourceImageName := len(c.SourceImageName) > 0 && len(c.SourceImage) == 0 && len(c.ExternalSourceImageURL) == 0
hasOnlyExternalSourceImageURL := len(c.ExternalSourceImageURL) > 0 && len(c.SourceImage) == 0 && len(c.SourceImageName) == 0
if c.SourceImage == "" && c.SourceImageName == "" && c.ExternalSourceImageURL == "" && c.SourceImageFilters.Filters.Empty() {
errs = append(errs, errors.New("Either a source_image, a source_image_name, an external_source_image_url or source_image_filter must be specified"))
} else if !(hasOnlySourceImage || hasOnlySourceImageName || hasOnlyExternalSourceImageURL) {
errs = append(errs, errors.New("Only a source_image, a source_image_name or an external_source_image_url can be specified, not multiple."))
}
// if external_source_image_format is not set use qcow2 as default
if c.ExternalSourceImageFormat == "" {
c.ExternalSourceImageFormat = "qcow2"
}
if c.Flavor == "" {
@ -283,9 +297,9 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
// if neither ID or image name is provided outside the filter, build the
// filter
if len(c.SourceImage) == 0 && len(c.SourceImageName) == 0 {
// if neither ID, image name or external image URL is provided outside the filter,
// build the filter
if len(c.SourceImage) == 0 && len(c.SourceImageName) == 0 && len(c.ExternalSourceImageURL) == 0 {
listOpts, filterErr := c.SourceImageFilters.Filters.Build()
@ -295,6 +309,11 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
c.sourceImageOpts = *listOpts
}
// if c.ExternalSourceImageURL is set use a generated source image name
if c.ExternalSourceImageURL != "" {
c.SourceImageName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
return errs
}

View File

@ -2,6 +2,7 @@ package openstack
import (
"os"
"regexp"
"testing"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
@ -132,6 +133,49 @@ func TestRunConfigPrepare_FloatingIPPoolCompat(t *testing.T) {
}
}
func TestRunConfigPrepare_ExternalSourceImageURL(t *testing.T) {
c := testRunConfig()
// test setting both ExternalSourceImageURL and SourceImage causes an error
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
// test setting both ExternalSourceImageURL and SourceImageName causes an error
c.SourceImage = ""
c.SourceImageName = "abcd"
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
// test neither setting SourceImage, SourceImageName or ExternalSourceImageURL causes an error
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
// test setting only ExternalSourceImageURL passes
c.SourceImage = ""
c.SourceImageName = ""
c.ExternalSourceImageURL = "http://example.com/image.qcow2"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
// test default values
if c.ExternalSourceImageFormat != "qcow2" {
t.Fatalf("ExternalSourceImageFormat should have been set to default: qcow2")
}
p := `packer_[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`
if matches, _ := regexp.MatchString(p, c.SourceImageName); !matches {
t.Fatalf("invalid format for SourceImageName: %s", c.SourceImageName)
}
}
// This test case confirms that only allowed fields will be set to values
// The checked values are non-nil for their target type
func TestBuildImageFilter(t *testing.T) {

View File

@ -4,7 +4,9 @@ import (
"context"
"fmt"
"log"
"time"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imageimport"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/gophercloud/gophercloud/pagination"
"github.com/hashicorp/packer/helper/multistep"
@ -12,11 +14,13 @@ import (
)
type StepSourceImageInfo struct {
SourceImage string
SourceImageName string
SourceImageOpts images.ListOpts
SourceMostRecent bool
SourceProperties map[string]string
SourceImage string
SourceImageName string
ExternalSourceImageURL string
ExternalSourceImageFormat string
SourceImageOpts images.ListOpts
SourceMostRecent bool
SourceProperties map[string]string
}
func PropertiesSatisfied(image *images.Image, props *map[string]string) bool {
@ -33,12 +37,6 @@ func (s *StepSourceImageInfo) Run(ctx context.Context, state multistep.StateBag)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if s.SourceImage != "" {
state.Put("source_image", s.SourceImage)
return multistep.ActionContinue
}
client, err := config.imageV2Client()
if err != nil {
err := fmt.Errorf("error creating image client: %s", err)
@ -47,6 +45,70 @@ func (s *StepSourceImageInfo) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionHalt
}
if s.ExternalSourceImageURL != "" {
createOpts := images.CreateOpts{
Name: s.SourceImageName,
ContainerFormat: "bare",
DiskFormat: s.ExternalSourceImageFormat,
Properties: map[string]string{
"packer_external_source_image_url": s.ExternalSourceImageURL,
"packer_external_source_image_format": s.ExternalSourceImageFormat,
},
}
ui.Say("Creating image using external source image with name " + s.SourceImageName)
ui.Say("Using disk format " + s.ExternalSourceImageFormat)
image, err := images.Create(client, createOpts).Extract()
if err != nil {
err := fmt.Errorf("Error creating source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Created image with ID " + image.ID)
importOpts := imageimport.CreateOpts{
Name: imageimport.WebDownloadMethod,
URI: s.ExternalSourceImageURL,
}
ui.Say("Importing External Source Image from URL " + s.ExternalSourceImageURL)
err = imageimport.Create(client, image.ID, importOpts).ExtractErr()
if err != nil {
err := fmt.Errorf("Error importing source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for image.Status != images.ImageStatusActive {
ui.Message("Image not Active, retrying in 10 seconds")
time.Sleep(10 * time.Second)
img, err := images.Get(client, image.ID).Extract()
if err != nil {
err := fmt.Errorf("Error querying image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image = img
}
s.SourceImage = image.ID
}
if s.SourceImage != "" {
state.Put("source_image", s.SourceImage)
return multistep.ActionContinue
}
if s.SourceImageName != "" {
s.SourceImageOpts = images.ListOpts{
Name: s.SourceImageName,
@ -117,5 +179,25 @@ func (s *StepSourceImageInfo) Run(ctx context.Context, state multistep.StateBag)
}
func (s *StepSourceImageInfo) Cleanup(state multistep.StateBag) {
// No cleanup required for backout
if s.ExternalSourceImageURL != "" {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
client, err := config.imageV2Client()
if err != nil {
err := fmt.Errorf("error creating image client: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Say(fmt.Sprintf("Deleting temporary external source image: %s ...", s.SourceImageName))
err = images.Delete(client, s.SourceImage).ExtractErr()
if err != nil {
err := fmt.Errorf("error cleaning up external source image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
}
}

View File

@ -86,6 +86,7 @@ type diskConfig struct {
Size string `mapstructure:"disk_size"`
CacheMode string `mapstructure:"cache_mode"`
DiskFormat string `mapstructure:"format"`
IOThread bool `mapstructure:"io_thread"`
}
type vgaConfig struct {
Type string `mapstructure:"type"`
@ -189,6 +190,17 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
log.Printf("Disk %d cache mode not set, using default 'none'", idx)
c.Disks[idx].CacheMode = "none"
}
if c.Disks[idx].IOThread {
// io thread is only supported by virtio-scsi-single controller
if c.SCSIController != "virtio-scsi-single" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("io thread option requires virtio-scsi-single controller"))
} else {
// ... and only for virtio and scsi disks
if !(c.Disks[idx].Type == "scsi" || c.Disks[idx].Type == "virtio") {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("io thread option requires scsi or a virtio disk"))
}
}
}
// For any storage pool types which aren't in rxStorageTypes in proxmox-api/proxmox/config_qemu.go:890
// (currently zfspool|lvm|rbd|cephfs), the format parameter is mandatory. Make sure this is still up to date
// when updating the vendored code!

View File

@ -230,6 +230,7 @@ type FlatdiskConfig struct {
Size *string `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
CacheMode *string `mapstructure:"cache_mode" cty:"cache_mode" hcl:"cache_mode"`
DiskFormat *string `mapstructure:"format" cty:"format" hcl:"format"`
IOThread *bool `mapstructure:"io_thread" cty:"io_thread" hcl:"io_thread"`
}
// FlatMapstructure returns a new FlatdiskConfig.
@ -250,6 +251,7 @@ func (*FlatdiskConfig) HCL2Spec() map[string]hcldec.Spec {
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false},
"cache_mode": &hcldec.AttrSpec{Name: "cache_mode", Type: cty.String, Required: false},
"format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false},
"io_thread": &hcldec.AttrSpec{Name: "io_thread", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -208,3 +208,52 @@ func TestPacketQueueSupportForNetworkAdapters(t *testing.T) {
}
}
}
func TestHardDiskControllerIOThreadSupport(t *testing.T) {
drivertests := []struct {
expectedToFail bool
controller string
disk_type string
}{
// io thread is only supported by virtio-scsi-single controller
// and only for virtio and scsi disks
{expectedToFail: false, controller: "virtio-scsi-single", disk_type: "scsi"},
{expectedToFail: false, controller: "virtio-scsi-single", disk_type: "virtio"},
{expectedToFail: true, controller: "virtio-scsi-single", disk_type: "sata"},
{expectedToFail: true, controller: "lsi", disk_type: "scsi"},
{expectedToFail: true, controller: "lsi53c810", disk_type: "virtio"},
}
for _, tt := range drivertests {
nic := make(map[string]interface{})
nic["bridge"] = "vmbr0"
nics := make([]map[string]interface{}, 0)
nics = append(nics, nic)
disk := make(map[string]interface{})
disk["type"] = tt.disk_type
disk["io_thread"] = true
disk["storage_pool"] = "local-lvm"
disk["storage_pool_type"] = "lvm"
disks := make([]map[string]interface{}, 0)
disks = append(disks, disk)
cfg := mandatoryConfig(t)
cfg["network_adapters"] = nics
cfg["disks"] = disks
cfg["scsi_controller"] = tt.controller
var c Config
_, err := c.Prepare(cfg)
if tt.expectedToFail == true && err == nil {
t.Error("expected config preparation to fail, but no error occured")
}
if tt.expectedToFail == false && err != nil {
t.Errorf("expected config preparation to succeed, but %s", err.Error())
}
}
}

View File

@ -142,6 +142,10 @@ func generateProxmoxDisks(disks []diskConfig) proxmox.QemuDevices {
setDeviceParamIfDefined(devs[idx], "storage_type", disks[idx].StoragePoolType)
setDeviceParamIfDefined(devs[idx], "cache", disks[idx].CacheMode)
setDeviceParamIfDefined(devs[idx], "format", disks[idx].DiskFormat)
if devs[idx]["type"] == "scsi" || devs[idx]["type"] == "virtio" {
setDeviceParamIfDefined(devs[idx], "iothread", strconv.FormatBool(disks[idx].IOThread))
}
}
return devs
}

View File

@ -137,6 +137,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
OutputDir: b.config.OutputDir,
SkipCompaction: b.config.SkipCompaction,
VMName: b.config.VMName,
QemuImgArgs: b.config.QemuImgArgs,
},
)

View File

@ -11,6 +11,7 @@ import (
type StepPrepareTools struct {
RemoteType string
ToolsUploadFlavor string
ToolsSourcePath string
}
func (c *StepPrepareTools) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@ -20,11 +21,15 @@ func (c *StepPrepareTools) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionContinue
}
if c.ToolsUploadFlavor == "" {
if c.ToolsUploadFlavor == "" && c.ToolsSourcePath == "" {
return multistep.ActionContinue
}
path := driver.ToolsIsoPath(c.ToolsUploadFlavor)
path := c.ToolsSourcePath
if path == "" {
path = driver.ToolsIsoPath(c.ToolsUploadFlavor)
}
if _, err := os.Stat(path); err != nil {
state.Put("error", fmt.Errorf(
"Couldn't find VMware tools for '%s'! VMware often downloads these\n"+

View File

@ -114,3 +114,65 @@ func TestStepPrepareTools_nonExist(t *testing.T) {
t.Fatal("should NOT have tools_upload_source")
}
}
func TestStepPrepareTools_SourcePath(t *testing.T) {
state := testState(t)
step := &StepPrepareTools{
RemoteType: "",
ToolsSourcePath: "/path/to/tool.iso",
}
driver := state.Get("driver").(*DriverMock)
// Mock results
driver.ToolsIsoPathResult = "foo"
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("Should have failed when stat failed %#v", action)
}
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
// Test the driver
if driver.ToolsIsoPathCalled {
t.Fatal("tools iso path should not be called when ToolsSourcePath is set")
}
// Test the resulting state
if _, ok := state.GetOk("tools_upload_source"); ok {
t.Fatal("should NOT have tools_upload_source")
}
}
func TestStepPrepareTools_SourcePath_exists(t *testing.T) {
state := testState(t)
step := &StepPrepareTools{
RemoteType: "",
ToolsSourcePath: "./step_prepare_tools.go",
}
driver := state.Get("driver").(*DriverMock)
// Mock results
driver.ToolsIsoPathResult = "foo"
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Step should succeed when stat succeeds: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Test the driver
if driver.ToolsIsoPathCalled {
t.Fatal("tools iso path should not be called when ToolsSourcePath is set")
}
// Test the resulting state
if _, ok := state.GetOk("tools_upload_source"); !ok {
t.Fatal("should have tools_upload_source")
}
}

View File

@ -3,6 +3,8 @@
package common
import (
"fmt"
"github.com/hashicorp/packer/template/interpolate"
)
@ -18,12 +20,22 @@ type ToolsConfig struct {
// the upload path is set to `{{.Flavor}}.iso`. This setting is not used
// when `remote_type` is `esx5`.
ToolsUploadPath string `mapstructure:"tools_upload_path" required:"false"`
// The path on your local machine to fetch the vmware tools from. If this
// is not set but the tools_upload_flavor is set, then Packer will try to
// load the VMWare tools from the VMWare installation directory.
ToolsSourcePath string `mapstructure:"tools_source_path" required:"false"`
}
func (c *ToolsConfig) Prepare(ctx *interpolate.Context) []error {
errs := []error{}
if c.ToolsUploadPath == "" {
if c.ToolsSourcePath != "" && c.ToolsUploadFlavor == "" {
errs = append(errs, fmt.Errorf("If you provide a "+
"tools_source_path, you must also provide either a "+
"tools_upload_flavor or a tools_upload_path."))
}
c.ToolsUploadPath = "{{ .Flavor }}.iso"
}
return nil
return errs
}

View File

@ -0,0 +1,65 @@
package common
import (
"testing"
"github.com/hashicorp/packer/template/interpolate"
)
func TestToolsConfigPrepare_Empty(t *testing.T) {
c := &ToolsConfig{}
errs := c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}
if c.ToolsUploadPath != "{{ .Flavor }}.iso" {
t.Fatal("should have defaulted tools upload path")
}
}
func TestToolsConfigPrepare_SetUploadPath(t *testing.T) {
c := &ToolsConfig{
ToolsUploadPath: "path/to/tools.iso",
}
errs := c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}
if c.ToolsUploadPath != "path/to/tools.iso" {
t.Fatal("should have used given tools upload path")
}
}
func TestToolsConfigPrepare_ErrorIfOnlySource(t *testing.T) {
c := &ToolsConfig{
ToolsSourcePath: "path/to/tools.iso",
}
errs := c.Prepare(interpolate.NewContext())
if len(errs) != 1 {
t.Fatalf("Should have received an error because the flavor and " +
"upload path aren't set")
}
}
func TestToolsConfigPrepare_SourceSuccess(t *testing.T) {
for _, c := range []*ToolsConfig{
&ToolsConfig{
ToolsSourcePath: "path/to/tools.iso",
ToolsUploadPath: "partypath.iso",
},
&ToolsConfig{
ToolsSourcePath: "path/to/tools.iso",
ToolsUploadFlavor: "linux",
},
} {
errs := c.Prepare(interpolate.NewContext())
if len(errs) != 0 {
t.Fatalf("Should not have received an error")
}
}
}

View File

@ -119,6 +119,7 @@ type FlatConfig struct {
SSHSkipRequestPty *bool `mapstructure:"ssh_skip_request_pty" cty:"ssh_skip_request_pty" hcl:"ssh_skip_request_pty"`
ToolsUploadFlavor *string `mapstructure:"tools_upload_flavor" required:"false" cty:"tools_upload_flavor" hcl:"tools_upload_flavor"`
ToolsUploadPath *string `mapstructure:"tools_upload_path" required:"false" cty:"tools_upload_path" hcl:"tools_upload_path"`
ToolsSourcePath *string `mapstructure:"tools_source_path" required:"false" cty:"tools_source_path" hcl:"tools_source_path"`
VMXData map[string]string `mapstructure:"vmx_data" required:"false" cty:"vmx_data" hcl:"vmx_data"`
VMXDataPost map[string]string `mapstructure:"vmx_data_post" required:"false" cty:"vmx_data_post" hcl:"vmx_data_post"`
VMXRemoveEthernet *bool `mapstructure:"vmx_remove_ethernet_interfaces" required:"false" cty:"vmx_remove_ethernet_interfaces" hcl:"vmx_remove_ethernet_interfaces"`
@ -263,6 +264,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_skip_request_pty": &hcldec.AttrSpec{Name: "ssh_skip_request_pty", Type: cty.Bool, Required: false},
"tools_upload_flavor": &hcldec.AttrSpec{Name: "tools_upload_flavor", Type: cty.String, Required: false},
"tools_upload_path": &hcldec.AttrSpec{Name: "tools_upload_path", Type: cty.String, Required: false},
"tools_source_path": &hcldec.AttrSpec{Name: "tools_source_path", Type: cty.String, Required: false},
"vmx_data": &hcldec.AttrSpec{Name: "vmx_data", Type: cty.Map(cty.String), Required: false},
"vmx_data_post": &hcldec.AttrSpec{Name: "vmx_data_post", Type: cty.Map(cty.String), Required: false},
"vmx_remove_ethernet_interfaces": &hcldec.AttrSpec{Name: "vmx_remove_ethernet_interfaces", Type: cty.Bool, Required: false},

View File

@ -104,6 +104,7 @@ type FlatConfig struct {
SSHSkipRequestPty *bool `mapstructure:"ssh_skip_request_pty" cty:"ssh_skip_request_pty" hcl:"ssh_skip_request_pty"`
ToolsUploadFlavor *string `mapstructure:"tools_upload_flavor" required:"false" cty:"tools_upload_flavor" hcl:"tools_upload_flavor"`
ToolsUploadPath *string `mapstructure:"tools_upload_path" required:"false" cty:"tools_upload_path" hcl:"tools_upload_path"`
ToolsSourcePath *string `mapstructure:"tools_source_path" required:"false" cty:"tools_source_path" hcl:"tools_source_path"`
VMXData map[string]string `mapstructure:"vmx_data" required:"false" cty:"vmx_data" hcl:"vmx_data"`
VMXDataPost map[string]string `mapstructure:"vmx_data_post" required:"false" cty:"vmx_data_post" hcl:"vmx_data_post"`
VMXRemoveEthernet *bool `mapstructure:"vmx_remove_ethernet_interfaces" required:"false" cty:"vmx_remove_ethernet_interfaces" hcl:"vmx_remove_ethernet_interfaces"`
@ -229,6 +230,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_skip_request_pty": &hcldec.AttrSpec{Name: "ssh_skip_request_pty", Type: cty.Bool, Required: false},
"tools_upload_flavor": &hcldec.AttrSpec{Name: "tools_upload_flavor", Type: cty.String, Required: false},
"tools_upload_path": &hcldec.AttrSpec{Name: "tools_upload_path", Type: cty.String, Required: false},
"tools_source_path": &hcldec.AttrSpec{Name: "tools_source_path", Type: cty.String, Required: false},
"vmx_data": &hcldec.AttrSpec{Name: "vmx_data", Type: cty.Map(cty.String), Required: false},
"vmx_data_post": &hcldec.AttrSpec{Name: "vmx_data_post", Type: cty.Map(cty.String), Required: false},
"vmx_remove_ethernet_interfaces": &hcldec.AttrSpec{Name: "vmx_remove_ethernet_interfaces", Type: cty.Bool, Required: false},

View File

@ -41,6 +41,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
&packerCommon.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&common.StepRemoteUpload{
Datastore: b.config.Datastore,
Host: b.config.Host,
SetHostForDatastoreUploads: b.config.SetHostForDatastoreUploads,
},
&StepCloneVM{
Config: &b.config.CloneConfig,
Location: &b.config.LocationConfig,
@ -49,6 +58,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&common.StepConfigureHardware{
Config: &b.config.HardwareConfig,
},
&common.StepAddCDRom{
Config: &b.config.CDRomConfig,
},
&common.StepConfigParams{
Config: &b.config.ConfigParamsConfig,
},
@ -62,6 +74,17 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
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(),
@ -98,10 +121,17 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&common.StepShutdown{
Config: &b.config.ShutdownConfig,
},
&common.StepRemoveFloppy{
Datastore: b.config.Datastore,
Host: b.config.Host,
},
)
}
steps = append(steps,
&common.StepRemoveCDRom{
Config: &b.config.RemoveCDRomConfig,
},
&common.StepCreateSnapshot{
CreateSnapshot: b.config.CreateSnapshot,
},

View File

@ -15,6 +15,7 @@ import (
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
packerCommon.HTTPConfig `mapstructure:",squash"`
packerCommon.CDConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
CloneConfig `mapstructure:",squash"`
@ -22,11 +23,14 @@ type Config struct {
common.HardwareConfig `mapstructure:",squash"`
common.ConfigParamsConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
common.BootConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
common.CDRomConfig `mapstructure:",squash"`
common.RemoveCDRomConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
common.BootConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
// Create a snapshot when set to `true`, so the VM can be used as a base
// for linked clones. Defaults to `false`.
@ -67,6 +71,8 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CDRomConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
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)...)

View File

@ -22,6 +22,8 @@ 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"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server" hcl:"vcenter_server"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
@ -58,6 +60,13 @@ type FlatConfig struct {
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters" hcl:"configuration_parameters"`
ToolsSyncTime *bool `mapstructure:"tools_sync_time" cty:"tools_sync_time" hcl:"tools_sync_time"`
ToolsUpgradePolicy *bool `mapstructure:"tools_upgrade_policy" cty:"tools_upgrade_policy" hcl:"tools_upgrade_policy"`
CdromType *string `mapstructure:"cdrom_type" cty:"cdrom_type" hcl:"cdrom_type"`
ISOPaths []string `mapstructure:"iso_paths" cty:"iso_paths" hcl:"iso_paths"`
RemoveCdrom *bool `mapstructure:"remove_cdrom" cty:"remove_cdrom" hcl:"remove_cdrom"`
FloppyIMGPath *string `mapstructure:"floppy_img_path" cty:"floppy_img_path" hcl:"floppy_img_path"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
BootOrder *string `mapstructure:"boot_order" cty:"boot_order" hcl:"boot_order"`
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"`
@ -147,6 +156,8 @@ 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},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"vcenter_server": &hcldec.AttrSpec{Name: "vcenter_server", Type: cty.String, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
@ -183,6 +194,13 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"configuration_parameters": &hcldec.AttrSpec{Name: "configuration_parameters", Type: cty.Map(cty.String), Required: false},
"tools_sync_time": &hcldec.AttrSpec{Name: "tools_sync_time", Type: cty.Bool, Required: false},
"tools_upgrade_policy": &hcldec.AttrSpec{Name: "tools_upgrade_policy", Type: cty.Bool, Required: false},
"cdrom_type": &hcldec.AttrSpec{Name: "cdrom_type", Type: cty.String, Required: false},
"iso_paths": &hcldec.AttrSpec{Name: "iso_paths", Type: cty.List(cty.String), Required: false},
"remove_cdrom": &hcldec.AttrSpec{Name: "remove_cdrom", Type: cty.Bool, Required: false},
"floppy_img_path": &hcldec.AttrSpec{Name: "floppy_img_path", Type: cty.String, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"boot_order": &hcldec.AttrSpec{Name: "boot_order", 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},

View File

@ -1,7 +1,7 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type CDRomConfig
package iso
package common
import (
"context"

View File

@ -1,5 +1,5 @@
// Code generated by "mapstructure-to-hcl2 -type CDRomConfig"; DO NOT EDIT.
package iso
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"context"
@ -96,7 +96,7 @@ func TestStepAddCDRom_Run(t *testing.T) {
},
{
name: "Add SATA Controller",
state: basicStateBag(),
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
CdromType: "sata",
@ -116,7 +116,7 @@ func TestStepAddCDRom_Run(t *testing.T) {
},
{
name: "Fail to add SATA Controller",
state: basicStateBag(),
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
CdromType: "sata",
@ -136,7 +136,7 @@ func TestStepAddCDRom_Run(t *testing.T) {
},
{
name: "IDE CDRom Type and Iso Path set",
state: basicStateBag(),
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
CdromType: "ide",
@ -156,7 +156,7 @@ func TestStepAddCDRom_Run(t *testing.T) {
},
{
name: "Fail to add cdrom from ISOPaths",
state: basicStateBag(),
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
ISOPaths: []string{"iso/path"},
@ -222,14 +222,14 @@ func TestStepAddCDRom_Run(t *testing.T) {
}
func cdAndIsoRemotePathStateBag() *multistep.BasicStateBag {
state := basicStateBag()
state := basicStateBag(nil)
state.Put("iso_remote_path", "remote/path")
state.Put("cd_path", "cd/path")
return state
}
func isoRemotePathStateBag() *multistep.BasicStateBag {
state := basicStateBag()
state := basicStateBag(nil)
state.Put("iso_remote_path", "remote/path")
return state
}

View File

@ -1,7 +1,7 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type FloppyConfig
package iso
package common
import (
"context"
@ -37,8 +37,8 @@ type StepAddFloppy struct {
func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
d := state.Get("driver").(*driver.VCenterDriver)
vm := state.Get("vm").(driver.VirtualMachine)
d := state.Get("driver").(driver.Driver)
if floppyPath, ok := state.GetOk("floppy_path"); ok {
ui.Say("Uploading created floppy image")
@ -90,7 +90,7 @@ func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
}
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.VCenterDriver)
d := state.Get("driver").(driver.Driver)
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting Floppy image ...")

View File

@ -1,5 +1,5 @@
// Code generated by "mapstructure-to-hcl2 -type FloppyConfig"; DO NOT EDIT.
package iso
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"

View File

@ -0,0 +1,453 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepAddFloppy_Run(t *testing.T) {
tc := []struct {
name string
floppyPath string
uploadedPath string
step *StepAddFloppy
expectedAction multistep.StepAction
vmMock *driver.VirtualMachineMock
expectedVmMock *driver.VirtualMachineMock
driverMock *driver.DriverMock
expectedDriverMock *driver.DriverMock
dsMock *driver.DatastoreMock
expectedDsMock *driver.DatastoreMock
fail bool
errMessage string
}{
{
name: "Add floppy from state floppy path",
floppyPath: "floppy/path",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionContinue,
vmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
GetDirCalled: true,
AddFloppyCalled: true,
AddFloppyImagePath: "resolved/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
ResolvePathReturn: "resolved/path",
},
expectedDsMock: &driver.DatastoreMock{
UploadFileCalled: true,
UploadFileSrc: "floppy/path",
UploadFileDst: "vm/dir/packer-tmp-created-floppy.flp",
UploadFileHost: "host",
UploadFileSetHost: true,
ResolvePathCalled: true,
ResolvePathReturn: "resolved/path",
},
fail: false,
},
{
name: "State floppy path - find datastore fail",
floppyPath: "floppy/path",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: new(driver.VirtualMachineMock),
driverMock: &driver.DriverMock{
FindDatastoreErr: fmt.Errorf("error finding datastore"),
},
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "error finding datastore",
},
{
name: "State floppy path - vm get dir fail",
floppyPath: "floppy/path",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
GetDirErr: fmt.Errorf("fail to get vm dir"),
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirCalled: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "fail to get vm dir",
},
{
name: "State floppy path - datastore upload file fail",
floppyPath: "floppy/path",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
GetDirCalled: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
UploadFileErr: fmt.Errorf("failed to upload file"),
},
expectedDsMock: &driver.DatastoreMock{
UploadFileCalled: true,
UploadFileSrc: "floppy/path",
UploadFileDst: "vm/dir/packer-tmp-created-floppy.flp",
UploadFileHost: "host",
UploadFileSetHost: true,
},
fail: true,
errMessage: "failed to upload file",
},
{
name: "State floppy path - vm fail to add floppy",
floppyPath: "floppy/path",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
AddFloppyErr: fmt.Errorf("failed to add floppy"),
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
GetDirCalled: true,
AddFloppyCalled: true,
AddFloppyImagePath: "resolved/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
ResolvePathReturn: "resolved/path",
},
expectedDsMock: &driver.DatastoreMock{
UploadFileCalled: true,
UploadFileSrc: "floppy/path",
UploadFileDst: "vm/dir/packer-tmp-created-floppy.flp",
UploadFileHost: "host",
UploadFileSetHost: true,
ResolvePathCalled: true,
ResolvePathReturn: "resolved/path",
},
fail: true,
errMessage: "failed to add floppy",
},
{
name: "Add floppy from FloppyIMGPath config",
step: &StepAddFloppy{
Config: &FloppyConfig{
FloppyIMGPath: "floppy/image/path",
},
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
AddFloppyCalled: true,
AddFloppyImagePath: "floppy/image/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Fail to add floppy from FloppyIMGPath config",
step: &StepAddFloppy{
Config: &FloppyConfig{
FloppyIMGPath: "floppy/image/path",
},
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
AddFloppyErr: fmt.Errorf("fail to add floppy"),
},
expectedVmMock: &driver.VirtualMachineMock{
AddFloppyCalled: true,
AddFloppyImagePath: "floppy/image/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "fail to add floppy",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
state.Put("vm", c.vmMock)
c.driverMock.DatastoreMock = c.dsMock
state.Put("driver", c.driverMock)
if c.floppyPath != "" {
state.Put("floppy_path", c.floppyPath)
}
if action := c.step.Run(context.TODO(), state); action != c.expectedAction {
t.Fatalf("unexpected action %v", action)
}
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
uploadedPath, _ := state.Get("uploaded_floppy_path").(string)
if uploadedPath != c.uploadedPath {
t.Fatalf("Unexpected uploaded path state %s", uploadedPath)
}
if diff := cmp.Diff(c.vmMock, c.expectedVmMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected VirtualMachine calls: %s", diff)
}
c.expectedDriverMock.DatastoreMock = c.expectedDsMock
if diff := cmp.Diff(c.driverMock, c.expectedDriverMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Driver calls: %s", diff)
}
if diff := cmp.Diff(c.dsMock, c.expectedDsMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Datastore calls: %s", diff)
}
})
}
}
func TestStepAddFloppy_Cleanup(t *testing.T) {
tc := []struct {
name string
uploadedPath string
multistepState string
step *StepAddFloppy
driverMock *driver.DriverMock
expectedDriverMock *driver.DriverMock
dsMock *driver.DatastoreMock
expectedDsMock *driver.DatastoreMock
fail bool
errMessage string
}{
{
name: "State cancelled clean up",
uploadedPath: "uploaded/path",
multistepState: multistep.StateCancelled,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteCalled: true,
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "uploaded/path",
},
fail: false,
},
{
name: "State halted clean up",
uploadedPath: "uploaded/path",
multistepState: multistep.StateHalted,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteCalled: true,
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "uploaded/path",
},
fail: false,
},
{
name: "Don't clean up without uploaded path",
multistepState: multistep.StateHalted,
step: new(StepAddFloppy),
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Don't clean up if state is not halted or canceled",
multistepState: "",
step: new(StepAddFloppy),
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Fail because datastore is not found",
uploadedPath: "uploaded/path",
multistepState: multistep.StateHalted,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: &driver.DriverMock{
FindDatastoreErr: fmt.Errorf("fail to find datastore"),
},
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "fail to find datastore",
},
{
name: "Fail to delete floppy",
uploadedPath: "uploaded/path",
multistepState: multistep.StateHalted,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeleteErr: fmt.Errorf("failed to delete floppy"),
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "uploaded/path",
},
fail: true,
errMessage: "failed to delete floppy",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
c.driverMock.DatastoreMock = c.dsMock
state.Put("driver", c.driverMock)
if c.uploadedPath != "" {
state.Put("uploaded_floppy_path", c.uploadedPath)
}
if c.multistepState != "" {
state.Put(c.multistepState, true)
}
c.step.Cleanup(state)
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
c.expectedDriverMock.DatastoreMock = c.expectedDsMock
if diff := cmp.Diff(c.driverMock, c.expectedDriverMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Driver calls: %s", diff)
}
if diff := cmp.Diff(c.dsMock, c.expectedDsMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Datastore calls: %s", diff)
}
})
}
}

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"context"

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"context"
@ -10,7 +10,7 @@ import (
)
func TestStepRemoteUpload_Run(t *testing.T) {
state := basicStateBag()
state := basicStateBag(nil)
driverMock := driver.NewDriverMock()
state.Put("driver", driverMock)
state.Put("iso_path", "[datastore] iso/path")
@ -48,7 +48,7 @@ func TestStepRemoteUpload_Run(t *testing.T) {
}
func TestStepRemoteUpload_SkipRun(t *testing.T) {
state := basicStateBag()
state := basicStateBag(nil)
driverMock := driver.NewDriverMock()
state.Put("driver", driverMock)

View File

@ -1,7 +1,7 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type RemoveCDRomConfig
package iso
package common
import (
"context"
@ -22,7 +22,7 @@ type StepRemoveCDRom struct {
func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
vm := state.Get("vm").(driver.VirtualMachine)
ui.Say("Eject CD-ROM drives...")
err := vm.EjectCdroms()

View File

@ -1,5 +1,5 @@
// Code generated by "mapstructure-to-hcl2 -type RemoveCDRomConfig"; DO NOT EDIT.
package iso
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"

View File

@ -0,0 +1,111 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepRemoveCDRom_Run(t *testing.T) {
tc := []struct {
name string
step *StepRemoveCDRom
expectedAction multistep.StepAction
vmMock *driver.VirtualMachineMock
expectedVmMock *driver.VirtualMachineMock
fail bool
errMessage string
}{
{
name: "Eject CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{},
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
},
fail: false,
},
{
name: "Failed to eject CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{},
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
EjectCdromsErr: fmt.Errorf("failed to eject cd-rom drives"),
},
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
},
fail: true,
errMessage: "failed to eject cd-rom drives",
},
{
name: "Eject and delete CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{
RemoveCdrom: true,
},
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
RemoveCdromsCalled: true,
},
fail: false,
},
{
name: "Fail to delete CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{
RemoveCdrom: true,
},
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
RemoveCdromsErr: fmt.Errorf("failed to delete cd-rom devices"),
},
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
RemoveCdromsCalled: true,
},
fail: true,
errMessage: "failed to delete cd-rom devices",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
state.Put("vm", c.vmMock)
if action := c.step.Run(context.TODO(), state); action != c.expectedAction {
t.Fatalf("unexpected action %v", action)
}
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
if diff := cmp.Diff(c.vmMock, c.expectedVmMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected VirtualMachine calls: %s", diff)
}
})
}
}

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"context"
@ -6,7 +6,6 @@ import (
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/vmware/govmomi/vim25/types"
)
type StepRemoveFloppy struct {
@ -16,16 +15,15 @@ type StepRemoveFloppy struct {
func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
d := state.Get("driver").(*driver.VCenterDriver)
vm := state.Get("vm").(driver.VirtualMachine)
d := state.Get("driver").(driver.Driver)
ui.Say("Deleting Floppy drives...")
devices, err := vm.Devices()
floppies, err := vm.FloppyDevices()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
floppies := devices.SelectByType((*types.VirtualFloppy)(nil))
if err = vm.RemoveDevice(true, floppies...); err != nil {
state.Put("error", err)
return multistep.ActionHalt

View File

@ -0,0 +1,213 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepRemoveFloppy_Run(t *testing.T) {
tc := []struct {
name string
uploadedPath string
step *StepRemoveFloppy
expectedAction multistep.StepAction
vmMock *driver.VirtualMachineMock
expectedVmMock *driver.VirtualMachineMock
driverMock *driver.DriverMock
expectedDriverMock *driver.DriverMock
dsMock *driver.DatastoreMock
expectedDsMock *driver.DatastoreMock
fail bool
errMessage string
}{
{
name: "Remove floppy drives and images",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepRemoveFloppy{
Datastore: "datastore",
Host: "host",
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "vm/dir/packer-tmp-created-floppy.flp",
},
fail: false,
},
{
name: "No floppy image to remove",
step: &StepRemoveFloppy{},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Fail to find floppy devices",
step: &StepRemoveFloppy{},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
FloppyDevicesErr: fmt.Errorf("failed to find floppy devices"),
},
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "failed to find floppy devices",
},
{
name: "Fail to remove floppy devices",
step: &StepRemoveFloppy{},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
RemoveDeviceErr: fmt.Errorf("failed to remove device"),
},
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "failed to remove device",
},
{
name: "Fail to find datastore",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepRemoveFloppy{
Datastore: "datastore",
Host: "host",
},
expectedAction: multistep.ActionHalt,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: &driver.DriverMock{
FindDatastoreErr: fmt.Errorf("failed to find datastore"),
},
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "failed to find datastore",
},
{
name: "Fail to delete floppy image",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepRemoveFloppy{
Datastore: "datastore",
Host: "host",
},
expectedAction: multistep.ActionHalt,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteErr: fmt.Errorf("failed to delete floppy"),
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "vm/dir/packer-tmp-created-floppy.flp",
},
fail: true,
errMessage: "failed to delete floppy",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
state.Put("vm", c.vmMock)
c.driverMock.DatastoreMock = c.dsMock
state.Put("driver", c.driverMock)
if c.uploadedPath != "" {
state.Put("uploaded_floppy_path", c.uploadedPath)
}
if action := c.step.Run(context.TODO(), state); action != c.expectedAction {
t.Fatalf("unexpected action %v", action)
}
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
if !c.fail {
if _, ok := state.GetOk("uploaded_floppy_path"); ok {
t.Fatalf("uploaded_floppy_path should not be in state")
}
}
if diff := cmp.Diff(c.vmMock, c.expectedVmMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected VirtualMachine calls: %s", diff)
}
c.expectedDriverMock.DatastoreMock = c.expectedDsMock
if diff := cmp.Diff(c.driverMock, c.expectedDriverMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Driver calls: %s", diff)
}
if diff := cmp.Diff(c.dsMock, c.expectedDsMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Datastore calls: %s", diff)
}
})
}
}

View File

@ -8,7 +8,20 @@ import (
type DatastoreMock struct {
FileExistsCalled bool
MakeDirectoryCalled bool
UploadFileCalled bool
ResolvePathCalled bool
ResolvePathReturn string
DeleteCalled bool
DeletePath string
DeleteErr error
UploadFileCalled bool
UploadFileSrc string
UploadFileDst string
UploadFileHost string
UploadFileSetHost bool
UploadFileErr error
}
func (ds *DatastoreMock) Info(params ...string) (*mo.Datastore, error) {
@ -29,16 +42,23 @@ func (ds *DatastoreMock) Reference() types.ManagedObjectReference {
}
func (ds *DatastoreMock) ResolvePath(path string) string {
return ""
ds.ResolvePathCalled = true
return ds.ResolvePathReturn
}
func (ds *DatastoreMock) UploadFile(src, dst, host string, setHost bool) error {
ds.UploadFileCalled = true
return nil
ds.UploadFileSrc = src
ds.UploadFileDst = dst
ds.UploadFileHost = host
ds.UploadFileSetHost = setHost
return ds.UploadFileErr
}
func (ds *DatastoreMock) Delete(path string) error {
return nil
ds.DeleteCalled = true
ds.DeletePath = path
return ds.DeleteErr
}
func (ds *DatastoreMock) MakeDirectory(path string) error {

View File

@ -1,6 +1,10 @@
package driver
import "testing"
import (
"testing"
"github.com/vmware/govmomi/simulator"
)
func TestDatastoreIsoPath(t *testing.T) {
tc := []struct {
@ -87,3 +91,84 @@ func TestDatastoreIsoPath(t *testing.T) {
}
}
}
func TestVCenterDriver_FindDatastore(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
_, datastore := sim.ChooseSimulatorPreCreatedDatastore()
_, host := sim.ChooseSimulatorPreCreatedHost()
tc := []struct {
name string
datastore string
host string
fail bool
errMessage string
}{
{
name: "should find datastore when name is provided",
datastore: datastore.Name,
fail: false,
},
{
name: "should find datastore when only host is provided",
host: host.Name,
fail: false,
},
{
name: "should not find invalid datastore",
datastore: "invalid",
fail: true,
},
{
name: "should not find invalid host",
host: "invalid",
fail: true,
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
ds, err := sim.driver.FindDatastore(c.datastore, c.host)
if c.fail {
if err == nil {
t.Fatalf("expected to fail")
}
if c.errMessage != "" && err.Error() != c.errMessage {
t.Fatalf("unexpected error message %s", err.Error())
}
} else {
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if ds == nil {
t.Fatalf("expected to find datastore")
}
}
})
}
}
func TestVCenterDriver_MultipleDatastoreError(t *testing.T) {
model := simulator.ESX()
model.Datastore = 2
sim, err := NewCustomVCenterSimulator(model)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
_, host := sim.ChooseSimulatorPreCreatedHost()
_, err = sim.driver.FindDatastore("", host.Name)
if err == nil {
t.Fatalf("expected to fail")
}
if err.Error() != "Host has multiple datastores. Specify it explicitly" {
t.Fatalf("unexpected error message %s", err.Error())
}
}

View File

@ -11,6 +11,9 @@ import (
type DriverMock struct {
FindDatastoreCalled bool
DatastoreMock *DatastoreMock
FindDatastoreName string
FindDatastoreHost string
FindDatastoreErr error
PreCleanShouldFail bool
PreCleanVMCalled bool
@ -32,7 +35,9 @@ func (d *DriverMock) FindDatastore(name string, host string) (Datastore, error)
if d.DatastoreMock == nil {
d.DatastoreMock = new(DatastoreMock)
}
return d.DatastoreMock, nil
d.FindDatastoreName = name
d.FindDatastoreHost = host
return d.DatastoreMock, d.FindDatastoreErr
}
func (d *DriverMock) NewVM(ref *types.ManagedObjectReference) VirtualMachine {

View File

@ -50,24 +50,89 @@ func newVMName() string {
return fmt.Sprintf("test-%v", rand.Intn(1000))
}
func NewSimulatorServer(model *simulator.Model) (*simulator.Server, error) {
err := model.Create()
type VCenterSimulator struct {
model *simulator.Model
server *simulator.Server
driver *VCenterDriver
}
func NewCustomVCenterSimulator(model *simulator.Model) (*VCenterSimulator, error) {
sim := new(VCenterSimulator)
sim.model = model
server, err := sim.NewSimulatorServer()
if err != nil {
sim.Close()
return nil, err
}
sim.server = server
driver, err := sim.NewSimulatorDriver()
if err != nil {
sim.Close()
return nil, err
}
sim.driver = driver
return sim, nil
}
func NewVCenterSimulator() (*VCenterSimulator, error) {
model := simulator.VPX()
model.Machine = 1
return NewCustomVCenterSimulator(model)
}
func (s *VCenterSimulator) Close() {
if s.model != nil {
s.model.Remove()
}
if s.server != nil {
s.server.Close()
}
}
//Simulator shortcut to choose any pre created VM.
func (s *VCenterSimulator) ChooseSimulatorPreCreatedVM() (VirtualMachine, *simulator.VirtualMachine) {
machine := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine)
ref := machine.Reference()
vm := s.driver.NewVM(&ref)
return vm, machine
}
//Simulator shortcut to choose any pre created Datastore.
func (s *VCenterSimulator) ChooseSimulatorPreCreatedDatastore() (Datastore, *simulator.Datastore) {
ds := simulator.Map.Any("Datastore").(*simulator.Datastore)
ref := ds.Reference()
datastore := s.driver.NewDatastore(&ref)
return datastore, ds
}
//Simulator shortcut to choose any pre created Host.
func (s *VCenterSimulator) ChooseSimulatorPreCreatedHost() (*Host, *simulator.HostSystem) {
h := simulator.Map.Any("HostSystem").(*simulator.HostSystem)
ref := h.Reference()
host := s.driver.NewHost(&ref)
return host, h
}
func (s *VCenterSimulator) NewSimulatorServer() (*simulator.Server, error) {
err := s.model.Create()
if err != nil {
return nil, err
}
model.Service.RegisterEndpoints = true
model.Service.TLS = new(tls.Config)
model.Service.ServeMux = http.NewServeMux()
return model.Service.NewServer(), nil
s.model.Service.RegisterEndpoints = true
s.model.Service.TLS = new(tls.Config)
s.model.Service.ServeMux = http.NewServeMux()
return s.model.Service.NewServer(), nil
}
func NewSimulatorDriver(s *simulator.Server) (*VCenterDriver, error) {
func (s *VCenterSimulator) NewSimulatorDriver() (*VCenterDriver, error) {
ctx := context.TODO()
user := &url.Userinfo{}
s.URL.User = user
s.server.URL.User = user
soapClient := soap.NewClient(s.URL, true)
soapClient := soap.NewClient(s.server.URL, true)
vimClient, err := vim25.NewClient(ctx, soapClient)
if err != nil {
return nil, err
@ -104,11 +169,3 @@ func NewSimulatorDriver(s *simulator.Server) (*VCenterDriver, error) {
}
return d, nil
}
//Simulator shortcut to choose any pre created VM.
func ChooseSimulatorPreCreatedVM(driverSim *VCenterDriver) VirtualMachine {
machine := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine)
ref := machine.Reference()
vm := driverSim.NewVM(&ref)
return vm
}

View File

@ -7,21 +7,13 @@ import (
)
func TestVCenterDriver_FindResourcePool(t *testing.T) {
model := simulator.VPX()
defer model.Remove()
s, err := NewSimulatorServer(model)
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
defer sim.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
res, err := driverSim.FindResourcePool("", "DC0_H0", "")
res, err := sim.driver.FindResourcePool("", "DC0_H0", "")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
@ -47,19 +39,13 @@ func TestVCenterDriver_FindResourcePoolStandaloneESX(t *testing.T) {
model.DelayConfig.MethodDelay = opts.DelayConfig.MethodDelay
model.DelayConfig.DelayJitter = opts.DelayConfig.DelayJitter
s, err := NewSimulatorServer(model)
sim, err := NewCustomVCenterSimulator(model)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
defer sim.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//
res, err := driverSim.FindResourcePool("", "localhost.localdomain", "")
res, err := sim.driver.FindResourcePool("", "localhost.localdomain", "")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
@ -72,7 +58,7 @@ func TestVCenterDriver_FindResourcePoolStandaloneESX(t *testing.T) {
}
// Invalid resource name should look for default resource pool
res, err = driverSim.FindResourcePool("", "localhost.localdomain", "invalid")
res, err = sim.driver.FindResourcePool("", "localhost.localdomain", "invalid")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}

View File

@ -24,6 +24,7 @@ import (
type VirtualMachine interface {
Info(params ...string) (*mo.VirtualMachine, error)
Devices() (object.VirtualDeviceList, error)
FloppyDevices() (object.VirtualDeviceList, error)
Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error)
updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error)
AddPublicKeys(ctx context.Context, publicKeys string) error
@ -284,6 +285,15 @@ func (vm *VirtualMachineDriver) Devices() (object.VirtualDeviceList, error) {
return vmInfo.Config.Hardware.Device, nil
}
func (vm *VirtualMachineDriver) FloppyDevices() (object.VirtualDeviceList, error) {
device, err := vm.Devices()
if err != nil {
return device, err
}
floppies := device.SelectByType((*types.VirtualFloppy)(nil))
return floppies, nil
}
func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) {
folder, err := vm.driver.FindFolder(config.Folder)
if err != nil {

View File

@ -5,27 +5,17 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25/types"
)
func TestVirtualMachineDriver_FindAndAddSATAController(t *testing.T) {
model := simulator.VPX()
model.Machine = 1
defer model.Remove()
s, err := NewSimulatorServer(model)
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
defer sim.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
vm := ChooseSimulatorPreCreatedVM(driverSim)
vm, _ := sim.ChooseSimulatorPreCreatedVM()
_, err = vm.FindSATAController()
if err != nil && !strings.Contains(err.Error(), "no available SATA controller") {
@ -49,23 +39,13 @@ func TestVirtualMachineDriver_FindAndAddSATAController(t *testing.T) {
}
func TestVirtualMachineDriver_CreateAndRemoveCdrom(t *testing.T) {
model := simulator.VPX()
model.Machine = 1
defer model.Remove()
s, err := NewSimulatorServer(model)
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
defer sim.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//Simulator shortcut to choose any pre created VM.
vm := ChooseSimulatorPreCreatedVM(driverSim)
vm, _ := sim.ChooseSimulatorPreCreatedVM()
// Add SATA Controller
if err := vm.AddSATAController(); err != nil {
@ -118,23 +98,13 @@ func TestVirtualMachineDriver_CreateAndRemoveCdrom(t *testing.T) {
}
func TestVirtualMachineDriver_EjectCdrom(t *testing.T) {
model := simulator.VPX()
model.Machine = 1
defer model.Remove()
s, err := NewSimulatorServer(model)
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
defer sim.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//Simulator shortcut to choose any pre created VM.
vm := ChooseSimulatorPreCreatedVM(driverSim)
vm, _ := sim.ChooseSimulatorPreCreatedVM()
// Add SATA Controller
if err := vm.AddSATAController(); err != nil {

View File

@ -32,6 +32,29 @@ type VirtualMachineMock struct {
AddCdromErr error
AddCdromTypes []string
AddCdromPaths []string
GetDirCalled bool
GetDirResponse string
GetDirErr error
AddFloppyCalled bool
AddFloppyImagePath string
AddFloppyErr error
FloppyDevicesErr error
FloppyDevicesReturn object.VirtualDeviceList
FloppyDevicesCalled bool
RemoveDeviceErr error
RemoveDeviceCalled bool
RemoveDeviceKeepFiles bool
RemoveDeviceDevices []types.BaseVirtualDevice
EjectCdromsCalled bool
EjectCdromsErr error
RemoveCdromsCalled bool
RemoveCdromsErr error
}
func (vm *VirtualMachineMock) Info(params ...string) (*mo.VirtualMachine, error) {
@ -42,6 +65,11 @@ func (vm *VirtualMachineMock) Devices() (object.VirtualDeviceList, error) {
return object.VirtualDeviceList{}, nil
}
func (vm *VirtualMachineMock) FloppyDevices() (object.VirtualDeviceList, error) {
vm.FloppyDevicesCalled = true
return vm.FloppyDevicesReturn, vm.FloppyDevicesErr
}
func (vm *VirtualMachineMock) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) {
return nil, nil
}
@ -124,7 +152,8 @@ func (vm *VirtualMachineMock) ImportToContentLibrary(template vcenter.Template)
}
func (vm *VirtualMachineMock) GetDir() (string, error) {
return "", nil
vm.GetDirCalled = true
return vm.GetDirResponse, vm.GetDirErr
}
func (vm *VirtualMachineMock) AddCdrom(cdromType string, isoPath string) error {
@ -136,7 +165,9 @@ func (vm *VirtualMachineMock) AddCdrom(cdromType string, isoPath string) error {
}
func (vm *VirtualMachineMock) AddFloppy(imgPath string) error {
return nil
vm.AddFloppyCalled = true
vm.AddFloppyImagePath = imgPath
return vm.AddFloppyErr
}
func (vm *VirtualMachineMock) SetBootOrder(order []string) error {
@ -144,7 +175,10 @@ func (vm *VirtualMachineMock) SetBootOrder(order []string) error {
}
func (vm *VirtualMachineMock) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
return nil
vm.RemoveDeviceCalled = true
vm.RemoveDeviceKeepFiles = keepFiles
vm.RemoveDeviceDevices = device
return vm.RemoveDeviceErr
}
func (vm *VirtualMachineMock) addDevice(device types.BaseVirtualDevice) error {
@ -186,9 +220,11 @@ func (vm *VirtualMachineMock) CreateCdrom(c *types.VirtualController) (*types.Vi
}
func (vm *VirtualMachineMock) RemoveCdroms() error {
return nil
vm.RemoveCdromsCalled = true
return vm.RemoveCdromsErr
}
func (vm *VirtualMachineMock) EjectCdroms() error {
return nil
vm.EjectCdromsCalled = true
return vm.EjectCdromsErr
}

View File

@ -28,25 +28,13 @@ func (vm *ReconfigureFail) ReconfigVMTask(req *types.ReconfigVM_Task) soap.HasFa
}
func TestVirtualMachineDriver_Configure(t *testing.T) {
model := simulator.VPX()
model.Machine = 1
defer model.Remove()
s, err := NewSimulatorServer(model)
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer s.Close()
defer sim.Close()
driverSim, err := NewSimulatorDriver(s)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//Simulator shortcut to choose any pre created VM.
machine := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine)
ref := machine.Reference()
vm := driverSim.NewVM(&ref)
vm, machine := sim.ChooseSimulatorPreCreatedVM()
// Happy test
hardwareConfig := &HardwareConfig{

View File

@ -52,7 +52,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&StepRemoteUpload{
&common.StepRemoteUpload{
Datastore: b.config.Datastore,
Host: b.config.Host,
SetHostForDatastoreUploads: b.config.SetHostForDatastoreUploads,
@ -65,7 +65,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&common.StepConfigureHardware{
Config: &b.config.HardwareConfig,
},
&StepAddCDRom{
&common.StepAddCDRom{
Config: &b.config.CDRomConfig,
},
&common.StepConfigParams{
@ -80,7 +80,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Directories: b.config.FloppyDirectories,
Label: b.config.FloppyLabel,
},
&StepAddFloppy{
&common.StepAddFloppy{
Config: &b.config.FloppyConfig,
Datastore: b.config.Datastore,
Host: b.config.Host,
@ -117,7 +117,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&common.StepShutdown{
Config: &b.config.ShutdownConfig,
},
&StepRemoveFloppy{
&common.StepRemoveFloppy{
Datastore: b.config.Datastore,
Host: b.config.Host,
},
@ -125,7 +125,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
steps = append(steps,
&StepRemoveCDRom{
&common.StepRemoveCDRom{
Config: &b.config.RemoveCDRomConfig,
},
&common.StepCreateSnapshot{

View File

@ -25,13 +25,13 @@ type Config struct {
packerCommon.ISOConfig `mapstructure:",squash"`
CDRomConfig `mapstructure:",squash"`
RemoveCDRomConfig `mapstructure:",squash"`
FloppyConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
common.BootConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.CDRomConfig `mapstructure:",squash"`
common.RemoveCDRomConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
common.BootConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`

View File

@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/hashicorp/packer/helper/builder/localexec"
@ -167,12 +168,34 @@ var supportedCDISOCreationCommands []cdISOCreationCommand = []cdISOCreationComma
},
}
func isCygwinExecutable(path string) bool {
return runtime.GOOS == "windows" && strings.Contains(path, "\\usr\\bin\\")
}
func toCygwinPath(path string) (string, error) {
c := exec.Command("cygpath", path)
cygwinPath, err := c.Output()
return strings.TrimSpace(string(cygwinPath)), err
}
func retrieveCDISOCreationCommand(label string, source string, dest string) (*exec.Cmd, error) {
for _, c := range supportedCDISOCreationCommands {
path, err := exec.LookPath(c.Name)
if err != nil {
continue
}
// if we are running a cygwin/msys2 executable we must convert the
// native win32 path to a cygwin/msys2/unix style path.
if isCygwinExecutable(path) {
source, err = toCygwinPath(source)
if err != nil {
return nil, err
}
dest, err = toCygwinPath(dest)
if err != nil {
return nil, err
}
}
return c.Command(path, label, source, dest), nil
}
var commands = make([]string, 0, len(supportedCDISOCreationCommands))

View File

@ -245,6 +245,7 @@ func TestStepDownload_download(t *testing.T) {
ui := &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
PB: &packer.NoopProgressTracker{},
}
dir := createTempDir(t)

View File

@ -16,6 +16,7 @@ func testState(t *testing.T) multistep.StateBag {
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
PB: &packer.NoopProgressTracker{},
})
return state
}

9
go.mod
View File

@ -22,7 +22,7 @@ require (
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 // indirect
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
github.com/aws/aws-sdk-go v1.30.8
github.com/aws/aws-sdk-go v1.34.26
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
github.com/cheggaaa/pb v1.0.27
@ -40,9 +40,6 @@ require (
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-resty/resty/v2 v2.3.0
github.com/gobwas/glob v0.2.3
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.0.4 // indirect
github.com/gofrs/flock v0.7.3
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/golang/protobuf v1.4.2 // indirect
@ -53,14 +50,14 @@ require (
github.com/gophercloud/gophercloud v0.12.0
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026
github.com/hashicorp/aws-sdk-go-base v0.6.0
github.com/hashicorp/consul/api v1.4.0
github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-cty-funcs v0.0.0-20200520133146-0d04eb807361
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
github.com/hashicorp/go-getter/gcs/v2 v2.0.0-20200604122502-a6995fa1edad
github.com/hashicorp/go-getter/s3/v2 v2.0.0-20200604122502-a6995fa1edad
github.com/hashicorp/go-getter/v2 v2.0.0-20200604122502-a6995fa1edad

52
go.sum
View File

@ -129,6 +129,9 @@ github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3A
github.com/aws/aws-sdk-go v1.16.22/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.30.8 h1:4BHbh8K3qKmcnAgToZ2LShldRF9inoqIBccpCLNCy3I=
github.com/aws/aws-sdk-go v1.30.8/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.34.26 h1:tw4nsSfGvCDnXt2xPe8NkxIrDui+asAWinMknPLEf80=
github.com/aws/aws-sdk-go v1.34.26/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
@ -172,8 +175,6 @@ github.com/digitalocean/godo v1.11.1 h1:OsTh37YFKk+g6DnAOrkXJ9oDArTkRx5UTkBJ2EWA
github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/diskfs/go-diskfs v1.1.1 h1:rMjLpaydtXGVZb7mdkRGK1+//30i76nKAit89zUzeaI=
github.com/diskfs/go-diskfs v1.1.1/go.mod h1:afUPxxu+x1snp4aCY2bKR0CoZ/YFJewV3X2UEr2nPZE=
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
@ -217,12 +218,6 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gofrs/flock v0.7.3 h1:I0EKY9l8HZCXTMYC4F80vwT6KNypV9uYKP3Alm/hjmQ=
github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
@ -288,6 +283,7 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -313,12 +309,12 @@ github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ=
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE=
github.com/hashicorp/aws-sdk-go-base v0.6.0 h1:qmUbzM36msbBF59YctwuO5w0M2oNXjlilgKpnEhx1uw=
github.com/hashicorp/aws-sdk-go-base v0.6.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY=
github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY=
github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU=
github.com/hashicorp/consul/sdk v0.4.0 h1:zBtCfKJZcJDBvSCkQJch4ulp59m1rATFLKwNo/LYY30=
@ -331,8 +327,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6K
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cty-funcs v0.0.0-20200520133146-0d04eb807361 h1:qFuR7ZaMDC5xelTZeNwsJ90I4+4km7ACDdAP+2w84xI=
github.com/hashicorp/go-cty-funcs v0.0.0-20200520133146-0d04eb807361/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA=
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840 h1:kgvybwEeu0SXktbB2y3uLHX9lklLo+nzUwh59A3jzQc=
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA=
github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA=
github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
github.com/hashicorp/go-getter/gcs/v2 v2.0.0-20200604122502-a6995fa1edad h1:QPLyAkuTS5Uf9uqJQxCTDDFgmD+gVAzSvqkGb3h+7oQ=
@ -380,7 +376,6 @@ github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
@ -517,8 +512,6 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -619,14 +612,6 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/sylviamoss/go-vnc v0.0.0-20200908143403-27ce26f2bed9 h1:uEIT4Mi8C8O+S44150F3lug+WaZ01aJ0sLHvN4npMsc=
github.com/sylviamoss/go-vnc v0.0.0-20200908143403-27ce26f2bed9/go.mod h1:+I9wdVxvb9OqA4vpPvYoLvAeSx+Ew5PF6SiOWekjkGE=
github.com/sylviamoss/go-vnc v0.0.0-20200908143844-452d6786ec66 h1:4pQ2Ex/3Fxn7WDvpa85XqHLkc+cSoceIefX/8mD518o=
github.com/sylviamoss/go-vnc v0.0.0-20200908143844-452d6786ec66/go.mod h1:+I9wdVxvb9OqA4vpPvYoLvAeSx+Ew5PF6SiOWekjkGE=
github.com/sylviamoss/go-vnc v0.0.0-20200908144818-694f171108bf h1:HUHmcnxuPAJktJM1acw5AckmPvOWz/BoenBLB+IMuo0=
github.com/sylviamoss/go-vnc v0.0.0-20200908144818-694f171108bf/go.mod h1:+I9wdVxvb9OqA4vpPvYoLvAeSx+Ew5PF6SiOWekjkGE=
github.com/sylviamoss/go-vnc v0.0.0-20200908145922-2061e37ffa00 h1:mURYI59JxRHmjEKY4ZZING6qm6e/R91tUqdwFmHoAjY=
github.com/sylviamoss/go-vnc v0.0.0-20200908145922-2061e37ffa00/go.mod h1:+I9wdVxvb9OqA4vpPvYoLvAeSx+Ew5PF6SiOWekjkGE=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible h1:bs+0lcG4RELNbE8PsBC9oaPP0/qExr0DuEGnZyocm84=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
@ -764,8 +749,6 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
@ -829,11 +812,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -880,7 +860,6 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2 h1:L/G4KZvrQn7FWLN/LlulBtBzrLUhqjiGfTWWDmrh+IQ=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
@ -894,16 +873,12 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200918201133-e94ab7288189 h1:7E/geNtekOV4N/07EhKz7zyXs0hZhoZZ19R2O2mMHoI=
golang.org/x/tools v0.0.0-20200918201133-e94ab7288189/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266 h1:k7tVuG0g1JwmD3Jh8oAl1vQ1C3jb4Hi/dUl1wWDBJpQ=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -918,7 +893,6 @@ google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY=
google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
@ -932,7 +906,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@ -963,14 +936,11 @@ google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200617032506-f1bdc9086088 h1:XXo4PvhJkaWYIkwn7bX7mcdB8RdcOvn12HbaUUAwX3E=
google.golang.org/genproto v0.0.0-20200617032506-f1bdc9086088/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200918140846-d0d605568037 h1:ujwz1DPMeHwCvo36rK5shXhAzc4GMRecrqQFaMZJBKQ=
@ -986,11 +956,9 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
@ -1002,7 +970,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
@ -1013,8 +980,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/djherbis/times.v1 v1.2.0 h1:UCvDKl1L/fmBygl2Y7hubXCnY7t4Yj46ZrBFNUipFbM=
gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@ -1034,7 +999,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/hashicorp/go-cty-funcs/cidr"
"github.com/hashicorp/go-cty-funcs/collection"
"github.com/hashicorp/go-cty-funcs/crypto"
"github.com/hashicorp/go-cty-funcs/encoding"
"github.com/hashicorp/go-cty-funcs/filesystem"
@ -41,7 +42,7 @@ func Functions(basedir string) map[string]function.Function {
"cidrnetmask": cidr.NetmaskFunc,
"cidrsubnet": cidr.SubnetFunc,
"cidrsubnets": cidr.SubnetsFunc,
"coalesce": stdlib.CoalesceFunc,
"coalesce": collection.CoalesceFunc,
"coalescelist": stdlib.CoalesceListFunc,
"compact": stdlib.CompactFunc,
"concat": stdlib.ConcatFunc,

View File

@ -7,7 +7,7 @@ build {
]
provisioner "shell" {
string = "string"
string = coalesce(null, "", "string")
int = "${41 + 1}"
int64 = "${42 + 1}"
bool = "true"

View File

@ -196,6 +196,7 @@ func wrappedMain() int {
Reader: os.Stdin,
Writer: os.Stdout,
ErrorWriter: os.Stdout,
PB: &packer.NoopProgressTracker{},
}
ui = basicUi
if !inPlugin {
@ -211,6 +212,7 @@ func wrappedMain() int {
fmt.Fprintf(os.Stderr, "No tty available: %s\n", err)
} else {
basicUi.TTY = TTY
basicUi.PB = &packer.UiProgressBar{}
defer TTY.Close()
}
}

View File

@ -15,25 +15,17 @@ func ProgressBarConfig(bar *pb.ProgressBar, prefix string) {
bar.Prefix(prefix)
}
var defaultUiProgressBar = &uiProgressBar{}
// uiProgressBar is a self managed progress bar singleton.
// decorate your struct with a *uiProgressBar to
// give it TrackProgress capabilities.
// In TrackProgress if uiProgressBar is nil
// defaultUiProgressBar will be used as
// the progress bar.
type uiProgressBar struct {
// UiProgressBar is a progress bar compatible with go-getter used in our
// UI structs.
type UiProgressBar struct {
lock sync.Mutex
pool *pb.Pool
pbs int
pbs int
}
func (p *uiProgressBar) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
func (p *UiProgressBar) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
if p == nil {
return defaultUiProgressBar.TrackProgress(src, currentSize, totalSize, stream)
return stream
}
p.lock.Lock()
defer p.lock.Unlock()

View File

@ -1,3 +1,3 @@
package packer
type uiProgressBar = NoopProgressTracker
type UiProgressBar = NoopProgressTracker

View File

@ -11,7 +11,7 @@ import (
// The following tests rarelly just happen. So we run them 100 times.
func TestProgressTracking_open_close(t *testing.T) {
var bar *uiProgressBar
var bar *UiProgressBar
tracker := bar.TrackProgress("1,", 1, 42, ioutil.NopCloser(nil))
tracker.Close()
@ -21,7 +21,7 @@ func TestProgressTracking_open_close(t *testing.T) {
}
func TestProgressTracking_multi_open_close(t *testing.T) {
var bar *uiProgressBar
var bar *UiProgressBar
g := errgroup.Group{}
for i := 0; i < 100; i++ {
@ -36,7 +36,7 @@ func TestProgressTracking_multi_open_close(t *testing.T) {
}
func TestProgressTracking_races(t *testing.T) {
var bar *uiProgressBar
var bar *UiProgressBar
g := errgroup.Group{}
for i := 0; i < 100; i++ {

View File

@ -40,11 +40,12 @@ type Ui interface {
Message(string)
Error(string)
Machine(string, ...string)
// TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser)
getter.ProgressTracker
}
type NoopUi struct {
NoopProgressTracker
PB NoopProgressTracker
}
var _ Ui = new(NoopUi)
@ -54,13 +55,16 @@ func (*NoopUi) Say(string) { return }
func (*NoopUi) Message(string) { return }
func (*NoopUi) Error(string) { return }
func (*NoopUi) Machine(string, ...string) { return }
func (u *NoopUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
return u.PB.TrackProgress(src, currentSize, totalSize, stream)
}
// ColoredUi is a UI that is colored using terminal colors.
type ColoredUi struct {
Color UiColor
ErrorColor UiColor
Ui Ui
*uiProgressBar
PB getter.ProgressTracker
}
var _ Ui = new(ColoredUi)
@ -189,7 +193,7 @@ type BasicUi struct {
l sync.Mutex
interrupted bool
TTY TTY
*uiProgressBar
PB getter.ProgressTracker
}
var _ Ui = new(BasicUi)
@ -304,11 +308,15 @@ func (rw *BasicUi) Machine(t string, args ...string) {
log.Printf("machine readable: %s %#v", t, args)
}
func (rw *BasicUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
return rw.PB.TrackProgress(src, currentSize, totalSize, stream)
}
// MachineReadableUi is a UI that only outputs machine-readable output
// to the given Writer.
type MachineReadableUi struct {
Writer io.Writer
NoopProgressTracker
PB NoopProgressTracker
}
var _ Ui = new(MachineReadableUi)
@ -366,11 +374,15 @@ func (u *MachineReadableUi) Machine(category string, args ...string) {
log.Printf("%d,%s,%s,%s\n", now.Unix(), target, category, argsString)
}
func (u *MachineReadableUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
return u.PB.TrackProgress(src, currentSize, totalSize, stream)
}
// TimestampedUi is a UI that wraps another UI implementation and
// prefixes each message with an RFC3339 timestamp
type TimestampedUi struct {
Ui Ui
*uiProgressBar
PB getter.ProgressTracker
}
var _ Ui = new(TimestampedUi)
@ -395,6 +407,10 @@ func (u *TimestampedUi) Machine(message string, args ...string) {
u.Ui.Machine(message, args...)
}
func (u *TimestampedUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
return u.Ui.TrackProgress(src, currentSize, totalSize, stream)
}
func (u *TimestampedUi) timestampLine(string string) string {
return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
}
@ -404,7 +420,7 @@ func (u *TimestampedUi) timestampLine(string string) string {
type SafeUi struct {
Sem chan int
Ui Ui
*uiProgressBar
PB getter.ProgressTracker
}
var _ Ui = new(SafeUi)
@ -440,3 +456,11 @@ func (u *SafeUi) Machine(t string, args ...string) {
u.Ui.Machine(t, args...)
<-u.Sem
}
func (u *SafeUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
u.Sem <- 1
ret := u.Ui.TrackProgress(src, currentSize, totalSize, stream)
<-u.Sem
return ret
}

View File

@ -49,7 +49,7 @@ func (tty *testTTY) ReadString() (string, error) {
func TestColoredUi(t *testing.T) {
bufferUi := testUi()
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi, defaultUiProgressBar}
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi, &UiProgressBar{}}
if !ui.supportsColors() {
t.Skip("skipping for ui without color support")
@ -81,7 +81,7 @@ func TestColoredUi(t *testing.T) {
func TestColoredUi_noColorEnv(t *testing.T) {
bufferUi := testUi()
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi, defaultUiProgressBar}
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi, &UiProgressBar{}}
// Set the env var to get rid of the color
oldenv := os.Getenv("PACKER_NO_COLOR")

View File

@ -18,7 +18,9 @@ type FlatConfig struct {
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"`
@ -28,6 +30,7 @@ type FlatConfig struct {
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"`
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"`
@ -68,7 +71,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*common.FlatAssumeRoleConfig)(nil).HCL2Spec())},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false},
@ -78,6 +83,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"skip_credential_validation": &hcldec.AttrSpec{Name: "skip_credential_validation", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"aws_polling": &hcldec.BlockSpec{TypeName: "aws_polling", Nested: hcldec.ObjectSpec((*common.FlatAWSPollingConfig)(nil).HCL2Spec())},

View File

@ -375,7 +375,7 @@ func (p *Provisioner) setupAdapter(ui packer.Ui, comm packer.Communicator) (stri
keyChecker := ssh.CertChecker{
UserKeyFallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if user := conn.User(); user != p.config.User {
return nil, errors.New(fmt.Sprintf("authentication failed: %s is not a valid user", user))
return nil, fmt.Errorf("authentication failed: %s is not a valid user", user)
}
if !bytes.Equal(k.Marshal(), pubKey.Marshal()) {
@ -648,15 +648,29 @@ func (p *Provisioner) executeGalaxy(ui packer.Ui, comm packer.Communicator) erro
collectionArgs = append(collectionArgs, "-p", filepath.ToSlash(p.config.CollectionsPath))
}
roleInstallError := p.invokeGalaxyCommand(roleArgs, ui, comm)
// Return the error if the role installation failed before attempting the collection install
if roleInstallError != nil {
return roleInstallError
// Search galaxy_file for roles and collections keywords
f, err := ioutil.ReadFile(galaxyFile)
if err != nil {
return err
}
// If all is well, proceed with collection install
// This variable isn't strictly necessary but including for readability to match the role installation
collectionInstallError := p.invokeGalaxyCommand(collectionArgs, ui, comm)
return collectionInstallError
hasRoles, _ := regexp.Match(`(?m)^roles:`, f)
hasCollections, _ := regexp.Match(`(?m)^collections:`, f)
// If if roles keyword present (v2 format), or no collections keywork present (v1), install roles
if hasRoles || !hasCollections {
if roleInstallError := p.invokeGalaxyCommand(roleArgs, ui, comm); roleInstallError != nil {
return roleInstallError
}
}
// If collections keyword present (v2 format), install collections
if hasCollections {
if collectionInstallError := p.invokeGalaxyCommand(collectionArgs, ui, comm); collectionInstallError != nil {
return collectionInstallError
}
}
return nil
}
// Intended to be invoked from p.executeGalaxy depending on the Ansible Galaxy parameters passed to Packer

View File

@ -70,7 +70,7 @@ $xml = [xml]@'
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<Priority>4</Priority>
</Settings>
<Actions Context="Author">

View File

@ -126,6 +126,7 @@ func TestProvisionerProvision_SendsFile(t *testing.T) {
b := bytes.NewBuffer(nil)
ui := &packer.BasicUi{
Writer: b,
PB: &packer.NoopProgressTracker{},
}
comm := &packer.MockCommunicator{}
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
@ -184,6 +185,7 @@ func TestProvisionerProvision_SendsFileMultipleFiles(t *testing.T) {
b := bytes.NewBuffer(nil)
ui := &packer.BasicUi{
Writer: b,
PB: &packer.NoopProgressTracker{},
}
comm := &packer.MockCommunicator{}
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
@ -253,6 +255,7 @@ func TestProvisionerProvision_SendsFileMultipleDirs(t *testing.T) {
b := bytes.NewBuffer(nil)
ui := &packer.BasicUi{
Writer: b,
PB: &packer.NoopProgressTracker{},
}
comm := &packer.MockCommunicator{}
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
@ -304,6 +307,7 @@ func TestProvisionerProvision_SendsFileMultipleFilesToFolder(t *testing.T) {
b := bytes.NewBuffer(nil)
ui := &packer.BasicUi{
Writer: b,
PB: &packer.NoopProgressTracker{},
}
comm := &packer.MockCommunicator{}
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
@ -361,6 +365,7 @@ func TestProvisionDownloadMkdirAll(t *testing.T) {
b := bytes.NewBuffer(nil)
ui := &packer.BasicUi{
Writer: b,
PB: &packer.NoopProgressTracker{},
}
comm := &packer.MockCommunicator{}
err = p.ProvisionDownload(ui, comm)

View File

@ -43,7 +43,7 @@ type Config struct {
// An optional endpoint URL (hostname only or fully qualified URI)
// that overrides the default generated endpoint for a client. Set this
// to `""` to use the default generated endpoint.
// to `nil` or the value to `""` to use the default generated endpoint.
//
// Note: You must still provide a `Region` value when specifying an
// endpoint for a client.
@ -138,7 +138,7 @@ type Config struct {
// `ExpectContinueTimeout` for information on adjusting the continue wait
// timeout. https://golang.org/pkg/net/http/#Transport
//
// You should use this flag to disble 100-Continue if you experience issues
// You should use this flag to disable 100-Continue if you experience issues
// with proxies or third party S3 compatible services.
S3Disable100Continue *bool
@ -183,7 +183,7 @@ type Config struct {
//
// Example:
// sess := session.Must(session.NewSession(aws.NewConfig()
// .WithEC2MetadataDiableTimeoutOverride(true)))
// .WithEC2MetadataDisableTimeoutOverride(true)))
//
// svc := s3.New(sess)
//
@ -194,7 +194,7 @@ type Config struct {
// both IPv4 and IPv6 addressing.
//
// Setting this for a service which does not support dual stack will fail
// to make requets. It is not recommended to set this value on the session
// to make requests. It is not recommended to set this value on the session
// as it will apply to all service clients created with the session. Even
// services which don't support dual stack endpoints.
//
@ -238,6 +238,7 @@ type Config struct {
// EnableEndpointDiscovery will allow for endpoint discovery on operations that
// have the definition in its model. By default, endpoint discovery is off.
// To use EndpointDiscovery, Endpoint should be unset or set to an empty string.
//
// Example:
// sess := session.Must(session.NewSession(&aws.Config{

View File

@ -225,6 +225,8 @@ var ValidateEndpointHandler = request.NamedHandler{Name: "core.ValidateEndpointH
if r.ClientInfo.SigningRegion == "" && aws.StringValue(r.Config.Region) == "" {
r.Error = aws.ErrMissingRegion
} else if r.ClientInfo.Endpoint == "" {
// Was any endpoint provided by the user, or one was derived by the
// SDK's endpoint resolver?
r.Error = aws.ErrMissingEndpoint
}
}}

View File

@ -17,8 +17,9 @@ var (
ErrSharedCredentialsHomeNotFound = awserr.New("UserHomeNotFound", "user home directory not found.", nil)
)
// A SharedCredentialsProvider retrieves credentials from the current user's home
// directory, and keeps track if those credentials are expired.
// A SharedCredentialsProvider retrieves access key pair (access key ID,
// secret access key, and session token if present) credentials from the current
// user's home directory, and keeps track if those credentials are expired.
//
// Profile ini file example: $HOME/.aws/credentials
type SharedCredentialsProvider struct {

View File

@ -169,6 +169,29 @@ type AssumeRoleProvider struct {
// size.
Policy *string
// The ARNs of IAM managed policies you want to use as managed session policies.
// The policies must exist in the same account as the role.
//
// This parameter is optional. You can provide up to 10 managed policy ARNs.
// However, the plain text that you use for both inline and managed session
// policies can't exceed 2,048 characters.
//
// An AWS conversion compresses the passed session policies and session tags
// into a packed binary format that has a separate limit. Your request can fail
// for this limit even if your plain text meets the other requirements. The
// PackedPolicySize response element indicates by percentage how close the policies
// and tags for your request are to the upper size limit.
//
// Passing policies to this operation returns new temporary credentials. The
// resulting session's permissions are the intersection of the role's identity-based
// policy and the session policies. You can use the role's temporary credentials
// in subsequent AWS API calls to access resources in the account that owns
// the role. You cannot use session policies to grant more permissions than
// those allowed by the identity-based policy of the role that is being assumed.
// For more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide.
PolicyArns []*sts.PolicyDescriptorType
// The identification number of the MFA device that is associated with the user
// who is making the AssumeRole call. Specify this value if the trust policy
// of the role being assumed includes a condition that requires MFA authentication.
@ -291,6 +314,7 @@ func (p *AssumeRoleProvider) RetrieveWithContext(ctx credentials.Context) (crede
RoleSessionName: aws.String(p.RoleSessionName),
ExternalId: p.ExternalID,
Tags: p.Tags,
PolicyArns: p.PolicyArns,
TransitiveTagKeys: p.TransitiveTagKeys,
}
if p.Policy != nil {

View File

@ -28,15 +28,46 @@ const (
// compare test values.
var now = time.Now
// TokenFetcher shuold return WebIdentity token bytes or an error
type TokenFetcher interface {
FetchToken(credentials.Context) ([]byte, error)
}
// FetchTokenPath is a path to a WebIdentity token file
type FetchTokenPath string
// FetchToken returns a token by reading from the filesystem
func (f FetchTokenPath) FetchToken(ctx credentials.Context) ([]byte, error) {
data, err := ioutil.ReadFile(string(f))
if err != nil {
errMsg := fmt.Sprintf("unable to read file at %s", f)
return nil, awserr.New(ErrCodeWebIdentity, errMsg, err)
}
return data, nil
}
// WebIdentityRoleProvider is used to retrieve credentials using
// an OIDC token.
type WebIdentityRoleProvider struct {
credentials.Expiry
PolicyArns []*sts.PolicyDescriptorType
client stsiface.STSAPI
// Duration the STS credentials will be valid for. Truncated to seconds.
// If unset, the assumed role will use AssumeRoleWithWebIdentity's default
// expiry duration. See
// https://docs.aws.amazon.com/sdk-for-go/api/service/sts/#STS.AssumeRoleWithWebIdentity
// for more information.
Duration time.Duration
// The amount of time the credentials will be refreshed before they expire.
// This is useful refresh credentials before they expire to reduce risk of
// using credentials as they expire. If unset, will default to no expiry
// window.
ExpiryWindow time.Duration
tokenFilePath string
client stsiface.STSAPI
tokenFetcher TokenFetcher
roleARN string
roleSessionName string
}
@ -52,9 +83,15 @@ func NewWebIdentityCredentials(c client.ConfigProvider, roleARN, roleSessionName
// NewWebIdentityRoleProvider will return a new WebIdentityRoleProvider with the
// provided stsiface.STSAPI
func NewWebIdentityRoleProvider(svc stsiface.STSAPI, roleARN, roleSessionName, path string) *WebIdentityRoleProvider {
return NewWebIdentityRoleProviderWithToken(svc, roleARN, roleSessionName, FetchTokenPath(path))
}
// NewWebIdentityRoleProviderWithToken will return a new WebIdentityRoleProvider with the
// provided stsiface.STSAPI and a TokenFetcher
func NewWebIdentityRoleProviderWithToken(svc stsiface.STSAPI, roleARN, roleSessionName string, tokenFetcher TokenFetcher) *WebIdentityRoleProvider {
return &WebIdentityRoleProvider{
client: svc,
tokenFilePath: path,
tokenFetcher: tokenFetcher,
roleARN: roleARN,
roleSessionName: roleSessionName,
}
@ -71,10 +108,9 @@ func (p *WebIdentityRoleProvider) Retrieve() (credentials.Value, error) {
// 'WebIdentityTokenFilePath' specified destination and if that is empty an
// error will be returned.
func (p *WebIdentityRoleProvider) RetrieveWithContext(ctx credentials.Context) (credentials.Value, error) {
b, err := ioutil.ReadFile(p.tokenFilePath)
b, err := p.tokenFetcher.FetchToken(ctx)
if err != nil {
errMsg := fmt.Sprintf("unable to read file at %s", p.tokenFilePath)
return credentials.Value{}, awserr.New(ErrCodeWebIdentity, errMsg, err)
return credentials.Value{}, awserr.New(ErrCodeWebIdentity, "failed fetching WebIdentity token: ", err)
}
sessionName := p.roleSessionName
@ -83,10 +119,18 @@ func (p *WebIdentityRoleProvider) RetrieveWithContext(ctx credentials.Context) (
// uses unix time in nanoseconds to uniquely identify sessions.
sessionName = strconv.FormatInt(now().UnixNano(), 10)
}
var duration *int64
if p.Duration != 0 {
duration = aws.Int64(int64(p.Duration / time.Second))
}
req, resp := p.client.AssumeRoleWithWebIdentityRequest(&sts.AssumeRoleWithWebIdentityInput{
PolicyArns: p.PolicyArns,
RoleArn: &p.roleARN,
RoleSessionName: &sessionName,
WebIdentityToken: aws.String(string(b)),
DurationSeconds: duration,
})
req.SetContext(ctx)

View File

@ -20,7 +20,7 @@ func (c *EC2Metadata) getToken(ctx aws.Context, duration time.Duration) (tokenOu
op := &request.Operation{
Name: "GetToken",
HTTPMethod: "PUT",
HTTPPath: "/api/token",
HTTPPath: "/latest/api/token",
}
var output tokenOutput
@ -62,7 +62,7 @@ func (c *EC2Metadata) GetMetadataWithContext(ctx aws.Context, p string) (string,
op := &request.Operation{
Name: "GetMetadata",
HTTPMethod: "GET",
HTTPPath: sdkuri.PathJoin("/meta-data", p),
HTTPPath: sdkuri.PathJoin("/latest/meta-data", p),
}
output := &metadataOutput{}
@ -88,7 +88,7 @@ func (c *EC2Metadata) GetUserDataWithContext(ctx aws.Context) (string, error) {
op := &request.Operation{
Name: "GetUserData",
HTTPMethod: "GET",
HTTPPath: "/user-data",
HTTPPath: "/latest/user-data",
}
output := &metadataOutput{}
@ -113,7 +113,7 @@ func (c *EC2Metadata) GetDynamicDataWithContext(ctx aws.Context, p string) (stri
op := &request.Operation{
Name: "GetDynamicData",
HTTPMethod: "GET",
HTTPPath: sdkuri.PathJoin("/dynamic", p),
HTTPPath: sdkuri.PathJoin("/latest/dynamic", p),
}
output := &metadataOutput{}

View File

@ -5,6 +5,10 @@
// variable "AWS_EC2_METADATA_DISABLED=true". This environment variable set to
// true instructs the SDK to disable the EC2 Metadata client. The client cannot
// be used while the environment variable is set to true, (case insensitive).
//
// The endpoint of the EC2 IMDS client can be configured via the environment
// variable, AWS_EC2_METADATA_SERVICE_ENDPOINT when creating the client with a
// Session. See aws/session#Options.EC2IMDSEndpoint for more details.
package ec2metadata
import (
@ -12,6 +16,7 @@ import (
"errors"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
@ -41,7 +46,7 @@ const (
enableTokenProviderHandlerName = "enableTokenProviderHandler"
// TTL constants
defaultTTL = 21600 * time.Second
defaultTTL = 21600 * time.Second
ttlExpirationWindow = 30 * time.Second
)
@ -69,6 +74,9 @@ func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata {
// a client when not using a session. Generally using just New with a session
// is preferred.
//
// Will remove the URL path from the endpoint provided to ensure the EC2 IMDS
// client is able to communicate with the EC2 IMDS API.
//
// If an unmodified HTTP client is provided from the stdlib default, or no client
// the EC2RoleProvider's EC2Metadata HTTP client's timeout will be shortened.
// To disable this set Config.EC2MetadataDisableTimeoutOverride to false. Enabled by default.
@ -86,6 +94,15 @@ func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegio
cfg.MaxRetries = aws.Int(2)
}
if u, err := url.Parse(endpoint); err == nil {
// Remove path from the endpoint since it will be added by requests.
// This is an artifact of the SDK adding `/latest` to the endpoint for
// EC2 IMDS, but this is now moved to the operation definition.
u.Path = ""
u.RawPath = ""
endpoint = u.String()
}
svc := &EC2Metadata{
Client: client.New(
cfg,

View File

@ -93,7 +93,7 @@ func decodeV3Endpoints(modelDef modelDefinition, opts DecodeModelOptions) (Resol
}
func custAddS3DualStack(p *partition) {
if p.ID != "aws" {
if !(p.ID == "aws" || p.ID == "aws-cn" || p.ID == "aws-us-gov") {
return
}

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,8 @@ import (
"strings"
)
var regionValidationRegex = regexp.MustCompile(`^[[:alnum:]]([[:alnum:]\-]*[[:alnum:]])?$`)
type partitions []partition
func (ps partitions) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
@ -124,7 +126,7 @@ func (p partition) EndpointFor(service, region string, opts ...func(*Options)) (
defs := []endpoint{p.Defaults, s.Defaults}
return e.resolve(service, p.ID, region, p.DNSSuffix, defs, opt), nil
return e.resolve(service, p.ID, region, p.DNSSuffix, defs, opt)
}
func serviceList(ss services) []string {
@ -233,7 +235,7 @@ func getByPriority(s []string, p []string, def string) string {
return s[0]
}
func (e endpoint) resolve(service, partitionID, region, dnsSuffix string, defs []endpoint, opts Options) ResolvedEndpoint {
func (e endpoint) resolve(service, partitionID, region, dnsSuffix string, defs []endpoint, opts Options) (ResolvedEndpoint, error) {
var merged endpoint
for _, def := range defs {
merged.mergeIn(def)
@ -260,6 +262,10 @@ func (e endpoint) resolve(service, partitionID, region, dnsSuffix string, defs [
region = signingRegion
}
if !validateInputRegion(region) {
return ResolvedEndpoint{}, fmt.Errorf("invalid region identifier format provided")
}
u := strings.Replace(hostname, "{service}", service, 1)
u = strings.Replace(u, "{region}", region, 1)
u = strings.Replace(u, "{dnsSuffix}", dnsSuffix, 1)
@ -274,7 +280,7 @@ func (e endpoint) resolve(service, partitionID, region, dnsSuffix string, defs [
SigningName: signingName,
SigningNameDerived: signingNameDerived,
SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
}
}, nil
}
func getEndpointScheme(protocols []string, disableSSL bool) string {
@ -339,3 +345,7 @@ const (
boxedFalse
boxedTrue
)
func validateInputRegion(region string) bool {
return regionValidationRegex.MatchString(region)
}

View File

@ -9,7 +9,8 @@ func isErrConnectionReset(err error) bool {
return false
}
if strings.Contains(err.Error(), "connection reset") ||
if strings.Contains(err.Error(), "use of closed network connection") ||
strings.Contains(err.Error(), "connection reset") ||
strings.Contains(err.Error(), "broken pipe") {
return true
}

Some files were not shown because too many files have changed in this diff Show More