Merge branch 'master' of github.com:hashicorp/packer into google-impersonation
This commit is contained in:
commit
b1c74f9df0
|
@ -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:
|
||||
|
|
|
@ -13,5 +13,6 @@ coverage:
|
|||
project: off
|
||||
patch: off
|
||||
|
||||
ignore: # ignore hcl2spec generated code for coverage
|
||||
ignore: # ignore hcl2spec generated code for coverage and mocks
|
||||
- "**/*.hcl2spec.go"
|
||||
- "**/*_mock.go"
|
48
CHANGELOG.md
48
CHANGELOG.md
|
@ -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]
|
||||
|
|
|
@ -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())},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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())},
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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())},
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -32,7 +32,7 @@ type Resource struct {
|
|||
DependsOn *[]string `json:"dependsOn,omitempty"`
|
||||
Plan *Plan `json:"plan,omitempty"`
|
||||
Properties *Properties `json:"properties,omitempty"`
|
||||
Tags *map[string]*string `json:"tags,omitempty"`
|
||||
Tags *map[string]string `json:"tags,omitempty"`
|
||||
Resources *[]Resource `json:"resources,omitempty"`
|
||||
Identity *Identity `json:"identity,omitempty"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -100,6 +104,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
&StepSourceImageInfo{
|
||||
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,
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
@ -14,6 +16,8 @@ import (
|
|||
type StepSourceImageInfo struct {
|
||||
SourceImage string
|
||||
SourceImageName string
|
||||
ExternalSourceImageURL string
|
||||
ExternalSourceImageFormat string
|
||||
SourceImageOpts images.ListOpts
|
||||
SourceMostRecent bool
|
||||
SourceProperties map[string]string
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -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"+
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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},
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,6 +23,9 @@ type Config struct {
|
|||
common.HardwareConfig `mapstructure:",squash"`
|
||||
common.ConfigParamsConfig `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"`
|
||||
|
@ -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)...)
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type CDRomConfig
|
||||
|
||||
package iso
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
|
@ -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"
|
|
@ -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
|
||||
}
|
|
@ -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 ...")
|
|
@ -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"
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package iso
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
|
@ -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)
|
||||
|
|
@ -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()
|
|
@ -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"
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -8,7 +8,20 @@ import (
|
|||
type DatastoreMock struct {
|
||||
FileExistsCalled bool
|
||||
MakeDirectoryCalled 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 {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -25,9 +25,9 @@ type Config struct {
|
|||
|
||||
packerCommon.ISOConfig `mapstructure:",squash"`
|
||||
|
||||
CDRomConfig `mapstructure:",squash"`
|
||||
RemoveCDRomConfig `mapstructure:",squash"`
|
||||
FloppyConfig `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"`
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
9
go.mod
|
@ -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
52
go.sum
|
@ -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=
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -7,7 +7,7 @@ build {
|
|||
]
|
||||
|
||||
provisioner "shell" {
|
||||
string = "string"
|
||||
string = coalesce(null, "", "string")
|
||||
int = "${41 + 1}"
|
||||
int64 = "${42 + 1}"
|
||||
bool = "true"
|
||||
|
|
2
main.go
2
main.go
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
package packer
|
||||
|
||||
type uiProgressBar = NoopProgressTracker
|
||||
type UiProgressBar = NoopProgressTracker
|
||||
|
|
|
@ -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++ {
|
||||
|
|
36
packer/ui.go
36
packer/ui.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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())},
|
||||
|
|
|
@ -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 {
|
||||
// Search galaxy_file for roles and collections keywords
|
||||
f, err := ioutil.ReadFile(galaxyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}}
|
||||
|
|
5
vendor/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go
generated
vendored
5
vendor/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go
generated
vendored
|
@ -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 {
|
||||
|
|
24
vendor/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go
generated
vendored
24
vendor/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go
generated
vendored
|
@ -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 {
|
||||
|
|
56
vendor/github.com/aws/aws-sdk-go/aws/credentials/stscreds/web_identity_provider.go
generated
vendored
56
vendor/github.com/aws/aws-sdk-go/aws/credentials/stscreds/web_identity_provider.go
generated
vendored
|
@ -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)
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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"
|
||||
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue