diff --git a/CHANGELOG.md b/CHANGELOG.md index 18a19b271..135cdeb89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,49 +1,115 @@ -## 1.5.0 (Upcoming) +## 1.5.2 (Upcoming) + +### IMPROVEMENTS: +* builder/amazon: Add source AMI owner ID/name to template engines [GH-8550] +* builder/azure: Set expiry for image versions in SIG [GH-8561] +* core: clean up messy log line in plugin execution. [GH-8542] + +### Bug Fixes: +* builder/virtualbox-ovf: Remove config dependency from StepImport [GH-8509] +* builder/virtualbox-vm: use config as a non pointer to avoid a panic [GH-8576] +* core: Fix crash when build.sources is set to an invalid name [GH-8569] +* core: Fix loading of external plugins. GH-8543] +* post-processor/vagrant: correctly handle the diskSize property as a qemu size + string [GH-8567] +* provisioner/ansible: Fix password sanitization to account for empty string + values. [GH-8570] + +## 1.5.1 (December 20, 2019) +This was a fast-follow release to fix a number of panics that we introduced when +making changes for HCL2. + +### IMPROVEMENTS: +* builder/alicloud: Add show_expired option for describing images [GH-8425] + +### Bug Fixes: +* builder/cloudstack: Fix panics associated with loading config [GH-8513] +* builder/hyperv/iso: Fix panics associated with loading config [GH-8513] +* builder/hyperv/vmcx: Fix panics associated with loading config [GH-8513] +* builder/jdcloud: Update jdcloud statebag to use pointers for config [GH-8518] +* builder/linode: Fix panics associated with loading config [GH-8513] +* builder/lxc: Fix panics associated with loading config [GH-8513] +* builder/lxd: Fix panics associated with loading config [GH-8513] +* builder/oneandone: Fix panics associated with loading config [GH-8513] +* builder/oracle/classic: Fix panics associated with loading config [GH-8513] +* builder/oracle/oci: Fix panics associated with loading config [GH-8513] +* builder/osc/bsuvolume: Fix panics associated with loading config [GH-8513] +* builder/parallels/pvm: Fix panics associated with loading config [GH-8513] +* builder/profitbricks: Fix panics associated with loading config [GH-8513] +* builder/scaleway: Fix panics associated with loading config [GH-8513] +* builder/vagrant: Fix panics associated with loading config [GH-8513] +* builder/virtualbox/ovf: Fix panics associated with loading config [GH-8513] +* builder/virtualbox: Configure NAT interface before forwarded port mapping + #8514 +* post-processor/vagrant-cloud: Configure NAT interface before forwarded port + mapping [GH-8514] + +## 1.5.0 (December 18, 2019) ### IMPROVEMENTS: * builder/amazon: Add no_ephemeral template option to remove ephemeral drives from launch mappings. [GH-8393] -* builder/amazon: Add validation for `subnet_id` when specifying `vpc_id` +* builder/amazon: Add validation for "subnet_id" when specifying "vpc_id" [GH-8360] [GH-8387] [GH-8391] * builder/amazon: allow enabling ena/sr-iov on ebssurrogate spot instances [GH-8397] * builder/amazon: Retry runinstances aws api call to mitigate throttling [GH-8342] +* builder/hyperone: Update builder schema and tags [GH-8444] * builder/qemu: Add display template option for qemu. [GH-7676] +* builder/qemu: Disk Size is now read as a string to support units. [GH-8320] + [GH-7546] +* builder/qemu: Add fixer to convert disk size from int to string [GH-8390] +* builder/qemu: Disk Size is now read as a string to support units. [GH-8320] + [GH-7546] * builder/qemu: When a user adds a new drive in qemuargs, process it to make sure that necessary settings are applied to that drive. [GH-8380] * builder/vmware: Fix error message when ovftool is missing [GH-8371] +* core: Cleanup logging for external plugins [GH-8471] +* core: HCL2 template support is now in beta. [GH-8423] +* core: Interpolation within provisioners can now access build-specific values + like Host IP, communicator password, and more. [GH-7866] * core: Various fixes to error handling. [GH-8343] [GH-8333] [GH-8316] [GH-8354] [GH-8361] [GH-8363] [GH-8370] +* post-processor/docker-tag: Add support for multiple tags. [GH-8392] * post-processor/shell-local: Add "valid_exit_codes" option to shell-local. [GH-8401] +* provisioner/chef-client: Add version selection option. [GH-8468] * provisioner/shell-local: Add "valid_exit_codes" option to shell-local. [GH-8401] -* provisioner/shell: Add support for the `env_var_format` parameter [GH-8319] +* provisioner/shell: Add support for the "env_var_format" parameter [GH-8319] ### BUG FIXES: +* builder/amazon: Fix request retry mechanism to launch aws instance [GH-8430] +* builder/azure: Fix PollDuration option which was overriden in some clients. + [GH-8490] * builder/hyperv: Fix bug in checking VM name that could cause flakiness if many VMs are defined. [GH-8357] * builder/vagrant: Use absolute path for Vagrantfile [GH-8321] * builder/virtualbox: Fix panic in snapshot builder. [GH-8336] [GH-8329] * communicator/winrm: Resolve ntlm nil pointer bug by bumping go-ntlmssp dependency [GH-8369] -* communicator: Fix proxy connection settings to use `SSHProxyUsername` and - `SSHProxyPassword` where relevant instead of bastion username and password. +* communicator: Fix proxy connection settings to use "SSHProxyUsername" and + "SSHProxyPassword" where relevant instead of bastion username and password. [GH-8375] * core: Fix bug where Packer froze if asked to log an extremely long line [GH-8356] * core: Fix iso_target_path option; don't cache when target path is non-nil [GH-8394] +* core: Return exit code 1 when builder type is not found [GH-8474] +* core: Return exit code 1 when builder type is not found [GH-8475] * core: Update to newest version of go-tty to re-enable CTRL-S and CTRL-Q usage [GH-8364] - ### BACKWARDS INCOMPATIBILITIES: * builder/amazon: Complete deprecation of clean_ami_name template func [GH-8320] [GH-8193] -* builder/qemu: Disk Size is now read as a string to support units. [GH-8320] - [GH-7546] +* core: Changes have been made to both the Prepare() method signature on the + builder interface and on the Provision() method signature on the + provisioner interface. [GH-7866] +* provisioner/ansible-local: The "galaxycommand" option has been renamed to + "galaxy_command". A fixer has been written for this, which can be invoked + with `packer fix`. [GH-8411] ## 1.4.5 (November 4, 2019) diff --git a/CODEOWNERS b/CODEOWNERS index dca84329b..e9dfc2d82 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,8 +2,8 @@ # builders -/builder/alicloud/ @chhaj5236 -/website/source/docs/builders/alicloud* @chhaj5236 +/builder/alicloud/ @chhaj5236 @alexyueer +/website/source/docs/builders/alicloud* @chhaj5236 @alexyueer /builder/azure/ @paulmey /website/source/docs/builders/azure* @paulmey diff --git a/builder/alicloud/ecs/access_config.go b/builder/alicloud/ecs/access_config.go index b7a5fe26b..e34a60ef8 100644 --- a/builder/alicloud/ecs/access_config.go +++ b/builder/alicloud/ecs/access_config.go @@ -3,29 +3,42 @@ package ecs import ( + "encoding/json" "fmt" + "io/ioutil" "os" + "runtime" "time" "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" "github.com/hashicorp/packer/template/interpolate" "github.com/hashicorp/packer/version" + "github.com/mitchellh/go-homedir" ) // Config of alicloud type AlicloudAccessConfig struct { - // This is the Alicloud access key. It must be provided, but it can also be + // This is the Alicloud access key. It must be provided when profile not exist, but it can also be // sourced from the ALICLOUD_ACCESS_KEY environment variable. - AlicloudAccessKey string `mapstructure:"access_key" required:"true"` - // This is the Alicloud secret key. It must be provided, but it can also be + AlicloudAccessKey string `mapstructure:"access_key" required:"false"` + // This is the Alicloud secret key. It must be provided when profile not exist, but it can also be // sourced from the ALICLOUD_SECRET_KEY environment variable. - AlicloudSecretKey string `mapstructure:"secret_key" required:"true"` - // This is the Alicloud region. It must be provided, but it can also be + AlicloudSecretKey string `mapstructure:"secret_key" required:"false"` + // This is the Alicloud region. It must be provided when profile not exist, but it can also be // sourced from the ALICLOUD_REGION environment variables. - AlicloudRegion string `mapstructure:"region" required:"true"` + AlicloudRegion string `mapstructure:"region" required:"false"` // The region validation can be skipped if this value is true, the default // value is false. AlicloudSkipValidation bool `mapstructure:"skip_region_validation" required:"false"` + // The image validation can be skipped if this value is true, the default + // value is false. + AlicloudSkipImageValidation bool `mapstructure:"skip_image_validation" required:"false"` + // This is th Alicloud profile. If access_key not exist, is must be provided, but it can also be + // sourced from the ALICLOUD_PROFILE environment variables. + AlicloudProfile string `mapstructure:"profile" required:"false"` + // This is the Alicloud shared credentials file path. If this file path exist, os will read access key + // and secret key from this file. + AlicloudSharedCredentialsFile string `mapstructure:"shared_credentials_file" required:"false"` // STS access token, can be set through template or by exporting as // environment variable such as `export SECURITY_TOKEN=value`. SecurityToken string `mapstructure:"security_token" required:"false"` @@ -45,8 +58,22 @@ func (c *AlicloudAccessConfig) Client() (*ClientWrapper, error) { c.SecurityToken = os.Getenv("SECURITY_TOKEN") } - client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey, - c.AlicloudSecretKey, c.SecurityToken) + var getProviderConfig = func(str string, key string) string { + value, err := getConfigFromProfile(c, key) + if err == nil && value != nil { + str = value.(string) + } + return str + } + + if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" { + c.AlicloudAccessKey = getProviderConfig(c.AlicloudAccessKey, "access_key_id") + c.AlicloudSecretKey = getProviderConfig(c.AlicloudSecretKey, "access_key_secret") + c.AlicloudRegion = getProviderConfig(c.AlicloudRegion, "region_id") + c.SecurityToken = getProviderConfig(c.SecurityToken, "sts_token") + } + + client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey, c.AlicloudSecretKey, c.SecurityToken) if err != nil { return nil, err } @@ -86,7 +113,13 @@ func (c *AlicloudAccessConfig) Config() error { if c.AlicloudSecretKey == "" { c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY") } - if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" { + if c.AlicloudProfile == "" { + c.AlicloudProfile = os.Getenv("ALICLOUD_PROFILE") + } + if c.AlicloudSharedCredentialsFile == "" { + c.AlicloudSharedCredentialsFile = os.Getenv("ALICLOUD_SHARED_CREDENTIALS_FILE") + } + if (c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "") && c.AlicloudProfile == "" { return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must be set in template file or environment variables.") } return nil @@ -128,3 +161,66 @@ func (c *AlicloudAccessConfig) getSupportedRegions() ([]string, error) { return validRegions, nil } + +func getConfigFromProfile(c *AlicloudAccessConfig, ProfileKey string) (interface{}, error) { + providerConfig := make(map[string]interface{}) + current := c.AlicloudProfile + if current != "" { + profilePath, err := homedir.Expand(c.AlicloudSharedCredentialsFile) + if err != nil { + return nil, err + } + if profilePath == "" { + profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("HOME")) + if runtime.GOOS == "windows" { + profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("USERPROFILE")) + } + } + _, err = os.Stat(profilePath) + if !os.IsNotExist(err) { + data, err := ioutil.ReadFile(profilePath) + if err != nil { + return nil, err + } + config := map[string]interface{}{} + err = json.Unmarshal(data, &config) + if err != nil { + return nil, err + } + for _, v := range config["profiles"].([]interface{}) { + if current == v.(map[string]interface{})["name"] { + providerConfig = v.(map[string]interface{}) + } + } + } + } + mode := "" + if v, ok := providerConfig["mode"]; ok { + mode = v.(string) + } else { + return v, nil + } + switch ProfileKey { + case "access_key_id", "access_key_secret": + if mode == "EcsRamRole" { + return "", nil + } + case "ram_role_name": + if mode != "EcsRamRole" { + return "", nil + } + case "sts_token": + if mode != "StsToken" { + return "", nil + } + case "ram_role_arn", "ram_session_name": + if mode != "RamRoleArn" { + return "", nil + } + case "expired_seconds": + if mode != "RamRoleArn" { + return float64(0), nil + } + } + return providerConfig[ProfileKey], nil +} diff --git a/builder/alicloud/ecs/access_config_test.go b/builder/alicloud/ecs/access_config_test.go index 053d50a16..68c23aa14 100644 --- a/builder/alicloud/ecs/access_config_test.go +++ b/builder/alicloud/ecs/access_config_test.go @@ -32,5 +32,21 @@ func TestAlicloudAccessConfigPrepareRegion(t *testing.T) { t.Fatalf("shouldn't have err: %s", err) } + c.AlicloudAccessKey = "" + if err := c.Prepare(nil); err == nil { + t.Fatalf("should have err") + } + + c.AlicloudProfile = "default" + if err := c.Prepare(nil); err != nil { + t.Fatalf("shouldn't have err: %s", err) + } + + c.AlicloudProfile = "" + os.Setenv("ALICLOUD_PROFILE", "default") + if err := c.Prepare(nil); err != nil { + t.Fatalf("shouldn't have err: %s", err) + } + c.AlicloudSkipValidation = false } diff --git a/builder/alicloud/ecs/builder.go b/builder/alicloud/ecs/builder.go index 5b593cee9..e7cfd5814 100644 --- a/builder/alicloud/ecs/builder.go +++ b/builder/alicloud/ecs/builder.go @@ -8,6 +8,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" @@ -41,7 +42,9 @@ const ( ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600 ) -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -53,7 +56,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, raws...) b.config.ctx.EnableEnv = true if err != nil { - return nil, err + return nil, nil, err } if b.config.PackerConfig.PackerForce { @@ -68,11 +71,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } packer.LogSecretFilter.Set(b.config.AlicloudAccessKey, b.config.AlicloudSecretKey) - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/alicloud/ecs/builder.hcl2spec.go b/builder/alicloud/ecs/builder.hcl2spec.go index 595ada017..2f5171f57 100644 --- a/builder/alicloud/ecs/builder.hcl2spec.go +++ b/builder/alicloud/ecs/builder.hcl2spec.go @@ -22,10 +22,13 @@ type FlatAlicloudDiskDevice struct { // FlatMapstructure returns a new FlatAlicloudDiskDevice. // FlatAlicloudDiskDevice is an auto-generated flat version of AlicloudDiskDevice. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*AlicloudDiskDevice) FlatMapstructure() interface{} { return new(FlatAlicloudDiskDevice) } +func (*AlicloudDiskDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatAlicloudDiskDevice) +} -// HCL2Spec returns the hcldec.Spec of a FlatAlicloudDiskDevice. -// This spec is used by HCL to read the fields of FlatAlicloudDiskDevice. +// HCL2Spec returns the hcl spec of a AlicloudDiskDevice. +// This spec is used by HCL to read the fields of AlicloudDiskDevice. +// The decoded values from this spec will then be applied to a FlatAlicloudDiskDevice. func (*FlatAlicloudDiskDevice) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "disk_name": &hcldec.AttrSpec{Name: "disk_name", Type: cty.String, Required: false}, @@ -50,10 +53,13 @@ type FlatConfig struct { PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` - AlicloudAccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"` - AlicloudSecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"` - AlicloudRegion *string `mapstructure:"region" required:"true" cty:"region"` + AlicloudAccessKey *string `mapstructure:"access_key" required:"false" cty:"access_key"` + AlicloudSecretKey *string `mapstructure:"secret_key" required:"false" cty:"secret_key"` + AlicloudRegion *string `mapstructure:"region" required:"false" cty:"region"` AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"` + AlicloudSkipImageValidation *bool `mapstructure:"skip_image_validation" required:"false" cty:"skip_image_validation"` + AlicloudProfile *string `mapstructure:"profile" required:"false" cty:"profile"` + AlicloudSharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file"` SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token"` AlicloudImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"` AlicloudImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"` @@ -121,8 +127,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -137,10 +143,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -154,6 +163,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false}, "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, "skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false}, + "skip_image_validation": &hcldec.AttrSpec{Name: "skip_image_validation", Type: cty.Bool, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false}, "security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false}, "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, "image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false}, @@ -169,7 +181,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "image_ignore_data_disks": &hcldec.AttrSpec{Name: "image_ignore_data_disks", Type: cty.Bool, Required: false}, "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false}, "system_disk_mapping": &hcldec.BlockSpec{TypeName: "system_disk_mapping", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())}, - "image_disk_mappings": &hcldec.BlockListSpec{TypeName: "image_disk_mappings", Nested: &hcldec.BlockSpec{TypeName: "image_disk_mappings", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())}}, + "image_disk_mappings": &hcldec.BlockListSpec{TypeName: "image_disk_mappings", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())}, "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, "zone_id": &hcldec.AttrSpec{Name: "zone_id", Type: cty.String, Required: false}, "io_optimized": &hcldec.AttrSpec{Name: "io_optimized", Type: cty.Bool, Required: false}, diff --git a/builder/alicloud/ecs/builder_test.go b/builder/alicloud/ecs/builder_test.go index e67861f5d..9a691de77 100644 --- a/builder/alicloud/ecs/builder_test.go +++ b/builder/alicloud/ecs/builder_test.go @@ -35,7 +35,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "access_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -50,7 +50,7 @@ func TestBuilderPrepare_ECSImageName(t *testing.T) { // Test good config["image_name"] = "ecs.n1.tiny" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -61,7 +61,7 @@ func TestBuilderPrepare_ECSImageName(t *testing.T) { // Test bad config["ecs_image_name"] = "foo {{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -72,7 +72,7 @@ func TestBuilderPrepare_ECSImageName(t *testing.T) { // Test bad delete(config, "image_name") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -87,7 +87,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -120,7 +120,7 @@ func TestBuilderPrepare_Devices(t *testing.T) { "disk_device": "/dev/xvdc", }, } - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -160,7 +160,7 @@ func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) { var b Builder config := testBuilderConfig() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -173,7 +173,7 @@ func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) { } config["image_ignore_data_disks"] = "false" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -186,7 +186,7 @@ func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) { } config["image_ignore_data_disks"] = "true" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -203,7 +203,7 @@ func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) { var b Builder config := testBuilderConfig() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -219,7 +219,7 @@ func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) { } config["wait_snapshot_ready_timeout"] = ALICLOUD_DEFAULT_TIMEOUT - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/alicloud/ecs/client_test.go b/builder/alicloud/ecs/client_test.go index 16bd3dbaf..63b9a6a85 100644 --- a/builder/alicloud/ecs/client_test.go +++ b/builder/alicloud/ecs/client_test.go @@ -33,14 +33,11 @@ func TestWaitForExpectedExceedRetryTimes(t *testing.T) { waitDone <- true }() - timeTolerance := 1 * time.Second select { case <-waitDone: if iter != defaultRetryTimes { t.Fatalf("WaitForExpected should terminate at the %d iterations", defaultRetryTimes) } - case <-time.After(defaultRetryTimes*defaultRetryInterval + timeTolerance): - t.Fatalf("WaitForExpected should terminate within %f seconds", (defaultRetryTimes*defaultRetryInterval + timeTolerance).Seconds()) } } diff --git a/builder/alicloud/ecs/step_check_source_image.go b/builder/alicloud/ecs/step_check_source_image.go index 541bd19eb..3eedec595 100644 --- a/builder/alicloud/ecs/step_check_source_image.go +++ b/builder/alicloud/ecs/step_check_source_image.go @@ -21,6 +21,9 @@ func (s *stepCheckAlicloudSourceImage) Run(ctx context.Context, state multistep. describeImagesRequest := ecs.CreateDescribeImagesRequest() describeImagesRequest.RegionId = config.AlicloudRegion describeImagesRequest.ImageId = config.AlicloudSourceImage + if config.AlicloudSkipImageValidation { + describeImagesRequest.ShowExpired = "true" + } imagesResponse, err := client.DescribeImages(describeImagesRequest) if err != nil { return halt(state, err, "Error querying alicloud image") diff --git a/builder/alicloud/ecs/step_config_security_group.go b/builder/alicloud/ecs/step_config_security_group.go index b147f0a30..8dfbcdc60 100644 --- a/builder/alicloud/ecs/step_config_security_group.go +++ b/builder/alicloud/ecs/step_config_security_group.go @@ -36,7 +36,7 @@ func (s *stepConfigAlicloudSecurityGroup) Run(ctx context.Context, state multist if len(s.SecurityGroupId) != 0 { describeSecurityGroupsRequest := ecs.CreateDescribeSecurityGroupsRequest() describeSecurityGroupsRequest.RegionId = s.RegionId - + describeSecurityGroupsRequest.SecurityGroupId = s.SecurityGroupId if networkType == InstanceNetworkVpc { vpcId := state.Get("vpcid").(string) describeSecurityGroupsRequest.VpcId = vpcId diff --git a/builder/alicloud/ecs/step_create_instance.go b/builder/alicloud/ecs/step_create_instance.go index 909720e2d..24d3acf27 100644 --- a/builder/alicloud/ecs/step_create_instance.go +++ b/builder/alicloud/ecs/step_create_instance.go @@ -77,6 +77,9 @@ func (s *stepCreateAlicloudInstance) Run(ctx context.Context, state multistep.St ui.Message(fmt.Sprintf("Created instance: %s", instanceId)) s.instance = &instances.Instances.Instance[0] state.Put("instance", s.instance) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", instanceId) return multistep.ActionContinue } diff --git a/builder/alicloud/ecs/step_create_snapshot.go b/builder/alicloud/ecs/step_create_snapshot.go index c9e934d60..32f155e08 100644 --- a/builder/alicloud/ecs/step_create_snapshot.go +++ b/builder/alicloud/ecs/step_create_snapshot.go @@ -3,9 +3,9 @@ package ecs import ( "context" "fmt" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors" "time" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors" "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index f1cf7432a..ddadd76c4 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -13,6 +13,7 @@ import ( "runtime" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/hcl/v2/hcldec" awscommon "github.com/hashicorp/packer/builder/amazon/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/chroot" @@ -182,7 +183,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = awscommon.TemplateFuncs err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, @@ -201,7 +204,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } if b.config.Architecture == "" { @@ -319,11 +322,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warns, errs + return nil, warns, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return warns, nil + return nil, warns, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/amazon/chroot/builder.hcl2spec.go b/builder/amazon/chroot/builder.hcl2spec.go index f1f0a2c50..bcd52bb91 100644 --- a/builder/amazon/chroot/builder.hcl2spec.go +++ b/builder/amazon/chroot/builder.hcl2spec.go @@ -72,10 +72,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -93,7 +96,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false}, "ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false}, "skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false}, - "tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, 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}, "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, @@ -102,7 +105,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false}, "region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false}, "skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false}, - "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "snapshot_tags", ElementType: cty.String, Required: false}, "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}, @@ -116,7 +119,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", 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())}, - "ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}}, + "ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}, "chroot_mounts": &hcldec.BlockListSpec{TypeName: "chroot_mounts", Nested: &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.String), Required: false}}, "command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false}, "copy_files": &hcldec.AttrSpec{Name: "copy_files", Type: cty.List(cty.String), Required: false}, @@ -133,7 +136,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "root_volume_type": &hcldec.AttrSpec{Name: "root_volume_type", Type: cty.String, Required: false}, "source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false}, "source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())}, - "root_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "root_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "root_volume_tags", ElementType: cty.String, Required: false}, "ami_architecture": &hcldec.AttrSpec{Name: "ami_architecture", Type: cty.String, Required: false}, } return s diff --git a/builder/amazon/chroot/builder_test.go b/builder/amazon/chroot/builder_test.go index e6999bff6..b181b878f 100644 --- a/builder/amazon/chroot/builder_test.go +++ b/builder/amazon/chroot/builder_test.go @@ -31,7 +31,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test good config["ami_name"] = "foo" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -42,7 +42,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test bad config["ami_name"] = "foo {{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -53,7 +53,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test bad delete(config, "ami_name") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -67,7 +67,7 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) { config := testConfig() config["chroot_mounts"] = nil - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -83,7 +83,7 @@ func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) { config["chroot_mounts"] = [][]string{ {"bad"}, } - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -96,7 +96,7 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) { config := testConfig() config["source_ami"] = "" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -105,7 +105,7 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) { } config["source_ami"] = "foo" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -119,7 +119,7 @@ func TestBuilderPrepare_CommandWrapper(t *testing.T) { config := testConfig() config["command_wrapper"] = "echo hi; {{.Command}}" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -132,7 +132,7 @@ func TestBuilderPrepare_CopyFiles(t *testing.T) { b := &Builder{} config := testConfig() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -150,7 +150,7 @@ func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) { config := testConfig() config["copy_files"] = []string{} - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -171,7 +171,7 @@ func TestBuilderPrepare_RootDeviceNameAndAMIMappings(t *testing.T) { config["root_device_name"] = "/dev/sda" config["ami_block_device_mappings"] = []interface{}{map[string]string{}} config["root_volume_size"] = 15 - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) == 0 { t.Fatal("Missing warning, stating block device mappings will be overwritten") } else if len(warnings) > 1 { @@ -187,7 +187,7 @@ func TestBuilderPrepare_AMIMappingsNoRootDeviceName(t *testing.T) { config := testConfig() config["ami_block_device_mappings"] = []interface{}{map[string]string{}} - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -201,7 +201,7 @@ func TestBuilderPrepare_RootDeviceNameNoAMIMappings(t *testing.T) { config := testConfig() config["root_device_name"] = "/dev/sda" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/amazon/common/access_config.hcl2spec.go b/builder/amazon/common/access_config.hcl2spec.go index b3bdad7e0..f61fa5e40 100644 --- a/builder/amazon/common/access_config.hcl2spec.go +++ b/builder/amazon/common/access_config.hcl2spec.go @@ -18,10 +18,13 @@ type FlatVaultAWSEngineOptions struct { // FlatMapstructure returns a new FlatVaultAWSEngineOptions. // FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*VaultAWSEngineOptions) FlatMapstructure() interface{} { return new(FlatVaultAWSEngineOptions) } +func (*VaultAWSEngineOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatVaultAWSEngineOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatVaultAWSEngineOptions. -// This spec is used by HCL to read the fields of FlatVaultAWSEngineOptions. +// HCL2Spec returns the hcl spec of a VaultAWSEngineOptions. +// This spec is used by HCL to read the fields of VaultAWSEngineOptions. +// The decoded values from this spec will then be applied to a FlatVaultAWSEngineOptions. func (*FlatVaultAWSEngineOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false}, diff --git a/builder/amazon/common/block_device.hcl2spec.go b/builder/amazon/common/block_device.hcl2spec.go index 1865bda3a..e0f3013f3 100644 --- a/builder/amazon/common/block_device.hcl2spec.go +++ b/builder/amazon/common/block_device.hcl2spec.go @@ -24,10 +24,13 @@ type FlatBlockDevice struct { // FlatMapstructure returns a new FlatBlockDevice. // FlatBlockDevice is an auto-generated flat version of BlockDevice. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*BlockDevice) FlatMapstructure() interface{} { return new(FlatBlockDevice) } +func (*BlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatBlockDevice) +} -// HCL2Spec returns the hcldec.Spec of a FlatBlockDevice. -// This spec is used by HCL to read the fields of FlatBlockDevice. +// HCL2Spec returns the hcl spec of a BlockDevice. +// This spec is used by HCL to read the fields of BlockDevice. +// The decoded values from this spec will then be applied to a FlatBlockDevice. func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "delete_on_termination": &hcldec.AttrSpec{Name: "delete_on_termination", Type: cty.Bool, Required: false}, diff --git a/builder/amazon/common/interpolate_build_info.go b/builder/amazon/common/interpolate_build_info.go index b141bc9d4..fcd69bd42 100644 --- a/builder/amazon/common/interpolate_build_info.go +++ b/builder/amazon/common/interpolate_build_info.go @@ -7,10 +7,12 @@ import ( ) type BuildInfoTemplate struct { - BuildRegion string - SourceAMI string - SourceAMIName string - SourceAMITags map[string]string + BuildRegion string + SourceAMI string + SourceAMIName string + SourceAMIOwner string + SourceAMIOwnerName string + SourceAMITags map[string]string } func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplate { @@ -28,9 +30,11 @@ func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplat } return &BuildInfoTemplate{ - BuildRegion: region, - SourceAMI: aws.StringValue(sourceAMI.ImageId), - SourceAMIName: aws.StringValue(sourceAMI.Name), - SourceAMITags: sourceAMITags, + BuildRegion: region, + SourceAMI: aws.StringValue(sourceAMI.ImageId), + SourceAMIName: aws.StringValue(sourceAMI.Name), + SourceAMIOwner: aws.StringValue(sourceAMI.OwnerId), + SourceAMIOwnerName: aws.StringValue(sourceAMI.ImageOwnerAlias), + SourceAMITags: sourceAMITags, } } diff --git a/builder/amazon/common/interpolate_build_info_test.go b/builder/amazon/common/interpolate_build_info_test.go index 7391fcd5d..057171f0b 100644 --- a/builder/amazon/common/interpolate_build_info_test.go +++ b/builder/amazon/common/interpolate_build_info_test.go @@ -11,8 +11,10 @@ import ( func testImage() *ec2.Image { return &ec2.Image{ - ImageId: aws.String("ami-abcd1234"), - Name: aws.String("ami_test_name"), + ImageId: aws.String("ami-abcd1234"), + Name: aws.String("ami_test_name"), + OwnerId: aws.String("ami_test_owner_id"), + ImageOwnerAlias: aws.String("ami_test_owner_alias"), Tags: []*ec2.Tag{ { Key: aws.String("key-1"), @@ -49,9 +51,11 @@ func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) { buildInfo := extractBuildInfo("foo", state) expected := BuildInfoTemplate{ - BuildRegion: "foo", - SourceAMI: "ami-abcd1234", - SourceAMIName: "ami_test_name", + BuildRegion: "foo", + SourceAMI: "ami-abcd1234", + SourceAMIName: "ami_test_name", + SourceAMIOwner: "ami_test_owner_id", + SourceAMIOwnerName: "ami_test_owner_alias", SourceAMITags: map[string]string{ "key-1": "value-1", "key-2": "value-2", diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index 07449a444..ec637ef4a 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -145,6 +145,8 @@ type RunConfig struct { // profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) // to launch the EC2 instance with. IamInstanceProfile string `mapstructure:"iam_instance_profile" required:"false"` + // Whether or not to check if the IAM instance profile exists. Defaults to false + SkipProfileValidation bool `mapstructure:"skip_profile_validation" required:"false"` // Temporary IAM instance profile policy document // If IamInstanceProfile is specified it will be used instead. Example: // @@ -375,8 +377,20 @@ type RunConfig struct { WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout" required:"false"` // Communicator settings - Comm communicator.Config `mapstructure:",squash"` - SSHInterface string `mapstructure:"ssh_interface"` + Comm communicator.Config `mapstructure:",squash"` + + // One of `public_ip`, `private_ip`, `public_dns`, or `private_dns`. If + // set, either the public IP address, private IP address, public DNS name + // or private DNS name will be used as the host for SSH. The default behaviour + // if inside a VPC is to use the public IP address if available, otherwise + // the private IP address will be used. If not in a VPC the public DNS name + // will be used. Also works for WinRM. + // + // Where Packer is configured for an outbound proxy but WinRM traffic + // should be direct, `ssh_interface` must be set to `private_dns` and + // `.compute.internal` included in the `NO_PROXY` environment + // variable. + SSHInterface string `mapstructure:"ssh_interface"` } func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { diff --git a/builder/amazon/common/run_config.hcl2spec.go b/builder/amazon/common/run_config.hcl2spec.go index 2ef6ce62d..d27b01148 100644 --- a/builder/amazon/common/run_config.hcl2spec.go +++ b/builder/amazon/common/run_config.hcl2spec.go @@ -17,10 +17,13 @@ type FlatAmiFilterOptions struct { // FlatMapstructure returns a new FlatAmiFilterOptions. // FlatAmiFilterOptions is an auto-generated flat version of AmiFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*AmiFilterOptions) FlatMapstructure() interface{} { return new(FlatAmiFilterOptions) } +func (*AmiFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatAmiFilterOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatAmiFilterOptions. -// This spec is used by HCL to read the fields of FlatAmiFilterOptions. +// HCL2Spec returns the hcl spec of a AmiFilterOptions. +// This spec is used by HCL to read the fields of AmiFilterOptions. +// The decoded values from this spec will then be applied to a FlatAmiFilterOptions. func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, @@ -40,14 +43,17 @@ type FlatPolicyDocument struct { // FlatMapstructure returns a new FlatPolicyDocument. // FlatPolicyDocument is an auto-generated flat version of PolicyDocument. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*PolicyDocument) FlatMapstructure() interface{} { return new(FlatPolicyDocument) } +func (*PolicyDocument) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatPolicyDocument) +} -// HCL2Spec returns the hcldec.Spec of a FlatPolicyDocument. -// This spec is used by HCL to read the fields of FlatPolicyDocument. +// HCL2Spec returns the hcl spec of a PolicyDocument. +// This spec is used by HCL to read the fields of PolicyDocument. +// The decoded values from this spec will then be applied to a FlatPolicyDocument. func (*FlatPolicyDocument) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false}, - "statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: &hcldec.BlockSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())}}, + "statement": &hcldec.BlockListSpec{TypeName: "statement", Nested: hcldec.ObjectSpec((*FlatStatement)(nil).HCL2Spec())}, } return s } @@ -61,12 +67,13 @@ type FlatSecurityGroupFilterOptions struct { // FlatMapstructure returns a new FlatSecurityGroupFilterOptions. // FlatSecurityGroupFilterOptions is an auto-generated flat version of SecurityGroupFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*SecurityGroupFilterOptions) FlatMapstructure() interface{} { +func (*SecurityGroupFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { return new(FlatSecurityGroupFilterOptions) } -// HCL2Spec returns the hcldec.Spec of a FlatSecurityGroupFilterOptions. -// This spec is used by HCL to read the fields of FlatSecurityGroupFilterOptions. +// HCL2Spec returns the hcl spec of a SecurityGroupFilterOptions. +// This spec is used by HCL to read the fields of SecurityGroupFilterOptions. +// The decoded values from this spec will then be applied to a FlatSecurityGroupFilterOptions. func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, @@ -85,10 +92,13 @@ type FlatStatement struct { // FlatMapstructure returns a new FlatStatement. // FlatStatement is an auto-generated flat version of Statement. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Statement) FlatMapstructure() interface{} { return new(FlatStatement) } +func (*Statement) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatStatement) +} -// HCL2Spec returns the hcldec.Spec of a FlatStatement. -// This spec is used by HCL to read the fields of FlatStatement. +// HCL2Spec returns the hcl spec of a Statement. +// This spec is used by HCL to read the fields of Statement. +// The decoded values from this spec will then be applied to a FlatStatement. func (*FlatStatement) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "effect": &hcldec.AttrSpec{Name: "effect", Type: cty.String, Required: false}, @@ -109,10 +119,13 @@ type FlatSubnetFilterOptions struct { // FlatMapstructure returns a new FlatSubnetFilterOptions. // FlatSubnetFilterOptions is an auto-generated flat version of SubnetFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*SubnetFilterOptions) FlatMapstructure() interface{} { return new(FlatSubnetFilterOptions) } +func (*SubnetFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatSubnetFilterOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatSubnetFilterOptions. -// This spec is used by HCL to read the fields of FlatSubnetFilterOptions. +// HCL2Spec returns the hcl spec of a SubnetFilterOptions. +// This spec is used by HCL to read the fields of SubnetFilterOptions. +// The decoded values from this spec will then be applied to a FlatSubnetFilterOptions. func (*FlatSubnetFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, @@ -131,10 +144,13 @@ type FlatVpcFilterOptions struct { // FlatMapstructure returns a new FlatVpcFilterOptions. // FlatVpcFilterOptions is an auto-generated flat version of VpcFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*VpcFilterOptions) FlatMapstructure() interface{} { return new(FlatVpcFilterOptions) } +func (*VpcFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatVpcFilterOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatVpcFilterOptions. -// This spec is used by HCL to read the fields of FlatVpcFilterOptions. +// HCL2Spec returns the hcl spec of a VpcFilterOptions. +// This spec is used by HCL to read the fields of VpcFilterOptions. +// The decoded values from this spec will then be applied to a FlatVpcFilterOptions. func (*FlatVpcFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, diff --git a/builder/amazon/common/step_get_password.go b/builder/amazon/common/step_get_password.go index 3b751cc98..a2d741af6 100644 --- a/builder/amazon/common/step_get_password.go +++ b/builder/amazon/common/step_get_password.go @@ -13,7 +13,6 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/packer/common/retry" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -91,16 +90,13 @@ WaitLoop: "Password (since debug is enabled): %s", s.Comm.WinRMPassword)) } // store so that we can access this later during provisioning - - commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName) + state.Put("winrm_password", s.Comm.WinRMPassword) packer.LogSecretFilter.Set(s.Comm.WinRMPassword) return multistep.ActionContinue } -func (s *StepGetPassword) Cleanup(multistep.StateBag) { - commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName) -} +func (s *StepGetPassword) Cleanup(multistep.StateBag) {} func (s *StepGetPassword) waitForPassword(ctx context.Context, state multistep.StateBag) (string, error) { ec2conn := state.Get("ec2").(*ec2.EC2) diff --git a/builder/amazon/common/step_iam_instance_profile.go b/builder/amazon/common/step_iam_instance_profile.go index 5e097b273..3ef72e9ee 100644 --- a/builder/amazon/common/step_iam_instance_profile.go +++ b/builder/amazon/common/step_iam_instance_profile.go @@ -2,11 +2,10 @@ package common import ( "context" + "encoding/json" "fmt" "log" - "encoding/json" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/packer/common/uuid" @@ -16,6 +15,7 @@ import ( type StepIamInstanceProfile struct { IamInstanceProfile string + SkipProfileValidation bool TemporaryIamInstanceProfilePolicyDocument *PolicyDocument createdInstanceProfileName string createdRoleName string @@ -30,16 +30,18 @@ func (s *StepIamInstanceProfile) Run(ctx context.Context, state multistep.StateB state.Put("iamInstanceProfile", "") if len(s.IamInstanceProfile) > 0 { - _, err := iamsvc.GetInstanceProfile( - &iam.GetInstanceProfileInput{ - InstanceProfileName: aws.String(s.IamInstanceProfile), - }, - ) - if err != nil { - err := fmt.Errorf("Couldn't find specified instance profile: %s", err) - log.Printf("[DEBUG] %s", err.Error()) - state.Put("error", err) - return multistep.ActionHalt + if !s.SkipProfileValidation { + _, err := iamsvc.GetInstanceProfile( + &iam.GetInstanceProfileInput{ + InstanceProfileName: aws.String(s.IamInstanceProfile), + }, + ) + if err != nil { + err := fmt.Errorf("Couldn't find specified instance profile: %s", err) + log.Printf("[DEBUG] %s", err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } } log.Printf("Using specified instance profile: %v", s.IamInstanceProfile) state.Put("iamInstanceProfile", s.IamInstanceProfile) diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index 0f1941b85..b83db0383 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -280,6 +280,9 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa } state.Put("instance", instance) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", instance.InstanceId) // If we're in a region that doesn't support tagging on instance creation, // do that now. diff --git a/builder/amazon/common/step_run_spot_instance.go b/builder/amazon/common/step_run_spot_instance.go index 06cb28912..d6db5be62 100644 --- a/builder/amazon/common/step_run_spot_instance.go +++ b/builder/amazon/common/step_run_spot_instance.go @@ -441,6 +441,9 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) } state.Put("instance", instance) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", instance.InstanceId) return multistep.ActionContinue } diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 4c7c46ad5..6bc2ec777 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -14,6 +14,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/hcl/v2/hcldec" awscommon "github.com/hashicorp/packer/builder/amazon/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -72,7 +73,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = awscommon.TemplateFuncs err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, @@ -89,7 +92,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } if b.config.PackerConfig.PackerForce { @@ -123,11 +126,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warns, errs + return nil, warns, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return warns, nil + return nil, warns, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -234,6 +237,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, &awscommon.StepIamInstanceProfile{ IamInstanceProfile: b.config.IamInstanceProfile, + SkipProfileValidation: b.config.SkipProfileValidation, TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument, }, &awscommon.StepCleanupVolumes{ diff --git a/builder/amazon/ebs/builder.hcl2spec.go b/builder/amazon/ebs/builder.hcl2spec.go index 231368838..27501d772 100644 --- a/builder/amazon/ebs/builder.hcl2spec.go +++ b/builder/amazon/ebs/builder.hcl2spec.go @@ -55,6 +55,7 @@ type FlatConfig struct { EbsOptimized *bool `mapstructure:"ebs_optimized" required:"false" cty:"ebs_optimized"` EnableT2Unlimited *bool `mapstructure:"enable_t2_unlimited" required:"false" cty:"enable_t2_unlimited"` IamInstanceProfile *string `mapstructure:"iam_instance_profile" required:"false" cty:"iam_instance_profile"` + SkipProfileValidation *bool `mapstructure:"skip_profile_validation" required:"false" cty:"skip_profile_validation"` TemporaryIamInstanceProfilePolicyDocument *common.FlatPolicyDocument `mapstructure:"temporary_iam_instance_profile_policy_document" required:"false" cty:"temporary_iam_instance_profile_policy_document"` InstanceInitiatedShutdownBehavior *string `mapstructure:"shutdown_behavior" required:"false" cty:"shutdown_behavior"` InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"` @@ -106,8 +107,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -126,10 +127,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -158,7 +162,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "ami_groups": &hcldec.AttrSpec{Name: "ami_groups", Type: cty.List(cty.String), Required: false}, "ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false}, "ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false}, - "tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, 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}, "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, @@ -167,7 +171,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false}, "region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false}, "skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false}, - "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "snapshot_tags", ElementType: cty.String, Required: false}, "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}, "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, @@ -177,6 +181,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "ebs_optimized": &hcldec.AttrSpec{Name: "ebs_optimized", Type: cty.Bool, Required: false}, "enable_t2_unlimited": &hcldec.AttrSpec{Name: "enable_t2_unlimited", Type: cty.Bool, Required: false}, "iam_instance_profile": &hcldec.AttrSpec{Name: "iam_instance_profile", Type: cty.String, Required: false}, + "skip_profile_validation": &hcldec.AttrSpec{Name: "skip_profile_validation", Type: cty.Bool, Required: false}, "temporary_iam_instance_profile_policy_document": &hcldec.BlockSpec{TypeName: "temporary_iam_instance_profile_policy_document", Nested: hcldec.ObjectSpec((*common.FlatPolicyDocument)(nil).HCL2Spec())}, "shutdown_behavior": &hcldec.AttrSpec{Name: "shutdown_behavior", Type: cty.String, Required: false}, "instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false}, @@ -239,9 +244,9 @@ 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}, - "ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: &hcldec.BlockSpec{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.BlockSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}}, - "run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, 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())}, + "run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false}, "no_ephemeral": &hcldec.AttrSpec{Name: "no_ephemeral", Type: cty.Bool, Required: false}, } return s diff --git a/builder/amazon/ebs/builder_test.go b/builder/amazon/ebs/builder_test.go index a8200b5a8..4ea816130 100644 --- a/builder/amazon/ebs/builder_test.go +++ b/builder/amazon/ebs/builder_test.go @@ -32,7 +32,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "access_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -48,7 +48,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test good config["ami_name"] = "foo" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -59,7 +59,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test bad config["ami_name"] = "foo {{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -70,7 +70,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test bad delete(config, "ami_name") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -85,7 +85,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -101,7 +101,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "terminate" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -111,7 +111,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "stop" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -121,7 +121,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test bad config["shutdown_behavior"] = "foobar" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/amazon/ebssurrogate/builder.go b/builder/amazon/ebssurrogate/builder.go index 24af4534f..052a01ee1 100644 --- a/builder/amazon/ebssurrogate/builder.go +++ b/builder/amazon/ebssurrogate/builder.go @@ -1,4 +1,5 @@ //go:generate struct-markdown +//go:generate mapstructure-to-hcl2 -type Config,RootBlockDevice,BlockDevice // The ebssurrogate package contains a packer.Builder implementation that // builds a new EBS-backed AMI using an ephemeral instance. @@ -11,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/hcl/v2/hcldec" awscommon "github.com/hashicorp/packer/builder/amazon/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -70,7 +72,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = awscommon.TemplateFuncs err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, @@ -87,7 +91,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } if b.config.PackerConfig.PackerForce { @@ -145,12 +149,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, errors.New(`The only valid ami_architecture values are "x86_64" and "arm64"`)) } if errs != nil && len(errs.Errors) > 0 { - return warns, errs + return nil, warns, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return warns, nil + return nil, warns, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -257,6 +261,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, &awscommon.StepIamInstanceProfile{ IamInstanceProfile: b.config.IamInstanceProfile, + SkipProfileValidation: b.config.SkipProfileValidation, TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument, }, &awscommon.StepCleanupVolumes{ diff --git a/builder/amazon/ebssurrogate/builder.hcl2spec.go b/builder/amazon/ebssurrogate/builder.hcl2spec.go new file mode 100644 index 000000000..81d98f448 --- /dev/null +++ b/builder/amazon/ebssurrogate/builder.hcl2spec.go @@ -0,0 +1,331 @@ +// Code generated by "mapstructure-to-hcl2 -type Config,RootBlockDevice,BlockDevice"; DO NOT EDIT. +package ebssurrogate + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/builder/amazon/common" + "github.com/zclconf/go-cty/cty" +) + +// FlatBlockDevice is an auto-generated flat version of BlockDevice. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatBlockDevice struct { + DeleteOnTermination *bool `mapstructure:"delete_on_termination" required:"false" cty:"delete_on_termination"` + DeviceName *string `mapstructure:"device_name" required:"false" cty:"device_name"` + Encrypted *bool `mapstructure:"encrypted" required:"false" cty:"encrypted"` + IOPS *int64 `mapstructure:"iops" required:"false" cty:"iops"` + NoDevice *bool `mapstructure:"no_device" required:"false" cty:"no_device"` + SnapshotId *string `mapstructure:"snapshot_id" required:"false" cty:"snapshot_id"` + VirtualName *string `mapstructure:"virtual_name" required:"false" cty:"virtual_name"` + VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"` + VolumeSize *int64 `mapstructure:"volume_size" required:"false" cty:"volume_size"` + KmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"` + OmitFromArtifact *bool `mapstructure:"omit_from_artifact" cty:"omit_from_artifact"` +} + +// FlatMapstructure returns a new FlatBlockDevice. +// FlatBlockDevice is an auto-generated flat version of BlockDevice. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*BlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatBlockDevice) +} + +// HCL2Spec returns the hcl spec of a BlockDevice. +// This spec is used by HCL to read the fields of BlockDevice. +// The decoded values from this spec will then be applied to a FlatBlockDevice. +func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "delete_on_termination": &hcldec.AttrSpec{Name: "delete_on_termination", Type: cty.Bool, Required: false}, + "device_name": &hcldec.AttrSpec{Name: "device_name", Type: cty.String, Required: false}, + "encrypted": &hcldec.AttrSpec{Name: "encrypted", Type: cty.Bool, Required: false}, + "iops": &hcldec.AttrSpec{Name: "iops", Type: cty.Number, Required: false}, + "no_device": &hcldec.AttrSpec{Name: "no_device", Type: cty.Bool, Required: false}, + "snapshot_id": &hcldec.AttrSpec{Name: "snapshot_id", Type: cty.String, Required: false}, + "virtual_name": &hcldec.AttrSpec{Name: "virtual_name", Type: cty.String, Required: false}, + "volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false}, + "volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false}, + "kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false}, + "omit_from_artifact": &hcldec.AttrSpec{Name: "omit_from_artifact", Type: cty.Bool, Required: false}, + } + return s +} + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"` + CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2"` + DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages"` + InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify"` + MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code"` + ProfileName *string `mapstructure:"profile" required:"false" cty:"profile"` + RawRegion *string `mapstructure:"region" required:"true" cty:"region"` + SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"` + SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"` + SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"` + Token *string `mapstructure:"token" required:"false" cty:"token"` + VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine"` + AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address"` + AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone"` + BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes"` + DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance"` + EbsOptimized *bool `mapstructure:"ebs_optimized" required:"false" cty:"ebs_optimized"` + EnableT2Unlimited *bool `mapstructure:"enable_t2_unlimited" required:"false" cty:"enable_t2_unlimited"` + IamInstanceProfile *string `mapstructure:"iam_instance_profile" required:"false" cty:"iam_instance_profile"` + SkipProfileValidation *bool `mapstructure:"skip_profile_validation" required:"false" cty:"skip_profile_validation"` + TemporaryIamInstanceProfilePolicyDocument *common.FlatPolicyDocument `mapstructure:"temporary_iam_instance_profile_policy_document" required:"false" cty:"temporary_iam_instance_profile_policy_document"` + InstanceInitiatedShutdownBehavior *string `mapstructure:"shutdown_behavior" required:"false" cty:"shutdown_behavior"` + InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"` + SecurityGroupFilter *common.FlatSecurityGroupFilterOptions `mapstructure:"security_group_filter" required:"false" cty:"security_group_filter"` + RunTags map[string]string `mapstructure:"run_tags" required:"false" cty:"run_tags"` + SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id"` + SecurityGroupIds []string `mapstructure:"security_group_ids" required:"false" cty:"security_group_ids"` + SourceAmi *string `mapstructure:"source_ami" required:"true" cty:"source_ami"` + SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter"` + SpotInstanceTypes []string `mapstructure:"spot_instance_types" required:"false" cty:"spot_instance_types"` + SpotPrice *string `mapstructure:"spot_price" required:"false" cty:"spot_price"` + SpotPriceAutoProduct *string `mapstructure:"spot_price_auto_product" required:"false" cty:"spot_price_auto_product"` + SpotTags map[string]string `mapstructure:"spot_tags" required:"false" cty:"spot_tags"` + SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter"` + SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id"` + TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name"` + TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs"` + UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"` + UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"` + VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter"` + VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id"` + WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout"` + Type *string `mapstructure:"communicator" cty:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` + SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface"` + AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name"` + AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description"` + AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type"` + AMIUsers []string `mapstructure:"ami_users" required:"false" cty:"ami_users"` + AMIGroups []string `mapstructure:"ami_groups" required:"false" cty:"ami_groups"` + AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false" cty:"ami_product_codes"` + AMIRegions []string `mapstructure:"ami_regions" required:"false" cty:"ami_regions"` + AMITags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"` + AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"` + AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"` + AMIForceDeregister *bool `mapstructure:"force_deregister" required:"false" cty:"force_deregister"` + AMIForceDeleteSnapshot *bool `mapstructure:"force_delete_snapshot" required:"false" cty:"force_delete_snapshot"` + AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot" required:"false" cty:"encrypt_boot"` + AMIKmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"` + AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false" cty:"region_kms_key_ids"` + AMISkipBuildRegion *bool `mapstructure:"skip_save_build_region" cty:"skip_save_build_region"` + SnapshotTags common.TagMap `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags"` + SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users"` + SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups"` + AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings"` + LaunchMappings []FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings"` + RootDevice *FlatRootBlockDevice `mapstructure:"ami_root_device" required:"true" cty:"ami_root_device"` + VolumeRunTags common.TagMap `mapstructure:"run_volume_tags" cty:"run_volume_tags"` + Architecture *string `mapstructure:"ami_architecture" required:"false" cty:"ami_architecture"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: 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}, + "custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", 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}, + "mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "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}, + "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())}, + "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, + "availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false}, + "block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false}, + "disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false}, + "ebs_optimized": &hcldec.AttrSpec{Name: "ebs_optimized", Type: cty.Bool, Required: false}, + "enable_t2_unlimited": &hcldec.AttrSpec{Name: "enable_t2_unlimited", Type: cty.Bool, Required: false}, + "iam_instance_profile": &hcldec.AttrSpec{Name: "iam_instance_profile", Type: cty.String, Required: false}, + "skip_profile_validation": &hcldec.AttrSpec{Name: "skip_profile_validation", Type: cty.Bool, Required: false}, + "temporary_iam_instance_profile_policy_document": &hcldec.BlockSpec{TypeName: "temporary_iam_instance_profile_policy_document", Nested: hcldec.ObjectSpec((*common.FlatPolicyDocument)(nil).HCL2Spec())}, + "shutdown_behavior": &hcldec.AttrSpec{Name: "shutdown_behavior", Type: cty.String, Required: false}, + "instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false}, + "security_group_filter": &hcldec.BlockSpec{TypeName: "security_group_filter", Nested: hcldec.ObjectSpec((*common.FlatSecurityGroupFilterOptions)(nil).HCL2Spec())}, + "run_tags": &hcldec.BlockAttrsSpec{TypeName: "run_tags", ElementType: cty.String, Required: false}, + "security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false}, + "security_group_ids": &hcldec.AttrSpec{Name: "security_group_ids", Type: cty.List(cty.String), Required: false}, + "source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false}, + "source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())}, + "spot_instance_types": &hcldec.AttrSpec{Name: "spot_instance_types", Type: cty.List(cty.String), Required: false}, + "spot_price": &hcldec.AttrSpec{Name: "spot_price", Type: cty.String, Required: false}, + "spot_price_auto_product": &hcldec.AttrSpec{Name: "spot_price_auto_product", Type: cty.String, Required: false}, + "spot_tags": &hcldec.BlockAttrsSpec{TypeName: "spot_tags", ElementType: cty.String, Required: false}, + "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, + "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, + "temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false}, + "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, + "user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false}, + "vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())}, + "vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false}, + "windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false}, + "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, + "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, + "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, + "ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false}, + "ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false}, + "ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false}, + "ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false}, + "ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false}, + "ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false}, + "ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false}, + "ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false}, + "ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false}, + "ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false}, + "ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false}, + "ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false}, + "ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false}, + "ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false}, + "ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false}, + "ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false}, + "ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false}, + "ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false}, + "ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false}, + "ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false}, + "ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false}, + "ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false}, + "ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false}, + "ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false}, + "ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false}, + "ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false}, + "winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false}, + "winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false}, + "winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false}, + "winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false}, + "winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false}, + "winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false}, + "winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false}, + "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, + "ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, 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}, + "ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false}, + "ami_users": &hcldec.AttrSpec{Name: "ami_users", Type: cty.List(cty.String), Required: false}, + "ami_groups": &hcldec.AttrSpec{Name: "ami_groups", Type: cty.List(cty.String), Required: false}, + "ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false}, + "ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, 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}, + "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, + "force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false}, + "encrypt_boot": &hcldec.AttrSpec{Name: "encrypt_boot", Type: cty.Bool, Required: false}, + "kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false}, + "region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false}, + "skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false}, + "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "snapshot_tags", ElementType: cty.String, Required: false}, + "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}, + "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((*FlatBlockDevice)(nil).HCL2Spec())}, + "ami_root_device": &hcldec.BlockSpec{TypeName: "ami_root_device", Nested: hcldec.ObjectSpec((*FlatRootBlockDevice)(nil).HCL2Spec())}, + "run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false}, + "ami_architecture": &hcldec.AttrSpec{Name: "ami_architecture", Type: cty.String, Required: false}, + } + return s +} + +// FlatRootBlockDevice is an auto-generated flat version of RootBlockDevice. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatRootBlockDevice struct { + SourceDeviceName *string `mapstructure:"source_device_name" cty:"source_device_name"` + DeviceName *string `mapstructure:"device_name" required:"false" cty:"device_name"` + DeleteOnTermination *bool `mapstructure:"delete_on_termination" required:"false" cty:"delete_on_termination"` + IOPS *int64 `mapstructure:"iops" required:"false" cty:"iops"` + VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"` + VolumeSize *int64 `mapstructure:"volume_size" required:"false" cty:"volume_size"` +} + +// FlatMapstructure returns a new FlatRootBlockDevice. +// FlatRootBlockDevice is an auto-generated flat version of RootBlockDevice. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*RootBlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatRootBlockDevice) +} + +// HCL2Spec returns the hcl spec of a RootBlockDevice. +// This spec is used by HCL to read the fields of RootBlockDevice. +// The decoded values from this spec will then be applied to a FlatRootBlockDevice. +func (*FlatRootBlockDevice) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "source_device_name": &hcldec.AttrSpec{Name: "source_device_name", Type: cty.String, Required: false}, + "device_name": &hcldec.AttrSpec{Name: "device_name", Type: cty.String, Required: false}, + "delete_on_termination": &hcldec.AttrSpec{Name: "delete_on_termination", Type: cty.Bool, Required: false}, + "iops": &hcldec.AttrSpec{Name: "iops", Type: cty.Number, Required: false}, + "volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false}, + "volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false}, + } + return s +} diff --git a/builder/amazon/ebssurrogate/builder_test.go b/builder/amazon/ebssurrogate/builder_test.go index 94fdacd6b..37490bc61 100644 --- a/builder/amazon/ebssurrogate/builder_test.go +++ b/builder/amazon/ebssurrogate/builder_test.go @@ -31,7 +31,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "access_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -46,7 +46,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/amazon/ebsvolume/builder.go b/builder/amazon/ebsvolume/builder.go index 5ed61279e..a71158778 100644 --- a/builder/amazon/ebsvolume/builder.go +++ b/builder/amazon/ebsvolume/builder.go @@ -1,4 +1,5 @@ //go:generate struct-markdown +//go:generate mapstructure-to-hcl2 -type Config,BlockDevice // The ebsvolume package contains a packer.Builder implementation that builds // EBS volumes for Amazon EC2 using an ephemeral instance, @@ -10,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/hcl/v2/hcldec" awscommon "github.com/hashicorp/packer/builder/amazon/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -79,7 +81,9 @@ type EngineVarsTemplate struct { SourceAMI string } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = awscommon.TemplateFuncs // Create passthrough for {{ .BuildRegion }} and {{ .SourceAMI }} variables // so we can fill them in later @@ -92,7 +96,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { InterpolateContext: &b.config.ctx, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors @@ -129,11 +133,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warns, errs + return nil, warns, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return warns, nil + return nil, warns, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -227,6 +231,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, &awscommon.StepIamInstanceProfile{ IamInstanceProfile: b.config.IamInstanceProfile, + SkipProfileValidation: b.config.SkipProfileValidation, TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument, }, instanceStep, diff --git a/builder/amazon/ebsvolume/builder.hcl2spec.go b/builder/amazon/ebsvolume/builder.hcl2spec.go new file mode 100644 index 000000000..34a5199cb --- /dev/null +++ b/builder/amazon/ebsvolume/builder.hcl2spec.go @@ -0,0 +1,258 @@ +// Code generated by "mapstructure-to-hcl2 -type Config,BlockDevice"; DO NOT EDIT. +package ebsvolume + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/builder/amazon/common" + "github.com/zclconf/go-cty/cty" +) + +// FlatBlockDevice is an auto-generated flat version of BlockDevice. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatBlockDevice struct { + DeleteOnTermination *bool `mapstructure:"delete_on_termination" required:"false" cty:"delete_on_termination"` + DeviceName *string `mapstructure:"device_name" required:"false" cty:"device_name"` + Encrypted *bool `mapstructure:"encrypted" required:"false" cty:"encrypted"` + IOPS *int64 `mapstructure:"iops" required:"false" cty:"iops"` + NoDevice *bool `mapstructure:"no_device" required:"false" cty:"no_device"` + SnapshotId *string `mapstructure:"snapshot_id" required:"false" cty:"snapshot_id"` + VirtualName *string `mapstructure:"virtual_name" required:"false" cty:"virtual_name"` + VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"` + VolumeSize *int64 `mapstructure:"volume_size" required:"false" cty:"volume_size"` + KmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"` + Tags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"` +} + +// FlatMapstructure returns a new FlatBlockDevice. +// FlatBlockDevice is an auto-generated flat version of BlockDevice. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*BlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatBlockDevice) +} + +// HCL2Spec returns the hcl spec of a BlockDevice. +// This spec is used by HCL to read the fields of BlockDevice. +// The decoded values from this spec will then be applied to a FlatBlockDevice. +func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "delete_on_termination": &hcldec.AttrSpec{Name: "delete_on_termination", Type: cty.Bool, Required: false}, + "device_name": &hcldec.AttrSpec{Name: "device_name", Type: cty.String, Required: false}, + "encrypted": &hcldec.AttrSpec{Name: "encrypted", Type: cty.Bool, Required: false}, + "iops": &hcldec.AttrSpec{Name: "iops", Type: cty.Number, Required: false}, + "no_device": &hcldec.AttrSpec{Name: "no_device", Type: cty.Bool, Required: false}, + "snapshot_id": &hcldec.AttrSpec{Name: "snapshot_id", Type: cty.String, Required: false}, + "virtual_name": &hcldec.AttrSpec{Name: "virtual_name", Type: cty.String, Required: false}, + "volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false}, + "volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false}, + "kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false}, + } + return s +} + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"` + CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2"` + DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages"` + InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify"` + MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code"` + ProfileName *string `mapstructure:"profile" required:"false" cty:"profile"` + RawRegion *string `mapstructure:"region" required:"true" cty:"region"` + SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"` + SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"` + SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"` + Token *string `mapstructure:"token" required:"false" cty:"token"` + VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine"` + AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address"` + AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone"` + BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes"` + DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance"` + EbsOptimized *bool `mapstructure:"ebs_optimized" required:"false" cty:"ebs_optimized"` + EnableT2Unlimited *bool `mapstructure:"enable_t2_unlimited" required:"false" cty:"enable_t2_unlimited"` + IamInstanceProfile *string `mapstructure:"iam_instance_profile" required:"false" cty:"iam_instance_profile"` + SkipProfileValidation *bool `mapstructure:"skip_profile_validation" required:"false" cty:"skip_profile_validation"` + TemporaryIamInstanceProfilePolicyDocument *common.FlatPolicyDocument `mapstructure:"temporary_iam_instance_profile_policy_document" required:"false" cty:"temporary_iam_instance_profile_policy_document"` + InstanceInitiatedShutdownBehavior *string `mapstructure:"shutdown_behavior" required:"false" cty:"shutdown_behavior"` + InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"` + SecurityGroupFilter *common.FlatSecurityGroupFilterOptions `mapstructure:"security_group_filter" required:"false" cty:"security_group_filter"` + RunTags map[string]string `mapstructure:"run_tags" required:"false" cty:"run_tags"` + SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id"` + SecurityGroupIds []string `mapstructure:"security_group_ids" required:"false" cty:"security_group_ids"` + SourceAmi *string `mapstructure:"source_ami" required:"true" cty:"source_ami"` + SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter"` + SpotInstanceTypes []string `mapstructure:"spot_instance_types" required:"false" cty:"spot_instance_types"` + SpotPrice *string `mapstructure:"spot_price" required:"false" cty:"spot_price"` + SpotPriceAutoProduct *string `mapstructure:"spot_price_auto_product" required:"false" cty:"spot_price_auto_product"` + SpotTags map[string]string `mapstructure:"spot_tags" required:"false" cty:"spot_tags"` + SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter"` + SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id"` + TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name"` + TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs"` + UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"` + UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"` + VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter"` + VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id"` + WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout"` + Type *string `mapstructure:"communicator" cty:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` + SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface"` + AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"` + AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"` + VolumeMappings []FlatBlockDevice `mapstructure:"ebs_volumes" required:"false" cty:"ebs_volumes"` + VolumeRunTags common.TagMap `mapstructure:"run_volume_tags" cty:"run_volume_tags"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: 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}, + "custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", 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}, + "mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "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}, + "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())}, + "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, + "availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false}, + "block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false}, + "disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false}, + "ebs_optimized": &hcldec.AttrSpec{Name: "ebs_optimized", Type: cty.Bool, Required: false}, + "enable_t2_unlimited": &hcldec.AttrSpec{Name: "enable_t2_unlimited", Type: cty.Bool, Required: false}, + "iam_instance_profile": &hcldec.AttrSpec{Name: "iam_instance_profile", Type: cty.String, Required: false}, + "skip_profile_validation": &hcldec.AttrSpec{Name: "skip_profile_validation", Type: cty.Bool, Required: false}, + "temporary_iam_instance_profile_policy_document": &hcldec.BlockSpec{TypeName: "temporary_iam_instance_profile_policy_document", Nested: hcldec.ObjectSpec((*common.FlatPolicyDocument)(nil).HCL2Spec())}, + "shutdown_behavior": &hcldec.AttrSpec{Name: "shutdown_behavior", Type: cty.String, Required: false}, + "instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false}, + "security_group_filter": &hcldec.BlockSpec{TypeName: "security_group_filter", Nested: hcldec.ObjectSpec((*common.FlatSecurityGroupFilterOptions)(nil).HCL2Spec())}, + "run_tags": &hcldec.BlockAttrsSpec{TypeName: "run_tags", ElementType: cty.String, Required: false}, + "security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false}, + "security_group_ids": &hcldec.AttrSpec{Name: "security_group_ids", Type: cty.List(cty.String), Required: false}, + "source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false}, + "source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())}, + "spot_instance_types": &hcldec.AttrSpec{Name: "spot_instance_types", Type: cty.List(cty.String), Required: false}, + "spot_price": &hcldec.AttrSpec{Name: "spot_price", Type: cty.String, Required: false}, + "spot_price_auto_product": &hcldec.AttrSpec{Name: "spot_price_auto_product", Type: cty.String, Required: false}, + "spot_tags": &hcldec.BlockAttrsSpec{TypeName: "spot_tags", ElementType: cty.String, Required: false}, + "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, + "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, + "temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false}, + "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, + "user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false}, + "vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())}, + "vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false}, + "windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false}, + "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, + "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, + "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, + "ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false}, + "ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false}, + "ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false}, + "ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false}, + "ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false}, + "ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false}, + "ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false}, + "ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false}, + "ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false}, + "ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false}, + "ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false}, + "ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false}, + "ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false}, + "ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false}, + "ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false}, + "ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false}, + "ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false}, + "ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false}, + "ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false}, + "ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false}, + "ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false}, + "ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false}, + "ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false}, + "ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false}, + "ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false}, + "ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false}, + "winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false}, + "winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false}, + "winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false}, + "winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false}, + "winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false}, + "winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false}, + "winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false}, + "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, + "ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, 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}, + "ebs_volumes": &hcldec.BlockListSpec{TypeName: "ebs_volumes", Nested: hcldec.ObjectSpec((*FlatBlockDevice)(nil).HCL2Spec())}, + "run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false}, + } + return s +} diff --git a/builder/amazon/ebsvolume/builder_test.go b/builder/amazon/ebsvolume/builder_test.go index 85c8ae833..0d8b27ac1 100644 --- a/builder/amazon/ebsvolume/builder_test.go +++ b/builder/amazon/ebsvolume/builder_test.go @@ -31,7 +31,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "access_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -46,7 +46,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -62,7 +62,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "terminate" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -72,7 +72,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "stop" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -82,7 +82,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test bad config["shutdown_behavior"] = "foobar" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 395fac197..36b429612 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -1,4 +1,5 @@ //go:generate struct-markdown +//go:generate mapstructure-to-hcl2 -type Config // The instance package contains a packer.Builder implementation that builds // AMIs for Amazon EC2 backed by instance storage, as opposed to EBS storage. @@ -13,6 +14,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/hcl/v2/hcldec" awscommon "github.com/hashicorp/packer/builder/amazon/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -93,7 +95,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { configs := make([]interface{}, len(raws)+1) configs[0] = map[string]interface{}{ "bundle_prefix": "image-{{timestamp}}", @@ -118,7 +122,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, configs...) if err != nil { - return nil, err + return nil, nil, err } if b.config.PackerConfig.PackerForce { @@ -218,10 +222,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warns, errs + return nil, warns, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return warns, nil + return nil, warns, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -318,6 +322,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, &awscommon.StepIamInstanceProfile{ IamInstanceProfile: b.config.IamInstanceProfile, + SkipProfileValidation: b.config.SkipProfileValidation, TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument, }, instanceStep, diff --git a/builder/amazon/instance/builder.hcl2spec.go b/builder/amazon/instance/builder.hcl2spec.go new file mode 100644 index 000000000..2726a0994 --- /dev/null +++ b/builder/amazon/instance/builder.hcl2spec.go @@ -0,0 +1,267 @@ +// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT. +package instance + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/builder/amazon/common" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"` + CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2"` + DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages"` + InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify"` + MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code"` + ProfileName *string `mapstructure:"profile" required:"false" cty:"profile"` + RawRegion *string `mapstructure:"region" required:"true" cty:"region"` + SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"` + SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"` + SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"` + Token *string `mapstructure:"token" required:"false" cty:"token"` + VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine"` + AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name"` + AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description"` + AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type"` + AMIUsers []string `mapstructure:"ami_users" required:"false" cty:"ami_users"` + AMIGroups []string `mapstructure:"ami_groups" required:"false" cty:"ami_groups"` + AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false" cty:"ami_product_codes"` + AMIRegions []string `mapstructure:"ami_regions" required:"false" cty:"ami_regions"` + AMITags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"` + AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"` + AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"` + AMIForceDeregister *bool `mapstructure:"force_deregister" required:"false" cty:"force_deregister"` + AMIForceDeleteSnapshot *bool `mapstructure:"force_delete_snapshot" required:"false" cty:"force_delete_snapshot"` + AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot" required:"false" cty:"encrypt_boot"` + AMIKmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"` + AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false" cty:"region_kms_key_ids"` + AMISkipBuildRegion *bool `mapstructure:"skip_save_build_region" cty:"skip_save_build_region"` + SnapshotTags common.TagMap `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags"` + SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users"` + SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups"` + AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address"` + AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone"` + BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes"` + DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance"` + EbsOptimized *bool `mapstructure:"ebs_optimized" required:"false" cty:"ebs_optimized"` + EnableT2Unlimited *bool `mapstructure:"enable_t2_unlimited" required:"false" cty:"enable_t2_unlimited"` + IamInstanceProfile *string `mapstructure:"iam_instance_profile" required:"false" cty:"iam_instance_profile"` + SkipProfileValidation *bool `mapstructure:"skip_profile_validation" required:"false" cty:"skip_profile_validation"` + TemporaryIamInstanceProfilePolicyDocument *common.FlatPolicyDocument `mapstructure:"temporary_iam_instance_profile_policy_document" required:"false" cty:"temporary_iam_instance_profile_policy_document"` + InstanceInitiatedShutdownBehavior *string `mapstructure:"shutdown_behavior" required:"false" cty:"shutdown_behavior"` + InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"` + SecurityGroupFilter *common.FlatSecurityGroupFilterOptions `mapstructure:"security_group_filter" required:"false" cty:"security_group_filter"` + RunTags map[string]string `mapstructure:"run_tags" required:"false" cty:"run_tags"` + SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id"` + SecurityGroupIds []string `mapstructure:"security_group_ids" required:"false" cty:"security_group_ids"` + SourceAmi *string `mapstructure:"source_ami" required:"true" cty:"source_ami"` + SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter"` + SpotInstanceTypes []string `mapstructure:"spot_instance_types" required:"false" cty:"spot_instance_types"` + SpotPrice *string `mapstructure:"spot_price" required:"false" cty:"spot_price"` + SpotPriceAutoProduct *string `mapstructure:"spot_price_auto_product" required:"false" cty:"spot_price_auto_product"` + SpotTags map[string]string `mapstructure:"spot_tags" required:"false" cty:"spot_tags"` + SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter"` + SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id"` + TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name"` + TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs"` + UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"` + UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"` + VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter"` + VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id"` + WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout"` + Type *string `mapstructure:"communicator" cty:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` + SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface"` + AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings"` + LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings"` + AccountId *string `mapstructure:"account_id" required:"true" cty:"account_id"` + BundleDestination *string `mapstructure:"bundle_destination" required:"false" cty:"bundle_destination"` + BundlePrefix *string `mapstructure:"bundle_prefix" required:"false" cty:"bundle_prefix"` + BundleUploadCommand *string `mapstructure:"bundle_upload_command" required:"false" cty:"bundle_upload_command"` + BundleVolCommand *string `mapstructure:"bundle_vol_command" required:"false" cty:"bundle_vol_command"` + S3Bucket *string `mapstructure:"s3_bucket" required:"true" cty:"s3_bucket"` + X509CertPath *string `mapstructure:"x509_cert_path" required:"true" cty:"x509_cert_path"` + X509KeyPath *string `mapstructure:"x509_key_path" required:"true" cty:"x509_key_path"` + X509UploadPath *string `mapstructure:"x509_upload_path" required:"false" cty:"x509_upload_path"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: 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}, + "custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", 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}, + "mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "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}, + "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())}, + "ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false}, + "ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false}, + "ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false}, + "ami_users": &hcldec.AttrSpec{Name: "ami_users", Type: cty.List(cty.String), Required: false}, + "ami_groups": &hcldec.AttrSpec{Name: "ami_groups", Type: cty.List(cty.String), Required: false}, + "ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false}, + "ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, 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}, + "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, + "force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false}, + "encrypt_boot": &hcldec.AttrSpec{Name: "encrypt_boot", Type: cty.Bool, Required: false}, + "kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false}, + "region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false}, + "skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false}, + "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "snapshot_tags", ElementType: cty.String, Required: false}, + "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}, + "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, + "availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false}, + "block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false}, + "disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false}, + "ebs_optimized": &hcldec.AttrSpec{Name: "ebs_optimized", Type: cty.Bool, Required: false}, + "enable_t2_unlimited": &hcldec.AttrSpec{Name: "enable_t2_unlimited", Type: cty.Bool, Required: false}, + "iam_instance_profile": &hcldec.AttrSpec{Name: "iam_instance_profile", Type: cty.String, Required: false}, + "skip_profile_validation": &hcldec.AttrSpec{Name: "skip_profile_validation", Type: cty.Bool, Required: false}, + "temporary_iam_instance_profile_policy_document": &hcldec.BlockSpec{TypeName: "temporary_iam_instance_profile_policy_document", Nested: hcldec.ObjectSpec((*common.FlatPolicyDocument)(nil).HCL2Spec())}, + "shutdown_behavior": &hcldec.AttrSpec{Name: "shutdown_behavior", Type: cty.String, Required: false}, + "instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false}, + "security_group_filter": &hcldec.BlockSpec{TypeName: "security_group_filter", Nested: hcldec.ObjectSpec((*common.FlatSecurityGroupFilterOptions)(nil).HCL2Spec())}, + "run_tags": &hcldec.BlockAttrsSpec{TypeName: "run_tags", ElementType: cty.String, Required: false}, + "security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false}, + "security_group_ids": &hcldec.AttrSpec{Name: "security_group_ids", Type: cty.List(cty.String), Required: false}, + "source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false}, + "source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())}, + "spot_instance_types": &hcldec.AttrSpec{Name: "spot_instance_types", Type: cty.List(cty.String), Required: false}, + "spot_price": &hcldec.AttrSpec{Name: "spot_price", Type: cty.String, Required: false}, + "spot_price_auto_product": &hcldec.AttrSpec{Name: "spot_price_auto_product", Type: cty.String, Required: false}, + "spot_tags": &hcldec.BlockAttrsSpec{TypeName: "spot_tags", ElementType: cty.String, Required: false}, + "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, + "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, + "temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false}, + "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, + "user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false}, + "vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())}, + "vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false}, + "windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false}, + "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, + "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, + "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, + "ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false}, + "ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false}, + "ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false}, + "ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false}, + "ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false}, + "ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false}, + "ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false}, + "ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false}, + "ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false}, + "ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false}, + "ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false}, + "ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false}, + "ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false}, + "ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false}, + "ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false}, + "ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false}, + "ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false}, + "ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false}, + "ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false}, + "ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false}, + "ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false}, + "ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false}, + "ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false}, + "ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false}, + "ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false}, + "ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false}, + "winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false}, + "winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false}, + "winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false}, + "winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false}, + "winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false}, + "winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false}, + "winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false}, + "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, + "ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, 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())}, + "account_id": &hcldec.AttrSpec{Name: "account_id", Type: cty.String, Required: false}, + "bundle_destination": &hcldec.AttrSpec{Name: "bundle_destination", Type: cty.String, Required: false}, + "bundle_prefix": &hcldec.AttrSpec{Name: "bundle_prefix", Type: cty.String, Required: false}, + "bundle_upload_command": &hcldec.AttrSpec{Name: "bundle_upload_command", Type: cty.String, Required: false}, + "bundle_vol_command": &hcldec.AttrSpec{Name: "bundle_vol_command", Type: cty.String, Required: false}, + "s3_bucket": &hcldec.AttrSpec{Name: "s3_bucket", Type: cty.String, Required: false}, + "x509_cert_path": &hcldec.AttrSpec{Name: "x509_cert_path", Type: cty.String, Required: false}, + "x509_key_path": &hcldec.AttrSpec{Name: "x509_key_path", Type: cty.String, Required: false}, + "x509_upload_path": &hcldec.AttrSpec{Name: "x509_upload_path", Type: cty.String, Required: false}, + } + return s +} diff --git a/builder/amazon/instance/builder_test.go b/builder/amazon/instance/builder_test.go index 0c2a05145..21d4d4030 100644 --- a/builder/amazon/instance/builder_test.go +++ b/builder/amazon/instance/builder_test.go @@ -47,7 +47,7 @@ func TestBuilderPrepare_AccountId(t *testing.T) { defer tempfile.Close() config["account_id"] = "" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -56,7 +56,7 @@ func TestBuilderPrepare_AccountId(t *testing.T) { } config["account_id"] = "foo" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -65,7 +65,7 @@ func TestBuilderPrepare_AccountId(t *testing.T) { } config["account_id"] = "0123-0456-7890" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -87,7 +87,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test good config["ami_name"] = "foo" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -98,7 +98,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test bad config["ami_name"] = "foo {{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -109,7 +109,7 @@ func TestBuilderPrepare_AMIName(t *testing.T) { // Test bad delete(config, "ami_name") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -126,7 +126,7 @@ func TestBuilderPrepare_BundleDestination(t *testing.T) { defer tempfile.Close() config["bundle_destination"] = "" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -146,7 +146,7 @@ func TestBuilderPrepare_BundlePrefix(t *testing.T) { defer os.Remove(tempfile.Name()) defer tempfile.Close() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -167,7 +167,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -184,7 +184,7 @@ func TestBuilderPrepare_S3Bucket(t *testing.T) { defer tempfile.Close() config["s3_bucket"] = "" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -193,7 +193,7 @@ func TestBuilderPrepare_S3Bucket(t *testing.T) { } config["s3_bucket"] = "foo" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -210,7 +210,7 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) { defer tempfile.Close() config["x509_cert_path"] = "" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -219,7 +219,7 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) { } config["x509_cert_path"] = "i/am/a/file/that/doesnt/exist" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -235,7 +235,7 @@ func TestBuilderPrepare_X509CertPath(t *testing.T) { defer tf.Close() config["x509_cert_path"] = tf.Name() - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -252,7 +252,7 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) { defer tempfile.Close() config["x509_key_path"] = "" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -261,7 +261,7 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) { } config["x509_key_path"] = "i/am/a/file/that/doesnt/exist" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -277,7 +277,7 @@ func TestBuilderPrepare_X509KeyPath(t *testing.T) { defer tf.Close() config["x509_key_path"] = tf.Name() - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -294,7 +294,7 @@ func TestBuilderPrepare_X509UploadPath(t *testing.T) { defer tempfile.Close() config["x509_upload_path"] = "" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/azure/arm/azure_client.go b/builder/azure/arm/azure_client.go index 10f593726..67c293366 100644 --- a/builder/azure/arm/azure_client.go +++ b/builder/azure/arm/azure_client.go @@ -237,7 +237,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string azureClient.GalleryImagesClient.RequestInspector = withInspection(maxlen) azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImagesClient.UserAgent) - azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration + azureClient.GalleryImagesClient.Client.PollingDuration = PollingDuration keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint) if err != nil { diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 2a06f1b84..f1cf0fed3 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -14,6 +14,7 @@ import ( "github.com/Azure/azure-sdk-for-go/storage" "github.com/Azure/go-autorest/autorest/adal" "github.com/dgrijalva/jwt-go" + "github.com/hashicorp/hcl/v2/hcldec" packerAzureCommon "github.com/hashicorp/packer/builder/azure/common" "github.com/hashicorp/packer/builder/azure/common/constants" "github.com/hashicorp/packer/builder/azure/common/lin" @@ -24,7 +25,7 @@ import ( ) type Builder struct { - config *Config + config Config stateBag multistep.StateBag runner multistep.Runner } @@ -34,20 +35,20 @@ const ( DefaultSecretName = "packerKeyVaultSecret" ) -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := newConfig(raws...) - if errs != nil { - return warnings, errs - } +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - b.config = c +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } b.stateBag = new(multistep.BasicStateBag) b.configureStateBag(b.stateBag) b.setTemplateParameters(b.stateBag) b.setImageParameters(b.stateBag) - return warnings, errs + return nil, warnings, errs } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -64,7 +65,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } log.Print(":: Configuration") - packerAzureCommon.DumpConfig(b.config, func(s string) { log.Print(s) }) + packerAzureCommon.DumpConfig(&b.config, func(s string) { log.Print(s) }) b.stateBag.Put("hook", hook) b.stateBag.Put(constants.Ui, ui) @@ -90,7 +91,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } resolver := newResourceResolver(azureClient) - if err := resolver.Resolve(b.config); err != nil { + if err := resolver.Resolve(&b.config); err != nil { return nil, err } if b.config.ClientConfig.ObjectID == "" { @@ -197,8 +198,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack if b.config.OSType == constants.Target_Linux { steps = []multistep.Step{ NewStepCreateResourceGroup(azureClient, ui), - NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment), - NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment), + NewStepValidateTemplate(azureClient, ui, &b.config, GetVirtualMachineDeployment), + NewStepDeployTemplate(azureClient, ui, &b.config, deploymentName, GetVirtualMachineDeployment), NewStepGetIPAddress(azureClient, ui, endpointConnectType), &communicator.StepConnectSSH{ Config: &b.config.Comm, @@ -212,10 +213,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack NewStepGetOSDisk(azureClient, ui), NewStepGetAdditionalDisks(azureClient, ui), NewStepPowerOffCompute(azureClient, ui), - NewStepSnapshotOSDisk(azureClient, ui, b.config), - NewStepSnapshotDataDisks(azureClient, ui, b.config), + NewStepSnapshotOSDisk(azureClient, ui, &b.config), + NewStepSnapshotDataDisks(azureClient, ui, &b.config), NewStepCaptureImage(azureClient, ui), - NewStepPublishToSharedImageGallery(azureClient, ui, b.config), + NewStepPublishToSharedImageGallery(azureClient, ui, &b.config), NewStepDeleteResourceGroup(azureClient, ui), NewStepDeleteOSDisk(azureClient, ui), NewStepDeleteAdditionalDisks(azureClient, ui), @@ -224,17 +225,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack keyVaultDeploymentName := b.stateBag.Get(constants.ArmKeyVaultDeploymentName).(string) steps = []multistep.Step{ NewStepCreateResourceGroup(azureClient, ui), - NewStepValidateTemplate(azureClient, ui, b.config, GetKeyVaultDeployment), - NewStepDeployTemplate(azureClient, ui, b.config, keyVaultDeploymentName, GetKeyVaultDeployment), + NewStepValidateTemplate(azureClient, ui, &b.config, GetKeyVaultDeployment), + NewStepDeployTemplate(azureClient, ui, &b.config, keyVaultDeploymentName, GetKeyVaultDeployment), NewStepGetCertificate(azureClient, ui), - NewStepSetCertificate(b.config, ui), - NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment), - NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment), + NewStepSetCertificate(&b.config, ui), + NewStepValidateTemplate(azureClient, ui, &b.config, GetVirtualMachineDeployment), + NewStepDeployTemplate(azureClient, ui, &b.config, deploymentName, GetVirtualMachineDeployment), NewStepGetIPAddress(azureClient, ui, endpointConnectType), - &StepSaveWinRMPassword{ - Password: b.config.tmpAdminPassword, - BuildName: b.config.PackerBuildName, - }, &communicator.StepConnectWinRM{ Config: &b.config.Comm, Host: func(stateBag multistep.StateBag) (string, error) { @@ -251,10 +248,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack NewStepGetOSDisk(azureClient, ui), NewStepGetAdditionalDisks(azureClient, ui), NewStepPowerOffCompute(azureClient, ui), - NewStepSnapshotOSDisk(azureClient, ui, b.config), - NewStepSnapshotDataDisks(azureClient, ui, b.config), + NewStepSnapshotOSDisk(azureClient, ui, &b.config), + NewStepSnapshotDataDisks(azureClient, ui, &b.config), NewStepCaptureImage(azureClient, ui), - NewStepPublishToSharedImageGallery(azureClient, ui, b.config), + NewStepPublishToSharedImageGallery(azureClient, ui, &b.config), NewStepDeleteResourceGroup(azureClient, ui), NewStepDeleteOSDisk(azureClient, ui), NewStepDeleteAdditionalDisks(azureClient, ui), @@ -398,6 +395,9 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) { stateBag.Put(constants.ArmManagedImageSharedGalleryImageName, b.config.SharedGalleryDestination.SigDestinationImageName) stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersion, b.config.SharedGalleryDestination.SigDestinationImageVersion) stateBag.Put(constants.ArmManagedImageSubscription, b.config.ClientConfig.SubscriptionID) + stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionEndOfLifeDate, b.config.SharedGalleryImageVersionEndOfLifeDate) + stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionReplicaCount, b.config.SharedGalleryImageVersionReplicaCount) + stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionExcludeFromLatest, b.config.SharedGalleryImageVersionExcludeFromLatest) } } diff --git a/builder/azure/arm/builder_test.go b/builder/azure/arm/builder_test.go index a71f92874..547a0c18a 100644 --- a/builder/azure/arm/builder_test.go +++ b/builder/azure/arm/builder_test.go @@ -7,8 +7,8 @@ import ( ) func TestStateBagShouldBePopulatedExpectedValues(t *testing.T) { - var testSubject = &Builder{} - _, err := testSubject.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) + var testSubject Builder + _, _, err := testSubject.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if err != nil { t.Fatalf("failed to prepare: %s", err) } diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 552a2389d..976dc2c84 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -27,7 +27,6 @@ import ( "github.com/hashicorp/packer/builder/azure/common/constants" "github.com/hashicorp/packer/builder/azure/pkcs12" "github.com/hashicorp/packer/common" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -120,8 +119,6 @@ type Config struct { // // Following is an example. // - // - // // "shared_image_gallery_destination": { // "resource_group": "ResourceGroup", // "gallery_name": "GalleryName", @@ -140,6 +137,16 @@ type Config struct { // its default of "60m" (valid time units include `s` for seconds, `m` for // minutes, and `h` for hours.) SharedGalleryTimeout time.Duration `mapstructure:"shared_image_gallery_timeout"` + // The end of life date (2006-01-02T15:04:05.99Z) of the gallery Image Version. This property + // can be used for decommissioning purposes. + SharedGalleryImageVersionEndOfLifeDate string `mapstructure:"shared_gallery_image_version_end_of_life_date" required:"false"` + // The number of replicas of the Image Version to be created per region. This + // property would take effect for a region when regionalReplicaCount is not specified. + // Replica count must be between 1 and 10. + SharedGalleryImageVersionReplicaCount int32 `mapstructure:"shared_image_gallery_replica_count" required:"false"` + // If set to true, Virtual Machines deployed from the latest version of the + // Image Definition won't use this Image Version. + SharedGalleryImageVersionExcludeFromLatest bool `mapstructure:"shared_gallery_image_version_exclude_from_latest" required:"false"` // PublisherName for your base image. See // [documentation](https://azure.microsoft.com/en-us/documentation/articles/resource-groups-vm-searching/) // for details. @@ -495,58 +502,57 @@ func (c *Config) createCertificate() (string, error) { return base64.StdEncoding.EncodeToString(bytes), nil } -func newConfig(raws ...interface{}) (*Config, []string, error) { - var c Config +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { c.ctx.Funcs = azcommon.TemplateFuncs - err := config.Decode(&c, &config.DecodeOpts{ + err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, }, raws...) if err != nil { - return nil, nil, err + return nil, err } - provideDefaultValues(&c) - setRuntimeValues(&c) - setUserNamePassword(&c) + provideDefaultValues(c) + setRuntimeValues(c) + setUserNamePassword(c) err = c.ClientConfig.SetDefaultValues() if err != nil { - return nil, nil, err + return nil, err } - err = setCustomData(&c) + err = setCustomData(c) if err != nil { - return nil, nil, err + return nil, err } // NOTE: if the user did not specify a communicator, then default to both // SSH and WinRM. This is for backwards compatibility because the code did // not specifically force the user to set a communicator. if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "ssh") { - err = setSshValues(&c) + err = setSshValues(c) if err != nil { - return nil, nil, err + return nil, err } } if c.Comm.Type == "" || strings.EqualFold(c.Comm.Type, "winrm") { - err = setWinRMCertificate(&c) + err = setWinRMCertificate(c) if err != nil { - return nil, nil, err + return nil, err } } var errs *packer.MultiError errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...) - assertRequiredParametersSet(&c, errs) - assertTagProperties(&c, errs) + assertRequiredParametersSet(c, errs) + assertTagProperties(c, errs) if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } - return &c, nil, nil + return nil, nil } func setSshValues(c *Config) error { @@ -601,7 +607,6 @@ func setRuntimeValues(c *Config) { c.tmpAdminPassword = tempName.AdminPassword // store so that we can access this later during provisioning - commonhelper.SetSharedState("winrm_password", c.tmpAdminPassword, c.PackerConfig.PackerBuildName) packer.LogSecretFilter.Set(c.tmpAdminPassword) c.tmpCertificatePassword = tempName.CertificatePassword diff --git a/builder/azure/arm/config.hcl2spec.go b/builder/azure/arm/config.hcl2spec.go index 264120891..826100044 100644 --- a/builder/azure/arm/config.hcl2spec.go +++ b/builder/azure/arm/config.hcl2spec.go @@ -9,138 +9,147 @@ import ( // FlatConfig is an auto-generated flat version of Config. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatConfig struct { - PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` - PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` - PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` - PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` - PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` - PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` - PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` - CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name"` - ClientID *string `mapstructure:"client_id" cty:"client_id"` - ClientSecret *string `mapstructure:"client_secret" cty:"client_secret"` - ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path"` - ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt"` - ObjectID *string `mapstructure:"object_id" cty:"object_id"` - TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id"` - SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id"` - CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix"` - CaptureContainerName *string `mapstructure:"capture_container_name" cty:"capture_container_name"` - SharedGallery *FlatSharedImageGallery `mapstructure:"shared_image_gallery" required:"false" cty:"shared_image_gallery"` - SharedGalleryDestination *FlatSharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination" cty:"shared_image_gallery_destination"` - SharedGalleryTimeout *string `mapstructure:"shared_image_gallery_timeout" cty:"shared_image_gallery_timeout"` - ImagePublisher *string `mapstructure:"image_publisher" required:"true" cty:"image_publisher"` - ImageOffer *string `mapstructure:"image_offer" required:"true" cty:"image_offer"` - ImageSku *string `mapstructure:"image_sku" required:"true" cty:"image_sku"` - ImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"` - ImageUrl *string `mapstructure:"image_url" required:"false" cty:"image_url"` - CustomManagedImageResourceGroupName *string `mapstructure:"custom_managed_image_resource_group_name" required:"false" cty:"custom_managed_image_resource_group_name"` - CustomManagedImageName *string `mapstructure:"custom_managed_image_name" required:"false" cty:"custom_managed_image_name"` - Location *string `mapstructure:"location" cty:"location"` - VMSize *string `mapstructure:"vm_size" required:"false" cty:"vm_size"` - ManagedImageResourceGroupName *string `mapstructure:"managed_image_resource_group_name" cty:"managed_image_resource_group_name"` - ManagedImageName *string `mapstructure:"managed_image_name" cty:"managed_image_name"` - ManagedImageStorageAccountType *string `mapstructure:"managed_image_storage_account_type" required:"false" cty:"managed_image_storage_account_type"` - ManagedImageOSDiskSnapshotName *string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false" cty:"managed_image_os_disk_snapshot_name"` - ManagedImageDataDiskSnapshotPrefix *string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false" cty:"managed_image_data_disk_snapshot_prefix"` - ManagedImageZoneResilient *bool `mapstructure:"managed_image_zone_resilient" required:"false" cty:"managed_image_zone_resilient"` - AzureTags map[string]*string `mapstructure:"azure_tags" required:"false" cty:"azure_tags"` - ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name"` - StorageAccount *string `mapstructure:"storage_account" cty:"storage_account"` - TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name"` - TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name"` - BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name"` - PrivateVirtualNetworkWithPublicIp *bool `mapstructure:"private_virtual_network_with_public_ip" required:"false" cty:"private_virtual_network_with_public_ip"` - VirtualNetworkName *string `mapstructure:"virtual_network_name" required:"false" cty:"virtual_network_name"` - VirtualNetworkSubnetName *string `mapstructure:"virtual_network_subnet_name" required:"false" cty:"virtual_network_subnet_name"` - VirtualNetworkResourceGroupName *string `mapstructure:"virtual_network_resource_group_name" required:"false" cty:"virtual_network_resource_group_name"` - CustomDataFile *string `mapstructure:"custom_data_file" required:"false" cty:"custom_data_file"` - PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info"` - PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout"` - OSType *string `mapstructure:"os_type" required:"false" cty:"os_type"` - OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb"` - AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size"` - DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type"` - AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses" cty:"allowed_inbound_ip_addresses"` - UserName *string `cty:"user_name"` - Password *string `cty:"password"` - Type *string `mapstructure:"communicator" cty:"communicator"` - PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` - SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` - SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` - SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` - SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` - SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` - SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"` - SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` - SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` - SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` - SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` - SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` - SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` - SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` - SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` - SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` - SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` - SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` - SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` - SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` - SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` - SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` - SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` - SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` - SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` - SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` - SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` - SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` - SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` - WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` - WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` - WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` - WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` - WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` - WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` - WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` - WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` - AsyncResourceGroupDelete *bool `mapstructure:"async_resourcegroup_delete" required:"false" cty:"async_resourcegroup_delete"` + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name"` + ClientID *string `mapstructure:"client_id" cty:"client_id"` + ClientSecret *string `mapstructure:"client_secret" cty:"client_secret"` + ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path"` + ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt"` + ObjectID *string `mapstructure:"object_id" cty:"object_id"` + TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id"` + SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id"` + CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix"` + CaptureContainerName *string `mapstructure:"capture_container_name" cty:"capture_container_name"` + SharedGallery *FlatSharedImageGallery `mapstructure:"shared_image_gallery" required:"false" cty:"shared_image_gallery"` + SharedGalleryDestination *FlatSharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination" cty:"shared_image_gallery_destination"` + SharedGalleryTimeout *string `mapstructure:"shared_image_gallery_timeout" cty:"shared_image_gallery_timeout"` + SharedGalleryImageVersionEndOfLifeDate *string `mapstructure:"shared_gallery_image_version_end_of_life_date" required:"false" cty:"shared_gallery_image_version_end_of_life_date"` + SharedGalleryImageVersionReplicaCount *int32 `mapstructure:"shared_image_gallery_replica_count" required:"false" cty:"shared_image_gallery_replica_count"` + SharedGalleryImageVersionExcludeFromLatest *bool `mapstructure:"shared_gallery_image_version_exclude_from_latest" required:"false" cty:"shared_gallery_image_version_exclude_from_latest"` + ImagePublisher *string `mapstructure:"image_publisher" required:"true" cty:"image_publisher"` + ImageOffer *string `mapstructure:"image_offer" required:"true" cty:"image_offer"` + ImageSku *string `mapstructure:"image_sku" required:"true" cty:"image_sku"` + ImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"` + ImageUrl *string `mapstructure:"image_url" required:"false" cty:"image_url"` + CustomManagedImageResourceGroupName *string `mapstructure:"custom_managed_image_resource_group_name" required:"false" cty:"custom_managed_image_resource_group_name"` + CustomManagedImageName *string `mapstructure:"custom_managed_image_name" required:"false" cty:"custom_managed_image_name"` + Location *string `mapstructure:"location" cty:"location"` + VMSize *string `mapstructure:"vm_size" required:"false" cty:"vm_size"` + ManagedImageResourceGroupName *string `mapstructure:"managed_image_resource_group_name" cty:"managed_image_resource_group_name"` + ManagedImageName *string `mapstructure:"managed_image_name" cty:"managed_image_name"` + ManagedImageStorageAccountType *string `mapstructure:"managed_image_storage_account_type" required:"false" cty:"managed_image_storage_account_type"` + ManagedImageOSDiskSnapshotName *string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false" cty:"managed_image_os_disk_snapshot_name"` + ManagedImageDataDiskSnapshotPrefix *string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false" cty:"managed_image_data_disk_snapshot_prefix"` + ManagedImageZoneResilient *bool `mapstructure:"managed_image_zone_resilient" required:"false" cty:"managed_image_zone_resilient"` + AzureTags map[string]*string `mapstructure:"azure_tags" required:"false" cty:"azure_tags"` + ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name"` + StorageAccount *string `mapstructure:"storage_account" cty:"storage_account"` + TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name"` + TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name"` + BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name"` + PrivateVirtualNetworkWithPublicIp *bool `mapstructure:"private_virtual_network_with_public_ip" required:"false" cty:"private_virtual_network_with_public_ip"` + VirtualNetworkName *string `mapstructure:"virtual_network_name" required:"false" cty:"virtual_network_name"` + VirtualNetworkSubnetName *string `mapstructure:"virtual_network_subnet_name" required:"false" cty:"virtual_network_subnet_name"` + VirtualNetworkResourceGroupName *string `mapstructure:"virtual_network_resource_group_name" required:"false" cty:"virtual_network_resource_group_name"` + CustomDataFile *string `mapstructure:"custom_data_file" required:"false" cty:"custom_data_file"` + PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info"` + PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout"` + OSType *string `mapstructure:"os_type" required:"false" cty:"os_type"` + OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb"` + AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size"` + DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type"` + AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses" cty:"allowed_inbound_ip_addresses"` + UserName *string `cty:"user_name"` + Password *string `cty:"password"` + Type *string `mapstructure:"communicator" cty:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` + SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` + AsyncResourceGroupDelete *bool `mapstructure:"async_resourcegroup_delete" required:"false" cty:"async_resourcegroup_delete"` } // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ - "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, - "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, - "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, - "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, - "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, - "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false}, - "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, - "cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false}, - "client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false}, - "client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false}, - "client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false}, - "client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false}, - "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, - "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, - "subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false}, - "capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false}, - "capture_container_name": &hcldec.AttrSpec{Name: "capture_container_name", Type: cty.String, Required: false}, - "shared_image_gallery": &hcldec.BlockSpec{TypeName: "shared_image_gallery", Nested: hcldec.ObjectSpec((*FlatSharedImageGallery)(nil).HCL2Spec())}, - "shared_image_gallery_destination": &hcldec.BlockSpec{TypeName: "shared_image_gallery_destination", Nested: hcldec.ObjectSpec((*FlatSharedImageGalleryDestination)(nil).HCL2Spec())}, - "shared_image_gallery_timeout": &hcldec.AttrSpec{Name: "shared_image_gallery_timeout", Type: cty.String, Required: false}, - "image_publisher": &hcldec.AttrSpec{Name: "image_publisher", Type: cty.String, Required: false}, - "image_offer": &hcldec.AttrSpec{Name: "image_offer", Type: cty.String, Required: false}, - "image_sku": &hcldec.AttrSpec{Name: "image_sku", Type: cty.String, Required: false}, - "image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false}, - "image_url": &hcldec.AttrSpec{Name: "image_url", Type: cty.String, Required: false}, + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false}, + "client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false}, + "client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false}, + "client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false}, + "client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false}, + "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, + "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, + "subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false}, + "capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false}, + "capture_container_name": &hcldec.AttrSpec{Name: "capture_container_name", Type: cty.String, Required: false}, + "shared_image_gallery": &hcldec.BlockSpec{TypeName: "shared_image_gallery", Nested: hcldec.ObjectSpec((*FlatSharedImageGallery)(nil).HCL2Spec())}, + "shared_image_gallery_destination": &hcldec.BlockSpec{TypeName: "shared_image_gallery_destination", Nested: hcldec.ObjectSpec((*FlatSharedImageGalleryDestination)(nil).HCL2Spec())}, + "shared_image_gallery_timeout": &hcldec.AttrSpec{Name: "shared_image_gallery_timeout", Type: cty.String, Required: false}, + "shared_gallery_image_version_end_of_life_date": &hcldec.AttrSpec{Name: "shared_gallery_image_version_end_of_life_date", Type: cty.String, Required: false}, + "shared_image_gallery_replica_count": &hcldec.AttrSpec{Name: "shared_image_gallery_replica_count", Type: cty.Number, Required: false}, + "shared_gallery_image_version_exclude_from_latest": &hcldec.AttrSpec{Name: "shared_gallery_image_version_exclude_from_latest", Type: cty.Bool, Required: false}, + "image_publisher": &hcldec.AttrSpec{Name: "image_publisher", Type: cty.String, Required: false}, + "image_offer": &hcldec.AttrSpec{Name: "image_offer", Type: cty.String, Required: false}, + "image_sku": &hcldec.AttrSpec{Name: "image_sku", Type: cty.String, Required: false}, + "image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false}, + "image_url": &hcldec.AttrSpec{Name: "image_url", Type: cty.String, Required: false}, "custom_managed_image_resource_group_name": &hcldec.AttrSpec{Name: "custom_managed_image_resource_group_name", Type: cty.String, Required: false}, "custom_managed_image_name": &hcldec.AttrSpec{Name: "custom_managed_image_name", Type: cty.String, Required: false}, "location": &hcldec.AttrSpec{Name: "location", Type: cty.String, Required: false}, @@ -228,10 +237,13 @@ type FlatPlanInformation struct { // FlatMapstructure returns a new FlatPlanInformation. // FlatPlanInformation is an auto-generated flat version of PlanInformation. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*PlanInformation) FlatMapstructure() interface{} { return new(FlatPlanInformation) } +func (*PlanInformation) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatPlanInformation) +} -// HCL2Spec returns the hcldec.Spec of a FlatPlanInformation. -// This spec is used by HCL to read the fields of FlatPlanInformation. +// HCL2Spec returns the hcl spec of a PlanInformation. +// This spec is used by HCL to read the fields of PlanInformation. +// The decoded values from this spec will then be applied to a FlatPlanInformation. func (*FlatPlanInformation) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "plan_name": &hcldec.AttrSpec{Name: "plan_name", Type: cty.String, Required: false}, @@ -255,10 +267,13 @@ type FlatSharedImageGallery struct { // FlatMapstructure returns a new FlatSharedImageGallery. // FlatSharedImageGallery is an auto-generated flat version of SharedImageGallery. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*SharedImageGallery) FlatMapstructure() interface{} { return new(FlatSharedImageGallery) } +func (*SharedImageGallery) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatSharedImageGallery) +} -// HCL2Spec returns the hcldec.Spec of a FlatSharedImageGallery. -// This spec is used by HCL to read the fields of FlatSharedImageGallery. +// HCL2Spec returns the hcl spec of a SharedImageGallery. +// This spec is used by HCL to read the fields of SharedImageGallery. +// The decoded values from this spec will then be applied to a FlatSharedImageGallery. func (*FlatSharedImageGallery) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "subscription": &hcldec.AttrSpec{Name: "subscription", Type: cty.String, Required: false}, @@ -283,12 +298,13 @@ type FlatSharedImageGalleryDestination struct { // FlatMapstructure returns a new FlatSharedImageGalleryDestination. // FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*SharedImageGalleryDestination) FlatMapstructure() interface{} { +func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { return new(FlatSharedImageGalleryDestination) } -// HCL2Spec returns the hcldec.Spec of a FlatSharedImageGalleryDestination. -// This spec is used by HCL to read the fields of FlatSharedImageGalleryDestination. +// HCL2Spec returns the hcl spec of a SharedImageGalleryDestination. +// This spec is used by HCL to read the fields of SharedImageGalleryDestination. +// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination. func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false}, diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index 52ad4814e..128df2be3 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -26,7 +26,8 @@ var requiredConfigValues = []string{ } func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) { - c, _, err := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + _, err := c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if err != nil { t.Error("Expected configuration creation to succeed, but it failed!\n") @@ -63,7 +64,8 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) { builderValues["managed_image_storage_account_type"] = "Premium_LRS" builderValues["disk_caching_type"] = "None" - c, _, err := newConfig(builderValues, getPackerConfiguration()) + var c Config + _, err := c.Prepare(builderValues, getPackerConfiguration()) if err != nil { t.Fatalf("newConfig failed: %s", err) @@ -99,7 +101,8 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) { } func TestConfigShouldDefaultVMSizeToStandardA1(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if c.VMSize != "Standard_A1" { t.Errorf("Expected 'VMSize' to default to 'Standard_A1', but got '%s'.", c.VMSize) @@ -107,7 +110,8 @@ func TestConfigShouldDefaultVMSizeToStandardA1(t *testing.T) { } func TestConfigShouldDefaultImageVersionToLatest(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if c.ImageVersion != "latest" { t.Errorf("Expected 'ImageVersion' to default to 'latest', but got '%s'.", c.ImageVersion) @@ -127,7 +131,8 @@ func TestConfigShouldNotDefaultImageVersionIfCustomImage(t *testing.T) { "communicator": "none", } - c, _, _ := newConfig(config, getPackerConfiguration()) + var c Config + c.Prepare(config, getPackerConfiguration()) if c.ImageVersion != "" { t.Errorf("Expected 'ImageVersion' to empty, but got '%s'.", c.ImageVersion) } @@ -153,7 +158,8 @@ func TestConfigShouldNormalizeOSTypeCase(t *testing.T) { for k, v := range os_types { for _, os_type := range v { config["os_type"] = os_type - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatalf("Expected config to accept the value %q, but it did not", os_type) } @@ -167,7 +173,8 @@ func TestConfigShouldNormalizeOSTypeCase(t *testing.T) { bad_os_types := []string{"", "does-not-exist"} for _, os_type := range bad_os_types { config["os_type"] = os_type - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatalf("Expected config to not accept the value %q, but it did", os_type) } @@ -191,7 +198,8 @@ func TestConfigShouldRejectCustomImageAndMarketPlace(t *testing.T) { for _, x := range marketPlace { config[x] = "ignore" - _, _, err := newConfig(config, packerConfiguration) + var c Config + _, err := c.Prepare(config, packerConfiguration) if err == nil { t.Errorf("Expected Config to reject image_url and %s, but it did not", x) } @@ -212,7 +220,8 @@ func TestConfigVirtualNetworkNameIsOptional(t *testing.T) { "virtual_network_name": "MyVirtualNetwork", } - c, _, _ := newConfig(config, getPackerConfiguration()) + var c Config + c.Prepare(config, getPackerConfiguration()) if c.VirtualNetworkName != "MyVirtualNetwork" { t.Errorf("Expected Config to set virtual_network_name to MyVirtualNetwork, but got %q", c.VirtualNetworkName) } @@ -241,7 +250,8 @@ func TestConfigVirtualNetworkResourceGroupNameMustBeSetWithVirtualNetworkName(t "virtual_network_resource_group_name": "MyVirtualNetworkRG", } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Error("Expected Config to reject virtual_network_resource_group_name, if virtual_network_name is not set.") } @@ -264,7 +274,8 @@ func TestConfigVirtualNetworkSubnetNameMustBeSetWithVirtualNetworkName(t *testin "virtual_network_subnet_name": "MyVirtualNetworkRG", } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Error("Expected Config to reject virtual_network_subnet_name, if virtual_network_name is not set.") } @@ -284,7 +295,8 @@ func TestConfigAllowedInboundIpAddressesIsOptional(t *testing.T) { "virtual_network_name": "MyVirtualNetwork", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -311,7 +323,8 @@ func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) { } config["allowed_inbound_ip_addresses"] = ipValue0 - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -321,7 +334,7 @@ func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) { } config["allowed_inbound_ip_addresses"] = cidrValue2 - c, _, err = newConfig(config, getPackerConfiguration()) + _, err = c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -331,7 +344,7 @@ func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) { } config["allowed_inbound_ip_addresses"] = []string{ipValue0, cidrValue2, ipValue1, cidrValue3} - c, _, err = newConfig(config, getPackerConfiguration()) + _, err = c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -357,13 +370,14 @@ func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) { } config["allowed_inbound_ip_addresses"] = []string{"127.0.0.1", "127.0.0.two"} - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses) } config["allowed_inbound_ip_addresses"] = []string{"192.168.100.1000/24", "10.10.1.16/32"} - c, _, err = newConfig(config, getPackerConfiguration()) + _, err = c.Prepare(config, getPackerConfiguration()) if err == nil { // 192.168.100.1000/24 is invalid t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses) @@ -384,20 +398,22 @@ func TestConfigShouldRejectInboundIpAddressesWithVirtualNetwork(t *testing.T) { "allowed_inbound_ip_addresses": "127.0.0.1", } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } config["virtual_network_name"] = "some_vnet_name" - _, _, err = newConfig(config, getPackerConfiguration()) + _, err = c.Prepare(config, getPackerConfiguration()) if err == nil { t.Errorf("Expected configuration creation to fail, but it succeeded with allowed_inbound_ip_addresses and virtual_network_name both specified") } } func TestConfigShouldDefaultToPublicCloud(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if c.ClientConfig.CloudEnvironmentName != "Public" { t.Errorf("Expected 'CloudEnvironmentName' to default to 'Public', but got '%s'.", c.ClientConfig.CloudEnvironmentName) @@ -448,7 +464,8 @@ func TestConfigInstantiatesCorrectAzureEnvironment(t *testing.T) { for _, x := range table { config["cloud_environment_name"] = x.name - c, _, err := newConfig(config, packerConfiguration) + var c Config + _, err := c.Prepare(config, packerConfiguration) if err != nil { t.Fatal(err) } @@ -463,7 +480,8 @@ func TestUserShouldProvideRequiredValues(t *testing.T) { builderValues := getArmBuilderConfiguration() // Ensure we can successfully create a config. - _, _, err := newConfig(builderValues, getPackerConfiguration()) + var c Config + _, err := c.Prepare(builderValues, getPackerConfiguration()) if err != nil { t.Error("Expected configuration creation to succeed, but it failed!\n") t.Fatalf(" -> %+v\n", builderValues) @@ -474,7 +492,8 @@ func TestUserShouldProvideRequiredValues(t *testing.T) { originalValue := builderValues[v] delete(builderValues, v) - _, _, err := newConfig(builderValues, getPackerConfiguration()) + var c Config + _, err := c.Prepare(builderValues, getPackerConfiguration()) if err == nil { t.Error("Expected configuration creation to fail, but it succeeded!\n") t.Fatalf(" -> %+v\n", builderValues) @@ -485,7 +504,8 @@ func TestUserShouldProvideRequiredValues(t *testing.T) { } func TestSystemShouldDefineRuntimeValues(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if c.Password == "" { t.Errorf("Expected Password to not be empty, but it was '%s'!", c.Password) @@ -513,7 +533,8 @@ func TestSystemShouldDefineRuntimeValues(t *testing.T) { } func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) parameters := c.toVirtualMachineCaptureParameters() if *parameters.DestinationContainerName != c.CaptureContainerName { @@ -530,7 +551,8 @@ func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) { } func TestConfigShouldSupportPackersConfigElements(t *testing.T) { - c, _, err := newConfig( + var c Config + _, err := c.Prepare( getArmBuilderConfiguration(), getPackerConfiguration(), getPackerCommunicatorConfiguration()) @@ -554,7 +576,8 @@ func TestWinRMConfigShouldSetRoundTripDecorator(t *testing.T) { config["winrm_username"] = "username" config["winrm_password"] = "password" - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -579,7 +602,8 @@ func TestUserDeviceLoginIsEnabledForLinux(t *testing.T) { "communicator": "none", } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatalf("failed to use device login for Linux: %s", err) } @@ -610,7 +634,8 @@ func TestConfigShouldRejectMalformedCaptureNamePrefix(t *testing.T) { for _, x := range wellFormedCaptureNamePrefix { config["capture_name_prefix"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Errorf("Expected test to pass, but it failed with the well-formed capture_name_prefix set to %q.", x) @@ -628,7 +653,8 @@ func TestConfigShouldRejectMalformedCaptureNamePrefix(t *testing.T) { for _, x := range malformedCaptureNamePrefix { config["capture_name_prefix"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Errorf("Expected test to fail, but it succeeded with the malformed capture_name_prefix set to %q.", x) @@ -660,7 +686,8 @@ func TestConfigShouldRejectMalformedCaptureContainerName(t *testing.T) { for _, x := range wellFormedCaptureContainerName { config["capture_container_name"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Errorf("Expected test to pass, but it failed with the well-formed capture_container_name set to %q.", x) @@ -678,7 +705,8 @@ func TestConfigShouldRejectMalformedCaptureContainerName(t *testing.T) { for _, x := range malformedCaptureContainerName { config["capture_container_name"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Errorf("Expected test to fail, but it succeeded with the malformed capture_container_name set to %q.", x) @@ -710,7 +738,8 @@ func TestConfigShouldRejectMalformedManagedImageOSDiskSnapshotName(t *testing.T) for _, x := range wellFormedManagedImageOSDiskSnapshotName { config["managed_image_os_disk_snapshot_name"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Errorf("Expected test to pass, but it failed with the well-formed managed_image_os_disk_snapshot_name set to %q.", x) @@ -727,7 +756,8 @@ func TestConfigShouldRejectMalformedManagedImageOSDiskSnapshotName(t *testing.T) for _, x := range malformedManagedImageOSDiskSnapshotName { config["managed_image_os_disk_snapshot_name"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Errorf("Expected test to fail, but it succeeded with the malformed managed_image_os_disk_snapshot_name set to %q.", x) @@ -760,7 +790,8 @@ func TestConfigShouldRejectMalformedManagedImageDataDiskSnapshotPrefix(t *testin for _, x := range wellFormedManagedImageDataDiskSnapshotPrefix { config["managed_image_data_disk_snapshot_prefix"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Errorf("Expected test to pass, but it failed with the well-formed managed_image_data_disk_snapshot_prefix set to %q.", x) @@ -777,7 +808,8 @@ func TestConfigShouldRejectMalformedManagedImageDataDiskSnapshotPrefix(t *testin for _, x := range malformedManagedImageDataDiskSnapshotPrefix { config["managed_image_data_disk_snapshot_prefix"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Errorf("Expected test to fail, but it succeeded with the malformed managed_image_data_disk_snapshot_prefix set to %q.", x) @@ -805,7 +837,8 @@ func TestConfigShouldAcceptTags(t *testing.T) { }, } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) @@ -855,7 +888,8 @@ func TestConfigShouldRejectTagsInExcessOf15AcceptTags(t *testing.T) { "azure_tags": tooManyTags, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject based on an excessive amount of tags (> 15)") @@ -887,7 +921,8 @@ func TestConfigShouldRejectExcessiveTagNameLength(t *testing.T) { "azure_tags": tags, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject tag name based on length (> 512)") } @@ -918,7 +953,8 @@ func TestConfigShouldRejectExcessiveTagValueLength(t *testing.T) { "azure_tags": tags, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject tag value based on length (> 256)") } @@ -935,7 +971,8 @@ func TestConfigZoneResilientShouldDefaultToFalse(t *testing.T) { "os_type": "linux", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -958,7 +995,8 @@ func TestConfigZoneResilientSetFromConfig(t *testing.T) { "managed_image_zone_resilient": true, } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -986,7 +1024,8 @@ func TestConfigShouldRejectMissingCustomDataFile(t *testing.T) { "custom_data_file": "/this/file/does/not/exist", } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject missing custom data file") } @@ -1006,7 +1045,8 @@ func TestConfigShouldRejectManagedImageOSDiskSnapshotNameWithoutManagedImageName "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject Managed Image build with OS disk snapshot name but without managed image name") } @@ -1026,7 +1066,8 @@ func TestConfigShouldRejectManagedImageOSDiskSnapshotNameWithoutManagedImageReso "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject Managed Image build with OS disk snapshot name but without managed image resource group name") } @@ -1046,7 +1087,8 @@ func TestConfigShouldRejectImageDataDiskSnapshotPrefixWithoutManagedImageName(t "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix but without managed image name") } @@ -1066,7 +1108,8 @@ func TestConfigShouldRejectImageDataDiskSnapshotPrefixWithoutManagedImageResourc "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix but without managed image resource group name") } @@ -1088,7 +1131,8 @@ func TestConfigShouldAcceptManagedImageOSDiskSnapshotNameAndManagedImageDataDisk "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal("expected config to accept platform managed image build") } @@ -1109,7 +1153,8 @@ func TestConfigShouldRejectManagedImageOSDiskSnapshotNameAndManagedImageDataDisk "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix and OS disk snapshot name with capture container name") } @@ -1130,7 +1175,8 @@ func TestConfigShouldRejectManagedImageOSDiskSnapshotNameAndManagedImageDataDisk "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject Managed Image build with data disk snapshot prefix and OS disk snapshot name with capture name prefix") } @@ -1151,7 +1197,8 @@ func TestConfigShouldAcceptPlatformManagedImageBuild(t *testing.T) { "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal("expected config to accept platform managed image build") } @@ -1175,7 +1222,8 @@ func TestConfigShouldRejectVhdAndManagedImageOutput(t *testing.T) { "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject VHD and Managed Image build") } @@ -1195,7 +1243,8 @@ func TestConfigShouldRejectManagedImageSourceAndVhdOutput(t *testing.T) { "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject VHD and Managed Image build") } @@ -1218,7 +1267,8 @@ func TestConfigShouldRejectCustomAndPlatformManagedImageBuild(t *testing.T) { "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject custom and platform input for a managed image build") } @@ -1239,7 +1289,8 @@ func TestConfigShouldRejectCustomAndImageUrlForManagedImageBuild(t *testing.T) { "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject custom and platform input for a managed image build") } @@ -1260,7 +1311,8 @@ func TestConfigShouldRejectMalformedManageImageStorageAccountTypes(t *testing.T) "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject custom and platform input for a managed image build") } @@ -1281,7 +1333,8 @@ func TestConfigShouldRejectMalformedDiskCachingType(t *testing.T) { "os_type": constants.Target_Linux, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject custom and platform input for a managed image build") } @@ -1305,7 +1358,8 @@ func TestConfigShouldAcceptManagedImageStorageAccountTypes(t *testing.T) { for _, x := range storage_account_types { config["managed_image_storage_account_type"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatalf("expected config to accept a managed_image_storage_account_type of %q", x) } @@ -1330,7 +1384,8 @@ func TestConfigShouldAcceptDiskCachingTypes(t *testing.T) { for _, x := range storage_account_types { config["disk_caching_type"] = x - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatalf("expected config to accept a disk_caching_type of %q", x) } @@ -1355,7 +1410,8 @@ func TestConfigShouldRejectTempAndBuildResourceGroupName(t *testing.T) { "build_resource_group_name": "rgn00", } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject the use of both temp_resource_group_name and build_resource_group_name") } @@ -1402,7 +1458,8 @@ func TestConfigShouldRejectInvalidResourceGroupNames(t *testing.T) { for _, y := range tests { config[x] = y.name - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if !y.ok && err == nil { t.Errorf("expected config to reject %q for setting %q", y.name, x) } else if y.ok && err != nil { @@ -1452,7 +1509,8 @@ func TestConfigShouldRejectManagedDiskNames(t *testing.T) { for _, y := range testsResourceGroupNames { config[settingUnderTest] = y.name - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if !y.ok && err == nil { t.Errorf("expected config to reject %q for setting %q", y.name, settingUnderTest) } else if y.ok && err != nil { @@ -1486,7 +1544,8 @@ func TestConfigShouldRejectManagedDiskNames(t *testing.T) { for _, y := range testNames { config[settingUnderTest] = y.name - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if !y.ok && err == nil { t.Logf("expected config to reject %q for setting %q", y.name, settingUnderTest) } else if y.ok && err != nil { @@ -1496,7 +1555,8 @@ func TestConfigShouldRejectManagedDiskNames(t *testing.T) { } func TestConfigAdditionalDiskDefaultIsNil(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if c.AdditionalDiskSize != nil { t.Errorf("Expected Config to not have a set of additional disks, but got a non nil value") } @@ -1519,7 +1579,8 @@ func TestConfigAdditionalDiskOverrideDefault(t *testing.T) { "disk_additional_size": {32, 64}, } - c, _, _ := newConfig(config, diskconfig, getPackerConfiguration()) + var c Config + c.Prepare(config, diskconfig, getPackerConfiguration()) if c.AdditionalDiskSize == nil { t.Errorf("Expected Config to have a set of additional disks, but got nil") } @@ -1561,19 +1622,20 @@ func TestPlanInfoConfiguration(t *testing.T) { } config["plan_info"] = planInfo - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject the use of plan_name without plan_product and plan_publisher") } planInfo["plan_product"] = "--plan-product--" - _, _, err = newConfig(config, getPackerConfiguration()) + _, err = c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject the use of plan_name and plan_product without plan_publisher") } planInfo["plan_publisher"] = "--plan-publisher--" - c, _, err := newConfig(config, getPackerConfiguration()) + _, err = c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatalf("expected config to accept a complete plan configuration: %s", err) } @@ -1610,7 +1672,8 @@ func TestPlanInfoPromotionCode(t *testing.T) { }, } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatalf("expected config to accept plan_info configuration, but got %s", err) } @@ -1659,7 +1722,8 @@ func TestPlanInfoTooManyTagsErrors(t *testing.T) { }, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Fatal("expected config to reject configuration due to excess tags") } @@ -1682,7 +1746,8 @@ func TestConfigShouldAllowTempNameOverrides(t *testing.T) { "temp_compute_name": "myTempComputeName", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Errorf("newConfig failed with %q", err) } @@ -1716,7 +1781,8 @@ func TestConfigShouldAllowAsyncResourceGroupOverride(t *testing.T) { "async_resourcegroup_delete": "true", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Errorf("newConfig failed with %q", err) } @@ -1738,7 +1804,8 @@ func TestConfigShouldAllowAsyncResourceGroupOverrideNoValue(t *testing.T) { "managed_image_resource_group_name": "ignore", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Errorf("newConfig failed with %q", err) } @@ -1761,10 +1828,10 @@ func TestConfigShouldAllowAsyncResourceGroupOverrideBadValue(t *testing.T) { "async_resourcegroup_delete": "asdasda", } - c, _, err := newConfig(config, getPackerConfiguration()) - if err != nil && c == nil { - t.Log("newConfig failed which is expected ", err) - + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) + if err != nil { + t.Log("newConfig failed which is expected ", err) } } @@ -1782,7 +1849,8 @@ func TestConfigShouldAllowSharedImageGalleryOptions(t *testing.T) { }, } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err == nil { t.Log("expected config to accept Shared Image Gallery options", err) } @@ -1807,7 +1875,8 @@ func TestConfigShouldRejectSharedImageGalleryWithVhdTarget(t *testing.T) { "capture_name_prefix": "ignore", } - _, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Log("expected an error if Shared Image Gallery source is used with VHD target", err) } @@ -1818,7 +1887,8 @@ func Test_GivenZoneNotSupportingResiliency_ConfigValidate_ShouldWarn(t *testing. builderValues["managed_image_zone_resilient"] = "true" builderValues["location"] = "ukwest" - c, _, err := newConfig(builderValues, getPackerConfiguration()) + var c Config + _, err := c.Prepare(builderValues, getPackerConfiguration()) if err != nil { t.Errorf("newConfig failed with %q", err) } @@ -1836,7 +1906,8 @@ func Test_GivenZoneSupportingResiliency_ConfigValidate_ShouldNotWarn(t *testing. builderValues["managed_image_zone_resilient"] = "true" builderValues["location"] = "westeurope" - c, _, err := newConfig(builderValues, getPackerConfiguration()) + var c Config + _, err := c.Prepare(builderValues, getPackerConfiguration()) if err != nil { t.Errorf("newConfig failed with %q", err) } diff --git a/builder/azure/arm/resource_resolver_test.go b/builder/azure/arm/resource_resolver_test.go index f8e339687..68b227ee0 100644 --- a/builder/azure/arm/resource_resolver_test.go +++ b/builder/azure/arm/resource_resolver_test.go @@ -5,14 +5,15 @@ import ( ) func TestResourceResolverIgnoresEmptyVirtualNetworkName(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) if c.VirtualNetworkName != "" { t.Fatalf("Expected VirtualNetworkName to be empty by default") } sut := newTestResourceResolver() sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called - sut.Resolve(c) + sut.Resolve(&c) if c.VirtualNetworkName != "" { t.Fatalf("Expected VirtualNetworkName to be empty") @@ -25,7 +26,8 @@ func TestResourceResolverIgnoresEmptyVirtualNetworkName(t *testing.T) { // If the user fully specified the virtual network name and resource group then // there is no need to do a lookup. func TestResourceResolverIgnoresSetVirtualNetwork(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) c.VirtualNetworkName = "--virtual-network-name--" c.VirtualNetworkResourceGroupName = "--virtual-network-resource-group-name--" c.VirtualNetworkSubnetName = "--virtual-network-subnet-name--" @@ -33,7 +35,7 @@ func TestResourceResolverIgnoresSetVirtualNetwork(t *testing.T) { sut := newTestResourceResolver() sut.findVirtualNetworkResourceGroup = nil // assert that this is not even called sut.findVirtualNetworkSubnet = nil // assert that this is not even called - sut.Resolve(c) + sut.Resolve(&c) if c.VirtualNetworkName != "--virtual-network-name--" { t.Fatalf("Expected VirtualNetworkName to be --virtual-network-name--") @@ -49,11 +51,12 @@ func TestResourceResolverIgnoresSetVirtualNetwork(t *testing.T) { // If the user set virtual network name then the code should resolve virtual network // resource group name. func TestResourceResolverSetVirtualNetworkResourceGroupName(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) c.VirtualNetworkName = "--virtual-network-name--" sut := newTestResourceResolver() - sut.Resolve(c) + sut.Resolve(&c) if c.VirtualNetworkResourceGroupName != "findVirtualNetworkResourceGroup is mocked" { t.Fatalf("Expected VirtualNetworkResourceGroupName to be 'findVirtualNetworkResourceGroup is mocked'") diff --git a/builder/azure/arm/step_publish_to_shared_image_gallery.go b/builder/azure/arm/step_publish_to_shared_image_gallery.go index 347a9002c..b3f24e6e6 100644 --- a/builder/azure/arm/step_publish_to_shared_image_gallery.go +++ b/builder/azure/arm/step_publish_to_shared_image_gallery.go @@ -3,7 +3,9 @@ package arm import ( "context" "fmt" + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute" + "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/packer/builder/azure/common/constants" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -11,7 +13,7 @@ import ( type StepPublishToSharedImageGallery struct { client *AzureClient - publish func(ctx context.Context, mdiID, miSigPubRg, miSIGalleryName, miSGImageName, miSGImageVersion string, miSigReplicationRegions []string, location string, tags map[string]*string) (string, error) + publish func(ctx context.Context, mdiID, miSigPubRg, miSIGalleryName, miSGImageName, miSGImageVersion string, miSigReplicationRegions []string, miSGImageVersionEndOfLifeDate string, miSGImageVersionExcludeFromLatest bool, miSigReplicaCount int32, location string, tags map[string]*string) (string, error) say func(message string) error func(e error) toSIG func() bool @@ -35,7 +37,7 @@ func NewStepPublishToSharedImageGallery(client *AzureClient, ui packer.Ui, confi return step } -func (s *StepPublishToSharedImageGallery) publishToSig(ctx context.Context, mdiID string, miSigPubRg string, miSIGalleryName string, miSGImageName string, miSGImageVersion string, miSigReplicationRegions []string, location string, tags map[string]*string) (string, error) { +func (s *StepPublishToSharedImageGallery) publishToSig(ctx context.Context, mdiID string, miSigPubRg string, miSIGalleryName string, miSGImageName string, miSGImageVersion string, miSigReplicationRegions []string, miSGImageVersionEndOfLifeDate string, miSGImageVersionExcludeFromLatest bool, miSigReplicaCount int32, location string, tags map[string]*string) (string, error) { replicationRegions := make([]compute.TargetRegion, len(miSigReplicationRegions)) for i, v := range miSigReplicationRegions { @@ -43,6 +45,17 @@ func (s *StepPublishToSharedImageGallery) publishToSig(ctx context.Context, mdiI replicationRegions[i] = compute.TargetRegion{Name: ®ionName} } + var endOfLifeDate *date.Time + if miSGImageVersionEndOfLifeDate != "" { + parseDate, err := date.ParseTime("2006-01-02T15:04:05.99Z", miSGImageVersionEndOfLifeDate) + if err != nil { + s.say(fmt.Sprintf("Error parsing date from shared_gallery_image_version_end_of_life_date: %s", err)) + return "", err + } + endOfLifeDate = &date.Time{Time: parseDate} + } else { + endOfLifeDate = (*date.Time)(nil) + } galleryImageVersion := compute.GalleryImageVersion{ Location: &location, Tags: tags, @@ -53,7 +66,10 @@ func (s *StepPublishToSharedImageGallery) publishToSig(ctx context.Context, mdiI ID: &mdiID, }, }, - TargetRegions: &replicationRegions, + TargetRegions: &replicationRegions, + EndOfLifeDate: endOfLifeDate, + ExcludeFromLatest: &miSGImageVersionExcludeFromLatest, + ReplicaCount: &miSigReplicaCount, }, }, } @@ -101,14 +117,27 @@ func (s *StepPublishToSharedImageGallery) Run(ctx context.Context, stateBag mult var targetManagedImageName = stateBag.Get(constants.ArmManagedImageName).(string) var managedImageSubscription = stateBag.Get(constants.ArmManagedImageSubscription).(string) var mdiID = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", managedImageSubscription, targetManagedImageResourceGroupName, targetManagedImageName) + miSGImageVersionEndOfLifeDate, _ := stateBag.Get(constants.ArmManagedImageSharedGalleryImageVersionEndOfLifeDate).(string) + miSGImageVersionExcludeFromLatest, _ := stateBag.Get(constants.ArmManagedImageSharedGalleryImageVersionExcludeFromLatest).(bool) + miSigReplicaCount, _ := stateBag.Get(constants.ArmManagedImageSharedGalleryImageVersionReplicaCount).(int32) + // Replica count must be between 1 and 10 inclusive. + if miSigReplicaCount <= 0 { + miSigReplicaCount = constants.SharedImageGalleryImageVersionDefaultMinReplicaCount + } else if miSigReplicaCount > 10 { + miSigReplicaCount = constants.SharedImageGalleryImageVersionDefaultMaxReplicaCount + } - s.say(fmt.Sprintf(" -> MDI ID used for SIG publish : '%s'", mdiID)) - s.say(fmt.Sprintf(" -> SIG publish resource group : '%s'", miSigPubRg)) - s.say(fmt.Sprintf(" -> SIG gallery name : '%s'", miSIGalleryName)) - s.say(fmt.Sprintf(" -> SIG image name : '%s'", miSGImageName)) - s.say(fmt.Sprintf(" -> SIG image version : '%s'", miSGImageVersion)) - s.say(fmt.Sprintf(" -> SIG replication regions : '%v'", miSigReplicationRegions)) - createdGalleryImageVersionID, err := s.publish(ctx, mdiID, miSigPubRg, miSIGalleryName, miSGImageName, miSGImageVersion, miSigReplicationRegions, location, tags) + s.say(fmt.Sprintf(" -> MDI ID used for SIG publish : '%s'", mdiID)) + s.say(fmt.Sprintf(" -> SIG publish resource group : '%s'", miSigPubRg)) + s.say(fmt.Sprintf(" -> SIG gallery name : '%s'", miSIGalleryName)) + s.say(fmt.Sprintf(" -> SIG image name : '%s'", miSGImageName)) + s.say(fmt.Sprintf(" -> SIG image version : '%s'", miSGImageVersion)) + s.say(fmt.Sprintf(" -> SIG replication regions : '%v'", miSigReplicationRegions)) + s.say(fmt.Sprintf(" -> SIG image version endoflife date : '%s'", miSGImageVersionEndOfLifeDate)) + s.say(fmt.Sprintf(" -> SIG image version exclude from latest : '%t'", miSGImageVersionExcludeFromLatest)) + s.say(fmt.Sprintf(" -> SIG replica count [1, 10] : '%d'", miSigReplicaCount)) + + createdGalleryImageVersionID, err := s.publish(ctx, mdiID, miSigPubRg, miSIGalleryName, miSGImageName, miSGImageVersion, miSigReplicationRegions, miSGImageVersionEndOfLifeDate, miSGImageVersionExcludeFromLatest, miSigReplicaCount, location, tags) if err != nil { stateBag.Put(constants.Error, err) diff --git a/builder/azure/arm/step_publish_to_shared_image_gallery_test.go b/builder/azure/arm/step_publish_to_shared_image_gallery_test.go index 97c7aa289..d11fb052b 100644 --- a/builder/azure/arm/step_publish_to_shared_image_gallery_test.go +++ b/builder/azure/arm/step_publish_to_shared_image_gallery_test.go @@ -2,14 +2,15 @@ package arm import ( "context" + "testing" + "github.com/hashicorp/packer/builder/azure/common/constants" "github.com/hashicorp/packer/helper/multistep" - "testing" ) func TestStepPublishToSharedImageGalleryShouldNotPublishForVhd(t *testing.T) { var testSubject = &StepPublishToSharedImageGallery{ - publish: func(context.Context, string, string, string, string, string, []string, string, map[string]*string) (string, error) { + publish: func(context.Context, string, string, string, string, string, []string, string, bool, int32, string, map[string]*string) (string, error) { return "test", nil }, say: func(message string) {}, @@ -30,7 +31,7 @@ func TestStepPublishToSharedImageGalleryShouldNotPublishForVhd(t *testing.T) { func TestStepPublishToSharedImageGalleryShouldPublishForManagedImageWithSig(t *testing.T) { var testSubject = &StepPublishToSharedImageGallery{ - publish: func(context.Context, string, string, string, string, string, []string, string, map[string]*string) (string, error) { + publish: func(context.Context, string, string, string, string, string, []string, string, bool, int32, string, map[string]*string) (string, error) { return "", nil }, say: func(message string) {}, diff --git a/builder/azure/arm/step_save_winrm_password.go b/builder/azure/arm/step_save_winrm_password.go deleted file mode 100644 index c23c6b26b..000000000 --- a/builder/azure/arm/step_save_winrm_password.go +++ /dev/null @@ -1,25 +0,0 @@ -package arm - -import ( - "context" - - commonhelper "github.com/hashicorp/packer/helper/common" - "github.com/hashicorp/packer/helper/multistep" - "github.com/hashicorp/packer/packer" -) - -type StepSaveWinRMPassword struct { - Password string - BuildName string -} - -func (s *StepSaveWinRMPassword) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - // store so that we can access this later during provisioning - commonhelper.SetSharedState("winrm_password", s.Password, s.BuildName) - packer.LogSecretFilter.Set(s.Password) - return multistep.ActionContinue -} - -func (s *StepSaveWinRMPassword) Cleanup(multistep.StateBag) { - commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName) -} diff --git a/builder/azure/arm/template_factory_test.go b/builder/azure/arm/template_factory_test.go index 069415893..7c465a791 100644 --- a/builder/azure/arm/template_factory_test.go +++ b/builder/azure/arm/template_factory_test.go @@ -13,8 +13,9 @@ import ( // Ensure the link values are not set, and the concrete values are set. func TestVirtualMachineDeployment00(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) - deployment, err := GetVirtualMachineDeployment(c) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -42,8 +43,9 @@ func TestVirtualMachineDeployment00(t *testing.T) { // Ensure the Virtual Machine template is a valid JSON document. func TestVirtualMachineDeployment01(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) - deployment, err := GetVirtualMachineDeployment(c) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -56,8 +58,9 @@ func TestVirtualMachineDeployment01(t *testing.T) { // Ensure the Virtual Machine template parameters are correct. func TestVirtualMachineDeployment02(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) - deployment, err := GetVirtualMachineDeployment(c) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -104,8 +107,9 @@ func TestVirtualMachineDeployment03(t *testing.T) { m["image_sku"] = "ImageSku" m["image_version"] = "ImageVersion" - c, _, _ := newConfig(m, getPackerConfiguration()) - deployment, err := GetVirtualMachineDeployment(c) + var c Config + c.Prepare(m, getPackerConfiguration()) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -130,12 +134,13 @@ func TestVirtualMachineDeployment04(t *testing.T) { "communicator": "none", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -162,12 +167,13 @@ func TestVirtualMachineDeployment05(t *testing.T) { "virtual_network_subnet_name": "virtualNetworkSubnetName", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -197,12 +203,13 @@ func TestVirtualMachineDeployment06(t *testing.T) { }, } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -227,7 +234,8 @@ func TestVirtualMachineDeployment07(t *testing.T) { "communicator": "none", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } @@ -247,7 +255,7 @@ growpart: base64CustomData := base64.StdEncoding.EncodeToString([]byte(customData)) c.customData = base64CustomData - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -271,12 +279,13 @@ func TestVirtualMachineDeployment08(t *testing.T) { "managed_image_resource_group_name": "ManagedImageResourceGroupName", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -302,12 +311,13 @@ func TestVirtualMachineDeployment09(t *testing.T) { "managed_image_resource_group_name": "ManagedImageResourceGroupName", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -339,12 +349,13 @@ func TestVirtualMachineDeployment10(t *testing.T) { "managed_image_resource_group_name": "ManagedImageResourceGroupName", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -375,12 +386,13 @@ func TestVirtualMachineDeployment11(t *testing.T) { "capture_container_name": "packerimages", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -409,12 +421,13 @@ func TestVirtualMachineDeployment12(t *testing.T) { "managed_image_resource_group_name": "ManagedImageResourceGroupName", } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -442,13 +455,14 @@ func TestVirtualMachineDeployment13(t *testing.T) { "allowed_inbound_ip_addresses": []string{"127.0.0.1", "192.168.100.0/24"}, } - c, _, err := newConfig(config, getPackerConfiguration()) + var c Config + _, err := c.Prepare(config, getPackerConfiguration()) if err != nil { t.Fatal(err) } c.tmpKeyVaultName = "--keyvault-name--" - deployment, err := GetVirtualMachineDeployment(c) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -461,8 +475,9 @@ func TestVirtualMachineDeployment13(t *testing.T) { // Ensure the link values are not set, and the concrete values are set. func TestKeyVaultDeployment00(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) - deployment, err := GetKeyVaultDeployment(c) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) + deployment, err := GetKeyVaultDeployment(&c) if err != nil { t.Fatal(err) } @@ -490,8 +505,9 @@ func TestKeyVaultDeployment00(t *testing.T) { // Ensure the KeyVault template is a valid JSON document. func TestKeyVaultDeployment01(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) - deployment, err := GetKeyVaultDeployment(c) + var c Config + c.Prepare(getArmBuilderConfiguration(), getPackerConfiguration()) + deployment, err := GetKeyVaultDeployment(&c) if err != nil { t.Fatal(err) } @@ -504,9 +520,10 @@ func TestKeyVaultDeployment01(t *testing.T) { // Ensure the KeyVault template parameters are correct. func TestKeyVaultDeployment02(t *testing.T) { - c, _, _ := newConfig(getArmBuilderConfigurationWithWindows(), getPackerConfiguration()) + var c Config + c.Prepare(getArmBuilderConfigurationWithWindows(), getPackerConfiguration()) - deployment, err := GetKeyVaultDeployment(c) + deployment, err := GetKeyVaultDeployment(&c) if err != nil { t.Fatal(err) } @@ -546,8 +563,9 @@ func TestKeyVaultDeployment03(t *testing.T) { }, } - c, _, _ := newConfig(tags, getArmBuilderConfigurationWithWindows(), getPackerConfiguration()) - deployment, err := GetKeyVaultDeployment(c) + var c Config + c.Prepare(tags, getArmBuilderConfigurationWithWindows(), getPackerConfiguration()) + deployment, err := GetKeyVaultDeployment(&c) if err != nil { t.Fatal(err) } @@ -567,8 +585,9 @@ func TestPlanInfo01(t *testing.T) { }, } - c, _, _ := newConfig(planInfo, getArmBuilderConfiguration(), getPackerConfiguration()) - deployment, err := GetVirtualMachineDeployment(c) + var c Config + c.Prepare(planInfo, getArmBuilderConfiguration(), getPackerConfiguration()) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } @@ -592,8 +611,9 @@ func TestPlanInfo02(t *testing.T) { }, } - c, _, _ := newConfig(planInfo, getArmBuilderConfiguration(), getPackerConfiguration()) - deployment, err := GetVirtualMachineDeployment(c) + var c Config + c.Prepare(planInfo, getArmBuilderConfiguration(), getPackerConfiguration()) + deployment, err := GetVirtualMachineDeployment(&c) if err != nil { t.Fatal(err) } diff --git a/builder/azure/chroot/builder.go b/builder/azure/chroot/builder.go index 91ceca991..bc35cea7a 100644 --- a/builder/azure/chroot/builder.go +++ b/builder/azure/chroot/builder.go @@ -1,4 +1,5 @@ //go:generate struct-markdown +//go:generate mapstructure-to-hcl2 -type Config // Package chroot is able to create an Azure managed image without requiring the // launch of a new virtual machine for every build. It does this by attaching and @@ -14,6 +15,7 @@ import ( "runtime" "strings" + "github.com/hashicorp/hcl/v2/hcldec" azcommon "github.com/hashicorp/packer/builder/azure/common" "github.com/hashicorp/packer/builder/azure/common/client" "github.com/hashicorp/packer/common" @@ -116,7 +118,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = azcommon.TemplateFuncs b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc() err := config.Decode(&b.config, &config.DecodeOpts{ @@ -134,7 +138,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } var errs *packer.MultiError @@ -143,7 +147,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Defaults err = b.config.ClientConfig.SetDefaultValues() if err != nil { - return nil, err + return nil, nil, err } if b.config.ChrootMounts == nil { @@ -254,11 +258,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil { - return warns, errs + return nil, warns, errs } packer.LogSecretFilter.Set(b.config.ClientConfig.ClientSecret, b.config.ClientConfig.ClientJWT) - return warns, nil + return nil, warns, nil } func checkDiskCacheType(s string) interface{} { diff --git a/builder/azure/chroot/builder.hcl2spec.go b/builder/azure/chroot/builder.hcl2spec.go new file mode 100644 index 000000000..50a42e314 --- /dev/null +++ b/builder/azure/chroot/builder.hcl2spec.go @@ -0,0 +1,92 @@ +// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT. +package chroot + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name"` + ClientID *string `mapstructure:"client_id" cty:"client_id"` + ClientSecret *string `mapstructure:"client_secret" cty:"client_secret"` + ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path"` + ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt"` + ObjectID *string `mapstructure:"object_id" cty:"object_id"` + TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id"` + SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id"` + FromScratch *bool `mapstructure:"from_scratch" cty:"from_scratch"` + Source *string `mapstructure:"source" required:"true" cty:"source"` + CommandWrapper *string `mapstructure:"command_wrapper" cty:"command_wrapper"` + PreMountCommands []string `mapstructure:"pre_mount_commands" cty:"pre_mount_commands"` + MountOptions []string `mapstructure:"mount_options" cty:"mount_options"` + MountPartition *string `mapstructure:"mount_partition" cty:"mount_partition"` + MountPath *string `mapstructure:"mount_path" cty:"mount_path"` + PostMountCommands []string `mapstructure:"post_mount_commands" cty:"post_mount_commands"` + ChrootMounts [][]string `mapstructure:"chroot_mounts" cty:"chroot_mounts"` + CopyFiles []string `mapstructure:"copy_files" cty:"copy_files"` + TemporaryOSDiskName *string `mapstructure:"temporary_os_disk_name" cty:"temporary_os_disk_name"` + OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" cty:"os_disk_size_gb"` + OSDiskStorageAccountType *string `mapstructure:"os_disk_storage_account_type" cty:"os_disk_storage_account_type"` + OSDiskCacheType *string `mapstructure:"os_disk_cache_type" cty:"os_disk_cache_type"` + OSDiskSkipCleanup *bool `mapstructure:"os_disk_skip_cleanup" cty:"os_disk_skip_cleanup"` + ImageResourceID *string `mapstructure:"image_resource_id" required:"true" cty:"image_resource_id"` + ImageHyperVGeneration *string `mapstructure:"image_hyperv_generation" cty:"image_hyperv_generation"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false}, + "client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false}, + "client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false}, + "client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false}, + "client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false}, + "object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false}, + "tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false}, + "subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false}, + "from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false}, + "source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false}, + "command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false}, + "pre_mount_commands": &hcldec.AttrSpec{Name: "pre_mount_commands", Type: cty.List(cty.String), Required: false}, + "mount_options": &hcldec.AttrSpec{Name: "mount_options", Type: cty.List(cty.String), Required: false}, + "mount_partition": &hcldec.AttrSpec{Name: "mount_partition", Type: cty.String, Required: false}, + "mount_path": &hcldec.AttrSpec{Name: "mount_path", Type: cty.String, Required: false}, + "post_mount_commands": &hcldec.AttrSpec{Name: "post_mount_commands", Type: cty.List(cty.String), Required: false}, + "chroot_mounts": &hcldec.BlockListSpec{TypeName: "chroot_mounts", Nested: &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.String), Required: false}}, + "copy_files": &hcldec.AttrSpec{Name: "copy_files", Type: cty.List(cty.String), Required: false}, + "temporary_os_disk_name": &hcldec.AttrSpec{Name: "temporary_os_disk_name", Type: cty.String, Required: false}, + "os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false}, + "os_disk_storage_account_type": &hcldec.AttrSpec{Name: "os_disk_storage_account_type", Type: cty.String, Required: false}, + "os_disk_cache_type": &hcldec.AttrSpec{Name: "os_disk_cache_type", Type: cty.String, Required: false}, + "os_disk_skip_cleanup": &hcldec.AttrSpec{Name: "os_disk_skip_cleanup", Type: cty.Bool, Required: false}, + "image_resource_id": &hcldec.AttrSpec{Name: "image_resource_id", Type: cty.String, Required: false}, + "image_hyperv_generation": &hcldec.AttrSpec{Name: "image_hyperv_generation", Type: cty.String, Required: false}, + } + return s +} diff --git a/builder/azure/chroot/builder_test.go b/builder/azure/chroot/builder_test.go index dd40f7737..f84dd9ccc 100644 --- a/builder/azure/chroot/builder_test.go +++ b/builder/azure/chroot/builder_test.go @@ -55,7 +55,7 @@ func TestBuilder_Prepare(t *testing.T) { t.Run(tt.name, func(t *testing.T) { b := &Builder{} - _, err := b.Prepare(tt.config) + _, _, err := b.Prepare(tt.config) if (err != nil) != tt.wantErr { t.Errorf("Builder.Prepare() error = %v, wantErr %v", err, tt.wantErr) diff --git a/builder/azure/chroot/diskattacher_test.go b/builder/azure/chroot/diskattacher_test.go index 3b8c55e4d..fb66fb9e3 100644 --- a/builder/azure/chroot/diskattacher_test.go +++ b/builder/azure/chroot/diskattacher_test.go @@ -2,9 +2,10 @@ package chroot import ( "context" - "github.com/Azure/go-autorest/autorest/to" "testing" + "github.com/Azure/go-autorest/autorest/to" + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute" "github.com/hashicorp/packer/builder/azure/common/client" "github.com/stretchr/testify/assert" diff --git a/builder/azure/chroot/step_create_image.go b/builder/azure/chroot/step_create_image.go index 4ae90d2fe..2b0640a50 100644 --- a/builder/azure/chroot/step_create_image.go +++ b/builder/azure/chroot/step_create_image.go @@ -3,13 +3,14 @@ package chroot import ( "context" "fmt" + "log" + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" "github.com/hashicorp/packer/builder/azure/common/client" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" - "log" ) var _ multistep.Step = &StepCreateImage{} diff --git a/builder/azure/common/client/platform_image.go b/builder/azure/common/client/platform_image.go index ac11ec935..9b8afb566 100644 --- a/builder/azure/common/client/platform_image.go +++ b/builder/azure/common/client/platform_image.go @@ -3,11 +3,12 @@ package client import ( "context" "fmt" + "regexp" + "strings" + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute/computeapi" "github.com/Azure/go-autorest/autorest/to" - "regexp" - "strings" ) var platformImageRegex = regexp.MustCompile(`^[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+$`) diff --git a/builder/azure/common/constants/stateBag.go b/builder/azure/common/constants/stateBag.go index c68afd508..0bf0ad1ae 100644 --- a/builder/azure/common/constants/stateBag.go +++ b/builder/azure/common/constants/stateBag.go @@ -9,6 +9,13 @@ const ( Thumbprint string = "thumbprint" Ui string = "ui" ) + +// Default replica count for image versions in shared image gallery +const ( + SharedImageGalleryImageVersionDefaultMinReplicaCount int32 = 1 + SharedImageGalleryImageVersionDefaultMaxReplicaCount int32 = 10 +) + const ( ArmCaptureTemplate string = "arm.CaptureTemplate" ArmComputeName string = "arm.ComputeName" @@ -30,18 +37,21 @@ const ( ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters" ArmIsExistingResourceGroup string = "arm.IsExistingResourceGroup" - ArmIsManagedImage string = "arm.IsManagedImage" - ArmManagedImageResourceGroupName string = "arm.ManagedImageResourceGroupName" - ArmManagedImageLocation string = "arm.ManagedImageLocation" - ArmManagedImageName string = "arm.ManagedImageName" - ArmManagedImageSigPublishResourceGroup string = "arm.ManagedImageSigPublishResourceGroup" - ArmManagedImageSharedGalleryName string = "arm.ManagedImageSharedGalleryName" - ArmManagedImageSharedGalleryImageName string = "arm.ManagedImageSharedGalleryImageName" - ArmManagedImageSharedGalleryImageVersion string = "arm.ManagedImageSharedGalleryImageVersion" - ArmManagedImageSharedGalleryReplicationRegions string = "arm.ManagedImageSharedGalleryReplicationRegions" - ArmManagedImageSharedGalleryId string = "arm.ArmManagedImageSharedGalleryId" - ArmManagedImageSubscription string = "arm.ArmManagedImageSubscription" - ArmAsyncResourceGroupDelete string = "arm.AsyncResourceGroupDelete" - ArmManagedImageOSDiskSnapshotName string = "arm.ManagedImageOSDiskSnapshotName" - ArmManagedImageDataDiskSnapshotPrefix string = "arm.ManagedImageDataDiskSnapshotPrefix" + ArmIsManagedImage string = "arm.IsManagedImage" + ArmManagedImageResourceGroupName string = "arm.ManagedImageResourceGroupName" + ArmManagedImageLocation string = "arm.ManagedImageLocation" + ArmManagedImageName string = "arm.ManagedImageName" + ArmManagedImageSigPublishResourceGroup string = "arm.ManagedImageSigPublishResourceGroup" + ArmManagedImageSharedGalleryName string = "arm.ManagedImageSharedGalleryName" + ArmManagedImageSharedGalleryImageName string = "arm.ManagedImageSharedGalleryImageName" + ArmManagedImageSharedGalleryImageVersion string = "arm.ManagedImageSharedGalleryImageVersion" + ArmManagedImageSharedGalleryReplicationRegions string = "arm.ManagedImageSharedGalleryReplicationRegions" + ArmManagedImageSharedGalleryId string = "arm.ArmManagedImageSharedGalleryId" + ArmManagedImageSharedGalleryImageVersionEndOfLifeDate string = "arm.ArmManagedImageSharedGalleryImageVersionEndOfLifeDate" + ArmManagedImageSharedGalleryImageVersionReplicaCount string = "arm.ArmManagedImageSharedGalleryImageVersionReplicaCount" + ArmManagedImageSharedGalleryImageVersionExcludeFromLatest string = "arm.ArmManagedImageSharedGalleryImageVersionExcludeFromLatest" + ArmManagedImageSubscription string = "arm.ArmManagedImageSubscription" + ArmAsyncResourceGroupDelete string = "arm.AsyncResourceGroupDelete" + ArmManagedImageOSDiskSnapshotName string = "arm.ManagedImageOSDiskSnapshotName" + ArmManagedImageDataDiskSnapshotPrefix string = "arm.ManagedImageDataDiskSnapshotPrefix" ) diff --git a/builder/cloudstack/builder.go b/builder/cloudstack/builder.go index dc1dace69..7255a057b 100644 --- a/builder/cloudstack/builder.go +++ b/builder/cloudstack/builder.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -15,20 +16,20 @@ const BuilderId = "packer.cloudstack" // Builder represents the CloudStack builder. type Builder struct { - config *Config + config Config runner multistep.Runner ui packer.Ui } -// Prepare implements the packer.Builder interface. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - config, errs := NewConfig(raws...) - if errs != nil { - return nil, errs - } - b.config = config +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return nil, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + errs := b.config.Prepare(raws...) + if errs != nil { + return nil, nil, errs + } + + return nil, nil, nil } // Run implements the packer.Builder interface. @@ -52,7 +53,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) state.Put("client", client) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) @@ -109,7 +110,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Build the artifact and return it artifact := &Artifact{ client: client, - config: b.config, + config: &b.config, template: state.Get("template").(*cloudstack.CreateTemplateResponse), } diff --git a/builder/cloudstack/builder_test.go b/builder/cloudstack/builder_test.go index 6d7ab1444..e6b94a4c0 100644 --- a/builder/cloudstack/builder_test.go +++ b/builder/cloudstack/builder_test.go @@ -40,10 +40,8 @@ func TestBuilder_Prepare(t *testing.T) { }, } - b := &Builder{} - for desc, tc := range cases { - _, errs := b.Prepare(tc.Config) + _, _, errs := (&Builder{}).Prepare(tc.Config) if tc.Err { if errs == nil { diff --git a/builder/cloudstack/config.go b/builder/cloudstack/config.go index cff9e2472..c3487afba 100644 --- a/builder/cloudstack/config.go +++ b/builder/cloudstack/config.go @@ -167,8 +167,7 @@ type Config struct { } // NewConfig parses and validates the given config. -func NewConfig(raws ...interface{}) (*Config, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) error { err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, @@ -179,7 +178,7 @@ func NewConfig(raws ...interface{}) (*Config, error) { }, }, raws...) if err != nil { - return nil, err + return err } var errs *packer.MultiError @@ -309,8 +308,8 @@ func NewConfig(raws ...interface{}) (*Config, error) { // Check for errors and return if we have any. if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return errs } - return c, nil + return nil } diff --git a/builder/cloudstack/config.hcl2spec.go b/builder/cloudstack/config.hcl2spec.go index b92d27d0c..79839316b 100644 --- a/builder/cloudstack/config.hcl2spec.go +++ b/builder/cloudstack/config.hcl2spec.go @@ -49,8 +49,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -104,10 +104,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/cloudstack/config_test.go b/builder/cloudstack/config_test.go index 54fb3248a..bff6c55e0 100644 --- a/builder/cloudstack/config_test.go +++ b/builder/cloudstack/config_test.go @@ -132,7 +132,8 @@ func TestNewConfig(t *testing.T) { raw[tc.Nullify] = nil } - _, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if tc.Err { if errs == nil { diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index 661e2c2a4..8dc6fdec1 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -10,6 +10,7 @@ import ( "net/url" "github.com/digitalocean/godo" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -25,14 +26,15 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = *c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return nil, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/digitalocean/builder_test.go b/builder/digitalocean/builder_test.go index 70ec6fea9..5113495cf 100644 --- a/builder/digitalocean/builder_test.go +++ b/builder/digitalocean/builder_test.go @@ -32,7 +32,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "api_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -47,7 +47,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -62,7 +62,7 @@ func TestBuilderPrepare_Region(t *testing.T) { // Test default delete(config, "region") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -75,7 +75,7 @@ func TestBuilderPrepare_Region(t *testing.T) { // Test set config["region"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -94,7 +94,7 @@ func TestBuilderPrepare_Size(t *testing.T) { // Test default delete(config, "size") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -107,7 +107,7 @@ func TestBuilderPrepare_Size(t *testing.T) { // Test set config["size"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -126,7 +126,7 @@ func TestBuilderPrepare_Image(t *testing.T) { // Test default delete(config, "image") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -139,7 +139,7 @@ func TestBuilderPrepare_Image(t *testing.T) { // Test set config["image"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -157,7 +157,7 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) { config := testConfig() // Test default - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -172,7 +172,7 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) { // Test set config["state_timeout"] = "5m" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -183,7 +183,7 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) { // Test bad config["state_timeout"] = "tubes" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -197,7 +197,7 @@ func TestBuilderPrepare_SnapshotTimeout(t *testing.T) { config := testConfig() // Test default - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -212,7 +212,7 @@ func TestBuilderPrepare_SnapshotTimeout(t *testing.T) { // Test set config["snapshot_timeout"] = "15m" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -223,7 +223,7 @@ func TestBuilderPrepare_SnapshotTimeout(t *testing.T) { // Test bad config["snapshot_timeout"] = "badstring" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -237,7 +237,7 @@ func TestBuilderPrepare_PrivateNetworking(t *testing.T) { config := testConfig() // Test default - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -252,7 +252,7 @@ func TestBuilderPrepare_PrivateNetworking(t *testing.T) { // Test set config["private_networking"] = true b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -270,7 +270,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) { config := testConfig() // Test default - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -285,7 +285,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) { // Test set config["snapshot_name"] = "foobarbaz" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -296,7 +296,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) { // Test set with template config["snapshot_name"] = "{{timestamp}}" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -316,7 +316,7 @@ func TestBuilderPrepare_DropletName(t *testing.T) { config := testConfig() // Test default - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -331,7 +331,7 @@ func TestBuilderPrepare_DropletName(t *testing.T) { // Test normal set config["droplet_name"] = "foobar" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -342,7 +342,7 @@ func TestBuilderPrepare_DropletName(t *testing.T) { // Test with template config["droplet_name"] = "foobar-{{timestamp}}" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -353,7 +353,7 @@ func TestBuilderPrepare_DropletName(t *testing.T) { // Test with bad template config["droplet_name"] = "foobar-{{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/digitalocean/config.go b/builder/digitalocean/config.go index b087cdb9e..a2f42fdae 100644 --- a/builder/digitalocean/config.go +++ b/builder/digitalocean/config.go @@ -89,8 +89,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { var md mapstructure.Metadata err := config.Decode(c, &config.DecodeOpts{ @@ -104,7 +103,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } // Defaults @@ -189,9 +188,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.APIToken) - return c, nil, nil + return nil, nil } diff --git a/builder/digitalocean/config.hcl2spec.go b/builder/digitalocean/config.hcl2spec.go index f39cc404d..a6f895ac7 100644 --- a/builder/digitalocean/config.hcl2spec.go +++ b/builder/digitalocean/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -77,10 +77,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/digitalocean/step_create_droplet.go b/builder/digitalocean/step_create_droplet.go index a64325d35..d3a54c955 100644 --- a/builder/digitalocean/step_create_droplet.go +++ b/builder/digitalocean/step_create_droplet.go @@ -63,6 +63,9 @@ func (s *stepCreateDroplet) Run(ctx context.Context, state multistep.StateBag) m // Store the droplet id for later state.Put("droplet_id", droplet.ID) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", droplet.ID) return multistep.ActionContinue } diff --git a/builder/docker/builder.go b/builder/docker/builder.go index 63cb8f55d..c309dafc1 100644 --- a/builder/docker/builder.go +++ b/builder/docker/builder.go @@ -4,6 +4,7 @@ import ( "context" "log" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -16,18 +17,19 @@ const ( ) type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -75,7 +77,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/docker/communicator_test.go b/builder/docker/communicator_test.go index b028d367a..afaa5e60f 100644 --- a/builder/docker/communicator_test.go +++ b/builder/docker/communicator_test.go @@ -35,7 +35,7 @@ func TestUploadDownload(t *testing.T) { // Setup the builder builder := &Builder{} - warnings, err := builder.Prepare(tpl.Builders["docker"].Config) + _, warnings, err := builder.Prepare(tpl.Builders["docker"].Config) if err != nil { t.Fatalf("Error preparing configuration %s", err) } @@ -118,7 +118,7 @@ func TestLargeDownload(t *testing.T) { // Setup the builder builder := &Builder{} - warnings, err := builder.Prepare(tpl.Builders["docker"].Config) + _, warnings, err := builder.Prepare(tpl.Builders["docker"].Config) if err != nil { t.Fatalf("Error preparing configuration %s", err) } @@ -222,7 +222,7 @@ func TestFixUploadOwner(t *testing.T) { // Setup the builder builder := &Builder{} - warnings, err := builder.Prepare(tpl.Builders["docker"].Config) + _, warnings, err := builder.Prepare(tpl.Builders["docker"].Config) if err != nil { t.Fatalf("Error preparing configuration %s", err) } diff --git a/builder/docker/config.go b/builder/docker/config.go index f67b48974..3785ad079 100644 --- a/builder/docker/config.go +++ b/builder/docker/config.go @@ -107,8 +107,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { c.FixUploadOwner = true @@ -124,7 +123,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } // Defaults @@ -191,8 +190,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } - return c, nil, nil + return nil, nil } diff --git a/builder/docker/config.hcl2spec.go b/builder/docker/config.hcl2spec.go index fe278055e..9183f7770 100644 --- a/builder/docker/config.hcl2spec.go +++ b/builder/docker/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -86,10 +86,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/docker/config_test.go b/builder/docker/config_test.go index 442c6ffe2..cf66e1a7d 100644 --- a/builder/docker/config_test.go +++ b/builder/docker/config_test.go @@ -14,7 +14,8 @@ func testConfig() map[string]interface{} { } func testConfigStruct(t *testing.T) *Config { - c, warns, errs := NewConfig(testConfig()) + var c Config + warns, errs := c.Prepare(testConfig()) if len(warns) > 0 { t.Fatalf("bad: %#v", len(warns)) } @@ -22,7 +23,7 @@ func testConfigStruct(t *testing.T) *Config { t.Fatalf("bad: %#v", errs) } - return c + return &c } func testConfigErr(t *testing.T, warns []string, err error) { @@ -55,17 +56,18 @@ func TestConfigPrepare_exportPath(t *testing.T) { // No export path. This is invalid. Previously this would not error during // validation and as a result the failure would happen at build time. delete(raw, "export_path") - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) testConfigErr(t, warns, errs) // Good export path raw["export_path"] = "good" - _, warns, errs = NewConfig(raw) + warns, errs = c.Prepare(raw) testConfigOk(t, warns, errs) // Bad export path (directory) raw["export_path"] = td - _, warns, errs = NewConfig(raw) + warns, errs = c.Prepare(raw) testConfigErr(t, warns, errs) } @@ -74,17 +76,17 @@ func TestConfigPrepare_exportPathAndCommit(t *testing.T) { // Export but no commit (explicit default) raw["commit"] = false - _, warns, errs := NewConfig(raw) + warns, errs := (&Config{}).Prepare(raw) testConfigOk(t, warns, errs) // Commit AND export specified (invalid) raw["commit"] = true - _, warns, errs = NewConfig(raw) + warns, errs = (&Config{}).Prepare(raw) testConfigErr(t, warns, errs) // Commit but no export delete(raw, "export_path") - _, warns, errs = NewConfig(raw) + warns, errs = (&Config{}).Prepare(raw) testConfigOk(t, warns, errs) } @@ -93,18 +95,18 @@ func TestConfigPrepare_exportDiscard(t *testing.T) { // Export but no discard (explicit default) raw["discard"] = false - _, warns, errs := NewConfig(raw) + warns, errs := (&Config{}).Prepare(raw) testConfigOk(t, warns, errs) // Discard AND export (invalid) raw["discard"] = true - _, warns, errs = NewConfig(raw) + warns, errs = (&Config{}).Prepare(raw) testConfigErr(t, warns, errs) // Discard but no export raw["discard"] = true delete(raw, "export_path") - _, warns, errs = NewConfig(raw) + warns, errs = (&Config{}).Prepare(raw) testConfigOk(t, warns, errs) } @@ -113,12 +115,13 @@ func TestConfigPrepare_image(t *testing.T) { // No image delete(raw, "image") - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) testConfigErr(t, warns, errs) // Good image raw["image"] = "path" - _, warns, errs = NewConfig(raw) + warns, errs = c.Prepare(raw) testConfigOk(t, warns, errs) } @@ -127,7 +130,8 @@ func TestConfigPrepare_pull(t *testing.T) { // No pull set delete(raw, "pull") - c, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) testConfigOk(t, warns, errs) if !c.Pull { t.Fatal("should pull by default") @@ -135,7 +139,7 @@ func TestConfigPrepare_pull(t *testing.T) { // Pull set raw["pull"] = false - c, warns, errs = NewConfig(raw) + warns, errs = c.Prepare(raw) testConfigOk(t, warns, errs) if c.Pull { t.Fatal("should not pull") diff --git a/builder/docker/step_commit.go b/builder/docker/step_commit.go index d8281bcee..33ccb090b 100644 --- a/builder/docker/step_commit.go +++ b/builder/docker/step_commit.go @@ -14,11 +14,17 @@ type StepCommit struct { } func (s *StepCommit) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + config, ok := state.Get("config").(*Config) + if !ok { + err := fmt.Errorf("error encountered obtaining docker config") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + driver := state.Get("driver").(Driver) containerId := state.Get("container_id").(string) - config := state.Get("config").(*Config) - ui := state.Get("ui").(packer.Ui) - if config.WindowsContainer { // docker can't commit a running Windows container err := driver.StopContainer(containerId) diff --git a/builder/docker/step_connect_docker.go b/builder/docker/step_connect_docker.go index a907a3ea2..0d064b269 100644 --- a/builder/docker/step_connect_docker.go +++ b/builder/docker/step_connect_docker.go @@ -12,7 +12,13 @@ import ( type StepConnectDocker struct{} func (s *StepConnectDocker) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*Config) + config, ok := state.Get("config").(*Config) + if !ok { + err := fmt.Errorf("error encountered obtaining docker config") + state.Put("error", err) + return multistep.ActionHalt + } + containerId := state.Get("container_id").(string) driver := state.Get("driver").(Driver) tempDir := state.Get("temp_dir").(string) diff --git a/builder/docker/step_export.go b/builder/docker/step_export.go index b02228036..99a8a1d11 100644 --- a/builder/docker/step_export.go +++ b/builder/docker/step_export.go @@ -14,11 +14,14 @@ import ( type StepExport struct{} func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*Config) - - driver := state.Get("driver").(Driver) - containerId := state.Get("container_id").(string) ui := state.Get("ui").(packer.Ui) + config, ok := state.Get("config").(*Config) + if !ok { + err := fmt.Errorf("error encountered obtaining docker config") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } // We should catch this in validation, but guard anyway if config.ExportPath == "" { @@ -44,6 +47,9 @@ func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multiste return multistep.ActionHalt } + driver := state.Get("driver").(Driver) + containerId := state.Get("container_id").(string) + ui.Say("Exporting the container") if err := driver.Export(containerId, f); err != nil { f.Close() diff --git a/builder/docker/step_pull.go b/builder/docker/step_pull.go index 712ef70c7..d2cf3cdda 100644 --- a/builder/docker/step_pull.go +++ b/builder/docker/step_pull.go @@ -12,9 +12,14 @@ import ( type StepPull struct{} func (s *StepPull) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*Config) - driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + config, ok := state.Get("config").(*Config) + if !ok { + err := fmt.Errorf("error encountered obtaining docker config") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } if !config.Pull { log.Println("Pull disabled, won't docker pull") @@ -38,6 +43,7 @@ func (s *StepPull) Run(ctx context.Context, state multistep.StateBag) multistep. config.LoginPassword = password } + driver := state.Get("driver").(Driver) if config.Login || config.EcrLogin { ui.Message("Logging in...") err := driver.Login( diff --git a/builder/docker/step_run.go b/builder/docker/step_run.go index 281505104..b91461ff0 100644 --- a/builder/docker/step_run.go +++ b/builder/docker/step_run.go @@ -13,10 +13,14 @@ type StepRun struct { } func (s *StepRun) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*Config) - driver := state.Get("driver").(Driver) - tempDir := state.Get("temp_dir").(string) ui := state.Get("ui").(packer.Ui) + config, ok := state.Get("config").(*Config) + if !ok { + err := fmt.Errorf("error encountered obtaining docker config") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } runConfig := ContainerConfig{ Image: config.Image, @@ -28,8 +32,11 @@ func (s *StepRun) Run(ctx context.Context, state multistep.StateBag) multistep.S for host, container := range config.Volumes { runConfig.Volumes[host] = container } + + tempDir := state.Get("temp_dir").(string) runConfig.Volumes[tempDir] = config.ContainerDir + driver := state.Get("driver").(Driver) ui.Say("Starting docker container...") containerId, err := driver.StartContainer(&runConfig) if err != nil { @@ -42,6 +49,9 @@ func (s *StepRun) Run(ctx context.Context, state multistep.StateBag) multistep.S // Save the container ID s.containerId = containerId state.Put("container_id", s.containerId) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.containerId) ui.Message(fmt.Sprintf("Container ID: %s", s.containerId)) return multistep.ActionContinue } diff --git a/builder/file/builder.go b/builder/file/builder.go index 0defa6049..e2756b141 100644 --- a/builder/file/builder.go +++ b/builder/file/builder.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "os" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -19,18 +20,19 @@ import ( const BuilderId = "packer.file" type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } // Run is where the actual build should take place. It takes a Build and a Ui. diff --git a/builder/file/config.go b/builder/file/config.go index fea112684..730c497f5 100644 --- a/builder/file/config.go +++ b/builder/file/config.go @@ -22,8 +22,7 @@ type Config struct { Content string `mapstructure:"content"` } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { warnings := []string{} err := config.Decode(c, &config.DecodeOpts{ @@ -33,7 +32,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, warnings, err + return warnings, err } var errs *packer.MultiError @@ -51,8 +50,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, warnings, errs + return warnings, errs } - return c, warnings, nil + return warnings, nil } diff --git a/builder/file/config.hcl2spec.go b/builder/file/config.hcl2spec.go index f47193d7d..627b92840 100644 --- a/builder/file/config.hcl2spec.go +++ b/builder/file/config.hcl2spec.go @@ -24,10 +24,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/file/config_test.go b/builder/file/config_test.go index 9d8f346fc..a701b9609 100644 --- a/builder/file/config_test.go +++ b/builder/file/config_test.go @@ -16,7 +16,8 @@ func testConfig() map[string]interface{} { func TestContentSourceConflict(t *testing.T) { raw := testConfig() - _, _, errs := NewConfig(raw) + var c Config + _, errs := c.Prepare(raw) if !strings.Contains(errs.Error(), ErrContentSourceConflict.Error()) { t.Errorf("Expected config error: %s", ErrContentSourceConflict.Error()) } @@ -26,7 +27,8 @@ func TestNoFilename(t *testing.T) { raw := testConfig() delete(raw, "filename") - _, _, errs := NewConfig(raw) + var c Config + _, errs := c.Prepare(raw) if errs == nil { t.Errorf("Expected config error: %s", ErrTargetRequired.Error()) } @@ -37,7 +39,8 @@ func TestNoContent(t *testing.T) { delete(raw, "content") delete(raw, "source") - _, warns, _ := NewConfig(raw) + var c Config + warns, _ := c.Prepare(raw) if len(warns) == 0 { t.Error("Expected config warning without any content") diff --git a/builder/googlecompute/builder.go b/builder/googlecompute/builder.go index 2a30d39c5..0d31530f4 100644 --- a/builder/googlecompute/builder.go +++ b/builder/googlecompute/builder.go @@ -7,6 +7,7 @@ import ( "fmt" "log" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -18,18 +19,18 @@ const BuilderId = "packer.googlecompute" // Builder represents a Packer Builder. type Builder struct { - config *Config + config Config runner multistep.Runner } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) if errs != nil { - return warnings, errs + return nil, warnings, errs } - b.config = c - return warnings, nil + return nil, warnings, nil } // Run executes a googlecompute Packer build and returns a packer.Artifact @@ -43,7 +44,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) @@ -97,7 +98,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack artifact := &Artifact{ image: state.Get("image").(*Image), driver: driver, - config: b.config, + config: &b.config, } return artifact, nil } diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index 7aeeb4cb5..0b6c3c267 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -187,8 +187,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { c.ctx.Funcs = TemplateFuncs err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, @@ -200,7 +199,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } var errs *packer.MultiError @@ -372,10 +371,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Check for any errors. if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } - return c, nil, nil + return nil, nil } type CustomerEncryptionKey struct { diff --git a/builder/googlecompute/config.hcl2spec.go b/builder/googlecompute/config.hcl2spec.go index cfa169a02..acc64f1c9 100644 --- a/builder/googlecompute/config.hcl2spec.go +++ b/builder/googlecompute/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -100,10 +100,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -206,10 +209,13 @@ type FlatCustomerEncryptionKey struct { // FlatMapstructure returns a new FlatCustomerEncryptionKey. // FlatCustomerEncryptionKey is an auto-generated flat version of CustomerEncryptionKey. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*CustomerEncryptionKey) FlatMapstructure() interface{} { return new(FlatCustomerEncryptionKey) } +func (*CustomerEncryptionKey) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatCustomerEncryptionKey) +} -// HCL2Spec returns the hcldec.Spec of a FlatCustomerEncryptionKey. -// This spec is used by HCL to read the fields of FlatCustomerEncryptionKey. +// HCL2Spec returns the hcl spec of a CustomerEncryptionKey. +// This spec is used by HCL to read the fields of CustomerEncryptionKey. +// The decoded values from this spec will then be applied to a FlatCustomerEncryptionKey. func (*FlatCustomerEncryptionKey) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "kms_key_name": &hcldec.AttrSpec{Name: "kms_key_name", Type: cty.String, Required: false}, diff --git a/builder/googlecompute/config_test.go b/builder/googlecompute/config_test.go index b2f05e139..7753e1d8a 100644 --- a/builder/googlecompute/config_test.go +++ b/builder/googlecompute/config_test.go @@ -242,7 +242,8 @@ func TestConfigPrepare(t *testing.T) { raw[tc.Key] = tc.Value } - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) if tc.Err { testConfigErr(t, warns, errs, tc.Key) @@ -302,7 +303,8 @@ func TestConfigPrepareAccelerator(t *testing.T) { } } - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) if tc.Err { testConfigErr(t, warns, errs, strings.TrimRight(errStr, ", ")) @@ -352,7 +354,8 @@ func TestConfigPrepareServiceAccount(t *testing.T) { } } - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) if tc.Err { testConfigErr(t, warns, errs, strings.TrimRight(errStr, ", ")) @@ -371,7 +374,8 @@ func TestConfigPrepareStartupScriptFile(t *testing.T) { "zone": "us-central1-a", } - _, _, errs := NewConfig(config) + var c Config + _, errs := c.Prepare(config) if errs == nil || !strings.Contains(errs.Error(), "startup_script_file") { t.Fatalf("should error: startup_script_file") @@ -398,10 +402,11 @@ func TestConfigDefaults(t *testing.T) { raw, tempfile := testConfig(t) defer os.Remove(tempfile) - c, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) testConfigOk(t, warns, errs) - actual := tc.Read(c) + actual := tc.Read(&c) if actual != tc.Value { t.Fatalf("bad: %#v", actual) } @@ -412,7 +417,8 @@ func TestImageName(t *testing.T) { raw, tempfile := testConfig(t) defer os.Remove(tempfile) - c, _, _ := NewConfig(raw) + var c Config + c.Prepare(raw) if !strings.HasPrefix(c.ImageName, "packer-") { t.Fatalf("ImageName should have 'packer-' prefix, found %s", c.ImageName) } @@ -425,7 +431,8 @@ func TestRegion(t *testing.T) { raw, tempfile := testConfig(t) defer os.Remove(tempfile) - c, _, _ := NewConfig(raw) + var c Config + c.Prepare(raw) if c.Region != "us-east1" { t.Fatalf("Region should be 'us-east1' given Zone of 'us-east1-a', but is %s", c.Region) } @@ -460,7 +467,8 @@ func testConfigStruct(t *testing.T) *Config { raw, tempfile := testConfig(t) defer os.Remove(tempfile) - c, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) if len(warns) > 0 { t.Fatalf("bad: %#v", len(warns)) } @@ -468,7 +476,7 @@ func testConfigStruct(t *testing.T) *Config { t.Fatalf("bad: %#v", errs) } - return c + return &c } func testConfigErr(t *testing.T, warns []string, err error, extra string) { diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index ca15eeccb..088c88925 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -176,6 +176,9 @@ func (s *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) // Things succeeded, store the name so we can remove it later state.Put("instance_name", name) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", name) return multistep.ActionContinue } diff --git a/builder/googlecompute/step_create_windows_password.go b/builder/googlecompute/step_create_windows_password.go index 2112b843c..8ac19f068 100644 --- a/builder/googlecompute/step_create_windows_password.go +++ b/builder/googlecompute/step_create_windows_password.go @@ -13,7 +13,6 @@ import ( "os" "time" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -119,7 +118,6 @@ func (s *StepCreateWindowsPassword) Run(ctx context.Context, state multistep.Sta } state.Put("winrm_password", data.password) - commonhelper.SetSharedState("winrm_password", data.password, c.PackerConfig.PackerBuildName) packer.LogSecretFilter.Set(data.password) return multistep.ActionContinue diff --git a/builder/hcloud/builder.go b/builder/hcloud/builder.go index 92f9f474b..e360149df 100644 --- a/builder/hcloud/builder.go +++ b/builder/hcloud/builder.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -22,13 +23,15 @@ type Builder struct { var pluginVersion = "1.0.0" -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - config, warnings, errs := NewConfig(raws...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) if errs != nil { - return warnings, errs + return nil, warnings, errs } - b.config = *config - return nil, nil + + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/hcloud/config.go b/builder/hcloud/config.go index 6e8b56ba6..878c7cc1f 100644 --- a/builder/hcloud/config.go +++ b/builder/hcloud/config.go @@ -50,9 +50,7 @@ type imageFilter struct { MostRecent bool `mapstructure:"most_recent"` } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) - +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { var md mapstructure.Metadata err := config.Decode(c, &config.DecodeOpts{ Metadata: &md, @@ -65,7 +63,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } // Defaults @@ -142,11 +140,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.HCloudToken) - return c, nil, nil + return nil, nil } func getServerIP(state multistep.StateBag) (string, error) { diff --git a/builder/hcloud/config.hcl2spec.go b/builder/hcloud/config.hcl2spec.go index 36ea266ff..cf1ff3186 100644 --- a/builder/hcloud/config.hcl2spec.go +++ b/builder/hcloud/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -75,10 +75,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -156,10 +159,13 @@ type FlatimageFilter struct { // FlatMapstructure returns a new FlatimageFilter. // FlatimageFilter is an auto-generated flat version of imageFilter. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*imageFilter) FlatMapstructure() interface{} { return new(FlatimageFilter) } +func (*imageFilter) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatimageFilter) +} -// HCL2Spec returns the hcldec.Spec of a FlatimageFilter. -// This spec is used by HCL to read the fields of FlatimageFilter. +// HCL2Spec returns the hcl spec of a imageFilter. +// This spec is used by HCL to read the fields of imageFilter. +// The decoded values from this spec will then be applied to a FlatimageFilter. func (*FlatimageFilter) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "with_selector": &hcldec.AttrSpec{Name: "with_selector", Type: cty.List(cty.String), Required: false}, diff --git a/builder/hcloud/step_create_server.go b/builder/hcloud/step_create_server.go index c5c38b9ad..f0d2f863a 100644 --- a/builder/hcloud/step_create_server.go +++ b/builder/hcloud/step_create_server.go @@ -85,6 +85,9 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu // Store the server id for later state.Put("server_id", serverCreateResult.Server.ID) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", serverCreateResult.Server.ID) if err := waitForAction(ctx, client, serverCreateResult.Action); err != nil { err := fmt.Errorf("Error creating server: %s", err) diff --git a/builder/hyperone/builder.go b/builder/hyperone/builder.go index d9e85e5f0..e4b1bee2a 100644 --- a/builder/hyperone/builder.go +++ b/builder/hyperone/builder.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -20,13 +21,13 @@ type Builder struct { client *openapi.APIClient } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - config, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - b.config = *config +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } cfg := openapi.NewConfiguration() cfg.AddDefaultHeader("x-auth-token", b.config.Token) @@ -43,7 +44,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.client = openapi.NewAPIClient(cfg) - return nil, nil + return nil, nil, nil } type wrappedCommandTemplate struct { diff --git a/builder/hyperone/config.go b/builder/hyperone/config.go index 6c5d5eefb..603651bfc 100644 --- a/builder/hyperone/config.go +++ b/builder/hyperone/config.go @@ -114,8 +114,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := &Config{} +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { var md mapstructure.Metadata err := config.Decode(c, &config.DecodeOpts{ @@ -133,12 +132,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } cliConfig, err := loadCLIConfig() if err != nil { - return nil, nil, err + return nil, err } // Defaults @@ -165,7 +164,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { if c.TokenLogin != "" && c.APIURL == "" { c.Token, err = fetchTokenBySSH(c.TokenLogin) if err != nil { - return nil, nil, err + return nil, err } } } @@ -181,7 +180,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { if c.ImageName == "" { name, err := interpolate.Render("packer-{{timestamp}}", nil) if err != nil { - return nil, nil, err + return nil, err } c.ImageName = name } @@ -217,7 +216,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { if c.ChrootMountPath == "" { path, err := interpolate.Render("/mnt/packer-hyperone-volumes/{{timestamp}}", nil) if err != nil { - return nil, nil, err + return nil, err } c.ChrootMountPath = path } @@ -281,12 +280,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.Token) - return c, nil, nil + return nil, nil } type cliConfig struct { diff --git a/builder/hyperone/config.hcl2spec.go b/builder/hyperone/config.hcl2spec.go index 4978d71b8..1fa450196 100644 --- a/builder/hyperone/config.hcl2spec.go +++ b/builder/hyperone/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -94,10 +94,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/hyperone/step_create_vm.go b/builder/hyperone/step_create_vm.go index 155f383a0..58c1dfbc0 100644 --- a/builder/hyperone/step_create_vm.go +++ b/builder/hyperone/step_create_vm.go @@ -67,6 +67,9 @@ func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis s.vmID = vm.Id state.Put("vm_id", vm.Id) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", vm.Id) hdds, _, err := client.VmApi.VmListHdd(ctx, vm.Id) if err != nil { diff --git a/builder/hyperv/common/output_config.hcl2spec.go b/builder/hyperv/common/output_config.hcl2spec.go index c3794c70d..683b8f314 100644 --- a/builder/hyperv/common/output_config.hcl2spec.go +++ b/builder/hyperv/common/output_config.hcl2spec.go @@ -15,10 +15,13 @@ type FlatOutputConfig struct { // FlatMapstructure returns a new FlatOutputConfig. // FlatOutputConfig is an auto-generated flat version of OutputConfig. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*OutputConfig) FlatMapstructure() interface{} { return new(FlatOutputConfig) } +func (*OutputConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatOutputConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatOutputConfig. -// This spec is used by HCL to read the fields of FlatOutputConfig. +// HCL2Spec returns the hcl spec of a OutputConfig. +// This spec is used by HCL to read the fields of OutputConfig. +// The decoded values from this spec will then be applied to a FlatOutputConfig. func (*FlatOutputConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false}, diff --git a/builder/hyperv/common/step_clone_vm.go b/builder/hyperv/common/step_clone_vm.go index 20d40abfe..5213574e1 100644 --- a/builder/hyperv/common/step_clone_vm.go +++ b/builder/hyperv/common/step_clone_vm.go @@ -154,6 +154,9 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.VMName) return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index f9a05f797..920edfa45 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -166,6 +166,9 @@ func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.VMName) return multistep.ActionContinue } diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 1b40fac84..4eebfe8f6 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" @@ -84,8 +85,9 @@ type Config struct { ctx interpolate.Context } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -96,7 +98,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors and warnings @@ -164,10 +166,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warnings, errs + return nil, warnings, errs } - return warnings, nil + return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing @@ -181,7 +183,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) diff --git a/builder/hyperv/iso/builder.hcl2spec.go b/builder/hyperv/iso/builder.hcl2spec.go index 09f3fa0d3..d06b4b3af 100644 --- a/builder/hyperv/iso/builder.hcl2spec.go +++ b/builder/hyperv/iso/builder.hcl2spec.go @@ -60,8 +60,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -108,10 +108,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 640221de2..b3c4736f3 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -42,7 +42,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -60,7 +60,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config := testConfig() delete(config, "disk_size") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -74,7 +74,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config["disk_size"] = 256 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -96,7 +96,7 @@ func TestBuilderPrepare_DiskBlockSize(t *testing.T) { // Test default with empty disk_block_size delete(config, "disk_block_size") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -112,7 +112,7 @@ func TestBuilderPrepare_DiskBlockSize(t *testing.T) { for _, test_size := range test_sizes { config["disk_block_size"] = test_size b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if test_size > expected_max_block_size || test_size < expected_min_block_size { if len(warns) > 0 { t.Fatalf("bad, should have no warns: %#v", warns) @@ -153,7 +153,7 @@ func TestBuilderPrepare_FixedVHDFormat(t *testing.T) { // use_fixed_vhd_format should work with generation = 1, skip_compaction // = true, and differencing_disk = false - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -164,7 +164,7 @@ func TestBuilderPrepare_FixedVHDFormat(t *testing.T) { //use_fixed_vhd_format should not work with differencing_disk = true config["differencing_disk"] = true b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -176,7 +176,7 @@ func TestBuilderPrepare_FixedVHDFormat(t *testing.T) { //use_fixed_vhd_format should not work with skip_compaction = false config["skip_compaction"] = false b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -188,7 +188,7 @@ func TestBuilderPrepare_FixedVHDFormat(t *testing.T) { //use_fixed_vhd_format should not work with generation = 2 config["generation"] = 2 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -202,7 +202,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { config := testConfig() delete(config, "floppy_files") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -217,7 +217,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { floppiesPath := "../../../common/test-fixtures/floppies" config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppiesPath), fmt.Sprintf("%s/foo.ps1", floppiesPath)} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -236,7 +236,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) { config := testConfig() config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} b = Builder{} - _, errs := b.Prepare(config) + _, _, errs := b.Prepare(config) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -252,7 +252,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -267,7 +267,7 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { // Test bad config["iso_checksum"] = "" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -278,7 +278,7 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { // Test good config["iso_checksum"] = "FOo" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -294,7 +294,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test bad config["iso_checksum_type"] = "" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -305,7 +305,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test good config["iso_checksum_type"] = "mD5" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -320,7 +320,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test unknown config["iso_checksum_type"] = "fake" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -331,7 +331,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test none config["iso_checksum_type"] = "none" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) == 0 { t.Fatalf("bad: %#v", warns) } @@ -353,7 +353,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { // Test both empty config["iso_url"] = "" b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -364,7 +364,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { // Test iso_url set config["iso_url"] = "http://www.packer.io" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -381,7 +381,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { config["iso_url"] = "http://www.packer.io" config["iso_urls"] = []string{"http://www.packer.io"} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -397,7 +397,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { } b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -431,7 +431,7 @@ func TestBuilderPrepare_SizeNotRequiredWhenUsingExistingHarddrive(t *testing.T) } b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -455,7 +455,7 @@ func TestBuilderPrepare_SizeNotRequiredWhenUsingExistingHarddrive(t *testing.T) } b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -489,7 +489,7 @@ func TestBuilderPrepare_SizeIsRequiredWhenNotUsingExistingHarddrive(t *testing.T } b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -517,7 +517,7 @@ func TestBuilderPrepare_MaximumOfSixtyFourAdditionalDisks(t *testing.T) { config["disk_additional_size"] = disks b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -537,7 +537,7 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { config["winrm_host"] = "1.2.3.4" var b Builder - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -565,7 +565,7 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { config["ssh_host"] = "1.2.3.4" var b Builder - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -593,7 +593,7 @@ func TestUserVariablesInBootCommand(t *testing.T) { config[packer.UserVariablesConfigKey] = map[string]string{"test-variable": "test"} config["boot_command"] = []string{"blah {{user `test-variable`}} blah"} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -634,7 +634,7 @@ func TestBuilderPrepare_UseLegacyNetworkAdapter(t *testing.T) { config["use_legacy_network_adapter"] = true b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -646,7 +646,7 @@ func TestBuilderPrepare_UseLegacyNetworkAdapter(t *testing.T) { config["generation"] = 2 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index af93007e5..c7c840f40 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -10,6 +10,7 @@ import ( "os" "strings" + "github.com/hashicorp/hcl/v2/hcldec" hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" @@ -77,8 +78,9 @@ type Config struct { ctx interpolate.Context } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -89,7 +91,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors and warnings @@ -204,10 +206,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warnings, errs + return nil, warnings, errs } - return warnings, nil + return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing @@ -221,7 +223,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) @@ -235,23 +236,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Force: b.config.PackerForce, Path: b.config.OutputDir, }, - } - - if b.config.RawSingleISOUrl != "" || len(b.config.ISOUrls) > 0 { - steps = append(steps, - &common.StepDownload{ - Checksum: b.config.ISOChecksum, - ChecksumType: b.config.ISOChecksumType, - Description: "ISO", - ResultKey: "iso_path", - Url: b.config.ISOUrls, - Extension: b.config.TargetExtension, - TargetPath: b.config.TargetPath, - }, - ) - } - - steps = append(steps, + &common.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + ResultKey: "iso_path", + Url: b.config.ISOUrls, + Extension: b.config.TargetExtension, + TargetPath: b.config.TargetPath, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, Directories: b.config.FloppyConfig.FloppyDirectories, @@ -366,9 +359,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack OutputDir: b.config.OutputDir, SkipExport: b.config.SkipExport, }, + } - // the clean up actions for each step will be executed reverse order - ) + // the clean up actions for each step will be executed reverse order // Run the steps. b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) diff --git a/builder/hyperv/vmcx/builder.hcl2spec.go b/builder/hyperv/vmcx/builder.hcl2spec.go index 1ba1e83ee..628436bf0 100644 --- a/builder/hyperv/vmcx/builder.hcl2spec.go +++ b/builder/hyperv/vmcx/builder.hcl2spec.go @@ -60,8 +60,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -110,10 +110,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/hyperv/vmcx/builder_test.go b/builder/hyperv/vmcx/builder_test.go index 9f268aab4..2fa7ba3cf 100644 --- a/builder/hyperv/vmcx/builder_test.go +++ b/builder/hyperv/vmcx/builder_test.go @@ -49,7 +49,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) { defer os.RemoveAll(td) config["clone_from_vmcx_path"] = td - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -76,7 +76,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -90,7 +90,7 @@ func TestBuilderPrepare_CloneFromExistingMachineOrImportFromExportedMachineSetti config := testConfig() delete(config, "clone_from_vmcx_path") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -114,7 +114,7 @@ func TestBuilderPrepare_ExportedMachinePathDoesNotExist(t *testing.T) { config["clone_from_vmcx_path"] = td - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -138,7 +138,7 @@ func TestBuilderPrepare_ExportedMachinePathExists(t *testing.T) { config["clone_from_vmcx_path"] = td - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -154,7 +154,7 @@ func disabled_TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmcxPathRequ config["clone_from_vm_name"] = "test_machine_name_that_does_not_exist" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -184,7 +184,7 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { // Test bad config["iso_checksum"] = "" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -195,7 +195,7 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { // Test good config["iso_checksum"] = "FOo" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -219,7 +219,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test bad config["iso_checksum_type"] = "" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -230,7 +230,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test good config["iso_checksum_type"] = "mD5" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -245,7 +245,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test none config["iso_checksum_type"] = "none" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) == 0 { t.Fatalf("bad: %#v", warns) } @@ -276,7 +276,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { // Test both empty (should be allowed, as we cloning a vm so we probably don't need an ISO file) config["iso_url"] = "" b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -287,7 +287,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { // Test iso_url set config["iso_url"] = "http://www.packer.io" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -304,7 +304,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { config["iso_url"] = "http://www.packer.io" config["iso_urls"] = []string{"http://www.packer.io"} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -320,7 +320,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { } b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -350,7 +350,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { config["clone_from_vmcx_path"] = td delete(config, "floppy_files") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -365,7 +365,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { floppies_path := "../../../common/test-fixtures/floppies" config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -393,7 +393,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) { config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} b = Builder{} - _, errs := b.Prepare(config) + _, _, errs := b.Prepare(config) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -422,7 +422,7 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { config["winrm_host"] = "1.2.3.4" var b Builder - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -459,7 +459,7 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { config["ssh_host"] = "1.2.3.4" var b Builder - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -494,7 +494,7 @@ func TestUserVariablesInBootCommand(t *testing.T) { config[packer.UserVariablesConfigKey] = map[string]string{"test-variable": "test"} config["boot_command"] = []string{"blah {{user `test-variable`}} blah"} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/jdcloud/builder.go b/builder/jdcloud/builder.go index 4b3700f11..063bfda1f 100644 --- a/builder/jdcloud/builder.go +++ b/builder/jdcloud/builder.go @@ -3,6 +3,8 @@ package jdcloud import ( "context" "fmt" + + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" @@ -11,7 +13,9 @@ import ( "github.com/hashicorp/packer/template/interpolate" ) -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -22,19 +26,19 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, fmt.Errorf("[ERROR] Failed in decoding JSON->mapstructure") + return nil, nil, fmt.Errorf("[ERROR] Failed in decoding JSON->mapstructure") } errs := &packer.MultiError{} errs = packer.MultiErrorAppend(errs, b.config.JDCloudCredentialConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.JDCloudInstanceSpecConfig.Prepare(&b.config.ctx)...) if errs != nil && len(errs.Errors) != 0 { - return nil, errs + return nil, nil, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey) - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -42,7 +46,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack state := new(multistep.BasicStateBag) state.Put("hook", hook) state.Put("ui", ui) - state.Put("config", b.config) + state.Put("config", &b.config) steps := []multistep.Step{ diff --git a/builder/jdcloud/common.hcl2spec.go b/builder/jdcloud/common.hcl2spec.go index 0bc486d27..486aeb51d 100644 --- a/builder/jdcloud/common.hcl2spec.go +++ b/builder/jdcloud/common.hcl2spec.go @@ -48,8 +48,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -74,10 +74,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false}, diff --git a/builder/jdcloud/step_config_credentials.go b/builder/jdcloud/step_config_credentials.go index 1c6fdfc76..360a27629 100644 --- a/builder/jdcloud/step_config_credentials.go +++ b/builder/jdcloud/step_config_credentials.go @@ -3,10 +3,11 @@ package jdcloud import ( "context" "fmt" + "io/ioutil" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/jdcloud-api/jdcloud-sdk-go/services/vm/apis" - "io/ioutil" ) type stepConfigCredentials struct { diff --git a/builder/jdcloud/step_create_image.go b/builder/jdcloud/step_create_image.go index 03e2d3b60..c126d8047 100644 --- a/builder/jdcloud/step_create_image.go +++ b/builder/jdcloud/step_create_image.go @@ -3,10 +3,11 @@ package jdcloud import ( "context" "fmt" + "time" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/jdcloud-api/jdcloud-sdk-go/services/vm/apis" - "time" ) type stepCreateJDCloudImage struct { diff --git a/builder/jdcloud/step_create_instance.go b/builder/jdcloud/step_create_instance.go index fe66f464d..4395b1932 100644 --- a/builder/jdcloud/step_create_instance.go +++ b/builder/jdcloud/step_create_instance.go @@ -3,6 +3,9 @@ package jdcloud import ( "context" "fmt" + "regexp" + "time" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/jdcloud-api/jdcloud-sdk-go/core" @@ -11,8 +14,6 @@ import ( vpcApis "github.com/jdcloud-api/jdcloud-sdk-go/services/vpc/apis" vpcClient "github.com/jdcloud-api/jdcloud-sdk-go/services/vpc/client" vpc "github.com/jdcloud-api/jdcloud-sdk-go/services/vpc/models" - "regexp" - "time" ) type stepCreateJDCloudInstance struct { @@ -96,6 +97,9 @@ func (s *stepCreateJDCloudInstance) Run(_ context.Context, state multistep.State "Hi, we have created the instance, its name=%v , "+ "its id=%v, "+ "and its eip=%v :) ", instance.InstanceName, s.InstanceSpecConfig.InstanceId, eip.Result.ElasticIp.ElasticIpAddress)) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.InstanceSpecConfig.InstanceId) return multistep.ActionContinue } @@ -138,7 +142,7 @@ func (s *stepCreateJDCloudInstance) Cleanup(state multistep.StateBag) { func createElasticIp(state multistep.StateBag) (string, error) { - generalConfig := state.Get("config").(Config) + generalConfig := state.Get("config").(*Config) regionId := generalConfig.RegionId credential := core.NewCredentials(generalConfig.AccessKey, generalConfig.SecretKey) vpcclient := vpcClient.NewVpcClient(credential) diff --git a/builder/jdcloud/step_stop_instance.go b/builder/jdcloud/step_stop_instance.go index 1cf249c2c..0468b7469 100644 --- a/builder/jdcloud/step_stop_instance.go +++ b/builder/jdcloud/step_stop_instance.go @@ -3,6 +3,7 @@ package jdcloud import ( "context" "fmt" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/jdcloud-api/jdcloud-sdk-go/services/vm/apis" diff --git a/builder/jdcloud/step_validate_parameters.go b/builder/jdcloud/step_validate_parameters.go index 62c04078b..adfab5e14 100644 --- a/builder/jdcloud/step_validate_parameters.go +++ b/builder/jdcloud/step_validate_parameters.go @@ -3,6 +3,7 @@ package jdcloud import ( "context" "fmt" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" vm "github.com/jdcloud-api/jdcloud-sdk-go/services/vm/apis" diff --git a/builder/linode/builder.go b/builder/linode/builder.go index 3bc3dfb3f..e4638eb41 100644 --- a/builder/linode/builder.go +++ b/builder/linode/builder.go @@ -8,6 +8,7 @@ import ( "fmt" "log" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/linode/linodego" @@ -21,17 +22,18 @@ const BuilderID = "packer.linode" // Builder represents a Packer Builder. type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) if errs != nil { - return warnings, errs + return nil, warnings, errs } - b.config = c - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (ret packer.Artifact, err error) { @@ -45,7 +47,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (ret } state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/linode/builder_test.go b/builder/linode/builder_test.go index c80d1c76c..8176114a4 100644 --- a/builder/linode/builder_test.go +++ b/builder/linode/builder_test.go @@ -31,7 +31,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "linode_token": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -46,7 +46,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -61,7 +61,7 @@ func TestBuilderPrepare_Region(t *testing.T) { // Test default delete(config, "region") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -74,7 +74,7 @@ func TestBuilderPrepare_Region(t *testing.T) { // Test set config["region"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -93,7 +93,7 @@ func TestBuilderPrepare_Size(t *testing.T) { // Test default delete(config, "instance_type") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -106,7 +106,7 @@ func TestBuilderPrepare_Size(t *testing.T) { // Test set config["instance_type"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -125,7 +125,7 @@ func TestBuilderPrepare_Image(t *testing.T) { // Test default delete(config, "image") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -138,7 +138,7 @@ func TestBuilderPrepare_Image(t *testing.T) { // Test set config["image"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -156,7 +156,7 @@ func TestBuilderPrepare_ImageLabel(t *testing.T) { config := testConfig() // Test default - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -171,7 +171,7 @@ func TestBuilderPrepare_ImageLabel(t *testing.T) { // Test set config["image_label"] = "foobarbaz" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -182,7 +182,7 @@ func TestBuilderPrepare_ImageLabel(t *testing.T) { // Test set with template config["image_label"] = "{{timestamp}}" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -202,7 +202,7 @@ func TestBuilderPrepare_Label(t *testing.T) { config := testConfig() // Test default - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -217,7 +217,7 @@ func TestBuilderPrepare_Label(t *testing.T) { // Test normal set config["instance_label"] = "foobar" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -228,7 +228,7 @@ func TestBuilderPrepare_Label(t *testing.T) { // Test with template config["instance_label"] = "foobar-{{timestamp}}" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -239,7 +239,7 @@ func TestBuilderPrepare_Label(t *testing.T) { // Test with bad template config["instance_label"] = "foobar-{{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/linode/config.go b/builder/linode/config.go index ea8eac073..fd4233827 100644 --- a/builder/linode/config.go +++ b/builder/linode/config.go @@ -48,8 +48,7 @@ func createRandomRootPassword() (string, error) { return rootPass, nil } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { if err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, @@ -60,7 +59,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, }, raws...); err != nil { - return nil, nil, err + return nil, err } var errs *packer.MultiError @@ -136,9 +135,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.PersonalAccessToken) - return c, nil, nil + return nil, nil } diff --git a/builder/linode/config.hcl2spec.go b/builder/linode/config.hcl2spec.go index e22bc7507..5f9e8c8ed 100644 --- a/builder/linode/config.hcl2spec.go +++ b/builder/linode/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -72,10 +72,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/linode/step_create_linode.go b/builder/linode/step_create_linode.go index ca15facfd..90dc6342a 100644 --- a/builder/linode/step_create_linode.go +++ b/builder/linode/step_create_linode.go @@ -49,6 +49,9 @@ func (s *stepCreateLinode) Run(ctx context.Context, state multistep.StateBag) mu return multistep.ActionHalt } state.Put("instance", instance) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", instance.ID) } disk, err := s.findDisk(ctx, instance.ID) diff --git a/builder/lxc/builder.go b/builder/lxc/builder.go index 5c8747c7e..0d313c7b9 100644 --- a/builder/lxc/builder.go +++ b/builder/lxc/builder.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -19,18 +20,19 @@ type wrappedCommandTemplate struct { } type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, errs := NewConfig(raws...) - if errs != nil { - return nil, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return nil, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + errs := b.config.Prepare(raws...) + if errs != nil { + return nil, nil, errs + } + + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -51,7 +53,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Setup the state bag state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) state.Put("wrappedCommand", CommandWrapper(wrappedCommand)) diff --git a/builder/lxc/builder_test.go b/builder/lxc/builder_test.go index 4eeb27594..20108202d 100644 --- a/builder/lxc/builder_test.go +++ b/builder/lxc/builder_test.go @@ -25,7 +25,7 @@ func TestBuilderPrepare_ConfigFile(t *testing.T) { var b Builder // Good config := testConfig() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -37,7 +37,7 @@ func TestBuilderPrepare_ConfigFile(t *testing.T) { config = testConfig() delete(config, "config_file") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/lxc/config.go b/builder/lxc/config.go index 587e205a0..fbbd0e117 100644 --- a/builder/lxc/config.go +++ b/builder/lxc/config.go @@ -1,4 +1,5 @@ //go:generate struct-markdown +//go:generate mapstructure-to-hcl2 -type Config package lxc @@ -67,16 +68,15 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, error) { - var c Config +func (c *Config) Prepare(raws ...interface{}) error { var md mapstructure.Metadata - err := config.Decode(&c, &config.DecodeOpts{ + err := config.Decode(c, &config.DecodeOpts{ Metadata: &md, Interpolate: true, }, raws...) if err != nil { - return nil, err + return err } // Accumulate any errors @@ -107,8 +107,8 @@ func NewConfig(raws ...interface{}) (*Config, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return errs } - return &c, nil + return nil } diff --git a/builder/lxc/config.hcl2spec.go b/builder/lxc/config.hcl2spec.go new file mode 100644 index 000000000..22e0ffc8b --- /dev/null +++ b/builder/lxc/config.hcl2spec.go @@ -0,0 +1,66 @@ +// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT. +package lxc + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + ConfigFile *string `mapstructure:"config_file" required:"true" cty:"config_file"` + OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory"` + ContainerName *string `mapstructure:"container_name" required:"false" cty:"container_name"` + CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper"` + InitTimeout *string `mapstructure:"init_timeout" required:"false" cty:"init_timeout"` + CreateOptions []string `mapstructure:"create_options" required:"false" cty:"create_options"` + StartOptions []string `mapstructure:"start_options" required:"false" cty:"start_options"` + AttachOptions []string `mapstructure:"attach_options" required:"false" cty:"attach_options"` + Name *string `mapstructure:"template_name" required:"true" cty:"template_name"` + Parameters []string `mapstructure:"template_parameters" required:"false" cty:"template_parameters"` + EnvVars []string `mapstructure:"template_environment_vars" required:"true" cty:"template_environment_vars"` + TargetRunlevel *int `mapstructure:"target_runlevel" required:"false" cty:"target_runlevel"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "config_file": &hcldec.AttrSpec{Name: "config_file", Type: cty.String, Required: false}, + "output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false}, + "container_name": &hcldec.AttrSpec{Name: "container_name", Type: cty.String, Required: false}, + "command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false}, + "init_timeout": &hcldec.AttrSpec{Name: "init_timeout", Type: cty.String, Required: false}, + "create_options": &hcldec.AttrSpec{Name: "create_options", Type: cty.List(cty.String), Required: false}, + "start_options": &hcldec.AttrSpec{Name: "start_options", Type: cty.List(cty.String), Required: false}, + "attach_options": &hcldec.AttrSpec{Name: "attach_options", Type: cty.List(cty.String), Required: false}, + "template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false}, + "template_parameters": &hcldec.AttrSpec{Name: "template_parameters", Type: cty.List(cty.String), Required: false}, + "template_environment_vars": &hcldec.AttrSpec{Name: "template_environment_vars", Type: cty.List(cty.String), Required: false}, + "target_runlevel": &hcldec.AttrSpec{Name: "target_runlevel", Type: cty.Number, Required: false}, + } + return s +} diff --git a/builder/lxd/builder.go b/builder/lxd/builder.go index ce1656217..4cc58d8a4 100644 --- a/builder/lxd/builder.go +++ b/builder/lxd/builder.go @@ -3,6 +3,7 @@ package lxd import ( "context" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -17,18 +18,19 @@ type wrappedCommandTemplate struct { } type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, errs := NewConfig(raws...) - if errs != nil { - return nil, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return nil, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + errs := b.config.Prepare(raws...) + if errs != nil { + return nil, nil, errs + } + + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -45,7 +47,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Setup the state bag state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) state.Put("wrappedCommand", CommandWrapper(wrappedCommand)) diff --git a/builder/lxd/builder_test.go b/builder/lxd/builder_test.go index b2ea70e55..a2277932c 100644 --- a/builder/lxd/builder_test.go +++ b/builder/lxd/builder_test.go @@ -24,7 +24,7 @@ func TestBuilderPrepare_ConfigFile(t *testing.T) { var b Builder // Good config := testConfig() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -35,7 +35,7 @@ func TestBuilderPrepare_ConfigFile(t *testing.T) { // Good, remote image config = testConfig() config["image"] = "remote:bar" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -46,7 +46,7 @@ func TestBuilderPrepare_ConfigFile(t *testing.T) { // Good, remote output image config = testConfig() config["output_image"] = "remote:foo" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -58,7 +58,7 @@ func TestBuilderPrepare_ConfigFile(t *testing.T) { config = testConfig() delete(config, "image") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/lxd/config.go b/builder/lxd/config.go index 6914392b4..220c47ca0 100644 --- a/builder/lxd/config.go +++ b/builder/lxd/config.go @@ -43,16 +43,15 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, error) { - var c Config +func (c *Config) Prepare(raws ...interface{}) error { var md mapstructure.Metadata - err := config.Decode(&c, &config.DecodeOpts{ + err := config.Decode(c, &config.DecodeOpts{ Metadata: &md, Interpolate: true, }, raws...) if err != nil { - return nil, err + return err } // Accumulate any errors @@ -85,8 +84,8 @@ func NewConfig(raws ...interface{}) (*Config, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return errs } - return &c, nil + return nil } diff --git a/builder/lxd/config.hcl2spec.go b/builder/lxd/config.hcl2spec.go index 61cc74e19..e8d2d3b0d 100644 --- a/builder/lxd/config.hcl2spec.go +++ b/builder/lxd/config.hcl2spec.go @@ -29,10 +29,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/ncloud/builder.go b/builder/ncloud/builder.go index 36702c933..4df0ed8d4 100644 --- a/builder/ncloud/builder.go +++ b/builder/ncloud/builder.go @@ -4,6 +4,7 @@ import ( "context" ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -12,21 +13,22 @@ import ( // Builder assume this implements packer.Builder type Builder struct { - config *Config + config Config stateBag multistep.StateBag runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) if errs != nil { - return warnings, errs + return nil, warnings, errs } - b.config = c b.stateBag = new(multistep.BasicStateBag) - return warnings, nil + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -42,12 +44,12 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack if b.config.Comm.Type == "ssh" { steps = []multistep.Step{ - NewStepValidateTemplate(conn, ui, b.config), + NewStepValidateTemplate(conn, ui, &b.config), NewStepCreateLoginKey(conn, ui), - NewStepCreateServerInstance(conn, ui, b.config), - NewStepCreateBlockStorageInstance(conn, ui, b.config), - NewStepGetRootPassword(conn, ui, b.config), - NewStepCreatePublicIPInstance(conn, ui, b.config), + NewStepCreateServerInstance(conn, ui, &b.config), + NewStepCreateBlockStorageInstance(conn, ui, &b.config), + NewStepGetRootPassword(conn, ui, &b.config), + NewStepCreatePublicIPInstance(conn, ui, &b.config), &communicator.StepConnectSSH{ Config: &b.config.Comm, Host: func(stateBag multistep.StateBag) (string, error) { @@ -60,18 +62,18 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Comm: &b.config.Comm, }, NewStepStopServerInstance(conn, ui), - NewStepCreateServerImage(conn, ui, b.config), - NewStepDeleteBlockStorageInstance(conn, ui, b.config), + NewStepCreateServerImage(conn, ui, &b.config), + NewStepDeleteBlockStorageInstance(conn, ui, &b.config), NewStepTerminateServerInstance(conn, ui), } } else if b.config.Comm.Type == "winrm" { steps = []multistep.Step{ - NewStepValidateTemplate(conn, ui, b.config), + NewStepValidateTemplate(conn, ui, &b.config), NewStepCreateLoginKey(conn, ui), - NewStepCreateServerInstance(conn, ui, b.config), - NewStepCreateBlockStorageInstance(conn, ui, b.config), - NewStepGetRootPassword(conn, ui, b.config), - NewStepCreatePublicIPInstance(conn, ui, b.config), + NewStepCreateServerInstance(conn, ui, &b.config), + NewStepCreateBlockStorageInstance(conn, ui, &b.config), + NewStepGetRootPassword(conn, ui, &b.config), + NewStepCreatePublicIPInstance(conn, ui, &b.config), &communicator.StepConnectWinRM{ Config: &b.config.Comm, Host: func(stateBag multistep.StateBag) (string, error) { @@ -86,8 +88,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, &common.StepProvision{}, NewStepStopServerInstance(conn, ui), - NewStepCreateServerImage(conn, ui, b.config), - NewStepDeleteBlockStorageInstance(conn, ui, b.config), + NewStepCreateServerImage(conn, ui, &b.config), + NewStepDeleteBlockStorageInstance(conn, ui, &b.config), NewStepTerminateServerInstance(conn, ui), } } diff --git a/builder/ncloud/config.go b/builder/ncloud/config.go index b1f7f4416..aca3e04d8 100644 --- a/builder/ncloud/config.go +++ b/builder/ncloud/config.go @@ -60,8 +60,7 @@ type Config struct { } // NewConfig checks parameters -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { warnings := []string{} err := config.Decode(c, &config.DecodeOpts{ @@ -71,7 +70,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, warnings, err + return warnings, err } var errs *packer.MultiError @@ -115,7 +114,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { if c.BlockStorageSize < 10 || c.BlockStorageSize > 2000 { errs = packer.MultiErrorAppend(errs, errors.New("The size of BlockStorageSize is at least 10 GB and up to 2000GB")) } else if int(c.BlockStorageSize/10)*10 != c.BlockStorageSize { - return nil, nil, errors.New("BlockStorageSize must be a multiple of 10 GB") + return nil, errors.New("BlockStorageSize must be a multiple of 10 GB") } } @@ -136,8 +135,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, warnings, errs + return warnings, errs } - return c, warnings, nil + return warnings, nil } diff --git a/builder/ncloud/config.hcl2spec.go b/builder/ncloud/config.hcl2spec.go index f72c02456..90559be6e 100644 --- a/builder/ncloud/config.hcl2spec.go +++ b/builder/ncloud/config.hcl2spec.go @@ -58,8 +58,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -73,10 +73,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/ncloud/config_test.go b/builder/ncloud/config_test.go index 7ccc23478..0c793b05e 100644 --- a/builder/ncloud/config_test.go +++ b/builder/ncloud/config_test.go @@ -42,7 +42,8 @@ func testConfigForMemberServerImage() map[string]interface{} { func TestConfigWithServerImageProductCode(t *testing.T) { raw := testConfig() - c, _, _ := NewConfig(raw) + var c Config + c.Prepare(raw) if c.AccessKey != "access_key" { t.Errorf("Expected 'access_key' to be set to '%s', but got '%s'.", raw["access_key"], c.AccessKey) @@ -76,7 +77,8 @@ func TestConfigWithServerImageProductCode(t *testing.T) { func TestConfigWithMemberServerImageCode(t *testing.T) { raw := testConfigForMemberServerImage() - c, _, _ := NewConfig(raw) + var c Config + c.Prepare(raw) if c.AccessKey != "access_key" { t.Errorf("Expected 'access_key' to be set to '%s', but got '%s'.", raw["access_key"], c.AccessKey) @@ -110,7 +112,8 @@ func TestConfigWithMemberServerImageCode(t *testing.T) { func TestEmptyConfig(t *testing.T) { raw := new(map[string]interface{}) - _, _, err := NewConfig(raw) + var c Config + _, err := c.Prepare(raw) if err == nil { t.Error("Expected Config to require 'access_key', 'secret_key' and some mandatory fields, but it did not") @@ -138,7 +141,8 @@ func TestExistsBothServerImageProductCodeAndMemberServerImageNoConfig(t *testing "member_server_image_no": "2440", } - _, _, err := NewConfig(raw) + var c Config + _, err := c.Prepare(raw) if !strings.Contains(err.Error(), "Only one of server_image_product_code and member_server_image_no can be set") { t.Error("Expected Config to require Only one of 'server_image_product_code' and 'member_server_image_no' can be set, but it did not") diff --git a/builder/ncloud/step_create_public_ip_instance.go b/builder/ncloud/step_create_public_ip_instance.go index 39c31966c..e4550e6ef 100644 --- a/builder/ncloud/step_create_public_ip_instance.go +++ b/builder/ncloud/step_create_public_ip_instance.go @@ -97,6 +97,9 @@ func (s *StepCreatePublicIPInstance) Run(ctx context.Context, state multistep.St if err == nil { state.Put("PublicIP", publicIPInstance.PublicIP) state.Put("PublicIPInstance", publicIPInstance) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", publicIPInstance) } return processStepResult(err, s.Error, state) diff --git a/builder/ncloud/step_create_server_instance.go b/builder/ncloud/step_create_server_instance.go index 2a72789f6..49648cbb0 100644 --- a/builder/ncloud/step_create_server_instance.go +++ b/builder/ncloud/step_create_server_instance.go @@ -96,6 +96,9 @@ func (s *StepCreateServerInstance) Run(ctx context.Context, state multistep.Stat serverInstanceNo, err := s.CreateServerInstance(loginKey.KeyName, zoneNo, feeSystemTypeCode) if err == nil { state.Put("InstanceNo", serverInstanceNo) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", serverInstanceNo) } return processStepResult(err, s.Error, state) diff --git a/builder/null/builder.go b/builder/null/builder.go index 5939cd024..e0568ce05 100644 --- a/builder/null/builder.go +++ b/builder/null/builder.go @@ -3,6 +3,7 @@ package null import ( "context" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -12,18 +13,19 @@ import ( const BuilderId = "fnoeding.null" type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -43,7 +45,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) - state.Put("config", b.config) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/null/config.go b/builder/null/config.go index a589b297b..79ed530b4 100644 --- a/builder/null/config.go +++ b/builder/null/config.go @@ -18,15 +18,14 @@ type Config struct { CommConfig communicator.Config `mapstructure:",squash"` } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - var c Config +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(&c, &config.DecodeOpts{ Interpolate: true, InterpolateFilter: &interpolate.RenderFilter{}, }, raws...) if err != nil { - return nil, nil, err + return nil, err } var errs *packer.MultiError @@ -60,8 +59,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } - return &c, nil, nil + return nil, nil } diff --git a/builder/null/config.hcl2spec.go b/builder/null/config.hcl2spec.go index cceed507b..fb2f31145 100644 --- a/builder/null/config.hcl2spec.go +++ b/builder/null/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -61,10 +61,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/null/config_test.go b/builder/null/config_test.go index 29114806b..4a922d629 100644 --- a/builder/null/config_test.go +++ b/builder/null/config_test.go @@ -16,7 +16,8 @@ func testConfig() map[string]interface{} { } func testConfigStruct(t *testing.T) *Config { - c, warns, errs := NewConfig(testConfig()) + var c Config + warns, errs := c.Prepare(testConfig()) if len(warns) > 0 { t.Fatalf("bad: %#v", len(warns)) } @@ -24,7 +25,7 @@ func testConfigStruct(t *testing.T) *Config { t.Fatalf("bad: %#v", errs) } - return c + return &c } func testConfigErr(t *testing.T, warns []string, err error) { @@ -50,7 +51,8 @@ func TestConfigPrepare_port(t *testing.T) { // default port should be 22 delete(raw, "port") - c, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) if c.CommConfig.SSHPort != 22 { t.Fatalf("bad: port should default to 22, not %d", c.CommConfig.SSHPort) } @@ -62,12 +64,13 @@ func TestConfigPrepare_host(t *testing.T) { // No host delete(raw, "ssh_host") - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) testConfigErr(t, warns, errs) // Good host raw["ssh_host"] = "good" - _, warns, errs = NewConfig(raw) + warns, errs = c.Prepare(raw) testConfigOk(t, warns, errs) } @@ -77,12 +80,12 @@ func TestConfigPrepare_sshCredential(t *testing.T) { // no ssh_password and no ssh_private_key_file delete(raw, "ssh_password") delete(raw, "ssh_private_key_file") - _, warns, errs := NewConfig(raw) + warns, errs := (&Config{}).Prepare(raw) testConfigErr(t, warns, errs) // only ssh_password raw["ssh_password"] = "good" - _, warns, errs = NewConfig(raw) + warns, errs = (&Config{}).Prepare(raw) testConfigOk(t, warns, errs) // only ssh_private_key_file @@ -90,11 +93,11 @@ func TestConfigPrepare_sshCredential(t *testing.T) { defer os.Remove(testFile) raw["ssh_private_key_file"] = testFile delete(raw, "ssh_password") - _, warns, errs = NewConfig(raw) + warns, errs = (&Config{}).Prepare(raw) testConfigOk(t, warns, errs) // both ssh_password and ssh_private_key_file set raw["ssh_password"] = "bad" - _, warns, errs = NewConfig(raw) + warns, errs = (&Config{}).Prepare(raw) testConfigErr(t, warns, errs) } diff --git a/builder/oneandone/builder.go b/builder/oneandone/builder.go index 5ea7674bc..9ad5a643c 100644 --- a/builder/oneandone/builder.go +++ b/builder/oneandone/builder.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -14,25 +15,26 @@ import ( const BuilderId = "packer.oneandone" type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/oneandone/builder_test.go b/builder/oneandone/builder_test.go index daa8536e3..46045bf9d 100644 --- a/builder/oneandone/builder_test.go +++ b/builder/oneandone/builder_test.go @@ -30,7 +30,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "api_key": []string{}, } - warns, err := b.Prepare(c) + _, warns, err := b.Prepare(c) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -46,7 +46,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { config := testConfig() config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/oneandone/config.go b/builder/oneandone/config.go index 68f2203f3..73914a13f 100644 --- a/builder/oneandone/config.go +++ b/builder/oneandone/config.go @@ -31,11 +31,10 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - var c Config +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { var md mapstructure.Metadata - err := config.Decode(&c, &config.DecodeOpts{ + err := config.Decode(c, &config.DecodeOpts{ Metadata: &md, Interpolate: true, InterpolateContext: &c.ctx, @@ -46,7 +45,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } var errs *packer.MultiError @@ -107,8 +106,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.Token) - return &c, nil, nil + return nil, nil } diff --git a/builder/oneandone/config.hcl2spec.go b/builder/oneandone/config.hcl2spec.go index ebed17e63..ed73682a2 100644 --- a/builder/oneandone/config.hcl2spec.go +++ b/builder/oneandone/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -69,10 +69,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/oneandone/step_create_server.go b/builder/oneandone/step_create_server.go index 1fc9863aa..6d70db862 100644 --- a/builder/oneandone/step_create_server.go +++ b/builder/oneandone/step_create_server.go @@ -94,6 +94,9 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu } state.Put("server_id", server_id) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", server_id) state.Put("server_ip", server.Ips[0].Ip) diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index 8411f803d..57ede0103 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -9,6 +9,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" @@ -35,13 +36,15 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors @@ -51,11 +54,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } if b.config.ImageConfig.ImageDiskFormat != "" && !b.config.RunConfig.UseBlockStorageVolume { - return nil, fmt.Errorf("use_blockstorage_volume must be true if image_disk_format is specified.") + return nil, nil, fmt.Errorf("use_blockstorage_volume must be true if image_disk_format is specified.") } // By default, instance name is same as image name @@ -64,7 +67,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } packer.LogSecretFilter.Set(b.config.Password) - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/openstack/builder.hcl2spec.go b/builder/openstack/builder.hcl2spec.go index 7977cee1f..e15db51fa 100644 --- a/builder/openstack/builder.hcl2spec.go +++ b/builder/openstack/builder.hcl2spec.go @@ -10,118 +10,121 @@ import ( // FlatConfig is an auto-generated flat version of Config. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatConfig struct { - PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` - PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` - PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` - PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` - PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` - PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` - PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` - Username *string `mapstructure:"username" required:"true" cty:"username"` - UserID *string `mapstructure:"user_id" cty:"user_id"` - Password *string `mapstructure:"password" required:"true" cty:"password"` - IdentityEndpoint *string `mapstructure:"identity_endpoint" required:"true" cty:"identity_endpoint"` - TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id"` - TenantName *string `mapstructure:"tenant_name" cty:"tenant_name"` - DomainID *string `mapstructure:"domain_id" cty:"domain_id"` - DomainName *string `mapstructure:"domain_name" required:"false" cty:"domain_name"` - Insecure *bool `mapstructure:"insecure" required:"false" cty:"insecure"` - Region *string `mapstructure:"region" required:"false" cty:"region"` - EndpointType *string `mapstructure:"endpoint_type" required:"false" cty:"endpoint_type"` - CACertFile *string `mapstructure:"cacert" required:"false" cty:"cacert"` - ClientCertFile *string `mapstructure:"cert" required:"false" cty:"cert"` - ClientKeyFile *string `mapstructure:"key" required:"false" cty:"key"` - Token *string `mapstructure:"token" required:"false" cty:"token"` - ApplicationCredentialName *string `mapstructure:"application_credential_name" required:"false" cty:"application_credential_name"` - ApplicationCredentialID *string `mapstructure:"application_credential_id" required:"false" cty:"application_credential_id"` - ApplicationCredentialSecret *string `mapstructure:"application_credential_secret" required:"false" cty:"application_credential_secret"` - Cloud *string `mapstructure:"cloud" required:"false" cty:"cloud"` - ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"` - ImageMetadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata"` - ImageVisibility images.ImageVisibility `mapstructure:"image_visibility" required:"false" cty:"image_visibility"` - ImageMembers []string `mapstructure:"image_members" required:"false" cty:"image_members"` - ImageDiskFormat *string `mapstructure:"image_disk_format" required:"false" cty:"image_disk_format"` - ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags"` - ImageMinDisk *int `mapstructure:"image_min_disk" required:"false" cty:"image_min_disk"` - Type *string `mapstructure:"communicator" cty:"communicator"` - PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` - SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` - SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` - SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` - SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` - SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` - SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"` - SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` - SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` - SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` - SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` - SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` - SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` - SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` - SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` - SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` - SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` - SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` - SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` - SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` - SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` - SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` - SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` - SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` - SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` - SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` - SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` - SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` - SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` - WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` - WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` - WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` - WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` - WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` - WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` - WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` - WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` - SSHInterface *string `mapstructure:"ssh_interface" required:"false" cty:"ssh_interface"` - SSHIPVersion *string `mapstructure:"ssh_ip_version" required:"false" cty:"ssh_ip_version"` - SourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image"` - SourceImageName *string `mapstructure:"source_image_name" required:"true" cty:"source_image_name"` - SourceImageFilters *FlatImageFilter `mapstructure:"source_image_filter" required:"true" cty:"source_image_filter"` - Flavor *string `mapstructure:"flavor" required:"true" cty:"flavor"` - AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone"` - RackconnectWait *bool `mapstructure:"rackconnect_wait" required:"false" cty:"rackconnect_wait"` - FloatingIPNetwork *string `mapstructure:"floating_ip_network" required:"false" cty:"floating_ip_network"` - InstanceFloatingIPNet *string `mapstructure:"instance_floating_ip_net" required:"false" cty:"instance_floating_ip_net"` - FloatingIP *string `mapstructure:"floating_ip" required:"false" cty:"floating_ip"` - ReuseIPs *bool `mapstructure:"reuse_ips" required:"false" cty:"reuse_ips"` - SecurityGroups []string `mapstructure:"security_groups" required:"false" cty:"security_groups"` - Networks []string `mapstructure:"networks" required:"false" cty:"networks"` - Ports []string `mapstructure:"ports" required:"false" cty:"ports"` - NetworkDiscoveryCIDRs []string `mapstructure:"network_discovery_cidrs" required:"false" cty:"network_discovery_cidrs"` - UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"` - UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"` - InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name"` - InstanceMetadata map[string]string `mapstructure:"instance_metadata" required:"false" cty:"instance_metadata"` - ForceDelete *bool `mapstructure:"force_delete" required:"false" cty:"force_delete"` - ConfigDrive *bool `mapstructure:"config_drive" required:"false" cty:"config_drive"` - FloatingIPPool *string `mapstructure:"floating_ip_pool" required:"false" cty:"floating_ip_pool"` - UseBlockStorageVolume *bool `mapstructure:"use_blockstorage_volume" required:"false" cty:"use_blockstorage_volume"` - VolumeName *string `mapstructure:"volume_name" required:"false" cty:"volume_name"` - VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"` - VolumeSize *int `mapstructure:"volume_size" required:"false" cty:"volume_size"` - VolumeAvailabilityZone *string `mapstructure:"volume_availability_zone" required:"false" cty:"volume_availability_zone"` - OpenstackProvider *string `mapstructure:"openstack_provider" cty:"openstack_provider"` - UseFloatingIp *bool `mapstructure:"use_floating_ip" required:"false" cty:"use_floating_ip"` + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + Username *string `mapstructure:"username" required:"true" cty:"username"` + UserID *string `mapstructure:"user_id" cty:"user_id"` + Password *string `mapstructure:"password" required:"true" cty:"password"` + IdentityEndpoint *string `mapstructure:"identity_endpoint" required:"true" cty:"identity_endpoint"` + TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id"` + TenantName *string `mapstructure:"tenant_name" cty:"tenant_name"` + DomainID *string `mapstructure:"domain_id" cty:"domain_id"` + DomainName *string `mapstructure:"domain_name" required:"false" cty:"domain_name"` + Insecure *bool `mapstructure:"insecure" required:"false" cty:"insecure"` + Region *string `mapstructure:"region" required:"false" cty:"region"` + EndpointType *string `mapstructure:"endpoint_type" required:"false" cty:"endpoint_type"` + CACertFile *string `mapstructure:"cacert" required:"false" cty:"cacert"` + ClientCertFile *string `mapstructure:"cert" required:"false" cty:"cert"` + ClientKeyFile *string `mapstructure:"key" required:"false" cty:"key"` + Token *string `mapstructure:"token" required:"false" cty:"token"` + ApplicationCredentialName *string `mapstructure:"application_credential_name" required:"false" cty:"application_credential_name"` + ApplicationCredentialID *string `mapstructure:"application_credential_id" required:"false" cty:"application_credential_id"` + ApplicationCredentialSecret *string `mapstructure:"application_credential_secret" required:"false" cty:"application_credential_secret"` + Cloud *string `mapstructure:"cloud" required:"false" cty:"cloud"` + ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"` + ImageMetadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata"` + ImageVisibility *images.ImageVisibility `mapstructure:"image_visibility" required:"false" cty:"image_visibility"` + ImageMembers []string `mapstructure:"image_members" required:"false" cty:"image_members"` + ImageDiskFormat *string `mapstructure:"image_disk_format" required:"false" cty:"image_disk_format"` + ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags"` + ImageMinDisk *int `mapstructure:"image_min_disk" required:"false" cty:"image_min_disk"` + Type *string `mapstructure:"communicator" cty:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` + SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` + SSHInterface *string `mapstructure:"ssh_interface" required:"false" cty:"ssh_interface"` + SSHIPVersion *string `mapstructure:"ssh_ip_version" required:"false" cty:"ssh_ip_version"` + SourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image"` + SourceImageName *string `mapstructure:"source_image_name" required:"true" cty:"source_image_name"` + SourceImageFilters *FlatImageFilter `mapstructure:"source_image_filter" required:"true" cty:"source_image_filter"` + Flavor *string `mapstructure:"flavor" required:"true" cty:"flavor"` + AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone"` + RackconnectWait *bool `mapstructure:"rackconnect_wait" required:"false" cty:"rackconnect_wait"` + FloatingIPNetwork *string `mapstructure:"floating_ip_network" required:"false" cty:"floating_ip_network"` + InstanceFloatingIPNet *string `mapstructure:"instance_floating_ip_net" required:"false" cty:"instance_floating_ip_net"` + FloatingIP *string `mapstructure:"floating_ip" required:"false" cty:"floating_ip"` + ReuseIPs *bool `mapstructure:"reuse_ips" required:"false" cty:"reuse_ips"` + SecurityGroups []string `mapstructure:"security_groups" required:"false" cty:"security_groups"` + Networks []string `mapstructure:"networks" required:"false" cty:"networks"` + Ports []string `mapstructure:"ports" required:"false" cty:"ports"` + NetworkDiscoveryCIDRs []string `mapstructure:"network_discovery_cidrs" required:"false" cty:"network_discovery_cidrs"` + UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"` + UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"` + InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name"` + InstanceMetadata map[string]string `mapstructure:"instance_metadata" required:"false" cty:"instance_metadata"` + ForceDelete *bool `mapstructure:"force_delete" required:"false" cty:"force_delete"` + ConfigDrive *bool `mapstructure:"config_drive" required:"false" cty:"config_drive"` + FloatingIPPool *string `mapstructure:"floating_ip_pool" required:"false" cty:"floating_ip_pool"` + UseBlockStorageVolume *bool `mapstructure:"use_blockstorage_volume" required:"false" cty:"use_blockstorage_volume"` + VolumeName *string `mapstructure:"volume_name" required:"false" cty:"volume_name"` + VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"` + VolumeSize *int `mapstructure:"volume_size" required:"false" cty:"volume_size"` + VolumeAvailabilityZone *string `mapstructure:"volume_availability_zone" required:"false" cty:"volume_availability_zone"` + OpenstackProvider *string `mapstructure:"openstack_provider" cty:"openstack_provider"` + UseFloatingIp *bool `mapstructure:"use_floating_ip" required:"false" cty:"use_floating_ip"` } // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -152,7 +155,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "cloud": &hcldec.AttrSpec{Name: "cloud", Type: cty.String, Required: false}, "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, "metadata": &hcldec.BlockAttrsSpec{TypeName: "metadata", ElementType: cty.String, Required: false}, - "image_visibility": &hcldec.AttrSpec{Name: "images.ImageVisibility", Type: cty.String, Required: false}, + "image_visibility": &hcldec.AttrSpec{Name: "image_visibility", Type: cty.String, Required: false}, "image_members": &hcldec.AttrSpec{Name: "image_members", Type: cty.List(cty.String), Required: false}, "image_disk_format": &hcldec.AttrSpec{Name: "image_disk_format", Type: cty.String, Required: false}, "image_tags": &hcldec.AttrSpec{Name: "image_tags", Type: cty.List(cty.String), Required: false}, @@ -241,10 +244,13 @@ type FlatImageFilter struct { // FlatMapstructure returns a new FlatImageFilter. // FlatImageFilter is an auto-generated flat version of ImageFilter. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*ImageFilter) FlatMapstructure() interface{} { return new(FlatImageFilter) } +func (*ImageFilter) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatImageFilter) +} -// HCL2Spec returns the hcldec.Spec of a FlatImageFilter. -// This spec is used by HCL to read the fields of FlatImageFilter. +// HCL2Spec returns the hcl spec of a ImageFilter. +// This spec is used by HCL to read the fields of ImageFilter. +// The decoded values from this spec will then be applied to a FlatImageFilter. func (*FlatImageFilter) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockSpec{TypeName: "filters", Nested: hcldec.ObjectSpec((*FlatImageFilterOptions)(nil).HCL2Spec())}, @@ -266,10 +272,13 @@ type FlatImageFilterOptions struct { // FlatMapstructure returns a new FlatImageFilterOptions. // FlatImageFilterOptions is an auto-generated flat version of ImageFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*ImageFilterOptions) FlatMapstructure() interface{} { return new(FlatImageFilterOptions) } +func (*ImageFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatImageFilterOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatImageFilterOptions. -// This spec is used by HCL to read the fields of FlatImageFilterOptions. +// HCL2Spec returns the hcl spec of a ImageFilterOptions. +// This spec is used by HCL to read the fields of ImageFilterOptions. +// The decoded values from this spec will then be applied to a FlatImageFilterOptions. func (*FlatImageFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false}, diff --git a/builder/openstack/builder_test.go b/builder/openstack/builder_test.go index 5fa296d30..0a9ac3288 100644 --- a/builder/openstack/builder_test.go +++ b/builder/openstack/builder_test.go @@ -20,7 +20,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "password": []string{}, } - warns, err := b.Prepare(c) + _, warns, err := b.Prepare(c) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/openstack/step_get_password.go b/builder/openstack/step_get_password.go index c97bc4944..fd9bfa156 100644 --- a/builder/openstack/step_get_password.go +++ b/builder/openstack/step_get_password.go @@ -8,7 +8,6 @@ import ( "time" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -78,12 +77,9 @@ func (s *StepGetPassword) Run(ctx context.Context, state multistep.StateBag) mul "Password (since debug is enabled) \"%s\"", s.Comm.WinRMPassword)) } - commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName) packer.LogSecretFilter.Set(s.Comm.WinRMPassword) return multistep.ActionContinue } -func (s *StepGetPassword) Cleanup(multistep.StateBag) { - commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName) -} +func (s *StepGetPassword) Cleanup(multistep.StateBag) {} diff --git a/builder/openstack/step_run_source_server.go b/builder/openstack/step_run_source_server.go index 3c3bdff1b..59cf63afe 100644 --- a/builder/openstack/step_run_source_server.go +++ b/builder/openstack/step_run_source_server.go @@ -128,6 +128,9 @@ func (s *StepRunSourceServer) Run(ctx context.Context, state multistep.StateBag) s.server = latestServer.(*servers.Server) state.Put("server", s.server) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.server.ID) return multistep.ActionContinue } diff --git a/builder/oracle/classic/builder.go b/builder/oracle/classic/builder.go index d97b3be10..466db5324 100644 --- a/builder/oracle/classic/builder.go +++ b/builder/oracle/classic/builder.go @@ -1,3 +1,5 @@ +//go:generate mapstructure-to-hcl2 -type Config + package classic import ( @@ -8,6 +10,7 @@ import ( "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-oracle-terraform/compute" "github.com/hashicorp/go-oracle-terraform/opc" + "github.com/hashicorp/hcl/v2/hcldec" ocommon "github.com/hashicorp/packer/builder/oracle/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -20,25 +23,26 @@ const BuilderId = "packer.oracle.classic" // Builder is a builder implementation that creates Oracle OCI custom images. type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(rawConfig ...interface{}) ([]string, error) { - config, err := NewConfig(rawConfig...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + err := b.config.Prepare(raws...) if err != nil { - return nil, err + return nil, nil, err } - b.config = config var errs *packer.MultiError errs = packer.MultiErrorAppend(errs, b.config.PVConfig.Prepare(&b.config.ctx)) if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -63,7 +67,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack runID := fmt.Sprintf("%s_%s", b.config.ImageName, os.Getenv("PACKER_RUN_UUID")) // Populate the state bag state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) state.Put("client", client) diff --git a/builder/oracle/classic/config.hcl2spec.go b/builder/oracle/classic/builder.hcl2spec.go similarity index 96% rename from builder/oracle/classic/config.hcl2spec.go rename to builder/oracle/classic/builder.hcl2spec.go index a65bc849e..d8bcf0079 100644 --- a/builder/oracle/classic/config.hcl2spec.go +++ b/builder/oracle/classic/builder.hcl2spec.go @@ -53,8 +53,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -82,10 +82,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/oracle/classic/config.go b/builder/oracle/classic/config.go index 4fc1ddb15..6f66d7a81 100644 --- a/builder/oracle/classic/config.go +++ b/builder/oracle/classic/config.go @@ -1,5 +1,3 @@ -//go:generate mapstructure-to-hcl2 -type Config - package classic import ( @@ -57,8 +55,7 @@ func (c *Config) Identifier(s string) string { return fmt.Sprintf("/Compute-%s/%s/%s", c.IdentityDomain, c.Username, s) } -func NewConfig(raws ...interface{}) (*Config, error) { - c := &Config{} +func (c *Config) Prepare(raws ...interface{}) error { // Decode from template err := config.Decode(c, &config.DecodeOpts{ @@ -66,12 +63,12 @@ func NewConfig(raws ...interface{}) (*Config, error) { InterpolateContext: &c.ctx, }, raws...) if err != nil { - return nil, fmt.Errorf("Failed to mapstructure Config: %+v", err) + return fmt.Errorf("Failed to mapstructure Config: %+v", err) } c.apiEndpointURL, err = url.Parse(c.APIEndpoint) if err != nil { - return nil, fmt.Errorf("Error parsing API Endpoint: %s", err) + return fmt.Errorf("Error parsing API Endpoint: %s", err) } // Set default source list if c.SSHSourceList == "" { @@ -127,7 +124,7 @@ func NewConfig(raws ...interface{}) (*Config, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return errs } // unpack attributes from json into config @@ -155,5 +152,5 @@ func NewConfig(raws ...interface{}) (*Config, error) { c.attribs = data } - return c, nil + return nil } diff --git a/builder/oracle/classic/config_test.go b/builder/oracle/classic/config_test.go index b120e70a3..8d55a1626 100644 --- a/builder/oracle/classic/config_test.go +++ b/builder/oracle/classic/config_test.go @@ -22,7 +22,8 @@ func testConfig() map[string]interface{} { func TestConfigAutoFillsSourceList(t *testing.T) { tc := testConfig() - conf, err := NewConfig(tc) + var conf Config + err := conf.Prepare(tc) if err != nil { t.Fatalf("Should not have error: %s", err.Error()) } @@ -47,7 +48,8 @@ func TestConfigValidationCatchesMissing(t *testing.T) { for _, key := range required { tc := testConfig() delete(tc, key) - _, err := NewConfig(tc) + var c Config + err := c.Prepare(tc) if err == nil { t.Fatalf("Test should have failed when config lacked %s!", key) } @@ -68,7 +70,8 @@ func TestConfigValidatesObjects(t *testing.T) { for _, tt := range objectTests { tc := testConfig() tc[s] = tt.object - _, err := NewConfig(tc) + var c Config + err := c.Prepare(tc) if tt.valid { assert.NoError(t, err, tt.object) } else { diff --git a/builder/oracle/oci/builder.go b/builder/oracle/oci/builder.go index a4b31b8e6..2fe35ebc1 100644 --- a/builder/oracle/oci/builder.go +++ b/builder/oracle/oci/builder.go @@ -6,6 +6,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" ocommon "github.com/hashicorp/packer/builder/oracle/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -22,29 +23,30 @@ const ociAPIVersion = "20160918" // Builder is a builder implementation that creates Oracle OCI custom images. type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(rawConfig ...interface{}) ([]string, error) { - config, err := NewConfig(rawConfig...) - if err != nil { - return nil, err - } - b.config = config +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return nil, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + err := b.config.Prepare(raws...) + if err != nil { + return nil, nil, err + } + + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { - driver, err := NewDriverOCI(b.config) + driver, err := NewDriverOCI(&b.config) if err != nil { return nil, err } // Populate the state bag state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/oracle/oci/config.go b/builder/oracle/oci/config.go index 2d11cb05a..52229cc48 100644 --- a/builder/oracle/oci/config.go +++ b/builder/oracle/oci/config.go @@ -75,8 +75,7 @@ func (c *Config) ConfigProvider() ocicommon.ConfigurationProvider { return c.configProvider } -func NewConfig(raws ...interface{}) (*Config, error) { - c := &Config{} +func (c *Config) Prepare(raws ...interface{}) error { // Decode from template err := config.Decode(c, &config.DecodeOpts{ @@ -84,7 +83,7 @@ func NewConfig(raws ...interface{}) (*Config, error) { InterpolateContext: &c.ctx, }, raws...) if err != nil { - return nil, fmt.Errorf("Failed to mapstructure Config: %+v", err) + return fmt.Errorf("Failed to mapstructure Config: %+v", err) } // Determine where the SDK config is located @@ -103,13 +102,13 @@ func NewConfig(raws ...interface{}) (*Config, error) { if c.KeyFile != "" { path, err := packer.ExpandUser(c.KeyFile) if err != nil { - return nil, err + return err } // Read API signing key keyContent, err = ioutil.ReadFile(path) if err != nil { - return nil, err + return err } } @@ -135,7 +134,7 @@ func NewConfig(raws ...interface{}) (*Config, error) { // Load API access configuration from SDK configProvider, err := ocicommon.ComposingConfigurationProvider(providers) if err != nil { - return nil, err + return err } var errs *packer.MultiError @@ -249,10 +248,10 @@ func NewConfig(raws ...interface{}) (*Config, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return errs } - return c, nil + return nil } // getDefaultOCISettingsPath uses os/user to compute the default diff --git a/builder/oracle/oci/config.hcl2spec.go b/builder/oracle/oci/config.hcl2spec.go index 78980b963..107c1ae43 100644 --- a/builder/oracle/oci/config.hcl2spec.go +++ b/builder/oracle/oci/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -82,10 +82,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/oracle/oci/config_test.go b/builder/oracle/oci/config_test.go index 37d23a258..117f1190d 100644 --- a/builder/oracle/oci/config_test.go +++ b/builder/oracle/oci/config_test.go @@ -69,7 +69,8 @@ func TestConfig(t *testing.T) { // Config tests t.Run("BaseConfig", func(t *testing.T) { raw := testConfig(cfgFile) - _, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration %+v", errs) @@ -80,7 +81,8 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) delete(raw, "access_cfg_file") - _, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) expectedErrors := []string{ "'user_ocid'", "'tenancy_ocid'", "'fingerprint'", "'key_file'", @@ -102,7 +104,8 @@ func TestConfig(t *testing.T) { raw["fingerprint"] = "00:00..." raw["key_file"] = keyFile.Name() - _, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("err: %+v", errs) @@ -112,7 +115,8 @@ func TestConfig(t *testing.T) { t.Run("TenancyReadFromAccessCfgFile", func(t *testing.T) { raw := testConfig(cfgFile) - c, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration %+v", errs) } @@ -131,7 +135,8 @@ func TestConfig(t *testing.T) { t.Run("RegionNotDefaultedToPHXWhenSetInOCISettings", func(t *testing.T) { raw := testConfig(cfgFile) - c, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration %+v", errs) } @@ -156,7 +161,8 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) delete(raw, k) - _, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if !strings.Contains(errs.Error(), k) { t.Errorf("Expected '%s' to contain '%s'", errs.Error(), k) @@ -168,7 +174,8 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) delete(raw, "image_name") - c, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration %+v", errs) } @@ -183,7 +190,8 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) raw["user_ocid"] = expected - c, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration %+v", errs) } @@ -199,7 +207,8 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) raw["tenancy_ocid"] = expected - c, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration %+v", errs) } @@ -215,7 +224,8 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) raw["region"] = expected - c, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration %+v", errs) } @@ -231,7 +241,8 @@ func TestConfig(t *testing.T) { raw := testConfig(cfgFile) raw["fingerprint"] = expected - c, errs := NewConfig(raw) + var c Config + errs := c.Prepare(raw) if errs != nil { t.Fatalf("Unexpected error in configuration: %+v", errs) } diff --git a/builder/oracle/oci/step_get_default_credentials.go b/builder/oracle/oci/step_get_default_credentials.go index 1f4ea20b3..0ed44b7f6 100644 --- a/builder/oracle/oci/step_get_default_credentials.go +++ b/builder/oracle/oci/step_get_default_credentials.go @@ -5,7 +5,6 @@ import ( "fmt" "log" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -52,7 +51,7 @@ func (s *stepGetDefaultCredentials) Run(ctx context.Context, state multistep.Sta } // store so that we can access this later during provisioning - commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName) + state.Put("winrm_password", s.Comm.WinRMPassword) packer.LogSecretFilter.Set(s.Comm.WinRMPassword) return multistep.ActionContinue } diff --git a/builder/oracle/oci/step_test.go b/builder/oracle/oci/step_test.go index 55afba523..deeff1b1a 100644 --- a/builder/oracle/oci/step_test.go +++ b/builder/oracle/oci/step_test.go @@ -16,7 +16,8 @@ func baseTestConfig() *Config { panic(err) } - cfg, err := NewConfig(map[string]interface{}{ + var c Config + err = c.Prepare(map[string]interface{}{ "availability_domain": "aaaa:US-ASHBURN-AD-1", // Image @@ -46,7 +47,7 @@ func baseTestConfig() *Config { if err != nil { panic(err) } - return cfg + return &c } func testState() multistep.StateBag { diff --git a/builder/osc/bsu/builder.go b/builder/osc/bsu/builder.go index ab4280417..dc7ce678c 100644 --- a/builder/osc/bsu/builder.go +++ b/builder/osc/bsu/builder.go @@ -13,6 +13,7 @@ import ( "fmt" "net/http" + "github.com/hashicorp/hcl/v2/hcldec" osccommon "github.com/hashicorp/packer/builder/osc/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -42,7 +43,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = osccommon.TemplateFuncs err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, @@ -59,7 +62,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } if b.config.PackerConfig.PackerForce { @@ -75,11 +78,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/osc/bsu/builder.hcl2spec.go b/builder/osc/bsu/builder.hcl2spec.go index 8bc75d0f7..0883acd80 100644 --- a/builder/osc/bsu/builder.hcl2spec.go +++ b/builder/osc/bsu/builder.hcl2spec.go @@ -98,8 +98,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -115,10 +115,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -145,14 +148,14 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "omi_groups": &hcldec.AttrSpec{Name: "omi_groups", Type: cty.List(cty.String), Required: false}, "omi_product_codes": &hcldec.AttrSpec{Name: "omi_product_codes", Type: cty.List(cty.String), Required: false}, "omi_regions": &hcldec.AttrSpec{Name: "omi_regions", Type: cty.List(cty.String), Required: false}, - "tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false}, "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, "force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false}, - "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "snapshot_tags", ElementType: cty.String, Required: false}, "snapshot_account_ids": &hcldec.AttrSpec{Name: "snapshot_account_ids", Type: cty.List(cty.String), Required: false}, "snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false}, - "omi_block_device_mappings": &hcldec.BlockListSpec{TypeName: "omi_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "omi_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}}, - "launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}}, + "omi_block_device_mappings": &hcldec.BlockListSpec{TypeName: "omi_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())}, "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, "subregion_name": &hcldec.AttrSpec{Name: "subregion_name", Type: cty.String, Required: false}, "block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false}, @@ -220,7 +223,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}, - "run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false}, } return s } diff --git a/builder/osc/bsu/builder_test.go b/builder/osc/bsu/builder_test.go index 1736044df..c700ccc06 100644 --- a/builder/osc/bsu/builder_test.go +++ b/builder/osc/bsu/builder_test.go @@ -32,7 +32,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "access_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -48,7 +48,7 @@ func TestBuilderPrepare_OMIName(t *testing.T) { // Test good config["omi_name"] = "foo" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -59,7 +59,7 @@ func TestBuilderPrepare_OMIName(t *testing.T) { // Test bad config["omi_name"] = "foo {{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -70,7 +70,7 @@ func TestBuilderPrepare_OMIName(t *testing.T) { // Test bad delete(config, "omi_name") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -85,7 +85,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -101,7 +101,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "terminate" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -111,7 +111,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "stop" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -121,7 +121,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test bad config["shutdown_behavior"] = "foobar" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/osc/bsusurrogate/builder.go b/builder/osc/bsusurrogate/builder.go index 1186a5bdc..3669c3952 100644 --- a/builder/osc/bsusurrogate/builder.go +++ b/builder/osc/bsusurrogate/builder.go @@ -1,3 +1,5 @@ +//go:generate mapstructure-to-hcl2 -type Config,RootBlockDevice + // Package bsusurrogate contains a packer.Builder implementation that // builds a new EBS-backed OMI using an ephemeral instance. package bsusurrogate @@ -9,6 +11,7 @@ import ( "fmt" "net/http" + "github.com/hashicorp/hcl/v2/hcldec" osccommon "github.com/hashicorp/packer/builder/osc/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -39,7 +42,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = osccommon.TemplateFuncs @@ -58,7 +63,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } if b.config.PackerConfig.PackerForce { @@ -90,11 +95,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return nil, nil + return nil, nil, nil } diff --git a/builder/osc/bsusurrogate/builder.hcl2spec.go b/builder/osc/bsusurrogate/builder.hcl2spec.go new file mode 100644 index 000000000..9b5370724 --- /dev/null +++ b/builder/osc/bsusurrogate/builder.hcl2spec.go @@ -0,0 +1,264 @@ +// Code generated by "mapstructure-to-hcl2 -type Config,RootBlockDevice"; DO NOT EDIT. +package bsusurrogate + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/builder/osc/common" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + AccessKey *string `mapstructure:"access_key" cty:"access_key"` + CustomEndpointOAPI *string `mapstructure:"custom_endpoint_oapi" cty:"custom_endpoint_oapi"` + InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify"` + MFACode *string `mapstructure:"mfa_code" cty:"mfa_code"` + ProfileName *string `mapstructure:"profile" cty:"profile"` + RawRegion *string `mapstructure:"region" cty:"region"` + SecretKey *string `mapstructure:"secret_key" cty:"secret_key"` + SkipValidation *bool `mapstructure:"skip_region_validation" cty:"skip_region_validation"` + SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"` + Token *string `mapstructure:"token" cty:"token"` + AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" cty:"associate_public_ip_address"` + Subregion *string `mapstructure:"subregion_name" cty:"subregion_name"` + BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" cty:"block_duration_minutes"` + DisableStopVm *bool `mapstructure:"disable_stop_vm" cty:"disable_stop_vm"` + BsuOptimized *bool `mapstructure:"bsu_optimized" cty:"bsu_optimized"` + EnableT2Unlimited *bool `mapstructure:"enable_t2_unlimited" cty:"enable_t2_unlimited"` + IamVmProfile *string `mapstructure:"iam_vm_profile" cty:"iam_vm_profile"` + VmInitiatedShutdownBehavior *string `mapstructure:"shutdown_behavior" cty:"shutdown_behavior"` + VmType *string `mapstructure:"vm_type" cty:"vm_type"` + SecurityGroupFilter *common.FlatSecurityGroupFilterOptions `mapstructure:"security_group_filter" cty:"security_group_filter"` + RunTags map[string]string `mapstructure:"run_tags" cty:"run_tags"` + SecurityGroupId *string `mapstructure:"security_group_id" cty:"security_group_id"` + SecurityGroupIds []string `mapstructure:"security_group_ids" cty:"security_group_ids"` + SourceOmi *string `mapstructure:"source_omi" cty:"source_omi"` + SourceOmiFilter *common.FlatOmiFilterOptions `mapstructure:"source_omi_filter" cty:"source_omi_filter"` + SpotPrice *string `mapstructure:"spot_price" cty:"spot_price"` + SpotPriceAutoProduct *string `mapstructure:"spot_price_auto_product" cty:"spot_price_auto_product"` + SpotTags map[string]string `mapstructure:"spot_tags" cty:"spot_tags"` + SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" cty:"subnet_filter"` + SubnetId *string `mapstructure:"subnet_id" cty:"subnet_id"` + TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"` + TemporarySGSourceCidr *string `mapstructure:"temporary_security_group_source_cidr" cty:"temporary_security_group_source_cidr"` + UserData *string `mapstructure:"user_data" cty:"user_data"` + UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file"` + NetFilter *common.FlatNetFilterOptions `mapstructure:"net_filter" cty:"net_filter"` + NetId *string `mapstructure:"net_id" cty:"net_id"` + WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" cty:"windows_password_timeout"` + Type *string `mapstructure:"communicator" cty:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` + SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface"` + OMIMappings []common.FlatBlockDevice `mapstructure:"omi_block_device_mappings" cty:"omi_block_device_mappings"` + LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" cty:"launch_block_device_mappings"` + OMIName *string `mapstructure:"omi_name" cty:"omi_name"` + OMIDescription *string `mapstructure:"omi_description" cty:"omi_description"` + OMIVirtType *string `mapstructure:"omi_virtualization_type" cty:"omi_virtualization_type"` + OMIAccountIDs []string `mapstructure:"omi_account_ids" cty:"omi_account_ids"` + OMIGroups []string `mapstructure:"omi_groups" cty:"omi_groups"` + OMIProductCodes []string `mapstructure:"omi_product_codes" cty:"omi_product_codes"` + OMIRegions []string `mapstructure:"omi_regions" cty:"omi_regions"` + OMITags common.TagMap `mapstructure:"tags" cty:"tags"` + OMIForceDeregister *bool `mapstructure:"force_deregister" cty:"force_deregister"` + OMIForceDeleteSnapshot *bool `mapstructure:"force_delete_snapshot" cty:"force_delete_snapshot"` + SnapshotTags common.TagMap `mapstructure:"snapshot_tags" cty:"snapshot_tags"` + SnapshotAccountIDs []string `mapstructure:"snapshot_account_ids" cty:"snapshot_account_ids"` + SnapshotGroups []string `mapstructure:"snapshot_groups" cty:"snapshot_groups"` + RootDevice *FlatRootBlockDevice `mapstructure:"omi_root_device" cty:"omi_root_device"` + VolumeRunTags common.TagMap `mapstructure:"run_volume_tags" cty:"run_volume_tags"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: 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}, + "custom_endpoint_oapi": &hcldec.AttrSpec{Name: "custom_endpoint_oapi", Type: cty.String, Required: false}, + "insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false}, + "mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "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}, + "token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false}, + "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, + "subregion_name": &hcldec.AttrSpec{Name: "subregion_name", Type: cty.String, Required: false}, + "block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false}, + "disable_stop_vm": &hcldec.AttrSpec{Name: "disable_stop_vm", Type: cty.Bool, Required: false}, + "bsu_optimized": &hcldec.AttrSpec{Name: "bsu_optimized", Type: cty.Bool, Required: false}, + "enable_t2_unlimited": &hcldec.AttrSpec{Name: "enable_t2_unlimited", Type: cty.Bool, Required: false}, + "iam_vm_profile": &hcldec.AttrSpec{Name: "iam_vm_profile", Type: cty.String, Required: false}, + "shutdown_behavior": &hcldec.AttrSpec{Name: "shutdown_behavior", Type: cty.String, Required: false}, + "vm_type": &hcldec.AttrSpec{Name: "vm_type", Type: cty.String, Required: false}, + "security_group_filter": &hcldec.BlockSpec{TypeName: "security_group_filter", Nested: hcldec.ObjectSpec((*common.FlatSecurityGroupFilterOptions)(nil).HCL2Spec())}, + "run_tags": &hcldec.BlockAttrsSpec{TypeName: "run_tags", ElementType: cty.String, Required: false}, + "security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false}, + "security_group_ids": &hcldec.AttrSpec{Name: "security_group_ids", Type: cty.List(cty.String), Required: false}, + "source_omi": &hcldec.AttrSpec{Name: "source_omi", Type: cty.String, Required: false}, + "source_omi_filter": &hcldec.BlockSpec{TypeName: "source_omi_filter", Nested: hcldec.ObjectSpec((*common.FlatOmiFilterOptions)(nil).HCL2Spec())}, + "spot_price": &hcldec.AttrSpec{Name: "spot_price", Type: cty.String, Required: false}, + "spot_price_auto_product": &hcldec.AttrSpec{Name: "spot_price_auto_product", Type: cty.String, Required: false}, + "spot_tags": &hcldec.BlockAttrsSpec{TypeName: "spot_tags", ElementType: cty.String, Required: false}, + "subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())}, + "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, + "temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false}, + "temporary_security_group_source_cidr": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidr", Type: cty.String, Required: false}, + "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, + "user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false}, + "net_filter": &hcldec.BlockSpec{TypeName: "net_filter", Nested: hcldec.ObjectSpec((*common.FlatNetFilterOptions)(nil).HCL2Spec())}, + "net_id": &hcldec.AttrSpec{Name: "net_id", Type: cty.String, Required: false}, + "windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false}, + "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, + "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, + "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, + "ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false}, + "ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false}, + "ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false}, + "ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false}, + "ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false}, + "ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false}, + "ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false}, + "ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false}, + "ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false}, + "ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false}, + "ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false}, + "ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false}, + "ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false}, + "ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false}, + "ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false}, + "ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false}, + "ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false}, + "ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false}, + "ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false}, + "ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false}, + "ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false}, + "ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false}, + "ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false}, + "ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false}, + "ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false}, + "ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false}, + "ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false}, + "winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false}, + "winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false}, + "winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false}, + "winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false}, + "winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false}, + "winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false}, + "winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false}, + "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, + "ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false}, + "omi_block_device_mappings": &hcldec.BlockListSpec{TypeName: "omi_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())}, + "omi_name": &hcldec.AttrSpec{Name: "omi_name", Type: cty.String, Required: false}, + "omi_description": &hcldec.AttrSpec{Name: "omi_description", Type: cty.String, Required: false}, + "omi_virtualization_type": &hcldec.AttrSpec{Name: "omi_virtualization_type", Type: cty.String, Required: false}, + "omi_account_ids": &hcldec.AttrSpec{Name: "omi_account_ids", Type: cty.List(cty.String), Required: false}, + "omi_groups": &hcldec.AttrSpec{Name: "omi_groups", Type: cty.List(cty.String), Required: false}, + "omi_product_codes": &hcldec.AttrSpec{Name: "omi_product_codes", Type: cty.List(cty.String), Required: false}, + "omi_regions": &hcldec.AttrSpec{Name: "omi_regions", Type: cty.List(cty.String), Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false}, + "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, + "force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false}, + "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "snapshot_tags", ElementType: cty.String, Required: false}, + "snapshot_account_ids": &hcldec.AttrSpec{Name: "snapshot_account_ids", Type: cty.List(cty.String), Required: false}, + "snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false}, + "omi_root_device": &hcldec.BlockSpec{TypeName: "omi_root_device", Nested: hcldec.ObjectSpec((*FlatRootBlockDevice)(nil).HCL2Spec())}, + "run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "run_volume_tags", ElementType: cty.String, Required: false}, + } + return s +} + +// FlatRootBlockDevice is an auto-generated flat version of RootBlockDevice. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatRootBlockDevice struct { + SourceDeviceName *string `mapstructure:"source_device_name" cty:"source_device_name"` + DeviceName *string `mapstructure:"device_name" cty:"device_name"` + DeleteOnVmDeletion *bool `mapstructure:"delete_on_vm_deletion" cty:"delete_on_vm_deletion"` + IOPS *int64 `mapstructure:"iops" cty:"iops"` + VolumeType *string `mapstructure:"volume_type" cty:"volume_type"` + VolumeSize *int64 `mapstructure:"volume_size" cty:"volume_size"` +} + +// FlatMapstructure returns a new FlatRootBlockDevice. +// FlatRootBlockDevice is an auto-generated flat version of RootBlockDevice. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*RootBlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatRootBlockDevice) +} + +// HCL2Spec returns the hcl spec of a RootBlockDevice. +// This spec is used by HCL to read the fields of RootBlockDevice. +// The decoded values from this spec will then be applied to a FlatRootBlockDevice. +func (*FlatRootBlockDevice) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "source_device_name": &hcldec.AttrSpec{Name: "source_device_name", Type: cty.String, Required: false}, + "device_name": &hcldec.AttrSpec{Name: "device_name", Type: cty.String, Required: false}, + "delete_on_vm_deletion": &hcldec.AttrSpec{Name: "delete_on_vm_deletion", Type: cty.Bool, Required: false}, + "iops": &hcldec.AttrSpec{Name: "iops", Type: cty.Number, Required: false}, + "volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false}, + "volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false}, + } + return s +} diff --git a/builder/osc/bsuvolume/builder.go b/builder/osc/bsuvolume/builder.go index e0b5a8fa7..44873cfd2 100644 --- a/builder/osc/bsuvolume/builder.go +++ b/builder/osc/bsuvolume/builder.go @@ -11,6 +11,7 @@ import ( "log" "net/http" + "github.com/hashicorp/hcl/v2/hcldec" osccommon "github.com/hashicorp/packer/builder/osc/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -44,7 +45,9 @@ type EngineVarsTemplate struct { SourceOMI string } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = osccommon.TemplateFuncs // Create passthrough for {{ .BuildRegion }} and {{ .SourceOMI }} variables // so we can fill them in later @@ -57,7 +60,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { InterpolateContext: &b.config.ctx, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors @@ -78,11 +81,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -101,7 +104,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) - state.Put("config", &b.config) state.Put("oapi", oapiconn) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/osc/bsuvolume/builder.hcl2spec.go b/builder/osc/bsuvolume/builder.hcl2spec.go index 70789c634..8c403743e 100644 --- a/builder/osc/bsuvolume/builder.hcl2spec.go +++ b/builder/osc/bsuvolume/builder.hcl2spec.go @@ -24,10 +24,13 @@ type FlatBlockDevice struct { // FlatMapstructure returns a new FlatBlockDevice. // FlatBlockDevice is an auto-generated flat version of BlockDevice. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*BlockDevice) FlatMapstructure() interface{} { return new(FlatBlockDevice) } +func (*BlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatBlockDevice) +} -// HCL2Spec returns the hcldec.Spec of a FlatBlockDevice. -// This spec is used by HCL to read the fields of FlatBlockDevice. +// HCL2Spec returns the hcl spec of a BlockDevice. +// This spec is used by HCL to read the fields of BlockDevice. +// The decoded values from this spec will then be applied to a FlatBlockDevice. func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "delete_on_vm_deletion": &hcldec.AttrSpec{Name: "delete_on_vm_deletion", Type: cty.Bool, Required: false}, @@ -38,7 +41,7 @@ func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec { "virtual_name": &hcldec.AttrSpec{Name: "virtual_name", Type: cty.String, Required: false}, "volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false}, "volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false}, - "tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false}, } return s } @@ -119,8 +122,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -136,10 +139,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -226,7 +232,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}, - "bsu_volumes": &hcldec.BlockListSpec{TypeName: "bsu_volumes", Nested: &hcldec.BlockSpec{TypeName: "bsu_volumes", Nested: hcldec.ObjectSpec((*FlatBlockDevice)(nil).HCL2Spec())}}, + "bsu_volumes": &hcldec.BlockListSpec{TypeName: "bsu_volumes", Nested: hcldec.ObjectSpec((*FlatBlockDevice)(nil).HCL2Spec())}, } return s } diff --git a/builder/osc/bsuvolume/builder_test.go b/builder/osc/bsuvolume/builder_test.go index 956fba60f..27c4a80c0 100644 --- a/builder/osc/bsuvolume/builder_test.go +++ b/builder/osc/bsuvolume/builder_test.go @@ -31,7 +31,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "access_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -46,7 +46,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -62,7 +62,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "terminate" config["skip_region_validation"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -72,7 +72,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test good config["shutdown_behavior"] = "stop" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -82,7 +82,7 @@ func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) { // Test bad config["shutdown_behavior"] = "foobar" - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/osc/chroot/builder.go b/builder/osc/chroot/builder.go index e6813d263..a4963deb6 100644 --- a/builder/osc/chroot/builder.go +++ b/builder/osc/chroot/builder.go @@ -13,6 +13,7 @@ import ( "net/http" "runtime" + "github.com/hashicorp/hcl/v2/hcldec" osccommon "github.com/hashicorp/packer/builder/osc/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -63,7 +64,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { b.config.ctx.Funcs = osccommon.TemplateFuncs err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, @@ -82,7 +85,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } if b.config.PackerConfig.PackerForce { @@ -178,11 +181,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warns, errs + return nil, warns, errs } packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token) - return warns, nil + return nil, warns, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/osc/chroot/builder.hcl2spec.go b/builder/osc/chroot/builder.hcl2spec.go index 0404b12c1..fc83b04b4 100644 --- a/builder/osc/chroot/builder.hcl2spec.go +++ b/builder/osc/chroot/builder.hcl2spec.go @@ -63,10 +63,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -76,7 +79,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false}, "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, - "omi_block_device_mappings": &hcldec.BlockListSpec{TypeName: "omi_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "omi_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}}, + "omi_block_device_mappings": &hcldec.BlockListSpec{TypeName: "omi_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}, "omi_name": &hcldec.AttrSpec{Name: "omi_name", Type: cty.String, Required: false}, "omi_description": &hcldec.AttrSpec{Name: "omi_description", Type: cty.String, Required: false}, "omi_virtualization_type": &hcldec.AttrSpec{Name: "omi_virtualization_type", Type: cty.String, Required: false}, @@ -85,10 +88,10 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "omi_product_codes": &hcldec.AttrSpec{Name: "omi_product_codes", Type: cty.List(cty.String), Required: false}, "omi_regions": &hcldec.AttrSpec{Name: "omi_regions", Type: cty.List(cty.String), Required: false}, "skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false}, - "tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false}, "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, "force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false}, - "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "snapshot_tags", ElementType: cty.String, Required: false}, "snapshot_account_ids": &hcldec.AttrSpec{Name: "snapshot_account_ids", 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}, @@ -116,7 +119,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "root_volume_type": &hcldec.AttrSpec{Name: "root_volume_type", Type: cty.String, Required: false}, "source_omi": &hcldec.AttrSpec{Name: "source_omi", Type: cty.String, Required: false}, "source_omi_filter": &hcldec.BlockSpec{TypeName: "source_omi_filter", Nested: hcldec.ObjectSpec((*common.FlatOmiFilterOptions)(nil).HCL2Spec())}, - "root_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false}, + "root_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "root_volume_tags", ElementType: cty.String, Required: false}, } return s } diff --git a/builder/osc/common/run_config.hcl2spec.go b/builder/osc/common/run_config.hcl2spec.go index 53edc1f9f..a61eaac64 100644 --- a/builder/osc/common/run_config.hcl2spec.go +++ b/builder/osc/common/run_config.hcl2spec.go @@ -22,10 +22,13 @@ type FlatBlockDevice struct { // FlatMapstructure returns a new FlatBlockDevice. // FlatBlockDevice is an auto-generated flat version of BlockDevice. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*BlockDevice) FlatMapstructure() interface{} { return new(FlatBlockDevice) } +func (*BlockDevice) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatBlockDevice) +} -// HCL2Spec returns the hcldec.Spec of a FlatBlockDevice. -// This spec is used by HCL to read the fields of FlatBlockDevice. +// HCL2Spec returns the hcl spec of a BlockDevice. +// This spec is used by HCL to read the fields of BlockDevice. +// The decoded values from this spec will then be applied to a FlatBlockDevice. func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "delete_on_vm_deletion": &hcldec.AttrSpec{Name: "delete_on_vm_deletion", Type: cty.Bool, Required: false}, @@ -49,10 +52,13 @@ type FlatNetFilterOptions struct { // FlatMapstructure returns a new FlatNetFilterOptions. // FlatNetFilterOptions is an auto-generated flat version of NetFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*NetFilterOptions) FlatMapstructure() interface{} { return new(FlatNetFilterOptions) } +func (*NetFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatNetFilterOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatNetFilterOptions. -// This spec is used by HCL to read the fields of FlatNetFilterOptions. +// HCL2Spec returns the hcl spec of a NetFilterOptions. +// This spec is used by HCL to read the fields of NetFilterOptions. +// The decoded values from this spec will then be applied to a FlatNetFilterOptions. func (*FlatNetFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, @@ -71,10 +77,13 @@ type FlatOmiFilterOptions struct { // FlatMapstructure returns a new FlatOmiFilterOptions. // FlatOmiFilterOptions is an auto-generated flat version of OmiFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*OmiFilterOptions) FlatMapstructure() interface{} { return new(FlatOmiFilterOptions) } +func (*OmiFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatOmiFilterOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatOmiFilterOptions. -// This spec is used by HCL to read the fields of FlatOmiFilterOptions. +// HCL2Spec returns the hcl spec of a OmiFilterOptions. +// This spec is used by HCL to read the fields of OmiFilterOptions. +// The decoded values from this spec will then be applied to a FlatOmiFilterOptions. func (*FlatOmiFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, @@ -93,12 +102,13 @@ type FlatSecurityGroupFilterOptions struct { // FlatMapstructure returns a new FlatSecurityGroupFilterOptions. // FlatSecurityGroupFilterOptions is an auto-generated flat version of SecurityGroupFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*SecurityGroupFilterOptions) FlatMapstructure() interface{} { +func (*SecurityGroupFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { return new(FlatSecurityGroupFilterOptions) } -// HCL2Spec returns the hcldec.Spec of a FlatSecurityGroupFilterOptions. -// This spec is used by HCL to read the fields of FlatSecurityGroupFilterOptions. +// HCL2Spec returns the hcl spec of a SecurityGroupFilterOptions. +// This spec is used by HCL to read the fields of SecurityGroupFilterOptions. +// The decoded values from this spec will then be applied to a FlatSecurityGroupFilterOptions. func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, @@ -117,10 +127,13 @@ type FlatSubnetFilterOptions struct { // FlatMapstructure returns a new FlatSubnetFilterOptions. // FlatSubnetFilterOptions is an auto-generated flat version of SubnetFilterOptions. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*SubnetFilterOptions) FlatMapstructure() interface{} { return new(FlatSubnetFilterOptions) } +func (*SubnetFilterOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatSubnetFilterOptions) +} -// HCL2Spec returns the hcldec.Spec of a FlatSubnetFilterOptions. -// This spec is used by HCL to read the fields of FlatSubnetFilterOptions. +// HCL2Spec returns the hcl spec of a SubnetFilterOptions. +// This spec is used by HCL to read the fields of SubnetFilterOptions. +// The decoded values from this spec will then be applied to a FlatSubnetFilterOptions. func (*FlatSubnetFilterOptions) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false}, diff --git a/builder/osc/common/step_get_password.go b/builder/osc/common/step_get_password.go index 63ebbb288..9971379d5 100644 --- a/builder/osc/common/step_get_password.go +++ b/builder/osc/common/step_get_password.go @@ -11,7 +11,6 @@ import ( "log" "time" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -95,19 +94,12 @@ WaitLoop: "Password (since debug is enabled): %s", s.Comm.WinRMPassword)) } // store so that we can access this later during provisioning - - err = commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName) - if err != nil { - log.Printf("[WARN] commonhelper.SetSharedState returned error: %s", err) - } packer.LogSecretFilter.Set(s.Comm.WinRMPassword) return multistep.ActionContinue } -func (s *StepGetPassword) Cleanup(multistep.StateBag) { - commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName) -} +func (s *StepGetPassword) Cleanup(multistep.StateBag) {} func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) { oapiconn := state.Get("oapi").(*oapi.Client) diff --git a/builder/osc/common/step_run_source_vm.go b/builder/osc/common/step_run_source_vm.go index 04fe1d5b3..a0f92a6a5 100644 --- a/builder/osc/common/step_run_source_vm.go +++ b/builder/osc/common/step_run_source_vm.go @@ -253,6 +253,9 @@ func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) mul } state.Put("vm", vm) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", vmId) // If we're in a region that doesn't support tagging on vm creation, // do that now. diff --git a/builder/parallels/common/step_run.go b/builder/parallels/common/step_run.go index d3d60fd1a..062312069 100644 --- a/builder/parallels/common/step_run.go +++ b/builder/parallels/common/step_run.go @@ -36,6 +36,9 @@ func (s *StepRun) Run(ctx context.Context, state multistep.StateBag) multistep.S } s.vmName = vmName + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", vmName) return multistep.ActionContinue } diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index b7ea4788f..31a160226 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" parallelscommon "github.com/hashicorp/packer/builder/parallels/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" @@ -82,7 +83,9 @@ type Config struct { ctx interpolate.Context } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -96,7 +99,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors and warnings @@ -169,10 +172,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warnings, errs + return nil, warnings, errs } - return warnings, nil + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/parallels/iso/builder.hcl2spec.go b/builder/parallels/iso/builder.hcl2spec.go index e8e6e506c..0a3bf313b 100644 --- a/builder/parallels/iso/builder.hcl2spec.go +++ b/builder/parallels/iso/builder.hcl2spec.go @@ -72,8 +72,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -98,10 +98,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/parallels/iso/builder_test.go b/builder/parallels/iso/builder_test.go index 88a28144e..e131bc137 100644 --- a/builder/parallels/iso/builder_test.go +++ b/builder/parallels/iso/builder_test.go @@ -32,7 +32,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -54,7 +54,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { config := testConfig() delete(config, "floppy_files") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -69,7 +69,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { floppies_path := "../../../common/test-fixtures/floppies" config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -88,7 +88,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) { config := testConfig() config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} b = Builder{} - _, errs := b.Prepare(config) + _, _, errs := b.Prepare(config) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -103,7 +103,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config := testConfig() delete(config, "disk_size") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -117,7 +117,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config["disk_size"] = 60000 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -136,7 +136,7 @@ func TestBuilderPrepare_DiskType(t *testing.T) { // Test a default disk_type delete(config, "disk_type") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -151,7 +151,7 @@ func TestBuilderPrepare_DiskType(t *testing.T) { // Test with a bad config["disk_type"] = "fake" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -163,7 +163,7 @@ func TestBuilderPrepare_DiskType(t *testing.T) { config["disk_type"] = "plain" config["skip_compaction"] = false b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) == 0 { t.Fatalf("should have warning") } @@ -175,7 +175,7 @@ func TestBuilderPrepare_DiskType(t *testing.T) { config["disk_type"] = "plain" config["skip_compaction"] = true b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -191,7 +191,7 @@ func TestBuilderPrepare_HardDriveInterface(t *testing.T) { // Test a default boot_wait delete(config, "hard_drive_interface") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -206,7 +206,7 @@ func TestBuilderPrepare_HardDriveInterface(t *testing.T) { // Test with a bad config["hard_drive_interface"] = "fake" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -217,7 +217,7 @@ func TestBuilderPrepare_HardDriveInterface(t *testing.T) { // Test with a good config["hard_drive_interface"] = "scsi" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -232,7 +232,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/parallels/pvm/builder.go b/builder/parallels/pvm/builder.go index a199650d1..bcecbcb02 100644 --- a/builder/parallels/pvm/builder.go +++ b/builder/parallels/pvm/builder.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" parallelscommon "github.com/hashicorp/packer/builder/parallels/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -15,19 +16,19 @@ import ( // Builder implements packer.Builder and builds the actual Parallels // images. type Builder struct { - config *Config + config Config runner multistep.Runner } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing @@ -41,7 +42,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) diff --git a/builder/parallels/pvm/config.go b/builder/parallels/pvm/config.go index 3fffe7b98..4f88ee193 100644 --- a/builder/parallels/pvm/config.go +++ b/builder/parallels/pvm/config.go @@ -49,8 +49,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, @@ -64,7 +63,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } if c.VMName == "" { @@ -102,8 +101,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Check for any errors. if errs != nil && len(errs.Errors) > 0 { - return nil, warnings, errs + return warnings, errs } - return c, warnings, nil + return warnings, nil } diff --git a/builder/parallels/pvm/config.hcl2spec.go b/builder/parallels/pvm/config.hcl2spec.go index 77d76625f..d15c6e839 100644 --- a/builder/parallels/pvm/config.hcl2spec.go +++ b/builder/parallels/pvm/config.hcl2spec.go @@ -53,8 +53,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -81,10 +81,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/parallels/pvm/config_test.go b/builder/parallels/pvm/config_test.go index 8c869c9fd..e7a2a5d59 100644 --- a/builder/parallels/pvm/config_test.go +++ b/builder/parallels/pvm/config_test.go @@ -53,13 +53,13 @@ func TestNewConfig_sourcePath(t *testing.T) { // Bad c := testConfig(t) delete(c, "source_path") - _, warns, errs := NewConfig(c) + warns, errs := (&Config{}).Prepare(c) testConfigErr(t, warns, errs) // Bad c = testConfig(t) c["source_path"] = "/i/dont/exist" - _, warns, errs = NewConfig(c) + warns, errs = (&Config{}).Prepare(c) testConfigErr(t, warns, errs) // Good @@ -68,7 +68,7 @@ func TestNewConfig_sourcePath(t *testing.T) { c = testConfig(t) c["source_path"] = tf.Name() - _, warns, errs = NewConfig(c) + warns, errs = (&Config{}).Prepare(c) testConfigOk(t, warns, errs) } @@ -76,7 +76,7 @@ func TestNewConfig_FloppyFiles(t *testing.T) { c := testConfig(t) floppies_path := "../../../common/test-fixtures/floppies" c["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} - _, _, err := NewConfig(c) + _, err := (&Config{}).Prepare(c) if err != nil { t.Fatalf("should not have error: %s", err) } @@ -85,7 +85,7 @@ func TestNewConfig_FloppyFiles(t *testing.T) { func TestNewConfig_InvalidFloppies(t *testing.T) { c := testConfig(t) c["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} - _, _, errs := NewConfig(c) + _, errs := (&Config{}).Prepare(c) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -96,18 +96,18 @@ func TestNewConfig_InvalidFloppies(t *testing.T) { } func TestNewConfig_shutdown_timeout(t *testing.T) { - c := testConfig(t) + cfg := testConfig(t) tf := getTempFile(t) defer os.Remove(tf.Name()) // Expect this to fail - c["source_path"] = tf.Name() - c["shutdown_timeout"] = "NaN" - _, warns, errs := NewConfig(c) + cfg["source_path"] = tf.Name() + cfg["shutdown_timeout"] = "NaN" + warns, errs := (&Config{}).Prepare(cfg) testConfigErr(t, warns, errs) // Passes when given a valid time duration - c["shutdown_timeout"] = "10s" - _, warns, errs = NewConfig(c) + cfg["shutdown_timeout"] = "10s" + warns, errs = (&Config{}).Prepare(cfg) testConfigOk(t, warns, errs) } diff --git a/builder/profitbricks/builder.go b/builder/profitbricks/builder.go index 3ab9d18fe..99666cd58 100644 --- a/builder/profitbricks/builder.go +++ b/builder/profitbricks/builder.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -13,24 +14,25 @@ import ( const BuilderId = "packer.profitbricks" type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("hook", hook) state.Put("ui", ui) steps := []multistep.Step{ diff --git a/builder/profitbricks/builder_test.go b/builder/profitbricks/builder_test.go index 98252b410..f16953a33 100644 --- a/builder/profitbricks/builder_test.go +++ b/builder/profitbricks/builder_test.go @@ -31,7 +31,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "api_key": []string{}, } - warns, err := b.Prepare(c) + _, warns, err := b.Prepare(c) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -47,7 +47,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { config := testConfig() config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/profitbricks/config.go b/builder/profitbricks/config.go index f3d366c60..275f7b5f2 100644 --- a/builder/profitbricks/config.go +++ b/builder/profitbricks/config.go @@ -34,8 +34,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - var c Config +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { var md mapstructure.Metadata err := config.Decode(&c, &config.DecodeOpts{ @@ -49,7 +48,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } var errs *packer.MultiError @@ -121,9 +120,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.PBUsername) - return &c, nil, nil + return nil, nil } diff --git a/builder/profitbricks/config.hcl2spec.go b/builder/profitbricks/config.hcl2spec.go index c0867c500..1dc8dc7c1 100644 --- a/builder/profitbricks/config.hcl2spec.go +++ b/builder/profitbricks/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -73,10 +73,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/profitbricks/step_create_server.go b/builder/profitbricks/step_create_server.go index 3d2e44d70..b7320d6d0 100644 --- a/builder/profitbricks/step_create_server.go +++ b/builder/profitbricks/step_create_server.go @@ -147,6 +147,9 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu state.Put("volume_id", server.Entities.Volumes.Items[0].Id) server = profitbricks.GetServer(datacenter.Id, server.Id) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", server.Id) state.Put("server_ip", server.Entities.Nics.Items[0].Properties.Ips[0]) diff --git a/builder/proxmox/builder.go b/builder/proxmox/builder.go index 083cfeb3d..e29645114 100644 --- a/builder/proxmox/builder.go +++ b/builder/proxmox/builder.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -27,13 +28,14 @@ var _ packer.Builder = &Builder{} var pluginVersion = "1.0.0" -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - config, warnings, errs := NewConfig(raws...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) if errs != nil { - return warnings, errs + return nil, warnings, errs } - b.config = *config - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/proxmox/config.go b/builder/proxmox/config.go index b3c08ecaf..00f082d07 100644 --- a/builder/proxmox/config.go +++ b/builder/proxmox/config.go @@ -71,8 +71,7 @@ type diskConfig struct { DiskFormat string `mapstructure:"format"` } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { // Agent defaults to true c.Agent = true @@ -88,7 +87,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } var errs *packer.MultiError @@ -207,11 +206,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.Password) - return c, nil, nil + return nil, nil } func contains(haystack []string, needle string) bool { diff --git a/builder/proxmox/config.hcl2spec.go b/builder/proxmox/config.hcl2spec.go index 797bf117b..e9c208c12 100644 --- a/builder/proxmox/config.hcl2spec.go +++ b/builder/proxmox/config.hcl2spec.go @@ -53,8 +53,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -89,10 +89,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -162,8 +165,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "cpu_type": &hcldec.AttrSpec{Name: "cpu_type", Type: cty.String, Required: false}, "sockets": &hcldec.AttrSpec{Name: "sockets", Type: cty.Number, Required: false}, "os": &hcldec.AttrSpec{Name: "os", Type: cty.String, Required: false}, - "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: &hcldec.BlockSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatnicConfig)(nil).HCL2Spec())}}, - "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: &hcldec.BlockSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*FlatdiskConfig)(nil).HCL2Spec())}}, + "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatnicConfig)(nil).HCL2Spec())}, + "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*FlatdiskConfig)(nil).HCL2Spec())}, "iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false}, "qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false}, "scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false}, @@ -188,10 +191,13 @@ type FlatdiskConfig struct { // FlatMapstructure returns a new FlatdiskConfig. // FlatdiskConfig is an auto-generated flat version of diskConfig. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*diskConfig) FlatMapstructure() interface{} { return new(FlatdiskConfig) } +func (*diskConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatdiskConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatdiskConfig. -// This spec is used by HCL to read the fields of FlatdiskConfig. +// HCL2Spec returns the hcl spec of a diskConfig. +// This spec is used by HCL to read the fields of diskConfig. +// The decoded values from this spec will then be applied to a FlatdiskConfig. func (*FlatdiskConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, @@ -216,10 +222,13 @@ type FlatnicConfig struct { // FlatMapstructure returns a new FlatnicConfig. // FlatnicConfig is an auto-generated flat version of nicConfig. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*nicConfig) FlatMapstructure() interface{} { return new(FlatnicConfig) } +func (*nicConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatnicConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatnicConfig. -// This spec is used by HCL to read the fields of FlatnicConfig. +// HCL2Spec returns the hcl spec of a nicConfig. +// This spec is used by HCL to read the fields of nicConfig. +// The decoded values from this spec will then be applied to a FlatnicConfig. func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "model": &hcldec.AttrSpec{Name: "model", Type: cty.String, Required: false}, diff --git a/builder/proxmox/config_test.go b/builder/proxmox/config_test.go index 84dd13c6d..f925a1adb 100644 --- a/builder/proxmox/config_test.go +++ b/builder/proxmox/config_test.go @@ -9,7 +9,8 @@ import ( ) func TestRequiredParameters(t *testing.T) { - _, _, err := NewConfig(make(map[string]interface{})) + var c Config + _, err := c.Prepare(make(map[string]interface{})) if err == nil { t.Fatal("Expected empty configuration to fail") } @@ -81,7 +82,7 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { } b := &Builder{} - warn, err := b.Prepare(tpl.Builders["proxmox"].Config) + _, warn, err := b.Prepare(tpl.Builders["proxmox"].Config) if err != nil { t.Fatal(err, warn) } @@ -149,7 +150,7 @@ func TestAgentSetToFalse(t *testing.T) { } b := &Builder{} - warn, err := b.Prepare(tpl.Builders["proxmox"].Config) + _, warn, err := b.Prepare(tpl.Builders["proxmox"].Config) if err != nil { t.Fatal(err, warn) } diff --git a/builder/proxmox/step_start_vm.go b/builder/proxmox/step_start_vm.go index 8020cf9db..378b689ea 100644 --- a/builder/proxmox/step_start_vm.go +++ b/builder/proxmox/step_start_vm.go @@ -76,6 +76,9 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist // Store the vm id for later state.Put("vmRef", vmRef) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", vmRef) ui.Say("Starting VM") _, err = client.StartVm(vmRef) diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 6d85d2654..ec510fdd2 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/common/shutdowncommand" @@ -336,7 +337,9 @@ type Config struct { ctx interpolate.Context } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -348,7 +351,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } var errs *packer.MultiError @@ -569,10 +572,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warnings, errs + return nil, warnings, errs } - return warnings, nil + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -636,7 +639,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack steps = append(steps, new(stepConfigureVNC), steprun, - new(stepConfigureQMP), + &stepConfigureQMP{ + VNCUsePassword: b.config.VNCUsePassword, + QMPSocketPath: b.config.QMPSocketPath, + }, &stepTypeBootCommand{}, ) diff --git a/builder/qemu/builder.hcl2spec.go b/builder/qemu/builder.hcl2spec.go index d82b4e464..5e08510d7 100644 --- a/builder/qemu/builder.hcl2spec.go +++ b/builder/qemu/builder.hcl2spec.go @@ -63,8 +63,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -115,10 +115,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/qemu/builder_test.go b/builder/qemu/builder_test.go index 4616ed3e2..4eea7174e 100644 --- a/builder/qemu/builder_test.go +++ b/builder/qemu/builder_test.go @@ -62,7 +62,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -101,7 +101,7 @@ func TestBuilderPrepare_VNCBindAddress(t *testing.T) { // Test a default boot_wait delete(config, "vnc_bind_address") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -122,7 +122,7 @@ func TestBuilderPrepare_DiskCompaction(t *testing.T) { config["skip_compaction"] = false config["disk_compression"] = true config["format"] = "img" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -141,7 +141,7 @@ func TestBuilderPrepare_DiskCompaction(t *testing.T) { config["disk_compression"] = true config["format"] = "qcow2" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -178,7 +178,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { delete(config, "disk_size") config["disk_size"] = tc.InputSize - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -198,7 +198,7 @@ func TestBuilderPrepare_AdditionalDiskSize(t *testing.T) { config["disk_additional_size"] = []string{"1M"} config["disk_image"] = true - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -209,7 +209,7 @@ func TestBuilderPrepare_AdditionalDiskSize(t *testing.T) { delete(config, "disk_image") config["disk_additional_size"] = []string{"1M"} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -228,7 +228,7 @@ func TestBuilderPrepare_Format(t *testing.T) { // Bad config["format"] = "illegal value" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -239,7 +239,7 @@ func TestBuilderPrepare_Format(t *testing.T) { // Good config["format"] = "qcow2" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -250,7 +250,7 @@ func TestBuilderPrepare_Format(t *testing.T) { // Good config["format"] = "raw" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -269,7 +269,7 @@ func TestBuilderPrepare_UseBackingFile(t *testing.T) { config["disk_image"] = false config["format"] = "qcow2" b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -281,7 +281,7 @@ func TestBuilderPrepare_UseBackingFile(t *testing.T) { config["disk_image"] = true config["format"] = "raw" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -293,7 +293,7 @@ func TestBuilderPrepare_UseBackingFile(t *testing.T) { config["disk_image"] = true config["format"] = "qcow2" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -307,7 +307,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { config := testConfig() delete(config, "floppy_files") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -322,7 +322,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { floppies_path := "../../common/test-fixtures/floppies" config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -341,7 +341,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) { config := testConfig() config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} b = Builder{} - _, errs := b.Prepare(config) + _, _, errs := b.Prepare(config) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -357,7 +357,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -379,7 +379,7 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { config["output_directory"] = dir b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -390,7 +390,7 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { // Test with a good one config["output_directory"] = "i-hope-i-dont-exist" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -405,7 +405,7 @@ func TestBuilderPrepare_ShutdownTimeout(t *testing.T) { // Test with a bad value config["shutdown_timeout"] = "this is not good" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -416,7 +416,7 @@ func TestBuilderPrepare_ShutdownTimeout(t *testing.T) { // Test with a good one config["shutdown_timeout"] = "5s" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -433,7 +433,7 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) { config["ssh_host_port_min"] = 1000 config["ssh_host_port_max"] = 500 b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -444,7 +444,7 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) { // Bad config["ssh_host_port_min"] = -500 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -456,7 +456,7 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) { config["ssh_host_port_min"] = 500 config["ssh_host_port_max"] = 1000 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -471,7 +471,7 @@ func TestBuilderPrepare_SSHPrivateKey(t *testing.T) { config["ssh_private_key_file"] = "" b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -481,7 +481,7 @@ func TestBuilderPrepare_SSHPrivateKey(t *testing.T) { config["ssh_private_key_file"] = "/i/dont/exist" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -503,7 +503,7 @@ func TestBuilderPrepare_SSHPrivateKey(t *testing.T) { config["ssh_private_key_file"] = tf.Name() b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -517,7 +517,7 @@ func TestBuilderPrepare_SSHPrivateKey(t *testing.T) { tf.Write([]byte(testPem)) config["ssh_private_key_file"] = tf.Name() b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -532,7 +532,7 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { // Test a default boot_wait delete(config, "ssh_wait_timeout") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -543,7 +543,7 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { // Test with a bad value config["ssh_wait_timeout"] = "this is not good" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -554,7 +554,7 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { // Test with a good one config["ssh_wait_timeout"] = "5s" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -569,7 +569,7 @@ func TestBuilderPrepare_QemuArgs(t *testing.T) { // Test with empty delete(config, "qemuargs") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -587,7 +587,7 @@ func TestBuilderPrepare_QemuArgs(t *testing.T) { } b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -611,7 +611,7 @@ func TestBuilderPrepare_VNCPassword(t *testing.T) { config["vnc_use_password"] = true config["output_directory"] = "not-a-real-directory" b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/qemu/step_configure_qmp.go b/builder/qemu/step_configure_qmp.go index fa988ad0b..a9f177893 100644 --- a/builder/qemu/step_configure_qmp.go +++ b/builder/qemu/step_configure_qmp.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "os" "time" "github.com/digitalocean/go-qemu/qmp" @@ -19,27 +20,26 @@ import ( // // Produces: type stepConfigureQMP struct { - monitor *qmp.SocketMonitor + monitor *qmp.SocketMonitor + VNCUsePassword bool + QMPSocketPath string } func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) - if !config.VNCUsePassword { + if !s.VNCUsePassword { return multistep.ActionContinue } - msg := fmt.Sprintf("QMP socket at: %s", config.QMPSocketPath) - ui.Say(msg) - log.Print(msg) + ui.Say(fmt.Sprintf("QMP socket at: %s", s.QMPSocketPath)) // Only initialize and open QMP when we have a use for it. // Open QMP socket var err error var cmd []byte var result []byte - s.monitor, err = qmp.NewSocketMonitor("unix", config.QMPSocketPath, 2*time.Second) + s.monitor, err = qmp.NewSocketMonitor("unix", s.QMPSocketPath, 2*time.Second) if err != nil { err := fmt.Errorf("Error opening QMP socket: %s", err) state.Put("error", err) @@ -69,8 +69,8 @@ func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) mu ui.Error(err.Error()) return multistep.ActionHalt } - msg = fmt.Sprintf("QMP Command: %s\nResult: %s", cmd, result) - log.Printf(msg) + + log.Printf("QMP Command: %s\nResult: %s", cmd, result) // Put QMP monitor in statebag in case there is a use in a following step // Uncomment for future case as it is unused for now @@ -85,5 +85,9 @@ func (s *stepConfigureQMP) Cleanup(multistep.StateBag) { if err != nil { log.Printf("failed to disconnect QMP: %v", err) } + // Delete file associated with qmp socket. + if err := os.Remove(s.QMPSocketPath); err != nil { + log.Printf("Failed to delete the qmp socket file: %s", err) + } } } diff --git a/builder/scaleway/builder.go b/builder/scaleway/builder.go index 47ddc8376..ac5f47561 100644 --- a/builder/scaleway/builder.go +++ b/builder/scaleway/builder.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -19,18 +20,19 @@ import ( const BuilderId = "hashicorp.scaleway" type Builder struct { - config *Config + config Config runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return nil, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -42,7 +44,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("client", client) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/scaleway/builder_test.go b/builder/scaleway/builder_test.go index a1a0fccc3..cfb192614 100644 --- a/builder/scaleway/builder_test.go +++ b/builder/scaleway/builder_test.go @@ -32,7 +32,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "api_token": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -46,7 +46,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { config := testConfig() config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -60,7 +60,7 @@ func TestBuilderPrepare_Region(t *testing.T) { config := testConfig() delete(config, "region") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -72,7 +72,7 @@ func TestBuilderPrepare_Region(t *testing.T) { config["region"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -90,7 +90,7 @@ func TestBuilderPrepare_CommercialType(t *testing.T) { config := testConfig() delete(config, "commercial_type") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -102,7 +102,7 @@ func TestBuilderPrepare_CommercialType(t *testing.T) { config["commercial_type"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -120,7 +120,7 @@ func TestBuilderPrepare_Image(t *testing.T) { config := testConfig() delete(config, "image") - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -132,7 +132,7 @@ func TestBuilderPrepare_Image(t *testing.T) { config["image"] = expected b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -149,7 +149,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) { var b Builder config := testConfig() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -163,7 +163,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) { config["snapshot_name"] = "foobarbaz" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -173,7 +173,7 @@ func TestBuilderPrepare_SnapshotName(t *testing.T) { config["snapshot_name"] = "{{timestamp}}" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -192,7 +192,7 @@ func TestBuilderPrepare_ServerName(t *testing.T) { var b Builder config := testConfig() - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -206,7 +206,7 @@ func TestBuilderPrepare_ServerName(t *testing.T) { config["server_name"] = "foobar" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -216,7 +216,7 @@ func TestBuilderPrepare_ServerName(t *testing.T) { config["server_name"] = "foobar-{{timestamp}}" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -226,7 +226,7 @@ func TestBuilderPrepare_ServerName(t *testing.T) { config["server_name"] = "foobar-{{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/scaleway/config.go b/builder/scaleway/config.go index 6fd862974..8a8e17adc 100644 --- a/builder/scaleway/config.go +++ b/builder/scaleway/config.go @@ -71,8 +71,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { var md mapstructure.Metadata err := config.Decode(c, &config.DecodeOpts{ @@ -86,7 +85,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } c.UserAgent = useragent.String() @@ -161,9 +160,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } packer.LogSecretFilter.Set(c.Token) - return c, nil, nil + return nil, nil } diff --git a/builder/scaleway/config.hcl2spec.go b/builder/scaleway/config.hcl2spec.go index 7d91dba08..89e5d85d5 100644 --- a/builder/scaleway/config.hcl2spec.go +++ b/builder/scaleway/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -73,10 +73,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/scaleway/step_create_server.go b/builder/scaleway/step_create_server.go index 9cfee4207..06a72d95a 100644 --- a/builder/scaleway/step_create_server.go +++ b/builder/scaleway/step_create_server.go @@ -60,6 +60,9 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu s.serverID = server state.Put("server_id", server) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.serverID) return multistep.ActionContinue } diff --git a/builder/tencentcloud/cvm/builder.go b/builder/tencentcloud/cvm/builder.go index 8934d0649..d13a09828 100644 --- a/builder/tencentcloud/cvm/builder.go +++ b/builder/tencentcloud/cvm/builder.go @@ -5,8 +5,8 @@ package cvm import ( "context" "fmt" - "log" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" @@ -31,7 +31,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -43,7 +45,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, raws...) b.config.ctx.EnableEnv = true if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors @@ -52,13 +54,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.TencentCloudImageConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.TencentCloudRunConfig.Prepare(&b.config.ctx)...) if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } packer.LogSecretFilter.Set(b.config.SecretId, b.config.SecretKey) - log.Printf("[DEBUG]packer config: %v", b.config) - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/tencentcloud/cvm/builder.hcl2spec.go b/builder/tencentcloud/cvm/builder.hcl2spec.go index 4b3c3914b..97ef30c46 100644 --- a/builder/tencentcloud/cvm/builder.hcl2spec.go +++ b/builder/tencentcloud/cvm/builder.hcl2spec.go @@ -81,8 +81,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -97,10 +97,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -129,7 +132,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false}, "disk_type": &hcldec.AttrSpec{Name: "disk_type", Type: cty.String, Required: false}, "disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false}, - "data_disks": &hcldec.BlockListSpec{TypeName: "data_disks", Nested: &hcldec.BlockSpec{TypeName: "data_disks", Nested: hcldec.ObjectSpec((*FlattencentCloudDataDisk)(nil).HCL2Spec())}}, + "data_disks": &hcldec.BlockListSpec{TypeName: "data_disks", Nested: hcldec.ObjectSpec((*FlattencentCloudDataDisk)(nil).HCL2Spec())}, "vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false}, "vpc_name": &hcldec.AttrSpec{Name: "vpc_name", Type: cty.String, Required: false}, "vpc_ip": &hcldec.AttrSpec{Name: "vpc_ip", Type: cty.String, Required: false}, diff --git a/builder/tencentcloud/cvm/run_config.hcl2spec.go b/builder/tencentcloud/cvm/run_config.hcl2spec.go index ea01fe69e..8a2dea24e 100644 --- a/builder/tencentcloud/cvm/run_config.hcl2spec.go +++ b/builder/tencentcloud/cvm/run_config.hcl2spec.go @@ -17,10 +17,13 @@ type FlattencentCloudDataDisk struct { // FlatMapstructure returns a new FlattencentCloudDataDisk. // FlattencentCloudDataDisk is an auto-generated flat version of tencentCloudDataDisk. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*tencentCloudDataDisk) FlatMapstructure() interface{} { return new(FlattencentCloudDataDisk) } +func (*tencentCloudDataDisk) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlattencentCloudDataDisk) +} -// HCL2Spec returns the hcldec.Spec of a FlattencentCloudDataDisk. -// This spec is used by HCL to read the fields of FlattencentCloudDataDisk. +// HCL2Spec returns the hcl spec of a tencentCloudDataDisk. +// This spec is used by HCL to read the fields of tencentCloudDataDisk. +// The decoded values from this spec will then be applied to a FlattencentCloudDataDisk. func (*FlattencentCloudDataDisk) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "disk_type": &hcldec.AttrSpec{Name: "disk_type", Type: cty.String, Required: false}, diff --git a/builder/tencentcloud/cvm/step_run_instance.go b/builder/tencentcloud/cvm/step_run_instance.go index b43e1fbf6..72f3f7b90 100644 --- a/builder/tencentcloud/cvm/step_run_instance.go +++ b/builder/tencentcloud/cvm/step_run_instance.go @@ -175,6 +175,9 @@ func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) mul } state.Put("instance", describeResp.Response.InstanceSet[0]) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.instanceId) Message(state, s.instanceId, "Instance created") return multistep.ActionContinue diff --git a/builder/triton/builder.go b/builder/triton/builder.go index ba6e2baa8..22759406b 100644 --- a/builder/triton/builder.go +++ b/builder/triton/builder.go @@ -4,6 +4,7 @@ import ( "context" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" @@ -20,7 +21,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { errs := &multierror.Error{} err := config.Decode(&b.config, &config.DecodeOpts{ @@ -42,7 +45,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.Comm.SSHAgentAuth = true } - return nil, errs.ErrorOrNil() + return nil, nil, errs.ErrorOrNil() } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/triton/config.hcl2spec.go b/builder/triton/config.hcl2spec.go index b6ee44651..4f1b5a871 100644 --- a/builder/triton/config.hcl2spec.go +++ b/builder/triton/config.hcl2spec.go @@ -67,8 +67,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -82,10 +82,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/triton/source_machine_config.hcl2spec.go b/builder/triton/source_machine_config.hcl2spec.go index 7dfed2c69..a7ac52f84 100644 --- a/builder/triton/source_machine_config.hcl2spec.go +++ b/builder/triton/source_machine_config.hcl2spec.go @@ -22,10 +22,13 @@ type FlatMachineImageFilter struct { // FlatMapstructure returns a new FlatMachineImageFilter. // FlatMachineImageFilter is an auto-generated flat version of MachineImageFilter. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*MachineImageFilter) FlatMapstructure() interface{} { return new(FlatMachineImageFilter) } +func (*MachineImageFilter) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatMachineImageFilter) +} -// HCL2Spec returns the hcldec.Spec of a FlatMachineImageFilter. -// This spec is used by HCL to read the fields of FlatMachineImageFilter. +// HCL2Spec returns the hcl spec of a MachineImageFilter. +// This spec is used by HCL to read the fields of MachineImageFilter. +// The decoded values from this spec will then be applied to a FlatMachineImageFilter. func (*FlatMachineImageFilter) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "most_recent": &hcldec.AttrSpec{Name: "most_recent", Type: cty.Bool, Required: false}, diff --git a/builder/triton/step_create_source_machine.go b/builder/triton/step_create_source_machine.go index 1aad77508..24080ba23 100644 --- a/builder/triton/step_create_source_machine.go +++ b/builder/triton/step_create_source_machine.go @@ -43,6 +43,9 @@ func (s *StepCreateSourceMachine) Run(ctx context.Context, state multistep.State } state.Put("machine", machineId) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", machineId) return multistep.ActionContinue } diff --git a/builder/ucloud/common/artifact.go b/builder/ucloud/common/artifact.go index 961bd9399..4daa8f68a 100644 --- a/builder/ucloud/common/artifact.go +++ b/builder/ucloud/common/artifact.go @@ -2,11 +2,12 @@ package common import ( "fmt" - "github.com/hashicorp/packer/packer" - "github.com/ucloud/ucloud-sdk-go/ucloud" "log" "sort" "strings" + + "github.com/hashicorp/packer/packer" + "github.com/ucloud/ucloud-sdk-go/ucloud" ) type Artifact struct { diff --git a/builder/ucloud/common/artifact_test.go b/builder/ucloud/common/artifact_test.go index d3f0f12d7..8154766e3 100644 --- a/builder/ucloud/common/artifact_test.go +++ b/builder/ucloud/common/artifact_test.go @@ -1,9 +1,10 @@ package common import ( - "github.com/hashicorp/packer/packer" "reflect" "testing" + + "github.com/hashicorp/packer/packer" ) func TestArtifact_Impl(t *testing.T) { diff --git a/builder/ucloud/common/client.go b/builder/ucloud/common/client.go index 0e1cf0434..58452ca18 100644 --- a/builder/ucloud/common/client.go +++ b/builder/ucloud/common/client.go @@ -7,7 +7,7 @@ import ( "github.com/ucloud/ucloud-sdk-go/services/unet" "github.com/ucloud/ucloud-sdk-go/services/vpc" "github.com/ucloud/ucloud-sdk-go/ucloud" - "github.com/ucloud/ucloud-sdk-go/ucloud/error" + uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error" ) type UCloudClient struct { diff --git a/builder/ucloud/common/image_config.hcl2spec.go b/builder/ucloud/common/image_config.hcl2spec.go index 9839cad4c..a26a1372a 100644 --- a/builder/ucloud/common/image_config.hcl2spec.go +++ b/builder/ucloud/common/image_config.hcl2spec.go @@ -18,10 +18,13 @@ type FlatImageDestination struct { // FlatMapstructure returns a new FlatImageDestination. // FlatImageDestination is an auto-generated flat version of ImageDestination. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*ImageDestination) FlatMapstructure() interface{} { return new(FlatImageDestination) } +func (*ImageDestination) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatImageDestination) +} -// HCL2Spec returns the hcldec.Spec of a FlatImageDestination. -// This spec is used by HCL to read the fields of FlatImageDestination. +// HCL2Spec returns the hcl spec of a ImageDestination. +// This spec is used by HCL to read the fields of ImageDestination. +// The decoded values from this spec will then be applied to a FlatImageDestination. func (*FlatImageDestination) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false}, diff --git a/builder/ucloud/common/utils.go b/builder/ucloud/common/utils.go index 1f58444d5..b922608d1 100644 --- a/builder/ucloud/common/utils.go +++ b/builder/ucloud/common/utils.go @@ -2,10 +2,11 @@ package common import ( "fmt" + "strings" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/ucloud/ucloud-sdk-go/services/uhost" - "strings" ) func CheckStringIn(val string, available []string) error { diff --git a/builder/ucloud/uhost/builder.go b/builder/ucloud/uhost/builder.go index a9903bf4a..676114a6d 100644 --- a/builder/ucloud/uhost/builder.go +++ b/builder/ucloud/uhost/builder.go @@ -7,6 +7,7 @@ package uhost import ( "context" + "github.com/hashicorp/hcl/v2/hcldec" ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -33,7 +34,9 @@ type Builder struct { runner multistep.Runner } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -45,7 +48,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, raws...) b.config.ctx.EnableEnv = true if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors @@ -55,11 +58,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) if errs != nil && len(errs.Errors) > 0 { - return nil, errs + return nil, nil, errs } packer.LogSecretFilter.Set(b.config.PublicKey, b.config.PrivateKey) - return nil, nil + return nil, nil, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/ucloud/uhost/builder.hcl2spec.go b/builder/ucloud/uhost/builder.hcl2spec.go index e35dee79e..262a64271 100644 --- a/builder/ucloud/uhost/builder.hcl2spec.go +++ b/builder/ucloud/uhost/builder.hcl2spec.go @@ -64,8 +64,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -80,10 +80,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -100,7 +103,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "base_url": &hcldec.AttrSpec{Name: "base_url", Type: cty.String, Required: false}, "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, "image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false}, - "image_copy_to_mappings": &hcldec.BlockListSpec{TypeName: "image_copy_to_mappings", Nested: &hcldec.BlockSpec{TypeName: "image_copy_to_mappings", Nested: hcldec.ObjectSpec((*common.FlatImageDestination)(nil).HCL2Spec())}}, + "image_copy_to_mappings": &hcldec.BlockListSpec{TypeName: "image_copy_to_mappings", Nested: hcldec.ObjectSpec((*common.FlatImageDestination)(nil).HCL2Spec())}, "wait_image_ready_timeout": &hcldec.AttrSpec{Name: "wait_image_ready_timeout", Type: cty.Number, Required: false}, "availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false}, "source_image_id": &hcldec.AttrSpec{Name: "source_image_id", Type: cty.String, Required: false}, diff --git a/builder/ucloud/uhost/builder_acc_test.go b/builder/ucloud/uhost/builder_acc_test.go index 0d5a82d6d..091ab8ab6 100644 --- a/builder/ucloud/uhost/builder_acc_test.go +++ b/builder/ucloud/uhost/builder_acc_test.go @@ -2,11 +2,12 @@ package uhost import ( "fmt" + "os" + "testing" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/packer" "github.com/stretchr/testify/assert" - "os" - "testing" builderT "github.com/hashicorp/packer/helper/builder/testing" ) diff --git a/builder/ucloud/uhost/builder_test.go b/builder/ucloud/uhost/builder_test.go index dca944bc5..cbc8d1d4e 100644 --- a/builder/ucloud/uhost/builder_test.go +++ b/builder/ucloud/uhost/builder_test.go @@ -1,10 +1,11 @@ package uhost import ( - ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" - "github.com/hashicorp/packer/packer" "reflect" "testing" + + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" + "github.com/hashicorp/packer/packer" ) func testBuilderConfig() map[string]interface{} { @@ -35,7 +36,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) { "public_key": []string{}, } - warnings, err := b.Prepare(c) + _, warnings, err := b.Prepare(c) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -50,7 +51,7 @@ func TestBuilderPrepare_ImageName(t *testing.T) { // Test good config["image_name"] = "foo" - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -61,7 +62,7 @@ func TestBuilderPrepare_ImageName(t *testing.T) { // Test bad config["image_name"] = "foo {{" b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -72,7 +73,7 @@ func TestBuilderPrepare_ImageName(t *testing.T) { // Test bad delete(config, "image_name") b = Builder{} - warnings, err = b.Prepare(config) + _, warnings, err = b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -87,7 +88,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } @@ -113,7 +114,7 @@ func TestBuilderPrepare_ImageDestinations(t *testing.T) { "description": "bar", }, } - warnings, err := b.Prepare(config) + _, warnings, err := b.Prepare(config) if len(warnings) > 0 { t.Fatalf("bad: %#v", warnings) } diff --git a/builder/ucloud/uhost/step_check_source_image.go b/builder/ucloud/uhost/step_check_source_image.go index fd77d7eee..66260df79 100644 --- a/builder/ucloud/uhost/step_check_source_image.go +++ b/builder/ucloud/uhost/step_check_source_image.go @@ -3,6 +3,7 @@ package uhost import ( "context" "fmt" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" diff --git a/builder/ucloud/uhost/step_config_security_group.go b/builder/ucloud/uhost/step_config_security_group.go index 66c4cd27c..f561bd003 100644 --- a/builder/ucloud/uhost/step_config_security_group.go +++ b/builder/ucloud/uhost/step_config_security_group.go @@ -3,6 +3,7 @@ package uhost import ( "context" "fmt" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" diff --git a/builder/ucloud/uhost/step_config_subnet.go b/builder/ucloud/uhost/step_config_subnet.go index b6bb4bd53..affb43ccd 100644 --- a/builder/ucloud/uhost/step_config_subnet.go +++ b/builder/ucloud/uhost/step_config_subnet.go @@ -3,6 +3,7 @@ package uhost import ( "context" "fmt" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" diff --git a/builder/ucloud/uhost/step_config_vpc.go b/builder/ucloud/uhost/step_config_vpc.go index e8e2bcec4..b6792318c 100644 --- a/builder/ucloud/uhost/step_config_vpc.go +++ b/builder/ucloud/uhost/step_config_vpc.go @@ -3,6 +3,7 @@ package uhost import ( "context" "fmt" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/helper/multistep" diff --git a/builder/ucloud/uhost/step_copy_image.go b/builder/ucloud/uhost/step_copy_image.go index 690b9deca..60061a33c 100644 --- a/builder/ucloud/uhost/step_copy_image.go +++ b/builder/ucloud/uhost/step_copy_image.go @@ -3,11 +3,12 @@ package uhost import ( "context" "fmt" - ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" - "github.com/hashicorp/packer/common/retry" "strings" "time" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" + "github.com/hashicorp/packer/common/retry" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/ucloud/ucloud-sdk-go/ucloud" diff --git a/builder/ucloud/uhost/step_create_image.go b/builder/ucloud/uhost/step_create_image.go index 235cdb7ff..e0004a1ce 100644 --- a/builder/ucloud/uhost/step_create_image.go +++ b/builder/ucloud/uhost/step_create_image.go @@ -3,9 +3,10 @@ package uhost import ( "context" "fmt" + "time" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/common/retry" - "time" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" diff --git a/builder/ucloud/uhost/step_create_instance.go b/builder/ucloud/uhost/step_create_instance.go index 3c60daef8..f80c14690 100644 --- a/builder/ucloud/uhost/step_create_instance.go +++ b/builder/ucloud/uhost/step_create_instance.go @@ -3,15 +3,16 @@ package uhost import ( "context" "fmt" + "math/rand" + "strings" + "time" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/ucloud/ucloud-sdk-go/services/uhost" "github.com/ucloud/ucloud-sdk-go/ucloud" - "math/rand" - "strings" - "time" ) type stepCreateInstance struct { @@ -77,6 +78,9 @@ func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) s.instanceId = instanceId state.Put("instance", instance) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", instance) if instance.BootDiskState != ucloudcommon.BootDiskStateNormal { ui.Say("Waiting for boot disk of instance initialized") diff --git a/builder/ucloud/uhost/step_pre_validate.go b/builder/ucloud/uhost/step_pre_validate.go index 843616c10..becb80b29 100644 --- a/builder/ucloud/uhost/step_pre_validate.go +++ b/builder/ucloud/uhost/step_pre_validate.go @@ -2,6 +2,7 @@ package uhost import ( "context" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" diff --git a/builder/ucloud/uhost/step_stop_instance.go b/builder/ucloud/uhost/step_stop_instance.go index 463428d1a..d9376e15e 100644 --- a/builder/ucloud/uhost/step_stop_instance.go +++ b/builder/ucloud/uhost/step_stop_instance.go @@ -3,9 +3,10 @@ package uhost import ( "context" "fmt" + "time" + ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/common/retry" - "time" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" diff --git a/builder/vagrant/builder.go b/builder/vagrant/builder.go index 5fb189e13..198cc753d 100644 --- a/builder/vagrant/builder.go +++ b/builder/vagrant/builder.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/communicator" @@ -24,7 +25,7 @@ import ( // Builder implements packer.Builder and builds the actual VirtualBox // images. type Builder struct { - config *Config + config Config runner multistep.Runner } @@ -131,9 +132,9 @@ type Config struct { ctx interpolate.Context } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - b.config = new(Config) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -144,7 +145,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors and warnings @@ -215,10 +216,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warnings, errs + return nil, warnings, errs } - return warnings, nil + return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing @@ -236,7 +237,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) diff --git a/builder/vagrant/builder.hcl2spec.go b/builder/vagrant/builder.hcl2spec.go index 1a9dece69..70aba5f49 100644 --- a/builder/vagrant/builder.hcl2spec.go +++ b/builder/vagrant/builder.hcl2spec.go @@ -62,8 +62,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -100,10 +100,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/vagrant/builder_test.go b/builder/vagrant/builder_test.go index 65c2e5e9f..704c23f2a 100644 --- a/builder/vagrant/builder_test.go +++ b/builder/vagrant/builder_test.go @@ -15,7 +15,6 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { } func TestBuilder_Prepare_ValidateSource(t *testing.T) { - b := &Builder{} type testCase struct { config map[string]interface{} errExpected bool @@ -83,7 +82,7 @@ func TestBuilder_Prepare_ValidateSource(t *testing.T) { } for _, tc := range cases { - _, err := b.Prepare(tc.config) + _, _, err := (&Builder{}).Prepare(tc.config) if (err != nil) != tc.errExpected { t.Fatalf("Unexpected behavior from test case %#v; %s.", tc.config, tc.reason) } diff --git a/builder/vagrant/step_up.go b/builder/vagrant/step_up.go index 6ca931f3a..e78834f36 100644 --- a/builder/vagrant/step_up.go +++ b/builder/vagrant/step_up.go @@ -35,7 +35,11 @@ func (s *StepUp) Run(ctx context.Context, state multistep.StateBag) multistep.St ui.Say("Calling Vagrant Up (this can take some time)...") - _, _, err := driver.Up(s.generateArgs()) + args := s.generateArgs() + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", args[0]) + _, _, err := driver.Up(args) if err != nil { state.Put("error", err) diff --git a/builder/virtualbox/common/driver_4_2.go b/builder/virtualbox/common/driver_4_2.go index 86edd9174..338aee4d1 100644 --- a/builder/virtualbox/common/driver_4_2.go +++ b/builder/virtualbox/common/driver_4_2.go @@ -76,14 +76,7 @@ func (d *VBox42Driver) CreateSCSIController(vmName string, name string) error { } func (d *VBox42Driver) Delete(name string) error { - ctx := context.TODO() - return retry.Config{ - Tries: 5, - RetryDelay: (&retry.Backoff{InitialBackoff: 1 * time.Second, MaxBackoff: 1 * time.Second, Multiplier: 2}).Linear, - }.Run(ctx, func(ctx context.Context) error { - err := d.VBoxManage("unregistervm", name, "--delete") - return err - }) + return d.VBoxManage("unregistervm", name, "--delete") } func (d *VBox42Driver) Iso() (string, error) { @@ -165,9 +158,6 @@ func (d *VBox42Driver) Stop(name string) error { return err } - // We sleep here for a little bit to let the session "unlock" - time.Sleep(2 * time.Second) - return nil } @@ -189,7 +179,18 @@ func (d *VBox42Driver) SuppressMessages() error { } func (d *VBox42Driver) VBoxManage(args ...string) error { - _, err := d.VBoxManageWithOutput(args...) + ctx := context.TODO() + err := retry.Config{ + Tries: 5, + ShouldRetry: func(err error) bool { + return strings.Contains(err.Error(), "VBOX_E_INVALID_OBJECT_STATE") + }, + RetryDelay: func() time.Duration { return 1 * time.Minute }, + }.Run(ctx, func(ctx context.Context) error { + _, err := d.VBoxManageWithOutput(args...) + return err + }) + return err } diff --git a/builder/virtualbox/common/shutdown_config.go b/builder/virtualbox/common/shutdown_config.go index 0a626c9c7..48ad9c61d 100644 --- a/builder/virtualbox/common/shutdown_config.go +++ b/builder/virtualbox/common/shutdown_config.go @@ -41,5 +41,9 @@ func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { c.ShutdownTimeout = 5 * time.Minute } + if c.PostShutdownDelay == 0 { + c.PostShutdownDelay = 2 * time.Second + } + return nil } diff --git a/builder/virtualbox/common/shutdown_config_test.go b/builder/virtualbox/common/shutdown_config_test.go index e5afd6f0b..3e177fd5f 100644 --- a/builder/virtualbox/common/shutdown_config_test.go +++ b/builder/virtualbox/common/shutdown_config_test.go @@ -49,8 +49,8 @@ func TestShutdownConfigPrepare_PostShutdownDelay(t *testing.T) { if len(errs) > 0 { t.Fatalf("err: %#v", errs) } - if c.PostShutdownDelay.Nanoseconds() != 0 { - t.Fatalf("bad: %s", c.PostShutdownDelay) + if c.PostShutdownDelay != 2*time.Second { + t.Fatalf("bad: PostShutdownDelay should be 2 seconds but was %s", c.PostShutdownDelay) } // Test with a good one diff --git a/builder/virtualbox/common/step_download_guest_additions.go b/builder/virtualbox/common/step_download_guest_additions.go index 77c7b733d..693e26dbd 100644 --- a/builder/virtualbox/common/step_download_guest_additions.go +++ b/builder/virtualbox/common/step_download_guest_additions.go @@ -67,16 +67,13 @@ func (s *StepDownloadGuestAdditions) Run(ctx context.Context, state multistep.St checksumType := "sha256" - // Grab the guest_additions_url as specified by the user. - url := s.GuestAdditionsURL - // Initialize the template context so we can interpolate some variables.. s.Ctx.Data = &guestAdditionsUrlTemplate{ Version: version, } // Interpolate any user-variables specified within the guest_additions_url - url, err = interpolate.Render(s.GuestAdditionsURL, &s.Ctx) + url, err := interpolate.Render(s.GuestAdditionsURL, &s.Ctx) if err != nil { err := fmt.Errorf("Error preparing guest additions url: %s", err) state.Put("error", err) diff --git a/builder/virtualbox/common/step_forward_ssh.go b/builder/virtualbox/common/step_forward_ssh.go index ecb0947e0..95bb93db8 100644 --- a/builder/virtualbox/common/step_forward_ssh.go +++ b/builder/virtualbox/common/step_forward_ssh.go @@ -63,9 +63,22 @@ func (s *StepForwardSSH) Run(ctx context.Context, state multistep.StateBag) mult s.l.Listener.Close() // free port, but don't unlock lock file sshHostPort = s.l.Port + // Make sure to configure the network interface to NAT + command := []string{ + "modifyvm", vmName, + "--nic1", + "nat", + } + if err := driver.VBoxManage(command...); err != nil { + err := fmt.Errorf("Failed to configure NAT interface: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + // Create a forwarded port mapping to the VM ui.Say(fmt.Sprintf("Creating forwarded port mapping for communicator (SSH, WinRM, etc) (host port %d)", sshHostPort)) - command := []string{ + command = []string{ "modifyvm", vmName, "--natpf1", fmt.Sprintf("packercomm,tcp,127.0.0.1,%d,,%d", sshHostPort, guestPort), diff --git a/builder/virtualbox/common/step_run.go b/builder/virtualbox/common/step_run.go index 46a45d001..8144087b2 100644 --- a/builder/virtualbox/common/step_run.go +++ b/builder/virtualbox/common/step_run.go @@ -57,6 +57,9 @@ func (s *StepRun) Run(ctx context.Context, state multistep.StateBag) multistep.S } s.vmName = vmName + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", s.vmName) return multistep.ActionContinue } diff --git a/builder/virtualbox/common/step_shutdown_test.go b/builder/virtualbox/common/step_shutdown_test.go index 82e72fd1c..ca8970230 100644 --- a/builder/virtualbox/common/step_shutdown_test.go +++ b/builder/virtualbox/common/step_shutdown_test.go @@ -109,70 +109,6 @@ func TestStepShutdown_shutdownTimeout(t *testing.T) { } } -func TestStepShutdown_shutdownDelay(t *testing.T) { - state := testState(t) - step := new(StepShutdown) - step.Command = "poweroff" - step.Timeout = 5 * time.Second - step.Delay = 2 * time.Second - step.DisableShutdown = false - - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) - state.Put("vmName", "foo") - - driver := state.Get("driver").(*DriverMock) - driver.IsRunningReturn = true - start := time.Now() - - go func() { - time.Sleep(10 * time.Millisecond) - driver.Lock() - defer driver.Unlock() - driver.IsRunningReturn = false - }() - - // Test the run - - if action := step.Run(context.Background(), state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - testDuration := time.Since(start) - if testDuration < 2500*time.Millisecond || testDuration > 2700*time.Millisecond { - t.Fatalf("incorrect duration %s", testDuration) - } - - if _, ok := state.GetOk("error"); ok { - t.Fatal("should NOT have error") - } - - step.Delay = 0 - - driver.IsRunningReturn = true - start = time.Now() - - go func() { - time.Sleep(10 * time.Millisecond) - driver.Lock() - defer driver.Unlock() - driver.IsRunningReturn = false - }() - - // Test the run - if action := step.Run(context.Background(), state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - testDuration = time.Since(start) - if testDuration > 700*time.Millisecond { - t.Fatalf("incorrect duration %s", testDuration) - } - - if _, ok := state.GetOk("error"); ok { - t.Fatal("should NOT have error") - } - -} - func TestStepShutdown_DisableShutdown(t *testing.T) { state := testState(t) step := new(StepShutdown) diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 02c7aeeac..2bf69b49f 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -9,6 +9,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/hcl/v2/hcldec" vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" @@ -130,7 +131,9 @@ type Config struct { ctx interpolate.Context } -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, @@ -145,7 +148,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { }, }, raws...) if err != nil { - return nil, err + return nil, nil, err } // Accumulate any errors and warnings @@ -267,10 +270,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if errs != nil && len(errs.Errors) > 0 { - return warnings, errs + return nil, warnings, errs } - return warnings, nil + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/virtualbox/iso/builder.hcl2spec.go b/builder/virtualbox/iso/builder.hcl2spec.go index effec9e79..8f7497ac8 100644 --- a/builder/virtualbox/iso/builder.hcl2spec.go +++ b/builder/virtualbox/iso/builder.hcl2spec.go @@ -73,8 +73,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -116,10 +116,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/virtualbox/iso/builder_acc_test.go b/builder/virtualbox/iso/builder_acc_test.go index 630d4094f..dd8fccaeb 100644 --- a/builder/virtualbox/iso/builder_acc_test.go +++ b/builder/virtualbox/iso/builder_acc_test.go @@ -1,30 +1,22 @@ package iso import ( + "io/ioutil" + "path/filepath" "testing" builderT "github.com/hashicorp/packer/helper/builder/testing" ) func TestBuilderAcc_basic(t *testing.T) { + templatePath := filepath.Join("testdata", "minimal.json") + bytes, err := ioutil.ReadFile(templatePath) + if err != nil { + t.Fatalf("failed to load template file %s", templatePath) + } + builderT.Test(t, builderT.TestCase{ Builder: &Builder{}, - Template: testBuilderAccBasic, + Template: string(bytes), }) } - -const testBuilderAccBasic = ` -{ - "builders": [{ - "type": "test", - "guest_os_type": "Ubuntu_64", - "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso", - "iso_checksum": "769474248a3897f4865817446f9a4a53", - "iso_checksum_type": "md5", - "ssh_username": "packer", - "ssh_password": "packer", - "ssh_wait_timeout": "30s", - "shutdown_command": "echo 'packer' | sudo -S shutdown -P now" - }] -} -` diff --git a/builder/virtualbox/iso/builder_test.go b/builder/virtualbox/iso/builder_test.go index 370ebe675..7f996efdf 100644 --- a/builder/virtualbox/iso/builder_test.go +++ b/builder/virtualbox/iso/builder_test.go @@ -32,7 +32,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -62,7 +62,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config := testConfig() delete(config, "disk_size") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -76,7 +76,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config["disk_size"] = 60000 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -94,7 +94,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { config := testConfig() delete(config, "floppy_files") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -109,7 +109,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { floppies_path := "../../../common/test-fixtures/floppies" config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -128,7 +128,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) { config := testConfig() config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} b = Builder{} - _, errs := b.Prepare(config) + _, _, errs := b.Prepare(config) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -144,7 +144,7 @@ func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) { // test default mode delete(config, "guest_additions_mode") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -155,7 +155,7 @@ func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) { // Test another mode config["guest_additions_mode"] = "attach" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -170,7 +170,7 @@ func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) { // Test bad mode config["guest_additions_mode"] = "teleport" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -184,7 +184,7 @@ func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) { config := testConfig() delete(config, "guest_additions_path") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -198,7 +198,7 @@ func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) { config["guest_additions_path"] = "foo" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -216,7 +216,7 @@ func TestBuilderPrepare_GuestAdditionsSHA256(t *testing.T) { config := testConfig() delete(config, "guest_additions_sha256") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -230,7 +230,7 @@ func TestBuilderPrepare_GuestAdditionsSHA256(t *testing.T) { config["guest_additions_sha256"] = "FOO" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -248,7 +248,7 @@ func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) { config := testConfig() config["guest_additions_url"] = "" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -262,7 +262,7 @@ func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) { config["guest_additions_url"] = "http://www.packer.io" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -277,7 +277,7 @@ func TestBuilderPrepare_HardDriveInterface(t *testing.T) { // Test a default boot_wait delete(config, "hard_drive_interface") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -292,7 +292,7 @@ func TestBuilderPrepare_HardDriveInterface(t *testing.T) { // Test with a bad config["hard_drive_interface"] = "fake" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -303,7 +303,7 @@ func TestBuilderPrepare_HardDriveInterface(t *testing.T) { // Test with a good config["hard_drive_interface"] = "sata" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -318,7 +318,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -333,7 +333,7 @@ func TestBuilderPrepare_ISOInterface(t *testing.T) { // Test a default boot_wait delete(config, "iso_interface") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -348,7 +348,7 @@ func TestBuilderPrepare_ISOInterface(t *testing.T) { // Test with a bad config["iso_interface"] = "fake" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -359,7 +359,7 @@ func TestBuilderPrepare_ISOInterface(t *testing.T) { // Test with a good config["iso_interface"] = "sata" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/virtualbox/iso/testdata/http/preseed.cfg b/builder/virtualbox/iso/testdata/http/preseed.cfg new file mode 100644 index 000000000..57885df31 --- /dev/null +++ b/builder/virtualbox/iso/testdata/http/preseed.cfg @@ -0,0 +1,42 @@ +# Preseeding only locale sets language, country and locale. +d-i debian-installer/locale string en_US + +# Keyboard selection. +d-i console-setup/ask_detect boolean false +d-i keyboard-configuration/xkb-keymap select us + +choose-mirror-bin mirror/http/proxy string +d-i base-installer/kernel/override-image string linux-server +d-i clock-setup/utc boolean true +d-i clock-setup/utc-auto boolean true +d-i finish-install/reboot_in_progress note +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i mirror/country string manual +d-i mirror/http/directory string /ubuntu/ +d-i mirror/http/hostname string archive.ubuntu.com +d-i mirror/http/proxy string +d-i partman-auto-lvm/guided_size string max +d-i partman-auto/choose_recipe select atomic +d-i partman-auto/method string lvm +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-lvm/device_remove_lvm boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman/confirm_write_new_label boolean true +d-i passwd/user-fullname string vagrant +d-i passwd/user-uid string 1000 +d-i passwd/user-password password vagrant +d-i passwd/user-password-again password vagrant +d-i passwd/username string vagrant +d-i pkgsel/include string openssh-server cryptsetup build-essential libssl-dev libreadline-dev zlib1g-dev linux-source dkms nfs-kernel-server nfs-common linux-headers-$(uname -r) perl +d-i pkgsel/install-language-support boolean false +d-i pkgsel/update-policy select none +d-i pkgsel/upgrade select full-upgrade +d-i time/zone string UTC +d-i user-setup/allow-password-weak boolean true +d-i user-setup/encrypt-home boolean false +tasksel tasksel/first multiselect standard, server diff --git a/builder/virtualbox/iso/testdata/minimal.json b/builder/virtualbox/iso/testdata/minimal.json new file mode 100644 index 000000000..9c8ef7730 --- /dev/null +++ b/builder/virtualbox/iso/testdata/minimal.json @@ -0,0 +1,46 @@ +{ + "builders": [ + { + "type": "test", + "iso_checksum": "946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2", + "iso_checksum_type": "sha256", + "iso_url": "http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04.1-server-amd64.iso", + "disk_size": "40960", + "guest_os_type": "Ubuntu_64", + + "ssh_password": "vagrant", + "ssh_username": "vagrant", + "ssh_wait_timeout": "10000s", + + "http_directory": "./testdata/http", + "boot_wait": "10s", + "boot_command": [ + "", + "", + "", + "/install/vmlinuz", + " auto", + " console-setup/ask_detect=false", + " console-setup/layoutcode=us", + " console-setup/modelcode=pc105", + " debconf/frontend=noninteractive", + " debian-installer=en_US.UTF-8", + " fb=false", + " initrd=/install/initrd.gz", + " kbd-chooser/method=us", + " keyboard-configuration/layout=USA", + " keyboard-configuration/variant=USA", + " locale=en_US.UTF-8", + " netcfg/get_domain=vm", + " netcfg/get_hostname=vagrant", + " noapic", + " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg", + " -- ", + "" + ], + + "shutdown_command": "echo 'vagrant' | sudo -S shutdown -P now", + "post_shutdown_delay": "60s" + } + ] +} diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index b20b5b663..3cbd16675 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -15,19 +16,19 @@ import ( // Builder implements packer.Builder and builds the actual VirtualBox // images. type Builder struct { - config *Config + config Config runner multistep.Runner } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing @@ -41,7 +42,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) @@ -85,8 +86,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Url: []string{b.config.SourcePath}, }, &StepImport{ - Name: b.config.VMName, - ImportFlags: b.config.ImportFlags, + Name: b.config.VMName, + ImportFlags: b.config.ImportFlags, + KeepRegistered: b.config.KeepRegistered, }, &vboxcommon.StepAttachGuestAdditions{ GuestAdditionsMode: b.config.GuestAdditionsMode, diff --git a/builder/virtualbox/ovf/config.go b/builder/virtualbox/ovf/config.go index 10999f463..ce5659bfe 100644 --- a/builder/virtualbox/ovf/config.go +++ b/builder/virtualbox/ovf/config.go @@ -5,7 +5,6 @@ package ovf import ( "fmt" - "os" "strings" vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common" @@ -83,8 +82,8 @@ type Config struct { // VBoxManage import. This can be useful for passing keepallmacs or // keepnatmacs options for existing ovf images. ImportOpts string `mapstructure:"import_opts" required:"false"` - // The path to an OVF or OVA file that acts as the - // source of this build. This currently must be a local file. + // The filepath or URL to an OVF or OVA file that acts as the + // source of this build. SourcePath string `mapstructure:"source_path" required:"true"` // The path where the OVA should be saved // after download. By default, it will go in the packer cache, with a hash of @@ -105,8 +104,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, @@ -121,7 +119,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } // Defaults @@ -163,11 +161,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required")) } - if _, err := os.Stat(c.SourcePath); err != nil { - packer.MultiErrorAppend(errs, - fmt.Errorf("Source file '%s' needs to exist at time of config validation! %v", c.SourcePath, err)) - } - validMode := false validModes := []string{ vboxcommon.GuestAdditionsModeDisable, @@ -201,7 +194,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Check for any errors. if errs != nil && len(errs.Errors) > 0 { - return nil, warnings, errs + return warnings, errs } // TODO: Write a packer fix and just remove import_opts @@ -209,5 +202,5 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.ImportFlags = append(c.ImportFlags, "--options", c.ImportOpts) } - return c, warnings, nil + return warnings, nil } diff --git a/builder/virtualbox/ovf/config.hcl2spec.go b/builder/virtualbox/ovf/config.hcl2spec.go index 4ac16c25a..e518e3cf7 100644 --- a/builder/virtualbox/ovf/config.hcl2spec.go +++ b/builder/virtualbox/ovf/config.hcl2spec.go @@ -62,8 +62,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -102,10 +102,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/virtualbox/ovf/config_test.go b/builder/virtualbox/ovf/config_test.go index e36c44fcf..f00d5bdfc 100644 --- a/builder/virtualbox/ovf/config_test.go +++ b/builder/virtualbox/ovf/config_test.go @@ -31,19 +31,21 @@ func getTempFile(t *testing.T) *os.File { } func TestNewConfig_FloppyFiles(t *testing.T) { - c := testConfig(t) + cfg := testConfig(t) floppies_path := "../../../common/test-fixtures/floppies" - c["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} - _, _, err := NewConfig(c) + cfg["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} + var c Config + _, err := c.Prepare(cfg) if err != nil { t.Fatalf("should not have error: %s", err) } } func TestNewConfig_InvalidFloppies(t *testing.T) { - c := testConfig(t) - c["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} - _, _, errs := NewConfig(c) + cfg := testConfig(t) + cfg["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} + var c Config + _, errs := c.Prepare(cfg) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -55,9 +57,10 @@ func TestNewConfig_InvalidFloppies(t *testing.T) { func TestNewConfig_sourcePath(t *testing.T) { // Okay, because it gets caught during download - c := testConfig(t) - delete(c, "source_path") - _, warns, err := NewConfig(c) + cfg := testConfig(t) + delete(cfg, "source_path") + var c Config + warns, err := c.Prepare(cfg) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -65,35 +68,13 @@ func TestNewConfig_sourcePath(t *testing.T) { t.Fatalf("should error with empty `source_path`") } - // Want this to fail on validation - c = testConfig(t) - c["source_path"] = "/i/dont/exist" - _, warns, err = NewConfig(c) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatalf("Nonexistent file should throw a validation error!") - } - - // Bad - c = testConfig(t) - c["source_path"] = "ftp://i/dont/exist" - _, warns, err = NewConfig(c) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatalf("should error") - } - // Good tf := getTempFile(t) defer os.Remove(tf.Name()) - c = testConfig(t) - c["source_path"] = tf.Name() - _, warns, err = NewConfig(c) + cfg = testConfig(t) + cfg["source_path"] = tf.Name() + warns, err = c.Prepare(cfg) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -103,14 +84,15 @@ func TestNewConfig_sourcePath(t *testing.T) { } func TestNewConfig_shutdown_timeout(t *testing.T) { - c := testConfig(t) + cfg := testConfig(t) tf := getTempFile(t) defer os.Remove(tf.Name()) // Expect this to fail - c["source_path"] = tf.Name() - c["shutdown_timeout"] = "NaN" - _, warns, err := NewConfig(c) + cfg["source_path"] = tf.Name() + cfg["shutdown_timeout"] = "NaN" + var c Config + warns, err := c.Prepare(cfg) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -119,8 +101,8 @@ func TestNewConfig_shutdown_timeout(t *testing.T) { } // Passes when given a valid time duration - c["shutdown_timeout"] = "10s" - _, warns, err = NewConfig(c) + cfg["shutdown_timeout"] = "10s" + warns, err = c.Prepare(cfg) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/virtualbox/ovf/step_import.go b/builder/virtualbox/ovf/step_import.go index caab759b2..b3dd53a19 100644 --- a/builder/virtualbox/ovf/step_import.go +++ b/builder/virtualbox/ovf/step_import.go @@ -11,8 +11,9 @@ import ( // This step imports an OVF VM into VirtualBox. type StepImport struct { - Name string - ImportFlags []string + Name string + ImportFlags []string + KeepRegistered bool vmName string } @@ -42,11 +43,10 @@ func (s *StepImport) Cleanup(state multistep.StateBag) { driver := state.Get("driver").(vboxcommon.Driver) ui := state.Get("ui").(packer.Ui) - config := state.Get("config").(*Config) _, cancelled := state.GetOk(multistep.StateCancelled) _, halted := state.GetOk(multistep.StateHalted) - if (config.KeepRegistered) && (!cancelled && !halted) { + if (s.KeepRegistered) && (!cancelled && !halted) { ui.Say("Keeping virtual machine registered with VirtualBox host (keep_registered = true)") return } diff --git a/builder/virtualbox/ovf/step_import_test.go b/builder/virtualbox/ovf/step_import_test.go index ffaecc0ba..ae7425a22 100644 --- a/builder/virtualbox/ovf/step_import_test.go +++ b/builder/virtualbox/ovf/step_import_test.go @@ -14,10 +14,8 @@ func TestStepImport_impl(t *testing.T) { func TestStepImport(t *testing.T) { state := testState(t) - c := testConfig(t) - config, _, _ := NewConfig(c) state.Put("vm_path", "foo") - state.Put("config", config) + step := new(StepImport) step.Name = "bar" @@ -45,16 +43,24 @@ func TestStepImport(t *testing.T) { } else if name != "bar" { t.Fatalf("bad: %#v", name) } +} - // Test cleanup - config.KeepRegistered = true +func TestStepImport_Cleanup(t *testing.T) { + state := testState(t) + state.Put("vm_path", "foo") + + step := new(StepImport) + step.vmName = "bar" + + driver := state.Get("driver").(*vboxcommon.DriverMock) + + step.KeepRegistered = true step.Cleanup(state) - if driver.DeleteCalled { t.Fatal("delete should not be called") } - config.KeepRegistered = false + state.Put(multistep.StateHalted, true) step.Cleanup(state) if !driver.DeleteCalled { t.Fatal("delete should be called") diff --git a/builder/virtualbox/vm/builder.go b/builder/virtualbox/vm/builder.go index 2336e58d5..3bab1edb7 100644 --- a/builder/virtualbox/vm/builder.go +++ b/builder/virtualbox/vm/builder.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -15,19 +16,19 @@ import ( // Builder implements packer.Builder and builds the actual VirtualBox // images. type Builder struct { - config *Config + config Config runner multistep.Runner } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing @@ -41,7 +42,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", b.config) state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) diff --git a/builder/virtualbox/vm/config.go b/builder/virtualbox/vm/config.go index 14b57295d..dc1a1fd66 100644 --- a/builder/virtualbox/vm/config.go +++ b/builder/virtualbox/vm/config.go @@ -44,8 +44,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, @@ -60,7 +59,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } // Defaults @@ -203,8 +202,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } // Check for any errors. if errs != nil && len(errs.Errors) > 0 { - return nil, warnings, errs + return warnings, errs } - return c, warnings, nil + return warnings, nil } diff --git a/builder/virtualbox/vm/config.hcl2spec.go b/builder/virtualbox/vm/config.hcl2spec.go index eafdf2428..a180a65f6 100644 --- a/builder/virtualbox/vm/config.hcl2spec.go +++ b/builder/virtualbox/vm/config.hcl2spec.go @@ -62,8 +62,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -98,10 +98,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/vmware/common/step_run.go b/builder/vmware/common/step_run.go index 5f03ebca5..e9ac27331 100644 --- a/builder/vmware/common/step_run.go +++ b/builder/vmware/common/step_run.go @@ -64,6 +64,10 @@ func (s *StepRun) Run(ctx context.Context, state multistep.StateBag) multistep.S return multistep.ActionHalt } + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", vmxPath) + return multistep.ActionContinue } diff --git a/builder/vmware/common/step_shutdown_test.go b/builder/vmware/common/step_shutdown_test.go index 0c3d98589..c59f495e6 100644 --- a/builder/vmware/common/step_shutdown_test.go +++ b/builder/vmware/common/step_shutdown_test.go @@ -62,7 +62,7 @@ func TestStepShutdown_command(t *testing.T) { var action multistep.StepAction select { case action = <-resultCh: - case <-time.After(300 * time.Millisecond): + case <-time.After(5 * time.Second): t.Fatal("should've returned by now") } diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 7e94d9663..4af036035 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/hashicorp/hcl/v2/hcldec" vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -18,16 +19,15 @@ type Builder struct { runner multistep.Runner } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) if errs != nil { - return warnings, errs + return nil, warnings, errs } - b.config = *c - - return warnings, nil + return nil, warnings, nil } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { diff --git a/builder/vmware/iso/builder_test.go b/builder/vmware/iso/builder_test.go index 195f8e381..38441d633 100644 --- a/builder/vmware/iso/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -33,7 +33,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -63,7 +63,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config := testConfig() delete(config, "disk_size") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -77,7 +77,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { config["disk_size"] = 60000 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -95,7 +95,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { config := testConfig() delete(config, "floppy_files") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -110,7 +110,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { floppies_path := "../../../common/test-fixtures/floppies" config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -129,7 +129,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) { config := testConfig() config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} b = Builder{} - _, errs := b.Prepare(config) + _, _, errs := b.Prepare(config) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } @@ -149,7 +149,7 @@ func TestBuilderPrepare_RemoteType(t *testing.T) { config["skip_validate_credentials"] = true // Bad config["remote_type"] = "foobar" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -161,7 +161,7 @@ func TestBuilderPrepare_RemoteType(t *testing.T) { // Bad config["remote_host"] = "" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -176,7 +176,7 @@ func TestBuilderPrepare_RemoteType(t *testing.T) { config["remote_password"] = "" config["remote_private_key_file"] = "" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -189,7 +189,7 @@ func TestBuilderPrepare_RemoteType(t *testing.T) { config["remote_host"] = "foobar.example.com" config["remote_password"] = "supersecret" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -207,7 +207,7 @@ func TestBuilderPrepare_RemoteExport(t *testing.T) { config["skip_validate_credentials"] = true // Bad config["remote_password"] = "" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) != 0 { t.Fatalf("bad: %#v", warns) } @@ -218,7 +218,7 @@ func TestBuilderPrepare_RemoteExport(t *testing.T) { // Good config["remote_password"] = "supersecret" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) != 0 { t.Fatalf("err: %s", err) } @@ -233,7 +233,7 @@ func TestBuilderPrepare_Format(t *testing.T) { // Bad config["format"] = "foobar" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -252,7 +252,7 @@ func TestBuilderPrepare_Format(t *testing.T) { config["skip_validate_credentials"] = true b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -268,7 +268,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { // Add a random key config["i_should_not_be_valid"] = true - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -290,7 +290,7 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { config["output_directory"] = dir b = Builder{} - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -301,7 +301,7 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { // Test with a good one config["output_directory"] = "i-hope-i-dont-exist" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -316,7 +316,7 @@ func TestBuilderPrepare_ToolsUploadPath(t *testing.T) { // Test a default delete(config, "tools_upload_path") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -331,7 +331,7 @@ func TestBuilderPrepare_ToolsUploadPath(t *testing.T) { // Test with a bad value config["tools_upload_path"] = "{{{nope}" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -342,7 +342,7 @@ func TestBuilderPrepare_ToolsUploadPath(t *testing.T) { // Test with a good one config["tools_upload_path"] = "hey" b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -357,7 +357,7 @@ func TestBuilderPrepare_VMXTemplatePath(t *testing.T) { // Test bad config["vmx_template_path"] = "/i/dont/exist/forreal" - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -379,7 +379,7 @@ func TestBuilderPrepare_VMXTemplatePath(t *testing.T) { config["vmx_template_path"] = tf.Name() b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -401,7 +401,7 @@ func TestBuilderPrepare_VMXTemplatePath(t *testing.T) { config["vmx_template_path"] = tf2.Name() b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -417,7 +417,7 @@ func TestBuilderPrepare_VNCPort(t *testing.T) { // Bad config["vnc_port_min"] = 1000 config["vnc_port_max"] = 500 - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -428,7 +428,7 @@ func TestBuilderPrepare_VNCPort(t *testing.T) { // Bad config["vnc_port_min"] = -500 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -440,7 +440,7 @@ func TestBuilderPrepare_VNCPort(t *testing.T) { config["vnc_port_min"] = 500 config["vnc_port_max"] = 1000 b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -458,7 +458,7 @@ func TestBuilderCheckCollisions(t *testing.T) { } { var b Builder - warns, _ := b.Prepare(config) + _, warns, _ := b.Prepare(config) if len(warns) != 1 { t.Fatalf("Should have warning about two collisions.") } @@ -466,7 +466,7 @@ func TestBuilderCheckCollisions(t *testing.T) { { config["vmx_template_path"] = "some/path.vmx" var b Builder - warns, _ := b.Prepare(config) + _, warns, _ := b.Prepare(config) if len(warns) != 0 { t.Fatalf("Should not check for collisions with custom template.") } @@ -484,7 +484,7 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { config["winrm_host"] = "1.2.3.4" var b Builder - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -512,7 +512,7 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { config["ssh_host"] = "1.2.3.4" var b Builder - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } diff --git a/builder/vmware/iso/config.go b/builder/vmware/iso/config.go index 44d577058..2062ee4e5 100644 --- a/builder/vmware/iso/config.go +++ b/builder/vmware/iso/config.go @@ -115,8 +115,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, @@ -128,7 +127,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } // Accumulate any errors and warnings @@ -263,10 +262,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if errs != nil && len(errs.Errors) > 0 { - return nil, warnings, errs + return warnings, errs } - return c, warnings, nil + return warnings, nil } func (c *Config) checkForVMXTemplateAndVMXDataCollisions() string { diff --git a/builder/vmware/iso/config.hcl2spec.go b/builder/vmware/iso/config.hcl2spec.go index 6dce149ae..21795f95a 100644 --- a/builder/vmware/iso/config.hcl2spec.go +++ b/builder/vmware/iso/config.hcl2spec.go @@ -92,8 +92,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -131,10 +131,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/vmware/iso/step_create_vmx_test.go b/builder/vmware/iso/step_create_vmx_test.go index 9bd4d6d4c..0c6fe6e97 100644 --- a/builder/vmware/iso/step_create_vmx_test.go +++ b/builder/vmware/iso/step_create_vmx_test.go @@ -137,17 +137,17 @@ func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisioner // create our config to test the vmware-iso builder components := packer.ComponentFinder{ - Builder: func(n string) (packer.Builder, error) { - return &Builder{}, nil + BuilderStore: packer.MapOfBuilder{ + "vmware-iso": func() (packer.Builder, error) { return &Builder{}, nil }, }, Hook: func(n string) (packer.Hook, error) { return &packer.DispatchHook{}, nil }, - PostProcessor: func(n string) (packer.PostProcessor, error) { - return &packer.MockPostProcessor{}, nil + ProvisionerStore: packer.MapOfProvisioner{ + "shell": func() (packer.Provisioner, error) { return &shell.Provisioner{}, nil }, }, - Provisioner: func(n string) (packer.Provisioner, error) { - return &shell.Provisioner{}, nil + PostProcessorStore: packer.MapOfPostProcessor{ + "something": func() (packer.PostProcessor, error) { return &packer.MockPostProcessor{}, nil }, }, } config := packer.CoreConfig{ diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index e1e33dd7c..d00e59ab1 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -7,6 +7,7 @@ import ( "log" "time" + "github.com/hashicorp/hcl/v2/hcldec" vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" @@ -17,19 +18,19 @@ import ( // Builder implements packer.Builder and builds the actual VMware // images. type Builder struct { - config *Config + config Config runner multistep.Runner } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) - if errs != nil { - return warnings, errs - } - b.config = c +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } - return warnings, nil +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) + if errs != nil { + return nil, warnings, errs + } + + return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing @@ -60,7 +61,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state. state := new(multistep.BasicStateBag) - state.Put("config", b.config) state.Put("debug", b.config.PackerDebug) state.Put("dir", dir) state.Put("driver", driver) diff --git a/builder/vmware/vmx/builder_test.go b/builder/vmware/vmx/builder_test.go index 841d4f718..4e2a165a7 100644 --- a/builder/vmware/vmx/builder_test.go +++ b/builder/vmware/vmx/builder_test.go @@ -24,7 +24,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { config["source_path"] = tf.Name() delete(config, "floppy_files") - warns, err := b.Prepare(config) + _, warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -39,7 +39,7 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) { floppies_path := "../../../common/test-fixtures/floppies" config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} b = Builder{} - warns, err = b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -58,7 +58,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) { config := testConfig(t) config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} b = Builder{} - _, errs := b.Prepare(config) + _, _, errs := b.Prepare(config) if errs == nil { t.Fatalf("Nonexistent floppies should trigger multierror") } diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index 7e5d8d1f0..4b8dbd8b4 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -55,8 +55,7 @@ type Config struct { ctx interpolate.Context } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := new(Config) +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, @@ -68,7 +67,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { }, }, raws...) if err != nil { - return nil, nil, err + return nil, err } // Defaults @@ -148,8 +147,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Check for any errors. if errs != nil && len(errs.Errors) > 0 { - return nil, warnings, errs + return warnings, errs } - return c, warnings, nil + return warnings, nil } diff --git a/builder/vmware/vmx/config.hcl2spec.go b/builder/vmware/vmx/config.hcl2spec.go index 15fc44b5e..72e0027e9 100644 --- a/builder/vmware/vmx/config.hcl2spec.go +++ b/builder/vmware/vmx/config.hcl2spec.go @@ -76,8 +76,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -107,10 +107,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/vmware/vmx/config_test.go b/builder/vmware/vmx/config_test.go index 4ee0b9426..4b5c63483 100644 --- a/builder/vmware/vmx/config_test.go +++ b/builder/vmware/vmx/config_test.go @@ -34,15 +34,15 @@ func testConfigOk(t *testing.T, warns []string, err error) { func TestNewConfig_sourcePath(t *testing.T) { // Bad - c := testConfig(t) - delete(c, "source_path") - _, warns, errs := NewConfig(c) + cfg := testConfig(t) + delete(cfg, "source_path") + warns, errs := (&Config{}).Prepare(cfg) testConfigErr(t, warns, errs) // Bad - c = testConfig(t) - c["source_path"] = "/i/dont/exist" - _, warns, errs = NewConfig(c) + cfg = testConfig(t) + cfg["source_path"] = "/i/dont/exist" + warns, errs = (&Config{}).Prepare(cfg) testConfigErr(t, warns, errs) // Good @@ -53,8 +53,8 @@ func TestNewConfig_sourcePath(t *testing.T) { tf.Close() defer os.Remove(tf.Name()) - c = testConfig(t) - c["source_path"] = tf.Name() - _, warns, errs = NewConfig(c) + cfg = testConfig(t) + cfg["source_path"] = tf.Name() + warns, errs = (&Config{}).Prepare(cfg) testConfigOk(t, warns, errs) } diff --git a/builder/yandex/builder.go b/builder/yandex/builder.go index 69bb9469f..d461eb525 100644 --- a/builder/yandex/builder.go +++ b/builder/yandex/builder.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/google/uuid" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" @@ -19,24 +20,24 @@ const BuilderID = "packer.yandex" // Builder represents a Packer Builder. type Builder struct { - config *Config + config Config runner multistep.Runner } -// Prepare processes the build configuration parameters. -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - c, warnings, errs := NewConfig(raws...) +func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + +func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { + warnings, errs := b.config.Prepare(raws...) if errs != nil { - return warnings, errs + return nil, warnings, errs } - b.config = c - return warnings, nil + return nil, warnings, nil } // Run executes a yandex Packer build and returns a packer.Artifact // representing a Yandex.Cloud compute image. func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { - driver, err := NewDriverYC(ui, b.config) + driver, err := NewDriverYC(ui, &b.config) ctx = requestid.ContextWithClientTraceID(ctx, uuid.New().String()) if err != nil { @@ -45,7 +46,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Set up the state state := &multistep.BasicStateBag{} - state.Put("config", b.config) + state.Put("config", &b.config) state.Put("driver", driver) state.Put("sdk", driver.SDK()) state.Put("hook", hook) @@ -91,7 +92,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack artifact := &Artifact{ image: image.(*compute.Image), - config: b.config, + config: &b.config, } return artifact, nil } diff --git a/builder/yandex/config.go b/builder/yandex/config.go index 2319203e1..5d3952a6a 100644 --- a/builder/yandex/config.go +++ b/builder/yandex/config.go @@ -122,15 +122,14 @@ type Config struct { StateTimeout time.Duration `mapstructure:"state_timeout" required:"false"` } -func NewConfig(raws ...interface{}) (*Config, []string, error) { - c := &Config{} +func (c *Config) Prepare(raws ...interface{}) ([]string, error) { c.ctx.Funcs = TemplateFuncs err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, }, raws...) if err != nil { - return nil, nil, err + return nil, err } var errs *packer.MultiError @@ -286,8 +285,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Check for any errors. if errs != nil && len(errs.Errors) > 0 { - return nil, nil, errs + return nil, errs } - return c, nil, nil + return nil, nil } diff --git a/builder/yandex/config.hcl2spec.go b/builder/yandex/config.hcl2spec.go index 69f6695d7..1fb0db29b 100644 --- a/builder/yandex/config.hcl2spec.go +++ b/builder/yandex/config.hcl2spec.go @@ -46,8 +46,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -94,10 +94,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/builder/yandex/config_test.go b/builder/yandex/config_test.go index 7e777d0e7..611474718 100644 --- a/builder/yandex/config_test.go +++ b/builder/yandex/config_test.go @@ -135,7 +135,8 @@ func TestConfigPrepare(t *testing.T) { delete(raw, "token") } - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) if tc.Err { testConfigErr(t, warns, errs, tc.Key) @@ -156,7 +157,8 @@ func TestConfigPrepareStartupScriptFile(t *testing.T) { "key": "file_not_exist", } - _, _, errs := NewConfig(config) + var c Config + _, errs := c.Prepare(config) if errs == nil || !strings.Contains(errs.Error(), "cannot access file 'file_not_exist' with content "+ "for value of metadata key 'key':") { @@ -183,10 +185,11 @@ func TestConfigDefaults(t *testing.T) { for _, tc := range cases { raw := testConfig(t) - c, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) testConfigOk(t, warns, errs) - actual := tc.Read(c) + actual := tc.Read(&c) if actual != tc.Value { t.Fatalf("bad: %#v", actual) } @@ -196,7 +199,8 @@ func TestConfigDefaults(t *testing.T) { func TestImageName(t *testing.T) { raw := testConfig(t) - c, _, _ := NewConfig(raw) + var c Config + c.Prepare(raw) if !strings.HasPrefix(c.ImageName, "packer-") { t.Fatalf("ImageName should have 'packer-' prefix, found %s", c.ImageName) } @@ -208,7 +212,8 @@ func TestImageName(t *testing.T) { func TestZone(t *testing.T) { raw := testConfig(t) - c, _, _ := NewConfig(raw) + var c Config + c.Prepare(raw) if c.Zone != "ru-central1-a" { t.Fatalf("Zone should be 'ru-central1-a' given, but is '%s'", c.Zone) } @@ -218,7 +223,8 @@ func TestGpuDefaultPlatformID(t *testing.T) { raw := testConfig(t) raw["instance_gpus"] = 1 - c, _, _ := NewConfig(raw) + var c Config + c.Prepare(raw) if c.PlatformID != "gpu-standard-v1" { t.Fatalf("expected 'gpu-standard-v1', but got '%s'", c.PlatformID) } @@ -229,7 +235,8 @@ func TestGpuWrongPlatformID(t *testing.T) { raw["instance_gpus"] = 1 raw["platform_id"] = "standard-v1" - _, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) testConfigErr(t, warns, errs, "incompatible GPU platform_id") } @@ -254,12 +261,13 @@ func testConfig(t *testing.T) (config map[string]interface{}) { func testConfigStruct(t *testing.T) *Config { raw := testConfig(t) - c, warns, errs := NewConfig(raw) + var c Config + warns, errs := c.Prepare(raw) require.True(t, len(warns) == 0, "bad: %#v", warns) require.NoError(t, errs, "should not have error: %s", errs) - return c + return &c } func testConfigErr(t *testing.T, warns []string, err error, extra string) { diff --git a/builder/yandex/step_create_instance.go b/builder/yandex/step_create_instance.go index 5d7b28f10..b13cbca1e 100644 --- a/builder/yandex/step_create_instance.go +++ b/builder/yandex/step_create_instance.go @@ -249,6 +249,9 @@ runcmd: } state.Put("disk_id", instance.BootDisk.DiskId) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", instance.Id) if s.Debug { ui.Message(fmt.Sprintf("Instance ID %s started. Current instance status %s", instance.Id, instance.Status)) diff --git a/cmd/hcl2-schema/hcl2-schema.go b/cmd/hcl2-schema/hcl2-schema.go deleted file mode 100644 index 7628e6185..000000000 --- a/cmd/hcl2-schema/hcl2-schema.go +++ /dev/null @@ -1,313 +0,0 @@ -package main - -import ( - "bytes" - "flag" - "fmt" - "go/ast" - "go/format" - "go/parser" - "go/token" - "io" - "io/ioutil" - "log" - "os" - "regexp" - "strings" - "text/template" - - "github.com/fatih/structtag" - "github.com/hashicorp/hcl/v2/hcldec" - "github.com/zclconf/go-cty/cty" -) - -var ( - typeNames = flag.String("type", "", "comma-separated list of type names; must be set") - output = flag.String("output", "", "output file name; default srcdir/_hcl2.go") - trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") -) - -// Usage is a replacement usage function for the flags package. -func Usage() { - fmt.Fprintf(os.Stderr, "Usage of hcl2-schema:\n") - fmt.Fprintf(os.Stderr, "\thcl2-schema [flags] -type T [directory]\n") - fmt.Fprintf(os.Stderr, "\thcl2-schema [flags] -type T files... # Must be a single package\n") - fmt.Fprintf(os.Stderr, "Flags:\n") - flag.PrintDefaults() -} - -func main() { - log.SetFlags(0) - log.SetPrefix("hcl2-schema: ") - flag.Usage = Usage - flag.Parse() - if len(*typeNames) == 0 { - flag.Usage() - os.Exit(2) - } - types := strings.Split(*typeNames, ",") - - // We accept either one directory or a list of files. Which do we have? - args := flag.Args() - if len(args) == 0 { - // Default: process whole package in current directory. - args = []string{os.Getenv("GOFILE")} - } - fname := args[0] - outputPath := fname[:len(fname)-2] + "hcl2spec.go" - - b, err := ioutil.ReadFile(fname) - if err != nil { - fmt.Printf("ReadFile: %+v", err) - os.Exit(1) - } - - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, fname, b, parser.ParseComments) - if err != nil { - fmt.Printf("ParseFile: %+v", err) - os.Exit(1) - } - output := Output{ - Args: strings.Join(os.Args[1:], " "), - Package: f.Name.String(), - } - - res := []StructDef{} - - for _, t := range types { - for _, decl := range f.Decls { - typeDecl, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - typeSpec, ok := typeDecl.Specs[0].(*ast.TypeSpec) - if !ok { - continue - } - structDecl, ok := typeSpec.Type.(*ast.StructType) - if !ok { - continue - } - if typeSpec.Name.String() != t { - continue - } - sd := StructDef{StructName: t} - fields := structDecl.Fields.List - for _, field := range fields { - - fieldType := string(b[field.Type.Pos()-1 : field.Type.End()-1]) - fieldName := fieldType[strings.Index(fieldType, ".")+1:] - if len(field.Names) > 0 { - fieldName = field.Names[0].Name - } - - if ast.IsExported(fieldName) { - continue - } - if strings.Contains(fieldType, "func") { - continue - } - fd := FieldDef{Name: fieldName} - - squash := false - accessor := ToSnakeCase(fieldName) - if field.Tag != nil { - tag := field.Tag.Value[1:] - tag = tag[:len(tag)-1] - tags, err := structtag.Parse(tag) - if err != nil { - log.Fatalf("structtag.Parse(%s): err: %v", field.Tag.Value, err) - } - if hsg, err := tags.Get("hcl2-schema-generator"); err == nil { - if len(hsg.Options) > 0 && hsg.Options[0] == "direct" { - fieldType = "direct" - } - if hsg.Name != "" { - accessor = hsg.Name - } - } else if mstr, err := tags.Get("mapstructure"); err == nil { - if len(mstr.Options) > 0 && mstr.Options[0] == "squash" { - squash = true - } - if mstr.Name != "" { - accessor = mstr.Name - } - } - } - - switch fieldType { - case "direct": - fd.Spec = `(&` + sd.StructName + `{}).` + fieldName + `.HCL2Spec()` - case "[]string", "[]*string": - fd.Spec = fmt.Sprintf("%#v", &hcldec.AttrSpec{ - Name: accessor, - Type: cty.List(cty.String), - Required: false, - }) - case "[]int", "[]uint", "[]int32", "[]int64": - fd.Spec = fmt.Sprintf("%#v", &hcldec.AttrSpec{ - Name: accessor, - Type: cty.List(cty.Number), - Required: false, - }) - case "[]byte", "string", "*string", "time.Duration", "*url.URL": - fd.Spec = fmt.Sprintf("%#v", &hcldec.AttrSpec{ - Name: accessor, - Type: cty.String, - Required: false, - }) - case "uint", "*int", "int", "int32", "int64", "float", "float32", "float64": - fd.Spec = fmt.Sprintf("%#v", &hcldec.AttrSpec{ - Name: accessor, - Type: cty.Number, - Required: false, - }) - case "bool", "config.Trilean": - fd.Spec = fmt.Sprintf("%#v", &hcldec.AttrSpec{ - Name: accessor, - Type: cty.Bool, - Required: false, - }) - case "osccommon.TagMap", "awscommon.TagMap", "TagMap", - "map[string]*string", "map[*string]*string", "map[string]string": - fd.Spec = fmt.Sprintf("%#v", &hcldec.BlockAttrsSpec{ - TypeName: accessor, - ElementType: cty.String, - Required: false, - }) - case "[][]string": - // TODO(azr): implement those - continue - case "communicator.Config": - // this one is manually set - continue - case "map[string]interface{}", "map[string]map[string]interface{}": - // probably never going to be supported - continue - case "common.PackerConfig": - // this one is deprecated ? - continue - default: // nested structures - if squash { - sd.Squashed = append(sd.Squashed, fieldName) - } else if strings.HasPrefix(fieldType, "[]") { - sd.NestedList = append(sd.NestedList, NestedFieldDef{ - FieldName: fieldName, - TypeName: fieldType, - Accessor: accessor, - }) - } else { - sd.Nested = append(sd.Nested, NestedFieldDef{ - FieldName: fieldName, - TypeName: fieldType, - Accessor: accessor, - }) - } - continue - } - - output.ImportCty = true - sd.Fields = append(sd.Fields, fd) - } - res = append(res, sd) - } - } - - output.StructDefs = res - - result := bytes.NewBuffer(nil) - - err = structDocsTemplate.Execute(result, output) - if err != nil { - log.Fatalf("err templating: %v", err) - } - - formattedBytes, err := format.Source(result.Bytes()) - if err != nil { - log.Printf("formatting err: %v", err) - formattedBytes = result.Bytes() - } - - outputFile, err := os.Create(outputPath) - if err != nil { - log.Fatalf("err: %v", err) - } - defer outputFile.Close() - - _, err = io.Copy(outputFile, bytes.NewBuffer(formattedBytes)) - if err != nil { - log.Fatalf("err: %v", err) - } -} - -type Output struct { - Args string - Package string - StructDefs []StructDef - ImportCty bool -} - -type FieldDef struct { - Name string - Spec string -} -type NestedFieldDef struct { - TypeName string - FieldName string - Accessor string -} - -type StructDef struct { - StructName string - Fields []FieldDef - Nested []NestedFieldDef - NestedList []NestedFieldDef - Squashed []string -} - -var structDocsTemplate = template.Must(template.New("structDocsTemplate"). - Funcs(template.FuncMap{ - // "indent": indent, - }). - Parse(`// Code generated by "hcl2-schema {{ .Args }}"; DO NOT EDIT.\n - -package {{ .Package }} - -import ( - "github.com/hashicorp/hcl/v2/hcldec" -{{- if .ImportCty }} - "github.com/zclconf/go-cty/cty" -{{end -}} -) -{{ range .StructDefs }} -{{ $StructName := .StructName}} -func (*{{ .StructName }}) HCL2Spec() map[string]hcldec.Spec { - s := map[string]hcldec.Spec{ - {{- range .Fields}} - "{{ .Name }}": {{ .Spec }}, - {{- end }} - {{- range .Nested}} - "{{ .Accessor }}": &hcldec.BlockObjectSpec{TypeName: "{{ .TypeName }}", LabelNames: []string(nil), Nested: hcldec.ObjectSpec((&{{ $StructName }}{}).{{ .FieldName }}.HCL2Spec())}, - {{- end }} - {{- range .NestedList }} - "{{ .Accessor }}": &hcldec.BlockListSpec{TypeName: "{{ .TypeName }}", Nested: hcldec.ObjectSpec((&{{ $StructName }}{}).{{ .FieldName }}[0].HCL2Spec()) }, - {{- end}} - } - {{- range .Squashed }} - for k,v := range (&{{ $StructName }}{}).{{ . }}.HCL2Spec() { - s[k] = v - } - {{- end}} - return s -} -{{end}}`)) - -var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") -var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") - -func ToSnakeCase(str string) string { - snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") - snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") - return strings.ToLower(snake) -} diff --git a/cmd/mapstructure-to-hcl2/mapstructure-to-hcl2.go b/cmd/mapstructure-to-hcl2/mapstructure-to-hcl2.go index da3a507bf..3c890b836 100644 --- a/cmd/mapstructure-to-hcl2/mapstructure-to-hcl2.go +++ b/cmd/mapstructure-to-hcl2/mapstructure-to-hcl2.go @@ -125,7 +125,7 @@ func main() { newStructName := "Flat" + id.Name structs = append(structs, StructDef{ OriginalStructName: id.Name, - StructName: newStructName, + FlatStructName: newStructName, Struct: flatenedStruct, }) @@ -150,22 +150,23 @@ func main() { return structs[i].OriginalStructName < structs[j].OriginalStructName }) for _, flatenedStruct := range structs { - fmt.Fprintf(out, "\n// %s is an auto-generated flat version of %s.", flatenedStruct.StructName, flatenedStruct.OriginalStructName) + fmt.Fprintf(out, "\n// %s is an auto-generated flat version of %s.", flatenedStruct.FlatStructName, flatenedStruct.OriginalStructName) fmt.Fprintf(out, "\n// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.") - fmt.Fprintf(out, "\ntype %s struct {\n", flatenedStruct.StructName) + fmt.Fprintf(out, "\ntype %s struct {\n", flatenedStruct.FlatStructName) outputStructFields(out, flatenedStruct.Struct) fmt.Fprint(out, "}\n") - fmt.Fprintf(out, "\n// FlatMapstructure returns a new %s.", flatenedStruct.StructName) - fmt.Fprintf(out, "\n// %s is an auto-generated flat version of %s.", flatenedStruct.StructName, flatenedStruct.OriginalStructName) + fmt.Fprintf(out, "\n// FlatMapstructure returns a new %s.", flatenedStruct.FlatStructName) + fmt.Fprintf(out, "\n// %s is an auto-generated flat version of %s.", flatenedStruct.FlatStructName, flatenedStruct.OriginalStructName) fmt.Fprintf(out, "\n// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.") - fmt.Fprintf(out, "\nfunc (*%s) FlatMapstructure() interface{} {", flatenedStruct.OriginalStructName) - fmt.Fprintf(out, "return new(%s)", flatenedStruct.StructName) + fmt.Fprintf(out, "\nfunc (*%s) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {", flatenedStruct.OriginalStructName) + fmt.Fprintf(out, "return new(%s)", flatenedStruct.FlatStructName) fmt.Fprint(out, "}\n") - fmt.Fprintf(out, "\n// HCL2Spec returns the hcldec.Spec of a %s.", flatenedStruct.StructName) - fmt.Fprintf(out, "\n// This spec is used by HCL to read the fields of %s.", flatenedStruct.StructName) - fmt.Fprintf(out, "\nfunc (*%s) HCL2Spec() map[string]hcldec.Spec {\n", flatenedStruct.StructName) + fmt.Fprintf(out, "\n// HCL2Spec returns the hcl spec of a %s.", flatenedStruct.OriginalStructName) + fmt.Fprintf(out, "\n// This spec is used by HCL to read the fields of %s.", flatenedStruct.OriginalStructName) + fmt.Fprintf(out, "\n// The decoded values from this spec will then be applied to a %s.", flatenedStruct.FlatStructName) + fmt.Fprintf(out, "\nfunc (*%s) HCL2Spec() map[string]hcldec.Spec {\n", flatenedStruct.FlatStructName) outputStructHCL2SpecBody(out, flatenedStruct.Struct) fmt.Fprint(out, "}\n") } @@ -196,7 +197,7 @@ func main() { type StructDef struct { OriginalStructName string - StructName string + FlatStructName string Struct *types.Struct } @@ -250,7 +251,13 @@ func outputHCL2SpecField(w io.Writer, accessor string, fieldType types.Type, tag }) case *types.Named: b := bytes.NewBuffer(nil) - outputHCL2SpecField(b, accessor, elem, tag) + underlyingType := elem.Underlying() + switch underlyingType.(type) { + case *types.Struct: + fmt.Fprintf(b, `hcldec.ObjectSpec((*%s)(nil).HCL2Spec())`, elem.String()) + default: + outputHCL2SpecField(b, accessor, elem, tag) + } fmt.Fprintf(w, `&hcldec.BlockListSpec{TypeName: "%s", Nested: %s}`, accessor, b.String()) case *types.Slice: b := bytes.NewBuffer(nil) @@ -266,11 +273,8 @@ func outputHCL2SpecField(w io.Writer, accessor string, fieldType types.Type, tag fmt.Fprintf(w, `&hcldec.BlockSpec{TypeName: "%s",`+ ` Nested: hcldec.ObjectSpec((*%s)(nil).HCL2Spec())}`, accessor, f.String()) default: - outputHCL2SpecField(w, f.String(), underlyingType, tag) + outputHCL2SpecField(w, accessor, underlyingType, tag) } - case *types.Struct: - fmt.Fprintf(w, `&hcldec.BlockObjectSpec{TypeName: "%s",`+ - ` Nested: hcldec.ObjectSpec((*%s)(nil).HCL2Spec())}`, accessor, fieldType.String()) default: fmt.Fprintf(w, `%#v`, &hcldec.AttrSpec{ Name: accessor, @@ -303,7 +307,9 @@ func basicKindToCtyType(kind types.BasicKind) cty.Type { func outputStructFields(w io.Writer, s *types.Struct) { for i := 0; i < s.NumFields(); i++ { field, tag := s.Field(i), s.Tag(i) - fmt.Fprintf(w, " %s `%s`\n", strings.Replace(field.String(), "field ", "", 1), tag) + fieldNameStr := field.String() + fieldNameStr = strings.Replace(fieldNameStr, "field ", "", 1) + fmt.Fprintf(w, " %s `%s`\n", fieldNameStr, tag) } } @@ -350,6 +356,9 @@ func getUsedImports(s *types.Struct) map[NamePath]*types.Package { continue } pkg := namedType.Obj().Pkg() + if pkg == nil { + continue + } res[NamePath{pkg.Name(), pkg.Path()}] = pkg } return res @@ -406,7 +415,11 @@ func getMapstructureSquashedStruct(topPkg *types.Package, utStruct *types.Struct if _, ok := field.Type().(*types.Signature); ok { continue // ignore funcs } - structtag, _ := structtag.Parse(tag) + structtag, err := structtag.Parse(tag) + if err != nil { + log.Printf("could not parse field tag %s of : %v", tag, err) + continue + } if ms, err := structtag.Get("mapstructure"); err != nil { //no mapstructure tag } else if ms.HasOption("squash") { @@ -437,22 +450,26 @@ func getMapstructureSquashedStruct(topPkg *types.Package, utStruct *types.Struct field = types.NewField(field.Pos(), field.Pkg(), field.Name(), types.NewPointer(types.Typ[types.Bool]), field.Embedded()) case "github.com/hashicorp/packer/provisioner/powershell.ExecutionPolicy": // TODO(azr): unhack this situation field = types.NewField(field.Pos(), field.Pkg(), field.Name(), types.NewPointer(types.Typ[types.String]), field.Embedded()) - } - if str, isStruct := f.Underlying().(*types.Struct); isStruct { - obj := flattenNamed(f, str) - field = types.NewField(field.Pos(), field.Pkg(), field.Name(), obj, field.Embedded()) - field = makePointer(field) - } - if slice, isSlice := f.Underlying().(*types.Slice); isSlice { - if f, fNamed := slice.Elem().(*types.Named); fNamed { - if str, isStruct := f.Underlying().(*types.Struct); isStruct { - // this is a slice of named structs; we want to change - // the struct ref to a 'FlatStruct'. - obj := flattenNamed(f, str) - slice := types.NewSlice(obj) - field = types.NewField(field.Pos(), field.Pkg(), field.Name(), slice, field.Embedded()) + default: + if str, isStruct := f.Underlying().(*types.Struct); isStruct { + obj := flattenNamed(f, str) + field = types.NewField(field.Pos(), field.Pkg(), field.Name(), obj, field.Embedded()) + field = makePointer(field) + } + if slice, isSlice := f.Underlying().(*types.Slice); isSlice { + if f, fNamed := slice.Elem().(*types.Named); fNamed { + if str, isStruct := f.Underlying().(*types.Struct); isStruct { + // this is a slice of named structs; we want to change + // the struct ref to a 'FlatStruct'. + obj := flattenNamed(f, str) + slice := types.NewSlice(obj) + field = types.NewField(field.Pos(), field.Pkg(), field.Name(), slice, field.Embedded()) + } } } + if _, isBasic := f.Underlying().(*types.Basic); isBasic { + field = makePointer(field) + } } case *types.Slice: if f, fNamed := f.Elem().(*types.Named); fNamed { diff --git a/command/build.go b/command/build.go index 7f582d38a..74b21b17f 100644 --- a/command/build.go +++ b/command/build.go @@ -13,6 +13,9 @@ import ( "sync" "syscall" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclparse" + "github.com/hashicorp/packer/hcl2template" "github.com/hashicorp/packer/helper/enumflag" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template" @@ -52,6 +55,7 @@ func (c *BuildCommand) Run(args []string) int { return c.RunContext(buildCtx, args) } +// Config is the command-configuration parsed from the command line. type Config struct { Color, Debug, Force, Timestamp bool ParallelBuilds int64 @@ -92,29 +96,69 @@ func (c *BuildCommand) ParseArgs(args []string) (Config, int) { return cfg, 0 } -func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { - cfg, ret := c.ParseArgs(args) - if ret != 0 { - return ret +func (c *BuildCommand) GetBuildsFromHCL(path string) ([]packer.Build, int) { + parser := &hcl2template.Parser{ + Parser: hclparse.NewParser(), + BuilderSchemas: c.CoreConfig.Components.BuilderStore, + ProvisionersSchemas: c.CoreConfig.Components.ProvisionerStore, + PostProcessorsSchemas: c.CoreConfig.Components.PostProcessorStore, } + builds, diags := parser.Parse(path) + { + // write HCL errors/diagnostics if any. + b := bytes.NewBuffer(nil) + err := hcl.NewDiagnosticTextWriter(b, parser.Files(), 80, false).WriteDiagnostics(diags) + if err != nil { + c.Ui.Error("could not write diagnostic: " + err.Error()) + return nil, 1 + } + if b.Len() != 0 { + c.Ui.Message(b.String()) + } + } + ret := 0 + if diags.HasErrors() { + ret = 1 + } + + return builds, ret +} + +func (c *BuildCommand) GetBuilds(path string) ([]packer.Build, int) { + + isHCLLoaded, err := isHCLLoaded(path) + if path != "-" && err != nil { + c.Ui.Error(fmt.Sprintf("could not tell whether %s is hcl enabled: %s", path, err)) + return nil, 1 + } + if isHCLLoaded { + return c.GetBuildsFromHCL(path) + } + + // TODO: uncomment in v1.5.1 once we've polished HCL a bit more. + // c.Ui.Say(`Legacy JSON Configuration Will Be Used. + // The template will be parsed in the legacy configuration style. This style + // will continue to work but users are encouraged to move to the new style. + // See: https://packer.io/guides/hcl + // `) + // Parse the template var tpl *template.Template - var err error - tpl, err = template.ParseFile(cfg.Path) + tpl, err = template.ParseFile(path) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) - return 1 + return nil, 1 } // Get the core core, err := c.Meta.Core(tpl) if err != nil { c.Ui.Error(err.Error()) - return 1 + return nil, 1 } - // Get the builds we care about + ret := 0 buildNames := c.Meta.BuildNames(core) builds := make([]packer.Build, 0, len(buildNames)) for _, n := range buildNames { @@ -123,11 +167,22 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { c.Ui.Error(fmt.Sprintf( "Failed to initialize build '%s': %s", n, err)) + ret = 1 continue } builds = append(builds, b) } + return builds, ret +} + +func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { + cfg, ret := c.ParseArgs(args) + if ret != 0 { + return ret + } + + builds, ret := c.GetBuilds(cfg.Path) if cfg.Debug { c.Ui.Say("Debug mode enabled. Builds will not be parallelized.") @@ -141,18 +196,17 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { packer.UiColorYellow, packer.UiColorBlue, } - buildUis := make(map[string]packer.Ui) - for i, b := range buildNames { - var ui packer.Ui - ui = c.Ui + buildUis := make(map[packer.Build]packer.Ui) + for i := range builds { + ui := c.Ui if cfg.Color { ui = &packer.ColoredUi{ Color: colors[i%len(colors)], Ui: ui, } if _, ok := c.Ui.(*packer.MachineReadableUi); !ok { - ui.Say(fmt.Sprintf("%s output will be in this color.", b)) - if i+1 == len(buildNames) { + ui.Say(fmt.Sprintf("%s: output will be in this color.", builds[i].Name())) + if i+1 == len(builds) { // Add a newline between the color output and the actual output c.Ui.Say("") } @@ -165,7 +219,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { } } - buildUis[b] = ui + buildUis[builds[i]] = ui } log.Printf("Build debug mode: %v", cfg.Debug) @@ -173,7 +227,8 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { log.Printf("On error: %v", cfg.OnError) // Set the debug and force mode and prepare all the builds - for _, b := range builds { + for i := range builds { + b := builds[i] log.Printf("Preparing build: %s", b.Name()) b.SetDebug(cfg.Debug) b.SetForce(cfg.Force) @@ -185,7 +240,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { return 1 } if len(warnings) > 0 { - ui := buildUis[b.Name()] + ui := buildUis[b] ui.Say(fmt.Sprintf("Warnings for build '%s':\n", b.Name())) for _, warning := range warnings { ui.Say(fmt.Sprintf("* %s", warning)) @@ -200,11 +255,11 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { sync.RWMutex m map[string][]packer.Artifact }{m: make(map[string][]packer.Artifact)} + // Get the builds we care about var errors = struct { sync.RWMutex m map[string]error }{m: make(map[string]error)} - limitParallel := semaphore.NewWeighted(cfg.ParallelBuilds) for i := range builds { if err := buildCtx.Err(); err != nil { @@ -214,7 +269,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { b := builds[i] name := b.Name() - ui := buildUis[name] + ui := buildUis[b] if err := limitParallel.Acquire(buildCtx, 1); err != nil { ui.Error(fmt.Sprintf("Build '%s' failed to acquire semaphore: %s", name, err)) errors.Lock() @@ -338,10 +393,10 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int { if len(errors.m) > 0 { // If any errors occurred, exit with a non-zero exit status - return 1 + ret = 1 } - return 0 + return ret } func (*BuildCommand) Help() string { diff --git a/command/build_parallel_test.go b/command/build_parallel_test.go index a2dc240ae..b6e4ceb8c 100644 --- a/command/build_parallel_test.go +++ b/command/build_parallel_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "path/filepath" "sync" "testing" @@ -28,7 +29,11 @@ type ParallelTestBuilder struct { wg sync.WaitGroup } -func (b *ParallelTestBuilder) Prepare(raws ...interface{}) ([]string, error) { return nil, nil } +func (b *ParallelTestBuilder) ConfigSpec() hcldec.ObjectSpec { return nil } + +func (b *ParallelTestBuilder) Prepare(raws ...interface{}) ([]string, []string, error) { + return nil, nil, nil +} func (b *ParallelTestBuilder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { ui.Say("building") @@ -39,7 +44,9 @@ func (b *ParallelTestBuilder) Run(ctx context.Context, ui packer.Ui, hook packer // LockedBuilder wont run until unlock is called type LockedBuilder struct{ unlock chan interface{} } -func (b *LockedBuilder) Prepare(raws ...interface{}) ([]string, error) { return nil, nil } +func (b *LockedBuilder) ConfigSpec() hcldec.ObjectSpec { return nil } + +func (b *LockedBuilder) Prepare(raws ...interface{}) ([]string, []string, error) { return nil, nil, nil } func (b *LockedBuilder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { ui.Say("locking build") @@ -57,25 +64,13 @@ func testMetaParallel(t *testing.T, builder *ParallelTestBuilder, locked *Locked return Meta{ CoreConfig: &packer.CoreConfig{ Components: packer.ComponentFinder{ - Builder: func(n string) (packer.Builder, error) { - switch n { - case "parallel-test": - return builder, nil - case "file": - return &file.Builder{}, nil - case "lock": - return locked, nil - default: - panic(n) - } + BuilderStore: packer.MapOfBuilder{ + "parallel-test": func() (packer.Builder, error) { return builder, nil }, + "file": func() (packer.Builder, error) { return &file.Builder{}, nil }, + "lock": func() (packer.Builder, error) { return locked, nil }, }, - Provisioner: func(n string) (packer.Provisioner, error) { - switch n { - case "sleep": - return &sleep.Provisioner{}, nil - default: - panic(n) - } + ProvisionerStore: packer.MapOfProvisioner{ + "sleep": func() (packer.Provisioner, error) { return &sleep.Provisioner{}, nil }, }, }, }, diff --git a/command/build_test.go b/command/build_test.go index d96ce6998..ca8252715 100644 --- a/command/build_test.go +++ b/command/build_test.go @@ -12,9 +12,9 @@ import ( "github.com/hashicorp/packer/builder/file" "github.com/hashicorp/packer/builder/null" "github.com/hashicorp/packer/packer" - shell_local "github.com/hashicorp/packer/post-processor/shell-local" + shell_local_pp "github.com/hashicorp/packer/post-processor/shell-local" "github.com/hashicorp/packer/provisioner/shell" - sl "github.com/hashicorp/packer/provisioner/shell-local" + shell_local "github.com/hashicorp/packer/provisioner/shell-local" ) func TestBuildOnlyFileCommaFlags(t *testing.T) { @@ -166,6 +166,30 @@ func TestBuildExceptFileCommaFlags(t *testing.T) { } } +func TestBuildWithNonExistingBuilder(t *testing.T) { + c := &BuildCommand{ + Meta: testMetaFile(t), + } + + args := []string{ + "-parallel=false", + `-except=`, + filepath.Join(testFixture("build-only"), "not-found.json"), + } + + defer cleanup() + + if code := c.Run(args); code != 1 { + t.Errorf("Expected to find exit code 1, found %d", code) + } + if !fileExists("chocolate.txt") { + t.Errorf("Expected to find chocolate.txt") + } + if fileExists("vanilla.txt") { + t.Errorf("NOT expected to find vanilla.tx") + } +} + // fileExists returns true if the filename is found func fileExists(filename string) bool { if _, err := os.Stat(filename); err == nil { @@ -178,22 +202,16 @@ func fileExists(filename string) bool { // available. This allows us to test a builder that writes files to disk. func testCoreConfigBuilder(t *testing.T) *packer.CoreConfig { components := packer.ComponentFinder{ - Builder: func(n string) (packer.Builder, error) { - if n == "file" { - return &file.Builder{}, nil - } - return &null.Builder{}, nil + BuilderStore: packer.MapOfBuilder{ + "file": func() (packer.Builder, error) { return &file.Builder{}, nil }, + "null": func() (packer.Builder, error) { return &null.Builder{}, nil }, }, - Provisioner: func(n string) (packer.Provisioner, error) { - if n == "shell" { - return &shell.Provisioner{}, nil - } else if n == "shell-local" { - return &sl.Provisioner{}, nil - } - return nil, fmt.Errorf("requested provisioner not implemented in this test") + ProvisionerStore: packer.MapOfProvisioner{ + "shell-local": func() (packer.Provisioner, error) { return &shell_local.Provisioner{}, nil }, + "shell": func() (packer.Provisioner, error) { return &shell.Provisioner{}, nil }, }, - PostProcessor: func(n string) (packer.PostProcessor, error) { - return &shell_local.PostProcessor{}, nil + PostProcessorStore: packer.MapOfPostProcessor{ + "shell-local": func() (packer.PostProcessor, error) { return &shell_local_pp.PostProcessor{}, nil }, }, } return &packer.CoreConfig{ diff --git a/command/build_timeout_test.go b/command/build_timeout_test.go index bba3bbca7..5657a37b0 100644 --- a/command/build_timeout_test.go +++ b/command/build_timeout_test.go @@ -15,23 +15,12 @@ import ( // available. This allows us to test a builder that writes files to disk. func testCoreConfigSleepBuilder(t *testing.T) *packer.CoreConfig { components := packer.ComponentFinder{ - Builder: func(n string) (packer.Builder, error) { - switch n { - case "file": - return &file.Builder{}, nil - default: - panic(n) - } + BuilderStore: packer.MapOfBuilder{ + "file": func() (packer.Builder, error) { return &file.Builder{}, nil }, }, - Provisioner: func(n string) (packer.Provisioner, error) { - switch n { - case "shell-local": - return &shell_local.Provisioner{}, nil - case "sleep": - return &sleep.Provisioner{}, nil - default: - panic(n) - } + ProvisionerStore: packer.MapOfProvisioner{ + "sleep": func() (packer.Provisioner, error) { return &sleep.Provisioner{}, nil }, + "shell-local": func() (packer.Provisioner, error) { return &shell_local.Provisioner{}, nil }, }, } return &packer.CoreConfig{ diff --git a/command/test-fixtures/build-only/not-found.json b/command/test-fixtures/build-only/not-found.json new file mode 100644 index 000000000..4f327d82f --- /dev/null +++ b/command/test-fixtures/build-only/not-found.json @@ -0,0 +1,41 @@ +{ + "builders": [ + { + "name": "chocolate", + "type": "file", + "content": "chocolate", + "target": "chocolate.txt" + }, + { + "name": "vanilla", + "type": "non-existing", + "content": "vanilla", + "target": "vanilla.txt" + } + ], + "post-processors": [ + [ + { + "only": [ + "vanilla" + ], + "name": "tomato", + "type": "shell-local", + "inline": [ + "echo tomato > tomato.txt" + ] + } + ], + [ + { + "only": [ + "chocolate" + ], + "type": "shell-local", + "inline": [ + "echo unnamed > unnamed.txt" + ] + } + ] + ] +} \ No newline at end of file diff --git a/command/utils.go b/command/utils.go new file mode 100644 index 000000000..1123a2f0a --- /dev/null +++ b/command/utils.go @@ -0,0 +1,22 @@ +package command + +import ( + "os" + "strings" +) + +func isDir(name string) (bool, error) { + s, err := os.Stat(name) + if err != nil { + return false, err + } + return s.IsDir(), nil +} + +func isHCLLoaded(name string) (bool, error) { + if strings.HasSuffix(name, ".pkr.hcl") || + strings.HasSuffix(name, ".pkr.json") { + return true, nil + } + return isDir(name) +} diff --git a/common/shell-local/config.go b/common/shell-local/config.go index e4c808de9..37c2605f8 100644 --- a/common/shell-local/config.go +++ b/common/shell-local/config.go @@ -5,6 +5,7 @@ package shell_local import ( "errors" "fmt" + // "log" "os" "path/filepath" "runtime" @@ -41,16 +42,15 @@ type Config struct { // End dedupe with postprocessor UseLinuxPathing bool `mapstructure:"use_linux_pathing"` - ctx interpolate.Context + // used to track the data sent to shell-local from the builder + // GeneratedData + + ctx interpolate.Context + generatedData map[string]interface{} } func Decode(config *Config, raws ...interface{}) error { - //Create passthrough for winrm password so we can fill it in once we know it - config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: `{{.WinRMPassword}}`, - } - - err := configHelper.Decode(&config, &configHelper.DecodeOpts{ + err := configHelper.Decode(config, &configHelper.DecodeOpts{ Interpolate: true, InterpolateContext: &config.ctx, InterpolateFilter: &interpolate.RenderFilter{ @@ -60,7 +60,8 @@ func Decode(config *Config, raws ...interface{}) error { }, }, raws...) if err != nil { - return fmt.Errorf("Error decoding config: %s, config is %#v, and raws is %#v", err, config, raws) + return fmt.Errorf("Error decoding config: %s", err) + // return fmt.Errorf("Error decoding config: %s, config is %#v, and raws is %#v", err, config, raws) } return nil diff --git a/common/shell-local/config.hcl2spec.go b/common/shell-local/config.hcl2spec.go index 257cd390d..e65a3a89d 100644 --- a/common/shell-local/config.hcl2spec.go +++ b/common/shell-local/config.hcl2spec.go @@ -33,10 +33,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/common/shell-local/run.go b/common/shell-local/run.go index 06b8c78d3..9f7170394 100644 --- a/common/shell-local/run.go +++ b/common/shell-local/run.go @@ -11,24 +11,20 @@ import ( "strings" "github.com/hashicorp/packer/common" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer/tmp" "github.com/hashicorp/packer/template/interpolate" ) -type ExecuteCommandTemplate struct { - Vars string - Script string - Command string - WinRMPassword string -} - -type EnvVarsTemplate struct { - WinRMPassword string -} - -func Run(ctx context.Context, ui packer.Ui, config *Config) (bool, error) { +func Run(ctx context.Context, ui packer.Ui, config *Config, generatedData map[string]interface{}) (bool, error) { + if generatedData != nil { + config.generatedData = generatedData + } else { + // No fear; probably just in the post-processor, not provisioner. + // Make sure it's not a nil map so we can assign to it later. + config.generatedData = make(map[string]interface{}) + } + config.ctx.Data = generatedData // Check if shell-local can even execute against this runtime OS if len(config.OnlyOn) > 0 { runCommand := false @@ -120,11 +116,6 @@ func createInlineScriptFile(config *Config) (string, error) { writer.WriteString(shebang) } - // generate context so you can interpolate the command - config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(config.PackerBuildName), - } - for _, command := range config.Inline { // interpolate command to check for template variables. command, err := interpolate.Render(command, &config.ctx) @@ -152,12 +143,11 @@ func createInlineScriptFile(config *Config) (string, error) { // user-provided ExecuteCommand or defaulting to something that makes sense for // the host OS func createInterpolatedCommands(config *Config, script string, flattenedEnvVars string) ([]string, error) { - config.ctx.Data = &ExecuteCommandTemplate{ - Vars: flattenedEnvVars, - Script: script, - Command: script, - WinRMPassword: getWinRMPassword(config.PackerBuildName), - } + config.generatedData["Vars"] = flattenedEnvVars + config.generatedData["Script"] = script + config.generatedData["Command"] = script + + config.ctx.Data = config.generatedData interpolatedCmds := make([]string, len(config.ExecuteCommand)) for i, cmd := range config.ExecuteCommand { @@ -192,10 +182,6 @@ func createFlattenedEnvVars(config *Config) (string, error) { envVars["PACKER_HTTP_PORT"] = httpPort } - // interpolate environment variables - config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(config.PackerBuildName), - } // Split vars into key/value components for _, envVar := range config.Vars { envVar, err := interpolate.Render(envVar, &config.ctx) @@ -221,9 +207,3 @@ func createFlattenedEnvVars(config *Config) (string, error) { } return flattened, nil } - -func getWinRMPassword(buildName string) string { - winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) - packer.LogSecretFilter.Set(winRMPass) - return winRMPass -} diff --git a/common/step_download.go b/common/step_download.go index a1fa6db89..3b0b84932 100644 --- a/common/step_download.go +++ b/common/step_download.go @@ -53,12 +53,18 @@ type StepDownload struct { } func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + if len(s.Url) == 0 { + log.Printf("No URLs were provided to Step Download. Continuing...") + return multistep.ActionContinue + } + defer log.Printf("Leaving retrieve loop for %s", s.Description) ui := state.Get("ui").(packer.Ui) ui.Say(fmt.Sprintf("Retrieving %s", s.Description)) var errs []error + for _, source := range s.Url { if ctx.Err() != nil { state.Put("error", fmt.Errorf("Download cancelled: %v", errs)) diff --git a/common/step_download_test.go b/common/step_download_test.go index 6a5a70bd8..cffed33d8 100644 --- a/common/step_download_test.go +++ b/common/step_download_test.go @@ -63,6 +63,11 @@ func TestStepDownload_Run(t *testing.T) { want multistep.StepAction wantFiles []string }{ + {"Empty URL field passes", + fields{Url: []string{}}, + multistep.ActionContinue, + nil, + }, {"not passing a checksum passes", fields{Url: []string{abs(t, "./test-fixtures/root/another.txt")}}, multistep.ActionContinue, diff --git a/common/step_provision.go b/common/step_provision.go index 07ba39f81..c9c399e16 100644 --- a/common/step_provision.go +++ b/common/step_provision.go @@ -4,8 +4,10 @@ import ( "context" "fmt" "log" + "os" "time" + "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -19,6 +21,57 @@ import ( // // Produces: // + +func PopulateProvisionHookData(state multistep.StateBag) map[string]interface{} { + hookData := make(map[string]interface{}) + + // Load Builder hook data from state, if it has been set. + hd, ok := state.GetOk("generated_data") + if ok { + hookData = hd.(map[string]interface{}) + } + + // instance_id is placed in state by the builders. + // Not yet implemented in Chroot, lxc/lxd, Azure, Qemu. + // Implemented in most others including digitalOcean (droplet id), + // docker (container_id), and clouds which use "server" internally instead + // of instance. + + // Also note that Chroot and lxc/lxd builders tend to have their own custom + // step_provision, so they won't use this code path. + id, ok := state.GetOk("instance_id") + if ok { + hookData["ID"] = id + } else { + // Warn user that the id isn't implemented + hookData["ID"] = "ERR_ID_NOT_IMPLEMENTED_BY_BUILDER" + } + hookData["PackerRunUUID"] = os.Getenv("PACKER_RUN_UUID") + + // Read communicator data into hook data + comm, ok := state.GetOk("communicator_config") + if !ok { + log.Printf("Unable to load config from state to populate provisionHookData") + return hookData + } + commConf := comm.(*communicator.Config) + + // Loop over all field values and retrieve them from the ssh config + hookData["Host"] = commConf.Host() + hookData["Port"] = commConf.Port() + hookData["User"] = commConf.User() + hookData["Password"] = commConf.Password() + hookData["ConnType"] = commConf.Type + hookData["SSHPublicKey"] = commConf.SSHPublicKey + hookData["SSHPrivateKey"] = commConf.SSHPrivateKey + + // Backwards compatibility; in practice, WinRMPassword is fulfilled by + // Password. + hookData["WinRMPassword"] = commConf.WinRMPassword + + return hookData +} + type StepProvision struct { Comm packer.Communicator } @@ -32,9 +85,12 @@ func (s *StepProvision) runWithHook(ctx context.Context, state multistep.StateBa comm = raw.(packer.Communicator) } } + hook := state.Get("hook").(packer.Hook) ui := state.Get("ui").(packer.Ui) + hookData := PopulateProvisionHookData(state) + // Run the provisioner in a goroutine so we can continually check // for cancellations... if hooktype == packer.HookProvision { @@ -44,7 +100,7 @@ func (s *StepProvision) runWithHook(ctx context.Context, state multistep.StateBa } errCh := make(chan error, 1) go func() { - errCh <- hook.Run(ctx, hooktype, ui, comm, nil) + errCh <- hook.Run(ctx, hooktype, ui, comm, hookData) }() for { diff --git a/config.go b/config.go index 1b7946b24..6270f466b 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "runtime" + "sort" "strings" "github.com/hashicorp/packer/command" @@ -27,9 +28,9 @@ type config struct { PluginMinPort int PluginMaxPort int - Builders map[string]string - PostProcessors map[string]string `json:"post-processors"` - Provisioners map[string]string + Builders packer.MapOfBuilder + Provisioners packer.MapOfProvisioner + PostProcessors packer.MapOfPostProcessor `json:"post-processors"` } // Decodes configuration in JSON format from the given io.Reader into @@ -58,7 +59,7 @@ func (c *config) Discover() error { if err != nil { log.Printf("[ERR] Error loading exe directory: %s", err) } else { - if err := c.discover(filepath.Dir(exePath)); err != nil { + if err := c.discoverExternalComponents(filepath.Dir(exePath)); err != nil { return err } } @@ -68,19 +69,19 @@ func (c *config) Discover() error { if err != nil { log.Printf("[ERR] Error loading config directory: %s", err) } else { - if err := c.discover(filepath.Join(dir, "plugins")); err != nil { + if err := c.discoverExternalComponents(filepath.Join(dir, "plugins")); err != nil { return err } } // Next, look in the CWD. - if err := c.discover("."); err != nil { + if err := c.discoverExternalComponents("."); err != nil { return err } // Finally, try to use an internal plugin. Note that this will not override // any previously-loaded plugins. - if err := c.discoverInternal(); err != nil { + if err := c.discoverInternalComponents(); err != nil { return err } @@ -89,51 +90,33 @@ func (c *config) Discover() error { // This is a proper packer.BuilderFunc that can be used to load packer.Builder // implementations from the defined plugins. -func (c *config) LoadBuilder(name string) (packer.Builder, error) { +func (c *config) StartBuilder(name string) (packer.Builder, error) { log.Printf("Loading builder: %s\n", name) - bin, ok := c.Builders[name] - if !ok { - log.Printf("Builder not found: %s\n", name) - return nil, nil - } - - return c.pluginClient(bin).Builder() + return c.Builders.Start(name) } // This is a proper implementation of packer.HookFunc that can be used // to load packer.Hook implementations from the defined plugins. -func (c *config) LoadHook(name string) (packer.Hook, error) { +func (c *config) StarHook(name string) (packer.Hook, error) { log.Printf("Loading hook: %s\n", name) return c.pluginClient(name).Hook() } // This is a proper packer.PostProcessorFunc that can be used to load // packer.PostProcessor implementations from defined plugins. -func (c *config) LoadPostProcessor(name string) (packer.PostProcessor, error) { +func (c *config) StartPostProcessor(name string) (packer.PostProcessor, error) { log.Printf("Loading post-processor: %s", name) - bin, ok := c.PostProcessors[name] - if !ok { - log.Printf("Post-processor not found: %s", name) - return nil, nil - } - - return c.pluginClient(bin).PostProcessor() + return c.PostProcessors.Start(name) } // This is a proper packer.ProvisionerFunc that can be used to load // packer.Provisioner implementations from defined plugins. -func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) { +func (c *config) StartProvisioner(name string) (packer.Provisioner, error) { log.Printf("Loading provisioner: %s\n", name) - bin, ok := c.Provisioners[name] - if !ok { - log.Printf("Provisioner not found: %s\n", name) - return nil, nil - } - - return c.pluginClient(bin).Provisioner() + return c.Provisioners.Start(name) } -func (c *config) discover(path string) error { +func (c *config) discoverExternalComponents(path string) error { var err error if !filepath.IsAbs(path) { @@ -142,32 +125,69 @@ func (c *config) discover(path string) error { return err } } + externallyUsed := []string{} - err = c.discoverSingle( - filepath.Join(path, "packer-builder-*"), &c.Builders) + pluginPaths, err := c.discoverSingle(filepath.Join(path, "packer-builder-*")) if err != nil { return err } + for pluginName, pluginPath := range pluginPaths { + newPath := pluginPath // this needs to be stored in a new variable for the func below + c.Builders[pluginName] = func() (packer.Builder, error) { + return c.pluginClient(newPath).Builder() + } + externallyUsed = append(externallyUsed, pluginName) + } + if len(externallyUsed) > 0 { + sort.Strings(externallyUsed) + log.Printf("using external builders %v", externallyUsed) + externallyUsed = nil + } - err = c.discoverSingle( - filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors) + pluginPaths, err = c.discoverSingle(filepath.Join(path, "packer-post-processor-*")) if err != nil { return err } + for pluginName, pluginPath := range pluginPaths { + newPath := pluginPath // this needs to be stored in a new variable for the func below + c.PostProcessors[pluginName] = func() (packer.PostProcessor, error) { + return c.pluginClient(newPath).PostProcessor() + } + externallyUsed = append(externallyUsed, pluginName) + } + if len(externallyUsed) > 0 { + sort.Strings(externallyUsed) + log.Printf("using external post-processors %v", externallyUsed) + externallyUsed = nil + } - return c.discoverSingle( - filepath.Join(path, "packer-provisioner-*"), &c.Provisioners) + pluginPaths, err = c.discoverSingle(filepath.Join(path, "packer-provisioner-*")) + if err != nil { + return err + } + for pluginName, pluginPath := range pluginPaths { + newPath := pluginPath // this needs to be stored in a new variable for the func below + c.Provisioners[pluginName] = func() (packer.Provisioner, error) { + return c.pluginClient(newPath).Provisioner() + } + externallyUsed = append(externallyUsed, pluginName) + } + if len(externallyUsed) > 0 { + sort.Strings(externallyUsed) + log.Printf("using external provisioners %v", externallyUsed) + externallyUsed = nil + } + + return nil } -func (c *config) discoverSingle(glob string, m *map[string]string) error { +func (c *config) discoverSingle(glob string) (map[string]string, error) { matches, err := filepath.Glob(glob) if err != nil { - return err + return nil, err } - if *m == nil { - *m = make(map[string]string) - } + res := make(map[string]string) prefix := filepath.Base(glob) prefix = prefix[:strings.Index(prefix, "*")] @@ -189,15 +209,15 @@ func (c *config) discoverSingle(glob string, m *map[string]string) error { } // Look for foo-bar-baz. The plugin name is "baz" - plugin := file[len(prefix):] - log.Printf("[DEBUG] Discovered plugin: %s = %s", plugin, match) - (*m)[plugin] = match + pluginName := file[len(prefix):] + log.Printf("[DEBUG] Discovered plugin: %s = %s", pluginName, match) + res[pluginName] = match } - return nil + return res, nil } -func (c *config) discoverInternal() error { +func (c *config) discoverInternalComponents() error { // Get the packer binary path packerPath, err := osext.Executable() if err != nil { @@ -206,31 +226,38 @@ func (c *config) discoverInternal() error { } for builder := range command.Builders { + builder := builder _, found := (c.Builders)[builder] if !found { - log.Printf("Using internal plugin for %s", builder) - (c.Builders)[builder] = fmt.Sprintf("%s%splugin%spacker-builder-%s", - packerPath, PACKERSPACE, PACKERSPACE, builder) + c.Builders[builder] = func() (packer.Builder, error) { + bin := fmt.Sprintf("%s%splugin%spacker-builder-%s", + packerPath, PACKERSPACE, PACKERSPACE, builder) + return c.pluginClient(bin).Builder() + } } } for provisioner := range command.Provisioners { + provisioner := provisioner _, found := (c.Provisioners)[provisioner] if !found { - log.Printf("Using internal plugin for %s", provisioner) - (c.Provisioners)[provisioner] = fmt.Sprintf( - "%s%splugin%spacker-provisioner-%s", - packerPath, PACKERSPACE, PACKERSPACE, provisioner) + c.Provisioners[provisioner] = func() (packer.Provisioner, error) { + bin := fmt.Sprintf("%s%splugin%spacker-provisioner-%s", + packerPath, PACKERSPACE, PACKERSPACE, provisioner) + return c.pluginClient(bin).Provisioner() + } } } for postProcessor := range command.PostProcessors { + postProcessor := postProcessor _, found := (c.PostProcessors)[postProcessor] if !found { - log.Printf("Using internal plugin for %s", postProcessor) - (c.PostProcessors)[postProcessor] = fmt.Sprintf( - "%s%splugin%spacker-post-processor-%s", - packerPath, PACKERSPACE, PACKERSPACE, postProcessor) + c.PostProcessors[postProcessor] = func() (packer.PostProcessor, error) { + bin := fmt.Sprintf("%s%splugin%spacker-post-processor-%s", + packerPath, PACKERSPACE, PACKERSPACE, postProcessor) + return c.pluginClient(bin).PostProcessor() + } } } @@ -240,12 +267,20 @@ func (c *config) discoverInternal() error { func (c *config) pluginClient(path string) *plugin.Client { originalPath := path + // Check for special case using `packer plugin PLUGIN` + args := []string{} + if strings.Contains(path, PACKERSPACE) { + parts := strings.Split(path, PACKERSPACE) + path = parts[0] + args = parts[1:] + } + // First attempt to find the executable by consulting the PATH. path, err := exec.LookPath(path) if err != nil { // If that doesn't work, look for it in the same directory // as the `packer` executable (us). - log.Printf("Plugin could not be found. Checking same directory as executable.") + log.Printf("Plugin could not be found at %s (%v). Checking same directory as executable.", originalPath, err) exePath, err := osext.Executable() if err != nil { log.Printf("Couldn't get current exe path: %s", err) @@ -255,14 +290,6 @@ func (c *config) pluginClient(path string) *plugin.Client { } } - // Check for special case using `packer plugin PLUGIN` - args := []string{} - if strings.Contains(path, PACKERSPACE) { - parts := strings.Split(path, PACKERSPACE) - path = parts[0] - args = parts[1:] - } - // If everything failed, just use the original path and let the error // bubble through. if path == "" { diff --git a/fix/fixer_galaxy_command.go b/fix/fixer_galaxy_command.go index 599f6b611..5e2ed8a37 100644 --- a/fix/fixer_galaxy_command.go +++ b/fix/fixer_galaxy_command.go @@ -66,5 +66,5 @@ func (FixerGalaxyCommand) Fix(input map[string]interface{}) (map[string]interfac } func (FixerGalaxyCommand) Synopsis() string { - return `Replaces "galaxycommad" in ansible-local provisioner configs with "galaxy_command"` + return `Replaces "galaxycommand" in ansible-local provisioner configs with "galaxy_command"` } diff --git a/go.mod b/go.mod index 27db76320..299a7f14d 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl/v2 v2.0.0 + github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 // indirect github.com/hashicorp/serf v0.8.2 // indirect github.com/hashicorp/vault v1.1.0 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d @@ -124,8 +125,6 @@ require ( github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b - github.com/onsi/ginkgo v1.7.0 // indirect - github.com/onsi/gomega v1.4.3 // indirect github.com/oracle/oci-go-sdk v1.8.0 github.com/outscale/osc-go v0.0.1 github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a @@ -157,7 +156,7 @@ require ( github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0 github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829 - github.com/zclconf/go-cty v1.1.0 + github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af go.opencensus.io v0.22.2 // indirect golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e golang.org/x/exp v0.0.0-20191129062945-2f5052295587 // indirect diff --git a/go.sum b/go.sum index 434ccc196..f6676f405 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,7 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3 h1:3b+p838vN4sc37brz9W2HDphtSwZFcXZwFLyzm5Vk28= github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU= +github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae h1:2Zmk+8cNvAGuY8AyvZuWpUdpQUAXwfom4ReVMe/CTIo= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= @@ -222,6 +223,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJ github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw= github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8= @@ -234,6 +236,7 @@ github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxB github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79 h1:RKu7yAXZTaQsxj1K9GDsh+QVw0+Wu1SWHxtbFN0n+hE= @@ -266,6 +269,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8= github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90= +github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 h1:PFfGModn55JA0oBsvFghhj0v93me+Ctr3uHC/UmFAls= +github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80/go.mod h1:Cxv+IJLuBiEhQ7pBYGEuORa0nr4U994pE8mYLuFd7v0= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= @@ -290,6 +295,7 @@ github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961 h1:a2 github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -476,6 +482,7 @@ github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1 h1:U6ufy3mLDgg9RYupntOvA github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvcciqcxEHac4CYj89xI= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311 h1:s5pyxd5S6wRs2WpEE0xRfWUF46Wbz44h203KnbX0ecI= github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= @@ -485,8 +492,13 @@ github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e h1:hzwq5G github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829 h1:2FGwbx03GpP1Ulzg/L46tSoKh9t4yg8BhMKQl/Ff1x8= github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854= +github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.1.1 h1:Shl2p9Dat0cqJfXu0DZa+cOTRPhXQjK8IYWD6GVfiqo= +github.com/zclconf/go-cty v1.1.1/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af h1:4arg31xOP/qIUV1YVbCWJtChPGzwGzgmlucVbddUq+Y= +github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= @@ -552,6 +564,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJV golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= @@ -707,6 +720,7 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= 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 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/hcl2template/common_test.go b/hcl2template/common_test.go new file mode 100644 index 000000000..474faa88c --- /dev/null +++ b/hcl2template/common_test.go @@ -0,0 +1,237 @@ +package hcl2template + +import ( + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclparse" + "github.com/hashicorp/packer/helper/config" + "github.com/hashicorp/packer/packer" + "github.com/zclconf/go-cty/cty" +) + +func getBasicParser() *Parser { + return &Parser{ + Parser: hclparse.NewParser(), + BuilderSchemas: mapOfBuilder(map[string]packer.Builder{ + "amazon-ebs": &MockBuilder{}, + "virtualbox-iso": &MockBuilder{}, + }), + ProvisionersSchemas: mapOfProvisioner(map[string]packer.Provisioner{ + "shell": &MockProvisioner{}, + "file": &MockProvisioner{}, + }), + PostProcessorsSchemas: mapOfPostProcessor(map[string]packer.PostProcessor{ + "amazon-import": &MockPostProcessor{}, + }), + } +} + +type mapOfBuilder map[string]packer.Builder + +func (mob mapOfBuilder) Has(builder string) bool { + _, res := mob[builder] + return res +} + +func (mob mapOfBuilder) Start(builder string) (packer.Builder, error) { + d, found := mob[builder] + var err error + if !found { + err = fmt.Errorf("Unknown entry %s", builder) + } + return d, err +} + +func (mob mapOfBuilder) List() []string { + res := []string{} + for k := range mob { + res = append(res, k) + } + return res +} + +type mapOfCommunicator map[string]packer.ConfigurableCommunicator + +func (mob mapOfCommunicator) Start(communicator string) (packer.ConfigurableCommunicator, error) { + c, found := mob[communicator] + var err error + if !found { + err = fmt.Errorf("Unknown entry %s", communicator) + } + return c, err +} + +type mapOfProvisioner map[string]packer.Provisioner + +func (mop mapOfProvisioner) Has(provisioner string) bool { + _, res := mop[provisioner] + return res +} + +func (mop mapOfProvisioner) Start(provisioner string) (packer.Provisioner, error) { + p, found := mop[provisioner] + var err error + if !found { + err = fmt.Errorf("Unknown provisioner %s", provisioner) + } + return p, err +} + +func (mod mapOfProvisioner) List() []string { + res := []string{} + for k := range mod { + res = append(res, k) + } + return res +} + +type mapOfPostProcessor map[string]packer.PostProcessor + +func (mop mapOfPostProcessor) Has(provisioner string) bool { + _, res := mop[provisioner] + return res +} + +func (mop mapOfPostProcessor) Start(postProcessor string) (packer.PostProcessor, error) { + p, found := mop[postProcessor] + var err error + if !found { + err = fmt.Errorf("Unknown post-processor %s", postProcessor) + } + return p, err +} + +func (mod mapOfPostProcessor) List() []string { + res := []string{} + for k := range mod { + res = append(res, k) + } + return res +} + +type parseTestArgs struct { + filename string +} + +type parseTest struct { + name string + parser *Parser + args parseTestArgs + + parseWantCfg *PackerConfig + parseWantDiags bool + parseWantDiagHasErrors bool + + getBuildsWantBuilds []packer.Build + getBuildsWantDiags bool + // getBuildsWantDiagHasErrors bool +} + +func testParse(t *testing.T, tests []parseTest) { + t.Helper() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCfg, gotDiags := tt.parser.parse(tt.args.filename) + if tt.parseWantDiags == (gotDiags == nil) { + t.Fatalf("Parser.parse() unexpected diagnostics. %s", gotDiags) + } + if tt.parseWantDiagHasErrors != gotDiags.HasErrors() { + t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags) + } + if diff := cmp.Diff(tt.parseWantCfg, gotCfg, + cmpopts.IgnoreUnexported(cty.Value{}, Source{}, ProvisionerBlock{}, PostProcessorBlock{}), + cmpopts.IgnoreTypes(HCL2Ref{}), + cmpopts.IgnoreTypes([]hcl.Range{}), + cmpopts.IgnoreTypes(hcl.Range{}), + cmpopts.IgnoreInterfaces(struct{ hcl.Expression }{}), + cmpopts.IgnoreInterfaces(struct{ hcl.Body }{}), + ); diff != "" { + t.Fatalf("Parser.parse() wrong packer config. %s", diff) + } + if gotDiags.HasErrors() { + return + } + + gotBuilds, gotDiags := tt.parser.getBuilds(gotCfg) + if tt.getBuildsWantDiags == (gotDiags == nil) { + t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags) + } + if diff := cmp.Diff(tt.getBuildsWantBuilds, gotBuilds, + cmpopts.IgnoreUnexported(packer.CoreBuild{}, + packer.CoreBuildProvisioner{}, + packer.CoreBuildPostProcessor{}, + ), + ); diff != "" { + t.Fatalf("Parser.getBuilds() wrong packer builds. %s", diff) + } + }) + } +} + +var ( + // everything in the tests is a basicNestedMockConfig this allow to test + // each known type to packer ( and embedding ) in one go. + basicNestedMockConfig = NestedMockConfig{ + String: "string", + Int: 42, + Int64: 43, + Bool: true, + Trilean: config.TriTrue, + Duration: 10 * time.Second, + MapStringString: map[string]string{ + "a": "b", + "c": "d", + }, + SliceString: []string{ + "a", + "b", + "c", + }, + } + + basicMockBuilder = &MockBuilder{ + Config: MockConfig{ + NestedMockConfig: basicNestedMockConfig, + Nested: basicNestedMockConfig, + NestedSlice: []NestedMockConfig{ + basicNestedMockConfig, + basicNestedMockConfig, + }, + }, + } + + basicMockProvisioner = &MockProvisioner{ + Config: MockConfig{ + NestedMockConfig: basicNestedMockConfig, + Nested: basicNestedMockConfig, + NestedSlice: []NestedMockConfig{ + {}, + }, + }, + } + basicMockPostProcessor = &MockPostProcessor{ + Config: MockConfig{ + NestedMockConfig: basicNestedMockConfig, + Nested: basicNestedMockConfig, + NestedSlice: []NestedMockConfig{ + {}, + }, + }, + } + basicMockCommunicator = &MockCommunicator{ + Config: MockConfig{ + NestedMockConfig: basicNestedMockConfig, + Nested: basicNestedMockConfig, + NestedSlice: []NestedMockConfig{ + {}, + }, + }, + } +) diff --git a/hcl2template/config_load.go b/hcl2template/config_load.go deleted file mode 100644 index 61bd281aa..000000000 --- a/hcl2template/config_load.go +++ /dev/null @@ -1,101 +0,0 @@ -package hcl2template - -import ( - "fmt" - - "github.com/hashicorp/hcl/v2" - "github.com/zclconf/go-cty/cty" -) - -type Artifacts map[ArtifactRef]*Artifact - -type Artifact struct { - Type string - Name string - - DeclRange hcl.Range - - Config hcl.Body -} - -func (a *Artifact) Ref() ArtifactRef { - return ArtifactRef{ - Type: a.Type, - Name: a.Name, - } -} - -type ArtifactRef struct { - Type string - Name string -} - -// NoArtifact is the zero value of ArtifactRef, representing the absense of an -// artifact. -var NoArtifact ArtifactRef - -func artifactRefFromAbsTraversal(t hcl.Traversal) (ArtifactRef, hcl.Diagnostics) { - var diags hcl.Diagnostics - if len(t) != 3 { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid artifact reference", - Detail: "An artifact reference must have three parts separated by periods: the keyword \"artifact\", the builder type name, and the artifact name.", - Subject: t.SourceRange().Ptr(), - }) - return NoArtifact, diags - } - - if t.RootName() != "artifact" { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid artifact reference", - Detail: "The first part of an artifact reference must be the keyword \"artifact\".", - Subject: t[0].SourceRange().Ptr(), - }) - return NoArtifact, diags - } - btStep, ok := t[1].(hcl.TraverseAttr) - if !ok { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid artifact reference", - Detail: "The second part of an artifact reference must be an identifier giving the builder type of the artifact.", - Subject: t[1].SourceRange().Ptr(), - }) - return NoArtifact, diags - } - nameStep, ok := t[2].(hcl.TraverseAttr) - if !ok { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid artifact reference", - Detail: "The third part of an artifact reference must be an identifier giving the name of the artifact.", - Subject: t[2].SourceRange().Ptr(), - }) - return NoArtifact, diags - } - - return ArtifactRef{ - Type: btStep.Name, - Name: nameStep.Name, - }, diags -} - -func (r ArtifactRef) String() string { - return fmt.Sprintf("%s.%s", r.Type, r.Name) -} - -// decodeBodyWithoutSchema is a generic alternative to hcldec.Decode that -// just extracts whatever attributes are present and rejects any nested blocks, -// for compatibility with legacy builders that can't provide explicit schema. -func decodeBodyWithoutSchema(body hcl.Body, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { - attrs, diags := body.JustAttributes() - vals := make(map[string]cty.Value) - for name, attr := range attrs { - val, moreDiags := attr.Expr.Value(ctx) - diags = append(diags, moreDiags...) - vals[name] = val - } - return cty.ObjectVal(vals), diags -} diff --git a/hcl2template/decode.go b/hcl2template/decode.go new file mode 100644 index 000000000..e7be2136e --- /dev/null +++ b/hcl2template/decode.go @@ -0,0 +1,15 @@ +package hcl2template + +import ( + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +type Decodable interface { + ConfigSpec() hcldec.ObjectSpec +} + +func decodeHCL2Spec(block *hcl.Block, ctx *hcl.EvalContext, dec Decodable) (cty.Value, hcl.Diagnostics) { + return hcldec.Decode(block.Body, dec.ConfigSpec(), ctx) +} diff --git a/hcl2template/load_test.go b/hcl2template/load_test.go deleted file mode 100644 index 0f4d6f91d..000000000 --- a/hcl2template/load_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package hcl2template - -import ( - "testing" - - awscommon "github.com/hashicorp/packer/builder/amazon/common" - - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclparse" - "github.com/zclconf/go-cty/cty" - - "github.com/hashicorp/packer/helper/communicator" - - amazonebs "github.com/hashicorp/packer/builder/amazon/ebs" - "github.com/hashicorp/packer/builder/virtualbox/iso" - - "github.com/hashicorp/packer/provisioner/file" - "github.com/hashicorp/packer/provisioner/shell" - - amazon_import "github.com/hashicorp/packer/post-processor/amazon-import" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" -) - -func getBasicParser() *Parser { - return &Parser{ - Parser: hclparse.NewParser(), - ProvisionersSchemas: map[string]Decodable{ - "shell": &shell.Config{}, - "file": &file.Config{}, - }, - PostProvisionersSchemas: map[string]Decodable{ - "amazon-import": &amazon_import.Config{}, - }, - CommunicatorSchemas: map[string]Decodable{ - "ssh": &communicator.SSH{}, - "winrm": &communicator.WinRM{}, - }, - SourceSchemas: map[string]Decodable{ - "amazon-ebs": &amazonebs.Config{}, - "virtualbox-iso": &iso.Config{}, - }, - } -} - -func TestParser_ParseFile(t *testing.T) { - defaultParser := getBasicParser() - - type fields struct { - Parser *hclparse.Parser - } - type args struct { - filename string - cfg *PackerConfig - } - tests := []struct { - name string - parser *Parser - args args - wantPackerConfig *PackerConfig - wantDiags bool - }{ - { - "valid " + sourceLabel + " load", - defaultParser, - args{"testdata/sources/basic.pkr.hcl", new(PackerConfig)}, - &PackerConfig{ - Sources: map[SourceRef]*Source{ - SourceRef{ - Type: "virtualbox-iso", - Name: "ubuntu-1204", - }: { - Type: "virtualbox-iso", - Name: "ubuntu-1204", - Cfg: &iso.FlatConfig{ - HTTPDir: strPtr("xxx"), - ISOChecksum: strPtr("769474248a3897f4865817446f9a4a53"), - ISOChecksumType: strPtr("md5"), - RawSingleISOUrl: strPtr("http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso"), - BootCommand: []string{"..."}, - ShutdownCommand: strPtr("echo 'vagrant' | sudo -S shutdown -P now"), - BootWait: strPtr("10s"), - VBoxManage: [][]string{}, - VBoxManagePost: [][]string{}, - }, - }, - SourceRef{ - Type: "amazon-ebs", - Name: "ubuntu-1604", - }: { - Type: "amazon-ebs", - Name: "ubuntu-1604", - Cfg: &amazonebs.FlatConfig{ - RawRegion: strPtr("eu-west-3"), - AMIEncryptBootVolume: boolPtr(true), - InstanceType: strPtr("t2.micro"), - SourceAmiFilter: &awscommon.FlatAmiFilterOptions{ - Filters: map[string]string{ - "name": "ubuntu/images/*ubuntu-xenial-{16.04}-amd64-server-*", - "root-device-type": "ebs", - "virtualization-type": "hvm", - }, - Owners: []string{"099720109477"}, - }, - AMIMappings: []awscommon.FlatBlockDevice{}, - LaunchMappings: []awscommon.FlatBlockDevice{}, - }, - }, - SourceRef{ - Type: "amazon-ebs", - Name: "that-ubuntu-1.0", - }: { - Type: "amazon-ebs", - Name: "that-ubuntu-1.0", - Cfg: &amazonebs.FlatConfig{ - RawRegion: strPtr("eu-west-3"), - AMIEncryptBootVolume: boolPtr(true), - InstanceType: strPtr("t2.micro"), - SourceAmiFilter: &awscommon.FlatAmiFilterOptions{ - MostRecent: boolPtr(true), - }, - AMIMappings: []awscommon.FlatBlockDevice{}, - LaunchMappings: []awscommon.FlatBlockDevice{}, - }, - }, - }, - }, - false, - }, - - { - "valid " + communicatorLabel + " load", - defaultParser, - args{"testdata/communicator/basic.pkr.hcl", new(PackerConfig)}, - &PackerConfig{ - Communicators: map[CommunicatorRef]*Communicator{ - {Type: "ssh", Name: "vagrant"}: { - Type: "ssh", Name: "vagrant", - Cfg: &communicator.FlatSSH{ - SSHUsername: strPtr("vagrant"), - SSHPassword: strPtr("s3cr4t"), - SSHClearAuthorizedKeys: boolPtr(true), - SSHHost: strPtr("sssssh.hashicorp.io"), - SSHHandshakeAttempts: intPtr(32), - SSHPort: intPtr(42), - SSHFileTransferMethod: strPtr("scp"), - SSHPrivateKeyFile: strPtr("file.pem"), - SSHPty: boolPtr(false), - SSHTimeout: strPtr("5m"), - SSHAgentAuth: boolPtr(false), - SSHDisableAgentForwarding: boolPtr(true), - SSHBastionHost: strPtr(""), - SSHBastionPort: intPtr(0), - SSHBastionAgentAuth: boolPtr(true), - SSHBastionUsername: strPtr(""), - SSHBastionPassword: strPtr(""), - SSHBastionPrivateKeyFile: strPtr(""), - SSHProxyHost: strPtr("ninja-potatoes.com"), - SSHProxyPort: intPtr(42), - SSHProxyUsername: strPtr("dark-father"), - SSHProxyPassword: strPtr("pickle-rick"), - SSHKeepAliveInterval: strPtr("10s"), - SSHReadWriteTimeout: strPtr("5m"), - }, - }, - }, - }, - false, - }, - - { - "duplicate " + sourceLabel, defaultParser, - args{"testdata/sources/basic.pkr.hcl", &PackerConfig{ - Sources: map[SourceRef]*Source{ - SourceRef{ - Type: "virtualbox-iso", - Name: "ubuntu-1204", - }: { - Type: "virtualbox-iso", - Name: "ubuntu-1204", - Cfg: &iso.FlatConfig{ - HTTPDir: strPtr("xxx"), - }, - }, - }, - }, - }, - &PackerConfig{ - Sources: map[SourceRef]*Source{ - SourceRef{ - Type: "virtualbox-iso", - Name: "ubuntu-1204", - }: { - Type: "virtualbox-iso", - Name: "ubuntu-1204", - Cfg: &iso.FlatConfig{ - HTTPDir: strPtr("xxx"), - }, - }, - SourceRef{ - Type: "amazon-ebs", - Name: "ubuntu-1604", - }: { - Type: "amazon-ebs", - Name: "ubuntu-1604", - Cfg: &amazonebs.FlatConfig{ - RawRegion: strPtr("eu-west-3"), - AMIEncryptBootVolume: boolPtr(true), - InstanceType: strPtr("t2.micro"), - SourceAmiFilter: &awscommon.FlatAmiFilterOptions{ - Filters: map[string]string{ - "name": "ubuntu/images/*ubuntu-xenial-{16.04}-amd64-server-*", - "root-device-type": "ebs", - "virtualization-type": "hvm", - }, - Owners: []string{"099720109477"}, - }, - AMIMappings: []awscommon.FlatBlockDevice{}, - LaunchMappings: []awscommon.FlatBlockDevice{}, - }, - }, - SourceRef{ - Type: "amazon-ebs", - Name: "that-ubuntu-1.0", - }: { - Type: "amazon-ebs", - Name: "that-ubuntu-1.0", - Cfg: &amazonebs.FlatConfig{ - RawRegion: strPtr("eu-west-3"), - AMIEncryptBootVolume: boolPtr(true), - InstanceType: strPtr("t2.micro"), - SourceAmiFilter: &awscommon.FlatAmiFilterOptions{ - MostRecent: boolPtr(true), - }, - AMIMappings: []awscommon.FlatBlockDevice{}, - LaunchMappings: []awscommon.FlatBlockDevice{}, - }, - }, - }, - }, - true, - }, - - {"valid variables load", defaultParser, - args{"testdata/variables/basic.pkr.hcl", new(PackerConfig)}, - &PackerConfig{ - Variables: PackerV1Variables{ - "image_name": "foo-image-{{user `my_secret`}}", - "key": "value", - "my_secret": "foo", - }, - }, - false, - }, - - {"valid " + buildLabel + " load", defaultParser, - args{"testdata/build/basic.pkr.hcl", new(PackerConfig)}, - &PackerConfig{ - Builds: Builds{ - { - Froms: BuildFromList{ - { - Src: SourceRef{"amazon-ebs", "ubuntu-1604"}, - }, - { - Src: SourceRef{"virtualbox-iso", "ubuntu-1204"}, - }, - }, - ProvisionerGroups: ProvisionerGroups{ - &ProvisionerGroup{ - CommunicatorRef: CommunicatorRef{"ssh", "vagrant"}, - Provisioners: []Provisioner{ - {Cfg: &shell.FlatConfig{ - Inline: []string{"echo '{{user `my_secret`}}' :D"}, - }}, - {Cfg: &shell.FlatConfig{ - Scripts: []string{"script-1.sh", "script-2.sh"}, - ValidExitCodes: []int{0, 42}, - }}, - {Cfg: &file.FlatConfig{ - Source: strPtr("app.tar.gz"), - Destination: strPtr("/tmp/app.tar.gz"), - }}, - }, - }, - }, - PostProvisionerGroups: ProvisionerGroups{ - &ProvisionerGroup{ - Provisioners: []Provisioner{ - {Cfg: &amazon_import.FlatConfig{ - Name: strPtr("that-ubuntu-1.0"), - }}, - }, - }, - }, - }, - &Build{ - Froms: BuildFromList{ - { - Src: SourceRef{"amazon", "that-ubuntu-1"}, - }, - }, - ProvisionerGroups: ProvisionerGroups{ - &ProvisionerGroup{ - Provisioners: []Provisioner{ - {Cfg: &shell.FlatConfig{ - Inline: []string{"echo HOLY GUACAMOLE !"}, - }}, - }, - }, - }, - }, - }, - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - p := tt.parser - f, moreDiags := p.ParseHCLFile(tt.args.filename) - if moreDiags != nil { - t.Fatalf("diags: %s", moreDiags) - } - diags := p.ParseFile(f, tt.args.cfg) - if tt.wantDiags == (diags == nil) { - for _, diag := range diags { - t.Errorf("PackerConfig.Load() unexpected diagnostics. %v", diag) - } - t.Error("") - } - if diff := cmp.Diff(tt.wantPackerConfig, tt.args.cfg, - cmpopts.IgnoreUnexported(cty.Value{}), - cmpopts.IgnoreTypes(HCL2Ref{}), - cmpopts.IgnoreTypes([]hcl.Range{}), - cmpopts.IgnoreTypes(hcl.Range{}), - cmpopts.IgnoreInterfaces(struct{ hcl.Expression }{}), - cmpopts.IgnoreInterfaces(struct{ hcl.Body }{}), - ); diff != "" { - t.Errorf("PackerConfig.Load() wrong packer config. %s", diff) - } - if t.Failed() { - t.Fatal() - } - }) - } -} - -func strPtr(s string) *string { return &s } -func intPtr(i int) *int { return &i } -func boolPtr(b bool) *bool { return &b } diff --git a/hcl2template/mock.go b/hcl2template/mock.go new file mode 100644 index 000000000..1386eb8b7 --- /dev/null +++ b/hcl2template/mock.go @@ -0,0 +1,129 @@ +//go:generate mapstructure-to-hcl2 -type MockConfig,NestedMockConfig + +package hcl2template + +import ( + "context" + "time" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/helper/config" + "github.com/hashicorp/packer/packer" +) + +type NestedMockConfig struct { + String string `mapstructure:"string"` + Int int `mapstructure:"int"` + Int64 int64 `mapstructure:"int64"` + Bool bool `mapstructure:"bool"` + Trilean config.Trilean `mapstructure:"trilean"` + Duration time.Duration `mapstructure:"duration"` + MapStringString map[string]string `mapstructure:"map_string_string"` + SliceString []string `mapstructure:"slice_string"` + NamedMapStringString NamedMapStringString `mapstructure:"named_map_string_string"` + NamedString NamedString `mapstructure:"named_string"` +} + +type MockConfig struct { + NestedMockConfig `mapstructure:",squash"` + Nested NestedMockConfig `mapstructure:"nested"` + NestedSlice []NestedMockConfig `mapstructure:"nested_slice"` +} + +////// +// MockBuilder +////// + +type MockBuilder struct { + Config MockConfig +} + +var _ packer.Builder = new(MockBuilder) + +func (b *MockBuilder) ConfigSpec() hcldec.ObjectSpec { return b.Config.FlatMapstructure().HCL2Spec() } + +func (b *MockBuilder) Prepare(raws ...interface{}) ([]string, []string, error) { + return nil, nil, config.Decode(&b.Config, &config.DecodeOpts{ + Interpolate: true, + }, raws...) +} + +func (b *MockBuilder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { + return nil, nil +} + +////// +// MockProvisioner +////// + +type MockProvisioner struct { + Config MockConfig +} + +var _ packer.Provisioner = new(MockProvisioner) + +func (b *MockProvisioner) ConfigSpec() hcldec.ObjectSpec { + return b.Config.FlatMapstructure().HCL2Spec() +} + +func (b *MockProvisioner) Prepare(raws ...interface{}) error { + return config.Decode(&b.Config, &config.DecodeOpts{ + Interpolate: true, + }, raws...) +} + +func (b *MockProvisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { + return nil +} + +////// +// MockPostProcessor +////// + +type MockPostProcessor struct { + Config MockConfig +} + +var _ packer.PostProcessor = new(MockPostProcessor) + +func (b *MockPostProcessor) ConfigSpec() hcldec.ObjectSpec { + return b.Config.FlatMapstructure().HCL2Spec() +} + +func (b *MockPostProcessor) Configure(raws ...interface{}) error { + return config.Decode(&b.Config, &config.DecodeOpts{ + Interpolate: true, + }, raws...) +} + +func (b *MockPostProcessor) PostProcess(ctx context.Context, ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, bool, error) { + return nil, false, false, nil +} + +////// +// MockCommunicator +////// + +type MockCommunicator struct { + Config MockConfig + packer.Communicator +} + +var _ packer.ConfigurableCommunicator = new(MockCommunicator) + +func (b *MockCommunicator) ConfigSpec() hcldec.ObjectSpec { + return b.Config.FlatMapstructure().HCL2Spec() +} + +func (b *MockCommunicator) Configure(raws ...interface{}) ([]string, error) { + return nil, config.Decode(&b.Config, &config.DecodeOpts{ + Interpolate: true, + }, raws...) +} + +////// +// Utils +////// + +type NamedMapStringString map[string]string +type NamedString string diff --git a/hcl2template/mock.hcl2spec.go b/hcl2template/mock.hcl2spec.go new file mode 100644 index 000000000..c5508c9bb --- /dev/null +++ b/hcl2template/mock.hcl2spec.go @@ -0,0 +1,93 @@ +// Code generated by "mapstructure-to-hcl2 -type MockConfig,NestedMockConfig"; DO NOT EDIT. +package hcl2template + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatMockConfig is an auto-generated flat version of MockConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatMockConfig struct { + String *string `mapstructure:"string" cty:"string"` + Int *int `mapstructure:"int" cty:"int"` + Int64 *int64 `mapstructure:"int64" cty:"int64"` + Bool *bool `mapstructure:"bool" cty:"bool"` + Trilean *bool `mapstructure:"trilean" cty:"trilean"` + Duration *string `mapstructure:"duration" cty:"duration"` + MapStringString map[string]string `mapstructure:"map_string_string" cty:"map_string_string"` + SliceString []string `mapstructure:"slice_string" cty:"slice_string"` + NamedMapStringString NamedMapStringString `mapstructure:"named_map_string_string" cty:"named_map_string_string"` + NamedString *NamedString `mapstructure:"named_string" cty:"named_string"` + Nested *FlatNestedMockConfig `mapstructure:"nested" cty:"nested"` + NestedSlice []FlatNestedMockConfig `mapstructure:"nested_slice" cty:"nested_slice"` +} + +// FlatMapstructure returns a new FlatMockConfig. +// FlatMockConfig is an auto-generated flat version of MockConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*MockConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatMockConfig) +} + +// HCL2Spec returns the hcl spec of a MockConfig. +// This spec is used by HCL to read the fields of MockConfig. +// The decoded values from this spec will then be applied to a FlatMockConfig. +func (*FlatMockConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "string": &hcldec.AttrSpec{Name: "string", Type: cty.String, Required: false}, + "int": &hcldec.AttrSpec{Name: "int", Type: cty.Number, Required: false}, + "int64": &hcldec.AttrSpec{Name: "int64", Type: cty.Number, Required: false}, + "bool": &hcldec.AttrSpec{Name: "bool", Type: cty.Bool, Required: false}, + "trilean": &hcldec.AttrSpec{Name: "trilean", Type: cty.Bool, Required: false}, + "duration": &hcldec.AttrSpec{Name: "duration", Type: cty.String, Required: false}, + "map_string_string": &hcldec.BlockAttrsSpec{TypeName: "map_string_string", ElementType: cty.String, Required: false}, + "slice_string": &hcldec.AttrSpec{Name: "slice_string", Type: cty.List(cty.String), Required: false}, + "named_map_string_string": &hcldec.BlockAttrsSpec{TypeName: "named_map_string_string", ElementType: cty.String, Required: false}, + "named_string": &hcldec.AttrSpec{Name: "named_string", Type: cty.String, Required: false}, + "nested": &hcldec.BlockSpec{TypeName: "nested", Nested: hcldec.ObjectSpec((*FlatNestedMockConfig)(nil).HCL2Spec())}, + "nested_slice": &hcldec.BlockListSpec{TypeName: "nested_slice", Nested: hcldec.ObjectSpec((*FlatNestedMockConfig)(nil).HCL2Spec())}, + } + return s +} + +// FlatNestedMockConfig is an auto-generated flat version of NestedMockConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatNestedMockConfig struct { + String *string `mapstructure:"string" cty:"string"` + Int *int `mapstructure:"int" cty:"int"` + Int64 *int64 `mapstructure:"int64" cty:"int64"` + Bool *bool `mapstructure:"bool" cty:"bool"` + Trilean *bool `mapstructure:"trilean" cty:"trilean"` + Duration *string `mapstructure:"duration" cty:"duration"` + MapStringString map[string]string `mapstructure:"map_string_string" cty:"map_string_string"` + SliceString []string `mapstructure:"slice_string" cty:"slice_string"` + NamedMapStringString NamedMapStringString `mapstructure:"named_map_string_string" cty:"named_map_string_string"` + NamedString *NamedString `mapstructure:"named_string" cty:"named_string"` +} + +// FlatMapstructure returns a new FlatNestedMockConfig. +// FlatNestedMockConfig is an auto-generated flat version of NestedMockConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*NestedMockConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatNestedMockConfig) +} + +// HCL2Spec returns the hcl spec of a NestedMockConfig. +// This spec is used by HCL to read the fields of NestedMockConfig. +// The decoded values from this spec will then be applied to a FlatNestedMockConfig. +func (*FlatNestedMockConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "string": &hcldec.AttrSpec{Name: "string", Type: cty.String, Required: false}, + "int": &hcldec.AttrSpec{Name: "int", Type: cty.Number, Required: false}, + "int64": &hcldec.AttrSpec{Name: "int64", Type: cty.Number, Required: false}, + "bool": &hcldec.AttrSpec{Name: "bool", Type: cty.Bool, Required: false}, + "trilean": &hcldec.AttrSpec{Name: "trilean", Type: cty.Bool, Required: false}, + "duration": &hcldec.AttrSpec{Name: "duration", Type: cty.String, Required: false}, + "map_string_string": &hcldec.BlockAttrsSpec{TypeName: "map_string_string", ElementType: cty.String, Required: false}, + "slice_string": &hcldec.AttrSpec{Name: "slice_string", Type: cty.List(cty.String), Required: false}, + "named_map_string_string": &hcldec.BlockAttrsSpec{TypeName: "named_map_string_string", ElementType: cty.String, Required: false}, + "named_string": &hcldec.AttrSpec{Name: "named_string", Type: cty.String, Required: false}, + } + return s +} diff --git a/hcl2template/parser.go b/hcl2template/parser.go index 97bd1e8d6..b79c8e3bb 100644 --- a/hcl2template/parser.go +++ b/hcl2template/parser.go @@ -2,12 +2,10 @@ package hcl2template import ( "fmt" - "io/ioutil" - "path/filepath" - "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclparse" + "github.com/hashicorp/packer/packer" ) const ( @@ -29,48 +27,21 @@ var configSchema = &hcl.BodySchema{ type Parser struct { *hclparse.Parser - ProvisionersSchemas map[string]Decodable + BuilderSchemas packer.BuilderStore - PostProvisionersSchemas map[string]Decodable + ProvisionersSchemas packer.ProvisionerStore - CommunicatorSchemas map[string]Decodable - - SourceSchemas map[string]Decodable + PostProcessorsSchemas packer.PostProcessorStore } -const hcl2FileExt = ".pkr.hcl" +const ( + hcl2FileExt = ".pkr.hcl" + hcl2JsonFileExt = ".pkr.json" +) -func (p *Parser) Parse(filename string) (*PackerConfig, hcl.Diagnostics) { - var diags hcl.Diagnostics +func (p *Parser) parse(filename string) (*PackerConfig, hcl.Diagnostics) { - hclFiles := []string{} - jsonFiles := []string{} - if strings.HasSuffix(filename, hcl2FileExt) { - hclFiles = append(hclFiles, hcl2FileExt) - } else if strings.HasSuffix(filename, ".json") { - jsonFiles = append(jsonFiles, hcl2FileExt) - } else { - fileInfos, err := ioutil.ReadDir(filename) - if err != nil { - diag := &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Cannot read hcl directory", - Detail: err.Error(), - } - diags = append(diags, diag) - } - for _, fileInfo := range fileInfos { - if fileInfo.IsDir() { - continue - } - filename := filepath.Join(filename, fileInfo.Name()) - if strings.HasSuffix(filename, hcl2FileExt) { - hclFiles = append(hclFiles, filename) - } else if strings.HasSuffix(filename, ".json") { - jsonFiles = append(jsonFiles, filename) - } - } - } + hclFiles, jsonFiles, diags := GetHCL2Files(filename) var files []*hcl.File for _, filename := range hclFiles { @@ -89,24 +60,21 @@ func (p *Parser) Parse(filename string) (*PackerConfig, hcl.Diagnostics) { cfg := &PackerConfig{} for _, file := range files { - moreDiags := p.ParseFile(file, cfg) + moreDiags := p.parseFile(file, cfg) diags = append(diags, moreDiags...) } - if diags.HasErrors() { - return cfg, diags - } - return cfg, nil + return cfg, diags } -// ParseFile filename content into cfg. +// parseFile filename content into cfg. // -// ParseFile may be called multiple times with the same cfg on a different file. +// parseFile may be called multiple times with the same cfg on a different file. // -// ParseFile returns as complete a config as we can manage, even if there are +// parseFile returns as complete a config as we can manage, even if there are // errors, since a partial result can be useful for careful analysis by // development tools such as text editor extensions. -func (p *Parser) ParseFile(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics { +func (p *Parser) parseFile(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics { var diags hcl.Diagnostics content, moreDiags := f.Body.Content(configSchema) @@ -115,12 +83,11 @@ func (p *Parser) ParseFile(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics { for _, block := range content.Blocks { switch block.Type { case sourceLabel: - if cfg.Sources == nil { - cfg.Sources = map[SourceRef]*Source{} - } - - source, moreDiags := p.decodeSource(block, p.SourceSchemas) + source, moreDiags := p.decodeSource(block) diags = append(diags, moreDiags...) + if moreDiags.HasErrors() { + continue + } ref := source.Ref() if existing := cfg.Sources[ref]; existing != nil { @@ -130,11 +97,15 @@ func (p *Parser) ParseFile(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics { Detail: fmt.Sprintf("This "+sourceLabel+" block has the "+ "same builder type and name as a previous block declared "+ "at %s. Each "+sourceLabel+" must have a unique name per builder type.", - existing.HCL2Ref.DeclRange), - Subject: &source.HCL2Ref.DeclRange, + existing.block.DefRange.Ptr()), + Subject: source.block.DefRange.Ptr(), }) continue } + + if cfg.Sources == nil { + cfg.Sources = map[SourceRef]*Source{} + } cfg.Sources[ref] = source case variablesLabel: @@ -143,38 +114,19 @@ func (p *Parser) ParseFile(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics { } moreDiags := cfg.Variables.decodeConfig(block) + if moreDiags.HasErrors() { + continue + } diags = append(diags, moreDiags...) case buildLabel: build, moreDiags := p.decodeBuildConfig(block) diags = append(diags, moreDiags...) - cfg.Builds = append(cfg.Builds, build) - - case communicatorLabel: - if cfg.Communicators == nil { - cfg.Communicators = map[CommunicatorRef]*Communicator{} - } - communicator, moreDiags := p.decodeCommunicatorConfig(block) - diags = append(diags, moreDiags...) - - ref := communicator.Ref() - - if existing := cfg.Communicators[ref]; existing != nil { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Duplicate " + communicatorLabel + " block", - Detail: fmt.Sprintf("This "+communicatorLabel+" block has the "+ - "same type and name as a previous block declared "+ - "at %s. Each "+communicatorLabel+" must have a unique name per type.", - existing.HCL2Ref.DeclRange), - Subject: &communicator.HCL2Ref.DeclRange, - }) + if moreDiags.HasErrors() { continue } - cfg.Communicators[ref] = communicator + cfg.Builds = append(cfg.Builds, build) - default: - panic(fmt.Sprintf("unexpected block type %q", block.Type)) // TODO(azr): err } } diff --git a/hcl2template/parser_test.go b/hcl2template/parser_test.go deleted file mode 100644 index 1056153d6..000000000 --- a/hcl2template/parser_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package hcl2template - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/hashicorp/hcl/v2" - "github.com/zclconf/go-cty/cty" - - awscommon "github.com/hashicorp/packer/builder/amazon/common" - amazonebs "github.com/hashicorp/packer/builder/amazon/ebs" - "github.com/hashicorp/packer/builder/virtualbox/iso" - - "github.com/hashicorp/packer/helper/communicator" - - amazon_import "github.com/hashicorp/packer/post-processor/amazon-import" - - "github.com/hashicorp/packer/provisioner/file" - "github.com/hashicorp/packer/provisioner/shell" -) - -func TestParser_Parse(t *testing.T) { - defaultParser := getBasicParser() - - type args struct { - filename string - } - tests := []struct { - name string - parser *Parser - args args - wantCfg *PackerConfig - wantDiags bool - }{ - {"complete", - defaultParser, - args{"testdata/complete"}, - &PackerConfig{ - Sources: map[SourceRef]*Source{ - SourceRef{ - Type: "virtualbox-iso", - Name: "ubuntu-1204", - }: { - Type: "virtualbox-iso", - Name: "ubuntu-1204", - Cfg: &iso.FlatConfig{ - HTTPDir: strPtr("xxx"), - ISOChecksum: strPtr("769474248a3897f4865817446f9a4a53"), - ISOChecksumType: strPtr("md5"), - RawSingleISOUrl: strPtr("http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso"), - BootCommand: []string{"..."}, - ShutdownCommand: strPtr("echo 'vagrant' | sudo -S shutdown -P now"), - BootWait: strPtr("10s"), - VBoxManage: [][]string{}, - VBoxManagePost: [][]string{}, - }, - }, - SourceRef{ - Type: "amazon-ebs", - Name: "ubuntu-1604", - }: { - Type: "amazon-ebs", - Name: "ubuntu-1604", - Cfg: &amazonebs.FlatConfig{ - RawRegion: strPtr("eu-west-3"), - AMIEncryptBootVolume: boolPtr(true), - InstanceType: strPtr("t2.micro"), - SourceAmiFilter: &awscommon.FlatAmiFilterOptions{ - Filters: map[string]string{ - "name": "ubuntu/images/*ubuntu-xenial-{16.04}-amd64-server-*", - "root-device-type": "ebs", - "virtualization-type": "hvm", - }, - Owners: []string{"099720109477"}, - }, - AMIMappings: []awscommon.FlatBlockDevice{}, - LaunchMappings: []awscommon.FlatBlockDevice{}, - }, - }, - SourceRef{ - Type: "amazon-ebs", - Name: "that-ubuntu-1.0", - }: { - Type: "amazon-ebs", - Name: "that-ubuntu-1.0", - Cfg: &amazonebs.FlatConfig{ - RawRegion: strPtr("eu-west-3"), - AMIEncryptBootVolume: boolPtr(true), - InstanceType: strPtr("t2.micro"), - SourceAmiFilter: &awscommon.FlatAmiFilterOptions{ - MostRecent: boolPtr(true), - }, - AMIMappings: []awscommon.FlatBlockDevice{}, - LaunchMappings: []awscommon.FlatBlockDevice{}, - }, - }, - }, - Communicators: map[CommunicatorRef]*Communicator{ - {Type: "ssh", Name: "vagrant"}: { - Type: "ssh", Name: "vagrant", - Cfg: &communicator.FlatSSH{ - SSHUsername: strPtr("vagrant"), - SSHPassword: strPtr("s3cr4t"), - SSHClearAuthorizedKeys: boolPtr(true), - SSHHost: strPtr("sssssh.hashicorp.io"), - SSHHandshakeAttempts: intPtr(32), - SSHPort: intPtr(42), - SSHFileTransferMethod: strPtr("scp"), - SSHPrivateKeyFile: strPtr("file.pem"), - SSHPty: boolPtr(false), - SSHTimeout: strPtr("5m"), - SSHAgentAuth: boolPtr(false), - SSHDisableAgentForwarding: boolPtr(true), - SSHBastionHost: strPtr(""), - SSHBastionPort: intPtr(0), - SSHBastionAgentAuth: boolPtr(true), - SSHBastionUsername: strPtr(""), - SSHBastionPassword: strPtr(""), - SSHBastionPrivateKeyFile: strPtr(""), - SSHProxyHost: strPtr("ninja-potatoes.com"), - SSHProxyPort: intPtr(42), - SSHProxyUsername: strPtr("dark-father"), - SSHProxyPassword: strPtr("pickle-rick"), - SSHKeepAliveInterval: strPtr("10s"), - SSHReadWriteTimeout: strPtr("5m"), - }, - }, - }, - Variables: PackerV1Variables{ - "image_name": "foo-image-{{user `my_secret`}}", - "key": "value", - "my_secret": "foo", - }, - Builds: Builds{ - { - Froms: BuildFromList{ - { - Src: SourceRef{"amazon-ebs", "ubuntu-1604"}, - }, - { - Src: SourceRef{"virtualbox-iso", "ubuntu-1204"}, - }, - }, - ProvisionerGroups: ProvisionerGroups{ - &ProvisionerGroup{ - CommunicatorRef: CommunicatorRef{"ssh", "vagrant"}, - Provisioners: []Provisioner{ - {Cfg: &shell.FlatConfig{ - Inline: []string{"echo '{{user `my_secret`}}' :D"}, - }}, - {Cfg: &shell.FlatConfig{ - Scripts: []string{"script-1.sh", "script-2.sh"}, - ValidExitCodes: []int{0, 42}, - }}, - {Cfg: &file.FlatConfig{ - Source: strPtr("app.tar.gz"), - Destination: strPtr("/tmp/app.tar.gz"), - }}, - }, - }, - }, - PostProvisionerGroups: ProvisionerGroups{ - &ProvisionerGroup{ - Provisioners: []Provisioner{ - {Cfg: &amazon_import.FlatConfig{ - Name: strPtr("that-ubuntu-1.0"), - }}, - }, - }, - }, - }, - &Build{ - Froms: BuildFromList{ - { - Src: SourceRef{"amazon", "that-ubuntu-1"}, - }, - }, - ProvisionerGroups: ProvisionerGroups{ - &ProvisionerGroup{ - Provisioners: []Provisioner{ - {Cfg: &shell.FlatConfig{ - Inline: []string{"echo HOLY GUACAMOLE !"}, - }}, - }, - }, - }, - }, - }, - }, false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotCfg, gotDiags := tt.parser.Parse(tt.args.filename) - if tt.wantDiags == (gotDiags == nil) { - t.Errorf("Parser.Parse() unexpected diagnostics. %s", gotDiags) - } - if diff := cmp.Diff(tt.wantCfg, gotCfg, - cmpopts.IgnoreUnexported(cty.Value{}), - cmpopts.IgnoreTypes(HCL2Ref{}), - cmpopts.IgnoreTypes([]hcl.Range{}), - cmpopts.IgnoreTypes(hcl.Range{}), - cmpopts.IgnoreInterfaces(struct{ hcl.Expression }{}), - cmpopts.IgnoreInterfaces(struct{ hcl.Body }{}), - ); diff != "" { - t.Errorf("Parser.Parse() wrong packer config. %s", diff) - } - - }) - } -} diff --git a/hcl2template/testdata/build.pkr.hcl/basic.pkr.hcl b/hcl2template/testdata/build.pkr.hcl/basic.pkr.hcl new file mode 100644 index 000000000..34d84edb9 --- /dev/null +++ b/hcl2template/testdata/build.pkr.hcl/basic.pkr.hcl @@ -0,0 +1,125 @@ + +// starts resources to provision them. +build { + sources = [ + "source.amazon-ebs.ubuntu-1604", + "source.virtualbox-iso.ubuntu-1204", + ] + + provisioner "shell" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { + } + } + + provisioner "file" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { + } + } + + post-processor "amazon-import" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { + } + } +} diff --git a/hcl2template/testdata/build/basic.pkr.hcl b/hcl2template/testdata/build/basic.pkr.hcl index c48847c94..34d84edb9 100644 --- a/hcl2template/testdata/build/basic.pkr.hcl +++ b/hcl2template/testdata/build/basic.pkr.hcl @@ -1,66 +1,125 @@ // starts resources to provision them. build { - from "src.amazon-ebs.ubuntu-1604" { - ami_name = "that-ubuntu-1.0" - } + sources = [ + "source.amazon-ebs.ubuntu-1604", + "source.virtualbox-iso.ubuntu-1204", + ] - from "src.virtualbox-iso.ubuntu-1204" { - // build name is defaulted from the label "src.virtualbox-iso.ubuntu-1204" - outout_dir = "path/" - } + provisioner "shell" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] - provision { - communicator = "comm.ssh.vagrant" - - shell { - inline = [ - "echo '{{user `my_secret`}}' :D" + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", ] } - shell { - valid_exit_codes = [ - 0, - 42, - ] - scripts = [ - "script-1.sh", - "script-2.sh", - ] - // override "vmware-iso" { // TODO(azr): handle common fields - // execute_command = "echo 'password' | sudo -S bash {{.Path}}" - // } + nested_slice { } - - file { - source = "app.tar.gz" - destination = "/tmp/app.tar.gz" - // timeout = "5s" // TODO(azr): handle common fields - } - } - post_provision { - amazon-import { - // only = ["src.virtualbox-iso.ubuntu-1204"] // TODO(azr): handle common fields - ami_name = "that-ubuntu-1.0" + provisioner "file" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { + } + } + + post-processor "amazon-import" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { } } } - -build { - // build an ami using the ami from the previous build block. - from "src.amazon.that-ubuntu-1.0" { - ami_name = "fooooobaaaar" - } - - provision { - - shell { - inline = [ - "echo HOLY GUACAMOLE !" - ] - } - } -} \ No newline at end of file diff --git a/hcl2template/testdata/build/invalid_source_reference.pkr.hcl b/hcl2template/testdata/build/invalid_source_reference.pkr.hcl new file mode 100644 index 000000000..a1c28dae9 --- /dev/null +++ b/hcl2template/testdata/build/invalid_source_reference.pkr.hcl @@ -0,0 +1,4 @@ + +build { + sources = ["ami"] +} diff --git a/hcl2template/testdata/build/post-processor_inexistent.pkr.hcl b/hcl2template/testdata/build/post-processor_inexistent.pkr.hcl new file mode 100644 index 000000000..179ff3475 --- /dev/null +++ b/hcl2template/testdata/build/post-processor_inexistent.pkr.hcl @@ -0,0 +1,6 @@ + +build { + post-processor "inexistant" { + foo = "bar" + } +} diff --git a/hcl2template/testdata/build/post-processor_untyped.pkr.hcl b/hcl2template/testdata/build/post-processor_untyped.pkr.hcl new file mode 100644 index 000000000..4b723360e --- /dev/null +++ b/hcl2template/testdata/build/post-processor_untyped.pkr.hcl @@ -0,0 +1,6 @@ + +build { + post-process { + foo = "bar" + } +} diff --git a/hcl2template/testdata/build/provisioner_inexistent.pkr.hcl b/hcl2template/testdata/build/provisioner_inexistent.pkr.hcl new file mode 100644 index 000000000..59b4dcbc5 --- /dev/null +++ b/hcl2template/testdata/build/provisioner_inexistent.pkr.hcl @@ -0,0 +1,6 @@ + +build { + provisioner "inexistant" { + foo = "bar" + } +} diff --git a/hcl2template/testdata/build/provisioner_untyped.pkr.hcl b/hcl2template/testdata/build/provisioner_untyped.pkr.hcl new file mode 100644 index 000000000..33a9fdb99 --- /dev/null +++ b/hcl2template/testdata/build/provisioner_untyped.pkr.hcl @@ -0,0 +1,6 @@ + +build { + provision { + foo = "bar" + } +} diff --git a/hcl2template/testdata/communicator/basic.pkr.hcl b/hcl2template/testdata/communicator/basic.pkr.hcl index b752333e9..4f5d8104e 100644 --- a/hcl2template/testdata/communicator/basic.pkr.hcl +++ b/hcl2template/testdata/communicator/basic.pkr.hcl @@ -1,27 +1,39 @@ communicator "ssh" "vagrant" { - ssh_password = "s3cr4t" - ssh_username = "vagrant" - ssh_agent_auth = false - ssh_bastion_agent_auth = true - ssh_bastion_host = "" - ssh_bastion_password = "" - ssh_bastion_port = 0 - ssh_bastion_private_key_file = "" - ssh_bastion_username = "" - ssh_clear_authorized_keys = true - ssh_disable_agent_forwarding = true - ssh_file_transfer_method = "scp" - ssh_handshake_attempts = 32 - ssh_host = "sssssh.hashicorp.io" - ssh_port = 42 - ssh_keep_alive_interval = "10s" - ssh_private_key_file = "file.pem" - ssh_proxy_host = "ninja-potatoes.com" - ssh_proxy_password = "pickle-rick" - ssh_proxy_port = "42" - ssh_proxy_username = "dark-father" - ssh_pty = false - ssh_read_write_timeout = "5m" - ssh_timeout = "5m" + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { + } } diff --git a/hcl2template/testdata/complete/build.pkr.hcl b/hcl2template/testdata/complete/build.pkr.hcl index c48847c94..de67e1c39 100644 --- a/hcl2template/testdata/complete/build.pkr.hcl +++ b/hcl2template/testdata/complete/build.pkr.hcl @@ -1,66 +1,124 @@ // starts resources to provision them. build { - from "src.amazon-ebs.ubuntu-1604" { - ami_name = "that-ubuntu-1.0" - } + sources = [ + "source.virtualbox-iso.ubuntu-1204", + ] - from "src.virtualbox-iso.ubuntu-1204" { - // build name is defaulted from the label "src.virtualbox-iso.ubuntu-1204" - outout_dir = "path/" - } + provisioner "shell" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] - provision { - communicator = "comm.ssh.vagrant" - - shell { - inline = [ - "echo '{{user `my_secret`}}' :D" + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", ] } - shell { - valid_exit_codes = [ - 0, - 42, - ] - scripts = [ - "script-1.sh", - "script-2.sh", - ] - // override "vmware-iso" { // TODO(azr): handle common fields - // execute_command = "echo 'password' | sudo -S bash {{.Path}}" - // } + nested_slice { } - - file { - source = "app.tar.gz" - destination = "/tmp/app.tar.gz" - // timeout = "5s" // TODO(azr): handle common fields - } - } - post_provision { - amazon-import { - // only = ["src.virtualbox-iso.ubuntu-1204"] // TODO(azr): handle common fields - ami_name = "that-ubuntu-1.0" + provisioner "file" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { + } + } + + post-processor "amazon-import" { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } + + nested_slice { } } } - -build { - // build an ami using the ami from the previous build block. - from "src.amazon.that-ubuntu-1.0" { - ami_name = "fooooobaaaar" - } - - provision { - - shell { - inline = [ - "echo HOLY GUACAMOLE !" - ] - } - } -} \ No newline at end of file diff --git a/hcl2template/testdata/complete/communicator.pkr.hcl b/hcl2template/testdata/complete/communicator.pkr.hcl deleted file mode 100644 index b752333e9..000000000 --- a/hcl2template/testdata/complete/communicator.pkr.hcl +++ /dev/null @@ -1,27 +0,0 @@ - -communicator "ssh" "vagrant" { - ssh_password = "s3cr4t" - ssh_username = "vagrant" - ssh_agent_auth = false - ssh_bastion_agent_auth = true - ssh_bastion_host = "" - ssh_bastion_password = "" - ssh_bastion_port = 0 - ssh_bastion_private_key_file = "" - ssh_bastion_username = "" - ssh_clear_authorized_keys = true - ssh_disable_agent_forwarding = true - ssh_file_transfer_method = "scp" - ssh_handshake_attempts = 32 - ssh_host = "sssssh.hashicorp.io" - ssh_port = 42 - ssh_keep_alive_interval = "10s" - ssh_private_key_file = "file.pem" - ssh_proxy_host = "ninja-potatoes.com" - ssh_proxy_password = "pickle-rick" - ssh_proxy_port = "42" - ssh_proxy_username = "dark-father" - ssh_pty = false - ssh_read_write_timeout = "5m" - ssh_timeout = "5m" -} diff --git a/hcl2template/testdata/complete/sources.pkr.hcl b/hcl2template/testdata/complete/sources.pkr.hcl index 43e3af082..b95308b5a 100644 --- a/hcl2template/testdata/complete/sources.pkr.hcl +++ b/hcl2template/testdata/complete/sources.pkr.hcl @@ -1,37 +1,71 @@ -// a source represents a reusable setting for a system boot/start. source "virtualbox-iso" "ubuntu-1204" { - iso_url = "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso" - iso_checksum = "769474248a3897f4865817446f9a4a53" - iso_checksum_type = "md5" + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] - boot_wait = "10s" - http_directory = "xxx" - boot_command = ["..."] - - shutdown_command = "echo 'vagrant' | sudo -S shutdown -P now" -} - -source "amazon-ebs" "ubuntu-1604" { - instance_type = "t2.micro" - encrypt_boot = true - region = "eu-west-3" - source_ami_filter { - filters { - virtualization-type = "hvm" - name = "ubuntu/images/*ubuntu-xenial-{16.04}-amd64-server-*" - root-device-type = "ebs" + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" } - owners = [ - "099720109477" + slice_string = [ + "a", + "b", + "c", ] } -} -source "amazon-ebs" "that-ubuntu-1.0" { - instance_type = "t2.micro" - encrypt_boot = true - region = "eu-west-3" - source_ami_filter { - most_recent = true + nested_slice { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] } -} + + nested_slice { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } +} \ No newline at end of file diff --git a/hcl2template/testdata/complete/subfolder/shouldnotload.pkr.hcl b/hcl2template/testdata/complete/subfolder/shouldnotload.pkr.hcl deleted file mode 100644 index 6f2d3076c..000000000 --- a/hcl2template/testdata/complete/subfolder/shouldnotload.pkr.hcl +++ /dev/null @@ -1,61 +0,0 @@ - -// starts resources to provision them. -build { - from "src.amazon-ebs.ubuntu-1604" { - ami_name = "that-ubuntu-1.0" - } - - from "src.virtualbox-iso.ubuntu-1204" { - // build name is defaulted from the label "src.virtualbox-iso.ubuntu-1204" - outout_dir = "path/" - } - - provision { - communicator = comm.ssh.vagrant - - shell { - inline = [ - "echo '{{user `my_secret`}}' :D" - ] - } - - shell { - script = [ - "script-1.sh", - "script-2.sh", - ] - override "vmware-iso" { - execute_command = "echo 'password' | sudo -S bash {{.Path}}" - } - } - - upload "log.go" "/tmp" { - timeout = "5s" - } - - } - - post_provision { - amazon-import { - only = ["src.virtualbox-iso.ubuntu-1204"] - ami_name = "that-ubuntu-1.0" - } - } -} - -build { - // build an ami using the ami from the previous build block. - from "src.amazon.that-ubuntu-1.0" { - ami_name = "fooooobaaaar" - } - - provision { - communicator = comm.ssh.vagrant - - shell { - inline = [ - "echo HOLY GUACAMOLE !" - ] - } - } -} \ No newline at end of file diff --git a/hcl2template/testdata/complete/variables.pkr.hcl b/hcl2template/testdata/complete/variables.pkr.hcl deleted file mode 100644 index d4e651247..000000000 --- a/hcl2template/testdata/complete/variables.pkr.hcl +++ /dev/null @@ -1,6 +0,0 @@ - -variables { - key = "value" - my_secret = "foo" - image_name = "foo-image-{{user `my_secret`}}" -} diff --git a/hcl2template/testdata/empty/.gitkeep b/hcl2template/testdata/empty/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/hcl2template/testdata/sources/basic.pkr.hcl b/hcl2template/testdata/sources/basic.pkr.hcl index 43e3af082..58d3164e5 100644 --- a/hcl2template/testdata/sources/basic.pkr.hcl +++ b/hcl2template/testdata/sources/basic.pkr.hcl @@ -1,37 +1,72 @@ // a source represents a reusable setting for a system boot/start. source "virtualbox-iso" "ubuntu-1204" { - iso_url = "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso" - iso_checksum = "769474248a3897f4865817446f9a4a53" - iso_checksum_type = "md5" + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] - boot_wait = "10s" - http_directory = "xxx" - boot_command = ["..."] - - shutdown_command = "echo 'vagrant' | sudo -S shutdown -P now" -} - -source "amazon-ebs" "ubuntu-1604" { - instance_type = "t2.micro" - encrypt_boot = true - region = "eu-west-3" - source_ami_filter { - filters { - virtualization-type = "hvm" - name = "ubuntu/images/*ubuntu-xenial-{16.04}-amd64-server-*" - root-device-type = "ebs" + nested { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" } - owners = [ - "099720109477" + slice_string = [ + "a", + "b", + "c", ] } -} -source "amazon-ebs" "that-ubuntu-1.0" { - instance_type = "t2.micro" - encrypt_boot = true - region = "eu-west-3" - source_ami_filter { - most_recent = true + nested_slice { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] } -} + + nested_slice { + string = "string" + int = 42 + int64 = 43 + bool = true + trilean = true + duration = "10s" + map_string_string { + a = "b" + c = "d" + } + slice_string = [ + "a", + "b", + "c", + ] + } +} \ No newline at end of file diff --git a/hcl2template/testdata/sources/duplicate.pkr.hcl b/hcl2template/testdata/sources/duplicate.pkr.hcl new file mode 100644 index 000000000..823d17e03 --- /dev/null +++ b/hcl2template/testdata/sources/duplicate.pkr.hcl @@ -0,0 +1,3 @@ + +source "virtualbox-iso" "ubuntu-1204" {} +source "virtualbox-iso" "ubuntu-1204" {} diff --git a/hcl2template/testdata/sources/inexistent.pkr.hcl b/hcl2template/testdata/sources/inexistent.pkr.hcl new file mode 100644 index 000000000..e9a112f17 --- /dev/null +++ b/hcl2template/testdata/sources/inexistent.pkr.hcl @@ -0,0 +1,4 @@ +// a source represents a reusable setting for a system boot/start. +source "inexistant" "ubuntu-1204" { + foo = "bar" +} \ No newline at end of file diff --git a/hcl2template/testdata/sources/unnamed.pkr.hcl b/hcl2template/testdata/sources/unnamed.pkr.hcl new file mode 100644 index 000000000..30adb0333 --- /dev/null +++ b/hcl2template/testdata/sources/unnamed.pkr.hcl @@ -0,0 +1,4 @@ +// a source represents a reusable setting for a system boot/start. +source "virtualbox-iso" { + foo = "bar" +} \ No newline at end of file diff --git a/hcl2template/testdata/sources/untyped.pkr.hcl b/hcl2template/testdata/sources/untyped.pkr.hcl new file mode 100644 index 000000000..28d4fad35 --- /dev/null +++ b/hcl2template/testdata/sources/untyped.pkr.hcl @@ -0,0 +1,4 @@ +// a source represents a reusable setting for a system boot/start. +source { + foo = "bar" +} \ No newline at end of file diff --git a/hcl2template/testdata/unknown/block_type.pkr.hcl b/hcl2template/testdata/unknown/block_type.pkr.hcl new file mode 100644 index 000000000..8ae2c40a3 --- /dev/null +++ b/hcl2template/testdata/unknown/block_type.pkr.hcl @@ -0,0 +1,3 @@ + +potato { +} diff --git a/hcl2template/types.build.from.go b/hcl2template/types.build.from.go index 66d3b7645..b84b6ffc6 100644 --- a/hcl2template/types.build.from.go +++ b/hcl2template/types.build.from.go @@ -2,28 +2,15 @@ package hcl2template import ( "strings" - - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/gohcl" - "github.com/hashicorp/hcl/v2/hclsyntax" ) -type BuildFromList []BuildFrom - -type BuildFrom struct { - // source to take config from - Src SourceRef `hcl:"-"` - - HCL2Ref HCL2Ref -} - func sourceRefFromString(in string) SourceRef { args := strings.Split(in, ".") if len(args) < 2 { return NoSource } if len(args) > 2 { - // src.type.name + // source.type.name args = args[1:] } return SourceRef{ @@ -31,38 +18,3 @@ func sourceRefFromString(in string) SourceRef { Name: args[1], } } - -func (bf *BuildFrom) decodeConfig(block *hcl.Block) hcl.Diagnostics { - - bf.Src = sourceRefFromString(block.Labels[0]) - bf.HCL2Ref.DeclRange = block.DefRange - - var b struct { - Config hcl.Body `hcl:",remain"` - } - diags := gohcl.DecodeBody(block.Body, nil, &b) - - if bf.Src == NoSource { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid " + sourceLabel + " reference", - Detail: "A " + sourceLabel + " type must start with a letter and " + - "may contain only letters, digits, underscores, and dashes." + - "A valid source reference looks like: `src.type.name`", - Subject: &block.LabelRanges[0], - }) - } - if !hclsyntax.ValidIdentifier(bf.Src.Type) || - !hclsyntax.ValidIdentifier(bf.Src.Name) { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid " + sourceLabel + " reference", - Detail: "A " + sourceLabel + " type must start with a letter and " + - "may contain only letters, digits, underscores, and dashes." + - "A valid source reference looks like: `src.type.name`", - Subject: &block.LabelRanges[0], - }) - } - - return diags -} diff --git a/hcl2template/types.build.go b/hcl2template/types.build.go index d758bfc5a..6f3485b95 100644 --- a/hcl2template/types.build.go +++ b/hcl2template/types.build.go @@ -2,58 +2,88 @@ package hcl2template import ( "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/hcl/v2/hclsyntax" ) const ( buildFromLabel = "from" - buildProvisionnersLabel = "provision" + buildSourceLabel = "source" - buildPostProvisionnersLabel = "post_provision" + buildProvisionerLabel = "provisioner" + + buildPostProcessorLabel = "post-processor" ) var buildSchema = &hcl.BodySchema{ Blocks: []hcl.BlockHeaderSchema{ - {Type: buildFromLabel, LabelNames: []string{"src"}}, - {Type: buildProvisionnersLabel}, - {Type: buildPostProvisionnersLabel}, + {Type: buildFromLabel, LabelNames: []string{"type"}}, + {Type: buildProvisionerLabel, LabelNames: []string{"type"}}, + {Type: buildPostProcessorLabel, LabelNames: []string{"type"}}, }, } -type Build struct { - // Ordered list of provisioner groups - ProvisionerGroups ProvisionerGroups +type BuildBlock struct { + ProvisionerBlocks []*ProvisionerBlock - // Ordered list of post-provisioner groups - PostProvisionerGroups ProvisionerGroups + PostProcessors []*PostProcessorBlock - // Ordered list of output stanzas - Froms BuildFromList + Froms []SourceRef HCL2Ref HCL2Ref } -type Builds []*Build +type Builds []*BuildBlock -func (p *Parser) decodeBuildConfig(block *hcl.Block) (*Build, hcl.Diagnostics) { - build := &Build{} +func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnostics) { + build := &BuildBlock{} - content, diags := block.Body.Content(buildSchema) + var b struct { + FromSources []string `hcl:"sources"` + Config hcl.Body `hcl:",remain"` + } + diags := gohcl.DecodeBody(block.Body, nil, &b) + + for _, buildFrom := range b.FromSources { + ref := sourceRefFromString(buildFrom) + + if ref == NoSource || + !hclsyntax.ValidIdentifier(ref.Type) || + !hclsyntax.ValidIdentifier(ref.Name) { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid " + sourceLabel + " reference", + Detail: "A " + sourceLabel + " type is made of three parts that are" + + "split by a dot `.`; each part must start with a letter and " + + "may contain only letters, digits, underscores, and dashes." + + "A valid source reference looks like: `source.type.name`", + Subject: block.DefRange.Ptr(), + }) + continue + } + + build.Froms = append(build.Froms, ref) + } + + content, moreDiags := b.Config.Content(buildSchema) + diags = append(diags, moreDiags...) for _, block := range content.Blocks { switch block.Type { - case buildFromLabel: - bf := BuildFrom{} - moreDiags := bf.decodeConfig(block) + case buildProvisionerLabel: + p, moreDiags := p.decodeProvisioner(block) diags = append(diags, moreDiags...) - build.Froms = append(build.Froms, bf) - case buildProvisionnersLabel: - pg, moreDiags := p.decodeProvisionerGroup(block, p.ProvisionersSchemas) + if moreDiags.HasErrors() { + continue + } + build.ProvisionerBlocks = append(build.ProvisionerBlocks, p) + case buildPostProcessorLabel: + pp, moreDiags := p.decodePostProcessorGroup(block) diags = append(diags, moreDiags...) - build.ProvisionerGroups = append(build.ProvisionerGroups, pg) - case buildPostProvisionnersLabel: - pg, moreDiags := p.decodeProvisionerGroup(block, p.PostProvisionersSchemas) - diags = append(diags, moreDiags...) - build.PostProvisionerGroups = append(build.PostProvisionerGroups, pg) + if moreDiags.HasErrors() { + continue + } + build.PostProcessors = append(build.PostProcessors, pp) } } diff --git a/hcl2template/types.build.post-processor.go b/hcl2template/types.build.post-processor.go new file mode 100644 index 000000000..7575b17c7 --- /dev/null +++ b/hcl2template/types.build.post-processor.go @@ -0,0 +1,61 @@ +package hcl2template + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/packer/packer" +) + +// PostProcessorBlock represents a parsed PostProcessorBlock +type PostProcessorBlock struct { + PType string + block *hcl.Block +} + +func (p *Parser) decodePostProcessorGroup(block *hcl.Block) (*PostProcessorBlock, hcl.Diagnostics) { + postProcessor := &PostProcessorBlock{ + PType: block.Labels[0], + block: block, + } + var diags hcl.Diagnostics + + if !p.PostProcessorsSchemas.Has(postProcessor.PType) { + diags = append(diags, &hcl.Diagnostic{ + Summary: "Unknown " + buildPostProcessorLabel + " type " + postProcessor.PType, + Subject: block.LabelRanges[0].Ptr(), + Detail: fmt.Sprintf("known provisioners: %v", p.ProvisionersSchemas.List()), + Severity: hcl.DiagError, + }) + return nil, diags + } + + return postProcessor, diags +} + +func (p *Parser) StartPostProcessor(pp *PostProcessorBlock) (packer.PostProcessor, hcl.Diagnostics) { + var diags hcl.Diagnostics + + postProcessor, err := p.PostProcessorsSchemas.Start(pp.PType) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Summary: "Failed loading " + pp.block.Type, + Subject: pp.block.LabelRanges[0].Ptr(), + Detail: err.Error(), + }) + return nil, diags + } + flatProvisinerCfg, moreDiags := decodeHCL2Spec(pp.block, nil, postProcessor) + diags = append(diags, moreDiags...) + err = postProcessor.Configure(flatProvisinerCfg) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Failed preparing " + pp.block.Type, + Detail: err.Error(), + Subject: pp.block.DefRange.Ptr(), + }) + return nil, diags + } + return postProcessor, diags +} diff --git a/hcl2template/types.build.provisioners.go b/hcl2template/types.build.provisioners.go index 77ec57c99..7fd832a6c 100644 --- a/hcl2template/types.build.provisioners.go +++ b/hcl2template/types.build.provisioners.go @@ -1,70 +1,81 @@ package hcl2template import ( + "fmt" "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/packer/helper/common" + "github.com/hashicorp/packer/packer" ) -// Provisioner represents a parsed provisioner -type Provisioner struct { - // Cfg is a parsed config - Cfg interface{} +// ProvisionerBlock represents a parsed provisioner +type ProvisionerBlock struct { + PType string + block *hcl.Block } -type ProvisionerGroup struct { - CommunicatorRef CommunicatorRef - - Provisioners []Provisioner - HCL2Ref HCL2Ref -} - -// ProvisionerGroups is a slice of provision blocks; which contains -// provisioners -type ProvisionerGroups []*ProvisionerGroup - -func (p *Parser) decodeProvisionerGroup(block *hcl.Block, provisionerSpecs map[string]Decodable) (*ProvisionerGroup, hcl.Diagnostics) { - var b struct { - Communicator string `hcl:"communicator,optional"` - Remain hcl.Body `hcl:",remain"` +func (p *Parser) decodeProvisioner(block *hcl.Block) (*ProvisionerBlock, hcl.Diagnostics) { + provisioner := &ProvisionerBlock{ + PType: block.Labels[0], + block: block, } + var diags hcl.Diagnostics - diags := gohcl.DecodeBody(block.Body, nil, &b) - - pg := &ProvisionerGroup{} - pg.CommunicatorRef = communicatorRefFromString(b.Communicator) - pg.HCL2Ref.DeclRange = block.DefRange - - buildSchema := &hcl.BodySchema{ - Blocks: []hcl.BlockHeaderSchema{}, - } - for k := range provisionerSpecs { - buildSchema.Blocks = append(buildSchema.Blocks, hcl.BlockHeaderSchema{ - Type: k, + if !p.ProvisionersSchemas.Has(provisioner.PType) { + diags = append(diags, &hcl.Diagnostic{ + Summary: "Unknown " + buildProvisionerLabel + " type " + provisioner.PType, + Subject: block.LabelRanges[0].Ptr(), + Detail: fmt.Sprintf("known provisioners: %v", p.ProvisionersSchemas.List()), + Severity: hcl.DiagError, }) + return nil, diags } + return provisioner, diags +} - content, moreDiags := b.Remain.Content(buildSchema) +func (p *Parser) StartProvisioner(pb *ProvisionerBlock, generatedVars []string) (packer.Provisioner, hcl.Diagnostics) { + var diags hcl.Diagnostics + + provisioner, err := p.ProvisionersSchemas.Start(pb.PType) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Summary: "Failed loading " + pb.block.Type, + Subject: pb.block.LabelRanges[0].Ptr(), + Detail: err.Error(), + }) + return nil, diags + } + flatProvisionerCfg, moreDiags := decodeHCL2Spec(pb.block, nil, provisioner) diags = append(diags, moreDiags...) - for _, block := range content.Blocks { - provisioner, found := provisionerSpecs[block.Type] - if !found { - diags = append(diags, &hcl.Diagnostic{ - Summary: "Unknown " + buildProvisionnersLabel + " type", - Subject: &block.LabelRanges[0], - }) - continue + if diags.HasErrors() { + return nil, diags + } + // manipulate generatedVars from builder to add to the interfaces being + // passed to the provisioner Prepare() + + // If the builder has provided a list of to-be-generated variables that + // should be made accessible to provisioners, pass that list into + // the provisioner prepare() so that the provisioner can appropriately + // validate user input against what will become available. Otherwise, + // only pass the default variables, using the basic placeholder data. + generatedPlaceholderMap := packer.BasicPlaceholderData() + if generatedVars != nil { + for _, k := range generatedVars { + generatedPlaceholderMap[k] = fmt.Sprintf("Generated_%s. "+ + common.PlaceholderMsg, k) } - flatProvisinerCfg, moreDiags := decodeDecodable(block, nil, provisioner) - diags = append(diags, moreDiags...) - pg.Provisioners = append(pg.Provisioners, Provisioner{flatProvisinerCfg}) } - - return pg, diags -} - -func (pgs ProvisionerGroups) FirstCommunicatorRef() CommunicatorRef { - if len(pgs) == 0 { - return NoCommunicator + // configs := make([]interface{}, 2) + // configs = append(, flatProvisionerCfg) + // configs = append(configs, generatedPlaceholderMap) + err = provisioner.Prepare(flatProvisionerCfg, generatedPlaceholderMap) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Failed preparing " + pb.block.Type, + Detail: err.Error(), + Subject: pb.block.DefRange.Ptr(), + }) + return nil, diags } - return pgs[0].CommunicatorRef + return provisioner, diags } diff --git a/hcl2template/types.build_test.go b/hcl2template/types.build_test.go new file mode 100644 index 000000000..2a346c84c --- /dev/null +++ b/hcl2template/types.build_test.go @@ -0,0 +1,98 @@ +package hcl2template + +import ( + "testing" + + "github.com/hashicorp/packer/packer" +) + +func TestParse_build(t *testing.T) { + defaultParser := getBasicParser() + + tests := []parseTest{ + {"basic build no src", + defaultParser, + parseTestArgs{"testdata/build/basic.pkr.hcl"}, + &PackerConfig{ + Builds: Builds{ + &BuildBlock{ + Froms: []SourceRef{ + { + Type: "amazon-ebs", + Name: "ubuntu-1604", + }, + refVBIsoUbuntu1204, + }, + ProvisionerBlocks: []*ProvisionerBlock{ + { + PType: "shell", + }, + { + PType: "file", + }, + }, + PostProcessors: []*PostProcessorBlock{ + { + PType: "amazon-import", + }, + }, + }, + }, + }, + false, false, + []packer.Build{}, + true, + }, + {"untyped provisioner", + defaultParser, + parseTestArgs{"testdata/build/provisioner_untyped.pkr.hcl"}, + &PackerConfig{ + Builds: nil, + }, + true, true, + nil, + false, + }, + {"inexistent provisioner", + defaultParser, + parseTestArgs{"testdata/build/provisioner_inexistent.pkr.hcl"}, + &PackerConfig{ + Builds: nil, + }, + true, true, + []packer.Build{&packer.CoreBuild{}}, + false, + }, + {"untyped post-processor", + defaultParser, + parseTestArgs{"testdata/build/post-processor_untyped.pkr.hcl"}, + &PackerConfig{ + Builds: nil, + }, + true, true, + []packer.Build{&packer.CoreBuild{}}, + false, + }, + {"inexistent post-processor", + defaultParser, + parseTestArgs{"testdata/build/post-processor_inexistent.pkr.hcl"}, + &PackerConfig{ + Builds: nil, + }, + true, true, + []packer.Build{}, + false, + }, + {"invalid source", + defaultParser, + parseTestArgs{"testdata/build/invalid_source_reference.pkr.hcl"}, + &PackerConfig{ + Builds: nil, + }, + true, true, + []packer.Build{}, + false, + }, + } + testParse(t, tests) +} diff --git a/hcl2template/types.communicator.go b/hcl2template/types.communicator.go deleted file mode 100644 index 0cc0841c6..000000000 --- a/hcl2template/types.communicator.go +++ /dev/null @@ -1,88 +0,0 @@ -package hcl2template - -import ( - "strings" - - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" -) - -type Communicator struct { - // Type of communicator; ex: ssh - Type string - // Given name - Name string - - Cfg interface{} - - HCL2Ref HCL2Ref -} - -func (communicator *Communicator) Ref() CommunicatorRef { - return CommunicatorRef{ - Type: communicator.Type, - Name: communicator.Name, - } -} - -func (p *Parser) decodeCommunicatorConfig(block *hcl.Block) (*Communicator, hcl.Diagnostics) { - - output := &Communicator{} - output.Type = block.Labels[0] - output.Name = block.Labels[1] - output.HCL2Ref.DeclRange = block.DefRange - - diags := hcl.Diagnostics{} - - communicator, found := p.CommunicatorSchemas[output.Type] - if !found { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unknown " + communicatorLabel + " type " + output.Type, - Detail: "A " + communicatorLabel + " type must start with a letter and " + - "may contain only letters, digits, underscores, and dashes.", - Subject: &block.DefRange, - }) - return output, diags - } - - flatCommunicator, moreDiags := decodeDecodable(block, nil, communicator) - diags = append(diags, moreDiags...) - output.Cfg = flatCommunicator - - if !hclsyntax.ValidIdentifier(output.Name) { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid " + communicatorLabel + " name", - Detail: "A " + communicatorLabel + " type must start with a letter and " + - "may contain only letters, digits, underscores, and dashes.", - Subject: &block.DefRange, - }) - } - - return output, diags -} - -type CommunicatorRef struct { - Type string - Name string -} - -// NoCommunicator is the zero value of CommunicatorRef, representing the -// absense of Communicator. -var NoCommunicator CommunicatorRef - -func communicatorRefFromString(in string) CommunicatorRef { - args := strings.Split(in, ".") - if len(args) < 2 { - return NoCommunicator - } - if len(args) > 2 { - // comm.type.name - args = args[1:] - } - return CommunicatorRef{ - Type: args[0], - Name: args[1], - } -} diff --git a/hcl2template/types.decodable.go b/hcl2template/types.decodable.go deleted file mode 100644 index 18c7dfe46..000000000 --- a/hcl2template/types.decodable.go +++ /dev/null @@ -1,56 +0,0 @@ -package hcl2template - -import ( - "fmt" - - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hcldec" - "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/gocty" -) - -type Decodable interface { - FlatMapstructure() interface{} -} - -type SelfSpecified interface { - HCL2Spec() map[string]hcldec.Spec -} - -func decodeDecodable(block *hcl.Block, ctx *hcl.EvalContext, dec Decodable) (interface{}, hcl.Diagnostics) { - var diags hcl.Diagnostics - - flatCfg := dec.FlatMapstructure() - var spec hcldec.ObjectSpec - if ss, selfSpecified := flatCfg.(SelfSpecified); selfSpecified { - spec = hcldec.ObjectSpec(ss.HCL2Spec()) - } else { - diags = append(diags, &hcl.Diagnostic{ - Summary: "Unknown type", - Subject: &block.DefRange, - Detail: fmt.Sprintf("Cannot get spec from a %T", flatCfg), - }) - return nil, diags - } - val, moreDiags := hcldec.Decode(block.Body, spec, ctx) - diags = append(diags, moreDiags...) - - err := gocty.FromCtyValue(val, flatCfg) - if err != nil { - switch err := err.(type) { - case cty.PathError: - diags = append(diags, &hcl.Diagnostic{ - Summary: "gocty.FromCtyValue: " + err.Error(), - Subject: &block.DefRange, - Detail: fmt.Sprintf("%v", err.Path), - }) - default: - diags = append(diags, &hcl.Diagnostic{ - Summary: "gocty.FromCtyValue: " + err.Error(), - Subject: &block.DefRange, - Detail: fmt.Sprintf("%v", err), - }) - } - } - return flatCfg, diags -} diff --git a/hcl2template/types.hcl_ref.go b/hcl2template/types.hcl_ref.go index 06568fb11..3c8060298 100644 --- a/hcl2template/types.hcl_ref.go +++ b/hcl2template/types.hcl_ref.go @@ -12,9 +12,3 @@ type HCL2Ref struct { // remainder of unparsed body Remain hcl.Body } - -// func (hr *HCL2Ref) Blah() { -// // hr.Remain. -// ctyjson.Marshal(nil, nil) -// hr.DeclRange. -// } diff --git a/hcl2template/types.packer_config.go b/hcl2template/types.packer_config.go index a1d3371e8..f3f061aa2 100644 --- a/hcl2template/types.packer_config.go +++ b/hcl2template/types.packer_config.go @@ -2,7 +2,7 @@ package hcl2template import ( "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/packer/template" + "github.com/hashicorp/packer/packer" ) // PackerConfig represents a loaded packer config @@ -12,154 +12,97 @@ type PackerConfig struct { Variables PackerV1Variables Builds Builds - - Communicators map[CommunicatorRef]*Communicator } -type PackerV1Build struct { - Builders []*template.Builder - Provisioners []*template.Provisioner - PostProcessors []*template.PostProcessor -} - -func (pkrCfg *PackerConfig) ToV1Build() PackerV1Build { +func (p *Parser) CoreBuildProvisioners(blocks []*ProvisionerBlock, generatedVars []string) ([]packer.CoreBuildProvisioner, hcl.Diagnostics) { var diags hcl.Diagnostics - res := PackerV1Build{} + res := []packer.CoreBuildProvisioner{} + for _, pb := range blocks { + provisioner, moreDiags := p.StartProvisioner(pb, generatedVars) + diags = append(diags, moreDiags...) + if moreDiags.HasErrors() { + continue + } + res = append(res, packer.CoreBuildProvisioner{ + PType: pb.PType, + Provisioner: provisioner, + }) + } + return res, diags +} - for _, build := range pkrCfg.Builds { - communicator, _ := pkrCfg.Communicators[build.ProvisionerGroups.FirstCommunicatorRef()] +func (p *Parser) CoreBuildPostProcessors(blocks []*PostProcessorBlock) ([]packer.CoreBuildPostProcessor, hcl.Diagnostics) { + var diags hcl.Diagnostics + res := []packer.CoreBuildPostProcessor{} + for _, pp := range blocks { + postProcessor, moreDiags := p.StartPostProcessor(pp) + diags = append(diags, moreDiags...) + if moreDiags.HasErrors() { + continue + } + res = append(res, packer.CoreBuildPostProcessor{ + PostProcessor: postProcessor, + PType: pp.PType, + }) + } + return res, diags +} + +func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics) { + res := []packer.Build{} + var diags hcl.Diagnostics + + for _, build := range cfg.Builds { for _, from := range build.Froms { - source, found := pkrCfg.Sources[from.Src] + src, found := cfg.Sources[from] if !found { diags = append(diags, &hcl.Diagnostic{ + Summary: "Unknown " + sourceLabel + " " + from.String(), + Subject: build.HCL2Ref.DeclRange.Ptr(), Severity: hcl.DiagError, - Summary: "Unknown " + sourceLabel + " reference", - Detail: "", - Subject: &from.HCL2Ref.DeclRange, }) - + continue + } + builder, moreDiags, generatedVars := p.StartBuilder(src) + diags = append(diags, moreDiags...) + if moreDiags.HasErrors() { + continue + } + provisioners, moreDiags := p.CoreBuildProvisioners(build.ProvisionerBlocks, generatedVars) + diags = append(diags, moreDiags...) + if moreDiags.HasErrors() { + continue + } + postProcessors, moreDiags := p.CoreBuildPostProcessors(build.PostProcessors) + pps := [][]packer.CoreBuildPostProcessor{} + if len(postProcessors) > 0 { + pps = [][]packer.CoreBuildPostProcessor{postProcessors} + } + diags = append(diags, moreDiags...) + if moreDiags.HasErrors() { continue } - // provisioners := build.ProvisionerGroups.FlatProvisioners() - // postProcessors := build.PostProvisionerGroups.FlatProvisioners() - - _ = from - _ = source - _ = communicator - // _ = provisioners - // _ = postProcessors + pcb := &packer.CoreBuild{ + Type: src.Type, + Builder: builder, + Provisioners: provisioners, + PostProcessors: pps, + Variables: cfg.Variables, + } + res = append(res, pcb) } } - return res + return res, diags } -func (pkrCfg *PackerConfig) ToTemplate() (*template.Template, error) { - var result template.Template - // var errs error +func (p *Parser) Parse(path string) ([]packer.Build, hcl.Diagnostics) { + cfg, diags := p.parse(path) + if diags.HasErrors() { + return nil, diags + } - result.Comments = nil - result.Variables = pkrCfg.Variables.Variables() - // TODO(azr): add sensitive variables - - builder := pkrCfg.ToV1Build() - _ = builder - - // // Gather all the post-processors - // if len(r.PostProcessors) > 0 { - // result.PostProcessors = make([][]*PostProcessor, 0, len(r.PostProcessors)) - // } - // for i, v := range r.PostProcessors { - // // Parse the configurations. We need to do this because post-processors - // // can take three different formats. - // configs, err := r.parsePostProcessor(i, v) - // if err != nil { - // errs = multierror.Append(errs, err) - // continue - // } - - // // Parse the PostProcessors out of the configs - // pps := make([]*PostProcessor, 0, len(configs)) - // for j, c := range configs { - // var pp PostProcessor - // if err := r.decoder(&pp, nil).Decode(c); err != nil { - // errs = multierror.Append(errs, fmt.Errorf( - // "post-processor %d.%d: %s", i+1, j+1, err)) - // continue - // } - - // // Type is required - // if pp.Type == "" { - // errs = multierror.Append(errs, fmt.Errorf( - // "post-processor %d.%d: type is required", i+1, j+1)) - // continue - // } - - // // Set the raw configuration and delete any special keys - // pp.Config = c - - // // The name defaults to the type if it isn't set - // if pp.Name == "" { - // pp.Name = pp.Type - // } - - // delete(pp.Config, "except") - // delete(pp.Config, "only") - // delete(pp.Config, "keep_input_artifact") - // delete(pp.Config, "type") - // delete(pp.Config, "name") - - // if len(pp.Config) == 0 { - // pp.Config = nil - // } - - // pps = append(pps, &pp) - // } - - // result.PostProcessors = append(result.PostProcessors, pps) - // } - - // // Gather all the provisioners - // if len(r.Provisioners) > 0 { - // result.Provisioners = make([]*Provisioner, 0, len(r.Provisioners)) - // } - // for i, v := range r.Provisioners { - // var p Provisioner - // if err := r.decoder(&p, nil).Decode(v); err != nil { - // errs = multierror.Append(errs, fmt.Errorf( - // "provisioner %d: %s", i+1, err)) - // continue - // } - - // // Type is required before any richer validation - // if p.Type == "" { - // errs = multierror.Append(errs, fmt.Errorf( - // "provisioner %d: missing 'type'", i+1)) - // continue - // } - - // // Set the raw configuration and delete any special keys - // p.Config = v.(map[string]interface{}) - - // delete(p.Config, "except") - // delete(p.Config, "only") - // delete(p.Config, "override") - // delete(p.Config, "pause_before") - // delete(p.Config, "type") - // delete(p.Config, "timeout") - - // if len(p.Config) == 0 { - // p.Config = nil - // } - - // result.Provisioners = append(result.Provisioners, &p) - // } - - // // If we have errors, return those with a nil result - // if errs != nil { - // return nil, errs - // } - - return &result, nil + builds, moreDiags := p.getBuilds(cfg) + return builds, append(diags, moreDiags...) } diff --git a/hcl2template/types.packer_config_test.go b/hcl2template/types.packer_config_test.go new file mode 100644 index 000000000..0e410a850 --- /dev/null +++ b/hcl2template/types.packer_config_test.go @@ -0,0 +1,102 @@ +package hcl2template + +import ( + "testing" + + "github.com/hashicorp/packer/packer" +) + +var ( + refVBIsoUbuntu1204 = SourceRef{Type: "virtualbox-iso", Name: "ubuntu-1204"} + refAWSEBSUbuntu1204 = SourceRef{Type: "amazon-ebs", Name: "ubuntu-1604"} +) + +func TestParser_complete(t *testing.T) { + defaultParser := getBasicParser() + + tests := []parseTest{ + {"working build", + defaultParser, + parseTestArgs{"testdata/complete"}, + &PackerConfig{ + Sources: map[SourceRef]*Source{ + refVBIsoUbuntu1204: &Source{Type: "virtualbox-iso", Name: "ubuntu-1204"}, + }, + Builds: Builds{ + &BuildBlock{ + Froms: []SourceRef{refVBIsoUbuntu1204}, + ProvisionerBlocks: []*ProvisionerBlock{ + {PType: "shell"}, + {PType: "file"}, + }, + PostProcessors: []*PostProcessorBlock{ + {PType: "amazon-import"}, + }, + }, + }, + }, + false, false, + []packer.Build{ + &packer.CoreBuild{ + Type: "virtualbox-iso", + Builder: basicMockBuilder, + Provisioners: []packer.CoreBuildProvisioner{ + {PType: "shell", Provisioner: basicMockProvisioner}, + {PType: "file", Provisioner: basicMockProvisioner}, + }, + PostProcessors: [][]packer.CoreBuildPostProcessor{ + { + {PType: "amazon-import", PostProcessor: basicMockPostProcessor}, + }, + }, + }, + }, + false, + }, + {"dir with no config files", + defaultParser, + parseTestArgs{"testdata/empty"}, + nil, + true, true, + nil, + false, + }, + {name: "inexistent dir", + parser: defaultParser, + args: parseTestArgs{"testdata/inexistent"}, + parseWantCfg: nil, + parseWantDiags: true, + parseWantDiagHasErrors: true, + }, + {name: "folder named build.pkr.hcl with an unknown src", + parser: defaultParser, + args: parseTestArgs{"testdata/build.pkr.hcl"}, + parseWantCfg: &PackerConfig{ + Builds: Builds{ + &BuildBlock{ + Froms: []SourceRef{refAWSEBSUbuntu1204, refVBIsoUbuntu1204}, + ProvisionerBlocks: []*ProvisionerBlock{ + {PType: "shell"}, + {PType: "file"}, + }, + PostProcessors: []*PostProcessorBlock{ + {PType: "amazon-import"}, + }, + }, + }, + }, + parseWantDiags: false, + parseWantDiagHasErrors: false, + getBuildsWantBuilds: []packer.Build{}, + getBuildsWantDiags: true, + }, + {name: "unknown block type", + parser: defaultParser, + args: parseTestArgs{"testdata/unknown"}, + parseWantCfg: &PackerConfig{}, + parseWantDiags: true, + parseWantDiagHasErrors: true, + }, + } + testParse(t, tests) +} diff --git a/hcl2template/types.source.go b/hcl2template/types.source.go index 17844b323..47eddfa70 100644 --- a/hcl2template/types.source.go +++ b/hcl2template/types.source.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/packer/packer" ) // A source field in an HCL file will load into the Source type. @@ -14,36 +15,55 @@ type Source struct { // Given name; if any Name string - Cfg interface{} - - HCL2Ref HCL2Ref + block *hcl.Block } -func (p *Parser) decodeSource(block *hcl.Block, sourceSpecs map[string]Decodable) (*Source, hcl.Diagnostics) { +func (p *Parser) decodeSource(block *hcl.Block) (*Source, hcl.Diagnostics) { source := &Source{ - Type: block.Labels[0], - Name: block.Labels[1], + Type: block.Labels[0], + Name: block.Labels[1], + block: block, } - source.HCL2Ref.DeclRange = block.DefRange - var diags hcl.Diagnostics - sourceSpec, found := sourceSpecs[source.Type] - if !found { + if !p.BuilderSchemas.Has(source.Type) { diags = append(diags, &hcl.Diagnostic{ - Summary: "Unknown " + sourceLabel + " type", - Subject: &block.LabelRanges[0], + Summary: "Unknown " + buildSourceLabel + " type " + source.Type, + Subject: block.LabelRanges[0].Ptr(), + Detail: fmt.Sprintf("known builders: %v", p.BuilderSchemas.List()), + Severity: hcl.DiagError, }) - return source, diags + return nil, diags } - flatSource, moreDiags := decodeDecodable(block, nil, sourceSpec) - diags = append(diags, moreDiags...) - source.Cfg = flatSource - return source, diags } +func (p *Parser) StartBuilder(source *Source) (packer.Builder, hcl.Diagnostics, []string) { + var diags hcl.Diagnostics + + builder, err := p.BuilderSchemas.Start(source.Type) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Summary: "Failed to load " + sourceLabel + " type", + Detail: err.Error(), + Subject: &source.block.LabelRanges[0], + }) + return builder, diags, nil + } + + decoded, moreDiags := decodeHCL2Spec(source.block, nil, builder) + diags = append(diags, moreDiags...) + if moreDiags.HasErrors() { + return nil, diags, nil + } + + generatedVars, warning, err := builder.Prepare(decoded) + moreDiags = warningErrorsToDiags(source.block, warning, err) + diags = append(diags, moreDiags...) + return builder, diags, generatedVars +} + func (source *Source) Ref() SourceRef { return SourceRef{ Type: source.Type, diff --git a/hcl2template/types.source_test.go b/hcl2template/types.source_test.go new file mode 100644 index 000000000..66d38860a --- /dev/null +++ b/hcl2template/types.source_test.go @@ -0,0 +1,75 @@ +package hcl2template + +import ( + "testing" + + "github.com/hashicorp/packer/packer" +) + +func TestParse_source(t *testing.T) { + defaultParser := getBasicParser() + + tests := []parseTest{ + {"two basic sources", + defaultParser, + parseTestArgs{"testdata/sources/basic.pkr.hcl"}, + &PackerConfig{ + Sources: map[SourceRef]*Source{ + SourceRef{ + Type: "virtualbox-iso", + Name: "ubuntu-1204", + }: { + Type: "virtualbox-iso", + Name: "ubuntu-1204", + }, + }, + }, + false, false, + []packer.Build{}, + false, + }, + {"untyped source", + defaultParser, + parseTestArgs{"testdata/sources/untyped.pkr.hcl"}, + &PackerConfig{}, + true, true, + nil, + false, + }, + {"unnamed source", + defaultParser, + parseTestArgs{"testdata/sources/unnamed.pkr.hcl"}, + &PackerConfig{}, + true, true, + nil, + false, + }, + {"inexistent source", + defaultParser, + parseTestArgs{"testdata/sources/inexistent.pkr.hcl"}, + &PackerConfig{}, + true, true, + nil, + false, + }, + {"duplicate source", + defaultParser, + parseTestArgs{"testdata/sources/duplicate.pkr.hcl"}, + &PackerConfig{ + Sources: map[SourceRef]*Source{ + SourceRef{ + Type: "virtualbox-iso", + Name: "ubuntu-1204", + }: { + Type: "virtualbox-iso", + Name: "ubuntu-1204", + }, + }, + }, + true, true, + nil, + false, + }, + } + testParse(t, tests) +} diff --git a/hcl2template/types.variable.go b/hcl2template/types.variables.go similarity index 100% rename from hcl2template/types.variable.go rename to hcl2template/types.variables.go diff --git a/hcl2template/types.variables_test.go b/hcl2template/types.variables_test.go new file mode 100644 index 000000000..cf60f25ef --- /dev/null +++ b/hcl2template/types.variables_test.go @@ -0,0 +1,29 @@ +package hcl2template + +import ( + "testing" + + "github.com/hashicorp/packer/packer" +) + +func TestParse_variables(t *testing.T) { + defaultParser := getBasicParser() + + tests := []parseTest{ + {"basic variables", + defaultParser, + parseTestArgs{"testdata/variables/basic.pkr.hcl"}, + &PackerConfig{ + Variables: PackerV1Variables{ + "image_name": "foo-image-{{user `my_secret`}}", + "key": "value", + "my_secret": "foo", + }, + }, + false, false, + []packer.Build{}, + false, + }, + } + testParse(t, tests) +} diff --git a/hcl2template/utils.go b/hcl2template/utils.go new file mode 100644 index 000000000..52429321b --- /dev/null +++ b/hcl2template/utils.go @@ -0,0 +1,90 @@ +package hcl2template + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/hashicorp/hcl/v2" +) + +func warningErrorsToDiags(block *hcl.Block, warnings []string, err error) hcl.Diagnostics { + var diags hcl.Diagnostics + + for _, warning := range warnings { + diags = append(diags, &hcl.Diagnostic{ + Summary: warning, + Subject: &block.DefRange, + Severity: hcl.DiagWarning, + }) + } + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Summary: err.Error(), + Subject: &block.DefRange, + Severity: hcl.DiagError, + }) + } + return diags +} + +func isDir(name string) (bool, error) { + s, err := os.Stat(name) + if err != nil { + return false, err + } + return s.IsDir(), nil +} + +func GetHCL2Files(filename string) (hclFiles, jsonFiles []string, diags hcl.Diagnostics) { + isDir, err := isDir(filename) + if err != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Cannot tell wether " + filename + " is a directory", + Detail: err.Error(), + }) + return nil, nil, diags + } + if !isDir { + if strings.HasSuffix(filename, hcl2JsonFileExt) { + return nil, []string{filename}, diags + } + if strings.HasSuffix(filename, hcl2FileExt) { + return []string{filename}, nil, diags + } + } + + fileInfos, err := ioutil.ReadDir(filename) + if err != nil { + diag := &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Cannot read hcl directory", + Detail: err.Error(), + } + diags = append(diags, diag) + return nil, nil, diags + } + for _, fileInfo := range fileInfos { + if fileInfo.IsDir() { + continue + } + filename := filepath.Join(filename, fileInfo.Name()) + if strings.HasSuffix(filename, hcl2FileExt) { + hclFiles = append(hclFiles, filename) + } else if strings.HasSuffix(filename, hcl2JsonFileExt) { + jsonFiles = append(jsonFiles, filename) + } + } + if len(hclFiles)+len(jsonFiles) == 0 { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Could not find any config file in " + filename, + Detail: "A config file must be suffixed with `.pkr.hcl` or " + + "`.pkr.json`. A folder can be referenced.", + }) + } + + return hclFiles, jsonFiles, diags +} diff --git a/helper/builder/testing/testing.go b/helper/builder/testing/testing.go index 223615fa3..df1bdad87 100644 --- a/helper/builder/testing/testing.go +++ b/helper/builder/testing/testing.go @@ -63,6 +63,13 @@ type TestT interface { Skip(args ...interface{}) } +type TestBuilderStore struct { + packer.BuilderStore + StartFn func(name string) (packer.Builder, error) +} + +func (tbs TestBuilderStore) Start(name string) (packer.Builder, error) { return tbs.StartFn(name) } + // Test performs an acceptance test on a backend with the given test case. // // Tests are not run unless an environmental variable "PACKER_ACC" is @@ -106,12 +113,14 @@ func Test(t TestT, c TestCase) { log.Printf("[DEBUG] Initializing core...") core, err := packer.NewCore(&packer.CoreConfig{ Components: packer.ComponentFinder{ - Builder: func(n string) (packer.Builder, error) { - if n == "test" { - return c.Builder, nil - } + BuilderStore: TestBuilderStore{ + StartFn: func(n string) (packer.Builder, error) { + if n == "test" { + return c.Builder, nil + } - return nil, nil + return nil, nil + }, }, }, Template: tpl, diff --git a/helper/common/shared_state.go b/helper/common/shared_state.go index 87e111cfb..10f3803ae 100644 --- a/helper/common/shared_state.go +++ b/helper/common/shared_state.go @@ -7,6 +7,11 @@ import ( "path/filepath" ) +// This is used in the BasicPlaceholderData() func in the packer/provisioner.go +// To force users to access generated data via the "generated" func. +const PlaceholderMsg = "To set this dynamically in the Packer template, " + + "you must use the `build` function" + // Used to set variables which we need to access later in the build, where // state bag and config information won't work func sharedStateFilename(suffix string, buildName string) string { diff --git a/helper/communicator/config.go b/helper/communicator/config.go index c6c5d880e..5cb3a4716 100644 --- a/helper/communicator/config.go +++ b/helper/communicator/config.go @@ -11,7 +11,9 @@ import ( "os" "time" + "github.com/hashicorp/hcl/v2/hcldec" packerssh "github.com/hashicorp/packer/communicator/ssh" + "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" helperssh "github.com/hashicorp/packer/helper/ssh" "github.com/hashicorp/packer/packer" @@ -111,7 +113,8 @@ type SSH struct { // use this option with a key pair already configured in the source AMI, // leave the `ssh_keypair_name` blank. To associate an existing key pair in // AWS with the source instance, set the `ssh_keypair_name` field to the - // name of the key pair. + // name of the key pair. The environment variable `SSH_AUTH_SOCK` must be + // set for this option to work properly. SSHAgentAuth bool `mapstructure:"ssh_agent_auth"` // If true, SSH agent forwarding will be disabled. Defaults to `false`. SSHDisableAgentForwarding bool `mapstructure:"ssh_disable_agent_forwarding"` @@ -160,10 +163,28 @@ type SSH struct { SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels"` // SSH Internals - SSHPublicKey []byte - SSHPrivateKey []byte + SSHPublicKey []byte `mapstructure:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key"` } +func (c *SSH) ConfigSpec() hcldec.ObjectSpec { return c.FlatMapstructure().HCL2Spec() } +func (c *WinRM) ConfigSpec() hcldec.ObjectSpec { return c.FlatMapstructure().HCL2Spec() } + +func (c *SSH) Configure(raws ...interface{}) ([]string, error) { + err := config.Decode(c, nil, raws...) + return nil, err +} + +func (c *WinRM) Configure(raws ...interface{}) ([]string, error) { + err := config.Decode(c, nil, raws...) + return nil, err +} + +var ( + _ packer.ConfigurableCommunicator = new(SSH) + _ packer.ConfigurableCommunicator = new(WinRM) +) + type SSHInterface struct { // One of `public_ip`, `private_ip`, `public_dns`, or `private_dns`. If // set, either the public IP address, private IP address, public DNS name diff --git a/helper/communicator/config.hcl2spec.go b/helper/communicator/config.hcl2spec.go index bf726f3cd..977ac1d41 100644 --- a/helper/communicator/config.hcl2spec.go +++ b/helper/communicator/config.hcl2spec.go @@ -39,8 +39,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -54,10 +54,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, @@ -135,17 +138,18 @@ type FlatSSH struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` } // FlatMapstructure returns a new FlatSSH. // FlatSSH is an auto-generated flat version of SSH. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*SSH) FlatMapstructure() interface{} { return new(FlatSSH) } +func (*SSH) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { return new(FlatSSH) } -// HCL2Spec returns the hcldec.Spec of a FlatSSH. -// This spec is used by HCL to read the fields of FlatSSH. +// HCL2Spec returns the hcl spec of a SSH. +// This spec is used by HCL to read the fields of SSH. +// The decoded values from this spec will then be applied to a FlatSSH. func (*FlatSSH) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, @@ -198,10 +202,11 @@ type FlatWinRM struct { // FlatMapstructure returns a new FlatWinRM. // FlatWinRM is an auto-generated flat version of WinRM. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*WinRM) FlatMapstructure() interface{} { return new(FlatWinRM) } +func (*WinRM) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { return new(FlatWinRM) } -// HCL2Spec returns the hcldec.Spec of a FlatWinRM. -// This spec is used by HCL to read the fields of FlatWinRM. +// HCL2Spec returns the hcl spec of a WinRM. +// This spec is used by HCL to read the fields of WinRM. +// The decoded values from this spec will then be applied to a FlatWinRM. func (*FlatWinRM) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false}, diff --git a/helper/communicator/step_connect.go b/helper/communicator/step_connect.go index 6bb062f80..7694e0b1a 100644 --- a/helper/communicator/step_connect.go +++ b/helper/communicator/step_connect.go @@ -101,7 +101,6 @@ func (s *StepConnect) Run(ctx context.Context, state multistep.StateBag) multist if host, err := s.Host(state); err == nil { ui.Say(fmt.Sprintf("Using %s communicator to connect: %s", s.Config.Type, host)) - } else { log.Printf("[DEBUG] Unable to get address during connection step: %s", err) } @@ -119,6 +118,10 @@ func (s *StepConnect) Run(ctx context.Context, state multistep.StateBag) multist } } + // Put communicator config into state so we can pass it to provisioners + // for specialized interpolation later + state.Put("communicator_config", s.Config) + return multistep.ActionContinue } diff --git a/helper/communicator/step_connect_ssh.go b/helper/communicator/step_connect_ssh.go index 962615c3a..1584c3e61 100644 --- a/helper/communicator/step_connect_ssh.go +++ b/helper/communicator/step_connect_ssh.go @@ -134,6 +134,8 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, ctx context.Contex log.Printf("[DEBUG] Error getting SSH address: %s", err) continue } + // store host and port in config so we can access them from provisioners + s.Config.SSHHost = host port := s.Config.SSHPort if s.SSHPort != nil { port, err = s.SSHPort(state) @@ -141,7 +143,9 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, ctx context.Contex log.Printf("[DEBUG] Error getting SSH port: %s", err) continue } + s.Config.SSHPort = port } + state.Put("communicator_config", s.Config) // Retrieve the SSH configuration sshConfig, err := s.SSHConfig(state) @@ -204,7 +208,6 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, ctx context.Contex } log.Printf("[INFO] Attempting SSH connection to %s...", address) - log.Printf("[DEBUG] Config to %#v...", config) comm, err = ssh.New(address, config) if err != nil { log.Printf("[DEBUG] SSH handshake err: %s", err) diff --git a/helper/communicator/step_connect_winrm.go b/helper/communicator/step_connect_winrm.go index 275468bc1..8825f98b0 100644 --- a/helper/communicator/step_connect_winrm.go +++ b/helper/communicator/step_connect_winrm.go @@ -103,6 +103,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, ctx context.Co log.Printf("[DEBUG] Error getting WinRM host: %s", err) continue } + s.Config.WinRMHost = host port := s.Config.WinRMPort if s.WinRMPort != nil { @@ -111,8 +112,11 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, ctx context.Co log.Printf("[DEBUG] Error getting WinRM port: %s", err) continue } + s.Config.WinRMPort = port } + state.Put("communicator_config", s.Config) + user := s.Config.WinRMUser password := s.Config.WinRMPassword if s.WinRMConfig != nil { diff --git a/helper/config/decode.go b/helper/config/decode.go index 43673e1dc..5205bf266 100644 --- a/helper/config/decode.go +++ b/helper/config/decode.go @@ -1,14 +1,19 @@ package config import ( + "encoding/json" "fmt" "reflect" "sort" "strings" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/template/interpolate" "github.com/mitchellh/mapstructure" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" + ctyjson "github.com/zclconf/go-cty/cty/json" ) // DecodeOpts are the options for decoding configuration. @@ -37,13 +42,45 @@ var DefaultDecodeHookFuncs = []mapstructure.DecodeHookFunc{ // Decode decodes the configuration into the target and optionally // automatically interpolates all the configuration as it goes. func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { + for i, raw := range raws { + // check for cty values and transform them to json then to a + // map[string]interface{} so that mapstructure can do its thing. + cval, ok := raw.(cty.Value) + if !ok { + continue + } + type flatConfigurer interface { + FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } + } + ctarget := target.(flatConfigurer) + flatCfg := ctarget.FlatMapstructure() + err := gocty.FromCtyValue(cval, flatCfg) + if err != nil { + switch err := err.(type) { + case cty.PathError: + return fmt.Errorf("%v: %v", err, err.Path) + } + return err + } + b, err := ctyjson.SimpleJSONValue{Value: cval}.MarshalJSON() + if err != nil { + return err + } + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + raws[i] = raw + } if config == nil { config = &DecodeOpts{Interpolate: true} } + // Detect user variables from the raws and merge them into our context + ctxData, raws := DetectContextData(raws...) + // Interpolate first if config.Interpolate { - // Detect user variables from the raws and merge them into our context ctx, err := DetectContext(raws...) if err != nil { return err @@ -55,6 +92,9 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { config.InterpolateContext.BuildType = ctx.BuildType config.InterpolateContext.TemplatePath = ctx.TemplatePath config.InterpolateContext.UserVariables = ctx.UserVariables + if config.InterpolateContext.Data == nil { + config.InterpolateContext.Data = ctxData + } } ctx = config.InterpolateContext @@ -103,7 +143,7 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { for _, unused := range md.Unused { if unused != "type" && !strings.HasPrefix(unused, "packer_") { err = multierror.Append(err, fmt.Errorf( - "unknown configuration key: %q", unused)) + "unknown configuration key: %q; raws is %#v \n\n and ctx data is %#v", unused, raws, ctxData)) } } if err != nil { @@ -114,6 +154,40 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { return nil } +func DetectContextData(raws ...interface{}) (map[interface{}]interface{}, []interface{}) { + // In provisioners, the last value pulled from raws is the placeholder data + // for build-specific variables. Pull these out to add to interpolation + // context. + + // Internally, our tests may cause this to be read as a map[string]string + placeholderData := raws[len(raws)-1] + if pd, ok := placeholderData.(map[string]string); ok { + if uuid, ok := pd["PackerRunUUID"]; ok { + if strings.Contains(uuid, "Build_PackerRunUUID.") { + cast := make(map[interface{}]interface{}) + for k, v := range pd { + cast[k] = v + } + raws = raws[:len(raws)-1] + return cast, raws + } + } + } + + // but with normal interface conversion across the rpc, it'll look like a + // map[interface]interface, not a map[string]string + if pd, ok := placeholderData.(map[interface{}]interface{}); ok { + if uuid, ok := pd["PackerRunUUID"]; ok { + if strings.Contains(uuid.(string), "Build_PackerRunUUID.") { + raws = raws[:len(raws)-1] + return pd, raws + } + } + } + + return nil, raws +} + // DetectContext builds a base interpolate.Context, automatically // detecting things like user variables from the raw configuration params. func DetectContext(raws ...interface{}) (*interpolate.Context, error) { diff --git a/main.go b/main.go index 4078d306f..bed90598c 100644 --- a/main.go +++ b/main.go @@ -62,8 +62,6 @@ func realMain() int { packer.LogSecretFilter.SetOutput(logWriter) - //packer.LogSecrets. - // Disable logging here log.SetOutput(ioutil.Discard) @@ -137,18 +135,22 @@ func wrappedMain() int { packer.LogSecretFilter.SetOutput(os.Stderr) log.SetOutput(&packer.LogSecretFilter) - log.Printf("[INFO] Packer version: %s", version.FormattedVersion()) - log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH) - log.Printf("Built with Go Version: %s", runtime.Version()) - inPlugin := os.Getenv(plugin.MagicCookieKey) == plugin.MagicCookieValue + if inPlugin { + // This prevents double-logging timestamps + log.SetFlags(0) + } + + log.Printf("[INFO] Packer version: %s [%s %s %s]", + version.FormattedVersion(), + runtime.Version(), + runtime.GOOS, runtime.GOARCH) config, err := loadConfig() if err != nil { fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err) return 1 } - log.Printf("Packer config: %+v", config) // Fire off the checkpoint. go runCheckpoint(config) @@ -195,7 +197,7 @@ func wrappedMain() int { currentPID := os.Getpid() backgrounded, err := checkProcess(currentPID) if err != nil { - fmt.Fprintf(os.Stderr, "cannot determind if process is in "+ + fmt.Fprintf(os.Stderr, "cannot determine if process is in "+ "background: %s\n", err) } if backgrounded { @@ -212,10 +214,11 @@ func wrappedMain() int { CommandMeta = &command.Meta{ CoreConfig: &packer.CoreConfig{ Components: packer.ComponentFinder{ - Builder: config.LoadBuilder, - Hook: config.LoadHook, - PostProcessor: config.LoadPostProcessor, - Provisioner: config.LoadProvisioner, + Hook: config.StarHook, + + BuilderStore: config.Builders, + ProvisionerStore: config.Provisioners, + PostProcessorStore: config.PostProcessors, }, Version: version.Version, }, @@ -289,6 +292,9 @@ func loadConfig() (*config, error) { var config config config.PluginMinPort = 10000 config.PluginMaxPort = 25000 + config.Builders = packer.MapOfBuilder{} + config.PostProcessors = packer.MapOfPostProcessor{} + config.Provisioners = packer.MapOfProvisioner{} if err := config.Discover(); err != nil { return nil, err } diff --git a/packer/build.go b/packer/build.go index badd67c1f..46f07e315 100644 --- a/packer/build.go +++ b/packer/build.go @@ -5,6 +5,8 @@ import ( "fmt" "log" "sync" + + "github.com/hashicorp/packer/helper/common" ) const ( @@ -79,21 +81,21 @@ type Build interface { SetOnError(string) } -// A build struct represents a single build job, the result of which should +// A CoreBuild struct represents a single build job, the result of which should // be a single machine image artifact. This artifact may be comprised of -// multiple files, of course, but it should be for only a single provider -// (such as VirtualBox, EC2, etc.). -type coreBuild struct { - name string - builder Builder - builderConfig interface{} - builderType string +// multiple files, of course, but it should be for only a single provider (such +// as VirtualBox, EC2, etc.). +type CoreBuild struct { + Type string + Builder Builder + BuilderConfig interface{} + BuilderType string hooks map[string][]Hook - postProcessors [][]coreBuildPostProcessor - provisioners []coreBuildProvisioner - cleanupProvisioner coreBuildProvisioner - templatePath string - variables map[string]string + Provisioners []CoreBuildProvisioner + PostProcessors [][]CoreBuildPostProcessor + CleanupProvisioner CoreBuildProvisioner + TemplatePath string + Variables map[string]string debug bool force bool @@ -102,32 +104,32 @@ type coreBuild struct { prepareCalled bool } -// Keeps track of the post-processor and the configuration of the -// post-processor used within a build. -type coreBuildPostProcessor struct { - processor PostProcessor - processorType string +// CoreBuildPostProcessor Keeps track of the post-processor and the +// configuration of the post-processor used within a build. +type CoreBuildPostProcessor struct { + PostProcessor PostProcessor + PType string config map[string]interface{} keepInputArtifact *bool } -// Keeps track of the provisioner and the configuration of the provisioner -// within the build. -type coreBuildProvisioner struct { - pType string - provisioner Provisioner +// CoreBuildProvisioner keeps track of the provisioner and the configuration of +// the provisioner within the build. +type CoreBuildProvisioner struct { + PType string + Provisioner Provisioner config []interface{} } // Returns the name of the build. -func (b *coreBuild) Name() string { - return b.name +func (b *CoreBuild) Name() string { + return b.Type } // Prepare prepares the build by doing some initialization for the builder // and any hooks. This _must_ be called prior to Run. The parameter is the // overrides for the variables within the template (if any). -func (b *coreBuild) Prepare() (warn []string, err error) { +func (b *CoreBuild) Prepare() (warn []string, err error) { b.l.Lock() defer b.l.Unlock() @@ -138,48 +140,62 @@ func (b *coreBuild) Prepare() (warn []string, err error) { b.prepareCalled = true packerConfig := map[string]interface{}{ - BuildNameConfigKey: b.name, - BuilderTypeConfigKey: b.builderType, + BuildNameConfigKey: b.Type, + BuilderTypeConfigKey: b.BuilderType, DebugConfigKey: b.debug, ForceConfigKey: b.force, OnErrorConfigKey: b.onError, - TemplatePathKey: b.templatePath, - UserVariablesConfigKey: b.variables, + TemplatePathKey: b.TemplatePath, + UserVariablesConfigKey: b.Variables, } // Prepare the builder - warn, err = b.builder.Prepare(b.builderConfig, packerConfig) + generatedVars, warn, err := b.Builder.Prepare(b.BuilderConfig, packerConfig) if err != nil { - log.Printf("Build '%s' prepare failure: %s\n", b.name, err) + log.Printf("Build '%s' prepare failure: %s\n", b.Type, err) return } + // If the builder has provided a list of to-be-generated variables that + // should be made accessible to provisioners, pass that list into + // the provisioner prepare() so that the provisioner can appropriately + // validate user input against what will become available. + generatedPlaceholderMap := BasicPlaceholderData() + if generatedVars != nil { + for _, k := range generatedVars { + generatedPlaceholderMap[k] = fmt.Sprintf("Build_%s. "+ + common.PlaceholderMsg, k) + } + } + // Prepare the provisioners - for _, coreProv := range b.provisioners { + for _, coreProv := range b.Provisioners { configs := make([]interface{}, len(coreProv.config), len(coreProv.config)+1) copy(configs, coreProv.config) configs = append(configs, packerConfig) + configs = append(configs, generatedPlaceholderMap) - if err = coreProv.provisioner.Prepare(configs...); err != nil { + if err = coreProv.Provisioner.Prepare(configs...); err != nil { return } } // Prepare the on-error-cleanup provisioner - if b.cleanupProvisioner.pType != "" { - configs := make([]interface{}, len(b.cleanupProvisioner.config), len(b.cleanupProvisioner.config)+1) - copy(configs, b.cleanupProvisioner.config) + if b.CleanupProvisioner.PType != "" { + configs := make([]interface{}, len(b.CleanupProvisioner.config), len(b.CleanupProvisioner.config)+1) + copy(configs, b.CleanupProvisioner.config) configs = append(configs, packerConfig) - err = b.cleanupProvisioner.provisioner.Prepare(configs...) + configs = append(configs, generatedPlaceholderMap) + err = b.CleanupProvisioner.Provisioner.Prepare(configs...) if err != nil { return } } // Prepare the post-processors - for _, ppSeq := range b.postProcessors { + for _, ppSeq := range b.PostProcessors { for _, corePP := range ppSeq { - err = corePP.processor.Configure(corePP.config, packerConfig) + err = corePP.PostProcessor.Configure(corePP.config, packerConfig) if err != nil { return } @@ -190,7 +206,7 @@ func (b *coreBuild) Prepare() (warn []string, err error) { } // Runs the actual build. Prepare must be called prior to running this. -func (b *coreBuild) Run(ctx context.Context, originalUi Ui) ([]Artifact, error) { +func (b *CoreBuild) Run(ctx context.Context, originalUi Ui) ([]Artifact, error) { if !b.prepareCalled { panic("Prepare must be called first") } @@ -203,24 +219,24 @@ func (b *coreBuild) Run(ctx context.Context, originalUi Ui) ([]Artifact, error) } // Add a hook for the provisioners if we have provisioners - if len(b.provisioners) > 0 { - hookedProvisioners := make([]*HookedProvisioner, len(b.provisioners)) - for i, p := range b.provisioners { + if len(b.Provisioners) > 0 { + hookedProvisioners := make([]*HookedProvisioner, len(b.Provisioners)) + for i, p := range b.Provisioners { var pConfig interface{} if len(p.config) > 0 { pConfig = p.config[0] } if b.debug { hookedProvisioners[i] = &HookedProvisioner{ - &DebuggedProvisioner{Provisioner: p.provisioner}, + &DebuggedProvisioner{Provisioner: p.Provisioner}, pConfig, - p.pType, + p.PType, } } else { hookedProvisioners[i] = &HookedProvisioner{ - p.provisioner, + p.Provisioner, pConfig, - p.pType, + p.PType, } } } @@ -234,11 +250,11 @@ func (b *coreBuild) Run(ctx context.Context, originalUi Ui) ([]Artifact, error) }) } - if b.cleanupProvisioner.pType != "" { + if b.CleanupProvisioner.PType != "" { hookedCleanupProvisioner := &HookedProvisioner{ - b.cleanupProvisioner.provisioner, - b.cleanupProvisioner.config, - b.cleanupProvisioner.pType, + b.CleanupProvisioner.Provisioner, + b.CleanupProvisioner.config, + b.CleanupProvisioner.PType, } hooks[HookCleanupProvision] = []Hook{&ProvisionHook{ Provisioners: []*HookedProvisioner{hookedCleanupProvisioner}, @@ -254,9 +270,9 @@ func (b *coreBuild) Run(ctx context.Context, originalUi Ui) ([]Artifact, error) Ui: originalUi, } - log.Printf("Running builder: %s", b.builderType) - ts := CheckpointReporter.AddSpan(b.builderType, "builder", b.builderConfig) - builderArtifact, err := b.builder.Run(ctx, builderUi, hook) + log.Printf("Running builder: %s", b.BuilderType) + ts := CheckpointReporter.AddSpan(b.BuilderType, "builder", b.BuilderConfig) + builderArtifact, err := b.Builder.Run(ctx, builderUi, hook) ts.End(err) if err != nil { return nil, err @@ -269,21 +285,21 @@ func (b *coreBuild) Run(ctx context.Context, originalUi Ui) ([]Artifact, error) } errors := make([]error, 0) - keepOriginalArtifact := len(b.postProcessors) == 0 + keepOriginalArtifact := len(b.PostProcessors) == 0 // Run the post-processors PostProcessorRunSeqLoop: - for _, ppSeq := range b.postProcessors { + for _, ppSeq := range b.PostProcessors { priorArtifact := builderArtifact for i, corePP := range ppSeq { ppUi := &TargetedUI{ - Target: fmt.Sprintf("%s (%s)", b.Name(), corePP.processorType), + Target: fmt.Sprintf("%s (%s)", b.Name(), corePP.PType), Ui: originalUi, } - builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.processorType)) - ts := CheckpointReporter.AddSpan(corePP.processorType, "post-processor", corePP.config) - artifact, defaultKeep, forceOverride, err := corePP.processor.PostProcess(ctx, ppUi, priorArtifact) + builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.PType)) + ts := CheckpointReporter.AddSpan(corePP.PType, "post-processor", corePP.config) + artifact, defaultKeep, forceOverride, err := corePP.PostProcessor.PostProcess(ctx, ppUi, priorArtifact) ts.End(err) if err != nil { errors = append(errors, fmt.Errorf("Post-processor failed: %s", err)) @@ -296,10 +312,10 @@ PostProcessorRunSeqLoop: } keep := defaultKeep - // When user has not set keep_input_artifuact + // When user has not set keep_input_artifact // corePP.keepInputArtifact is nil. // In this case, use the keepDefault provided by the postprocessor. - // When user _has_ set keep_input_atifact, go with that instead. + // When user _has_ set keep_input_artifact, go with that instead. // Exception: for postprocessors that will fail/become // useless if keep isn't true, heed forceOverride and keep the // input artifact regardless of user preference. @@ -308,7 +324,7 @@ PostProcessorRunSeqLoop: log.Printf("The %s post-processor forces "+ "keep_input_artifact=true to preserve integrity of the"+ "build chain. User-set keep_input_artifact=false will be"+ - "ignored.", corePP.processorType) + "ignored.", corePP.PType) } else { // User overrides default. keep = *corePP.keepInputArtifact @@ -321,7 +337,7 @@ PostProcessorRunSeqLoop: if !keepOriginalArtifact && keep { log.Printf( "Flagging to keep original artifact from post-processor '%s'", - corePP.processorType) + corePP.PType) keepOriginalArtifact = true } } else { @@ -330,10 +346,10 @@ PostProcessorRunSeqLoop: if keep { artifacts = append(artifacts, priorArtifact) } else { - log.Printf("Deleting prior artifact from post-processor '%s'", corePP.processorType) + log.Printf("Deleting prior artifact from post-processor '%s'", corePP.PType) if err := priorArtifact.Destroy(); err != nil { log.Printf("Error is %#v", err) - errors = append(errors, fmt.Errorf("Failed cleaning up prior artifact: %s; pp is %s", err, corePP.processorType)) + errors = append(errors, fmt.Errorf("Failed cleaning up prior artifact: %s; pp is %s", err, corePP.PType)) } } } @@ -352,7 +368,7 @@ PostProcessorRunSeqLoop: copy(artifacts[1:], artifacts) artifacts[0] = builderArtifact } else { - log.Printf("Deleting original artifact for build '%s'", b.name) + log.Printf("Deleting original artifact for build '%s'", b.Type) if err := builderArtifact.Destroy(); err != nil { errors = append(errors, fmt.Errorf("Error destroying builder artifact: %s; bad artifact: %#v", err, builderArtifact.Files())) } @@ -365,7 +381,7 @@ PostProcessorRunSeqLoop: return artifacts, err } -func (b *coreBuild) SetDebug(val bool) { +func (b *CoreBuild) SetDebug(val bool) { if b.prepareCalled { panic("prepare has already been called") } @@ -373,7 +389,7 @@ func (b *coreBuild) SetDebug(val bool) { b.debug = val } -func (b *coreBuild) SetForce(val bool) { +func (b *CoreBuild) SetForce(val bool) { if b.prepareCalled { panic("prepare has already been called") } @@ -381,7 +397,7 @@ func (b *coreBuild) SetForce(val bool) { b.force = val } -func (b *coreBuild) SetOnError(val string) { +func (b *CoreBuild) SetOnError(val string) { if b.prepareCalled { panic("prepare has already been called") } diff --git a/packer/build_test.go b/packer/build_test.go index 0c58b405d..3f657f63b 100644 --- a/packer/build_test.go +++ b/packer/build_test.go @@ -4,30 +4,32 @@ import ( "context" "reflect" "testing" + + "github.com/hashicorp/packer/helper/common" ) func boolPointer(tf bool) *bool { return &tf } -func testBuild() *coreBuild { - return &coreBuild{ - name: "test", - builder: &MockBuilder{ArtifactId: "b"}, - builderConfig: 42, - builderType: "foo", +func testBuild() *CoreBuild { + return &CoreBuild{ + Type: "test", + Builder: &MockBuilder{ArtifactId: "b"}, + BuilderConfig: 42, + BuilderType: "foo", hooks: map[string][]Hook{ "foo": {&MockHook{}}, }, - provisioners: []coreBuildProvisioner{ + Provisioners: []CoreBuildProvisioner{ {"mock-provisioner", &MockProvisioner{}, []interface{}{42}}, }, - postProcessors: [][]coreBuildPostProcessor{ + PostProcessors: [][]CoreBuildPostProcessor{ { {&MockPostProcessor{ArtifactId: "pp"}, "testPP", make(map[string]interface{}), boolPointer(true)}, }, }, - variables: make(map[string]string), + Variables: make(map[string]string), onError: "cleanup", } } @@ -54,7 +56,7 @@ func TestBuild_Prepare(t *testing.T) { packerConfig := testDefaultPackerConfig() build := testBuild() - builder := build.builder.(*MockBuilder) + builder := build.Builder.(*MockBuilder) build.Prepare() if !builder.PrepareCalled { @@ -64,17 +66,17 @@ func TestBuild_Prepare(t *testing.T) { t.Fatalf("bad: %#v", builder.PrepareConfig) } - coreProv := build.provisioners[0] - prov := coreProv.provisioner.(*MockProvisioner) + coreProv := build.Provisioners[0] + prov := coreProv.Provisioner.(*MockProvisioner) if !prov.PrepCalled { t.Fatal("prep should be called") } - if !reflect.DeepEqual(prov.PrepConfigs, []interface{}{42, packerConfig}) { + if !reflect.DeepEqual(prov.PrepConfigs, []interface{}{42, packerConfig, BasicPlaceholderData()}) { t.Fatalf("bad: %#v", prov.PrepConfigs) } - corePP := build.postProcessors[0][0] - pp := corePP.processor.(*MockPostProcessor) + corePP := build.PostProcessors[0][0] + pp := corePP.PostProcessor.(*MockPostProcessor) if !pp.ConfigureCalled { t.Fatal("should be called") } @@ -111,7 +113,7 @@ func TestBuildPrepare_BuilderWarnings(t *testing.T) { expected := []string{"foo"} build := testBuild() - builder := build.builder.(*MockBuilder) + builder := build.Builder.(*MockBuilder) builder.PrepareWarnings = expected warn, err := build.Prepare() @@ -128,7 +130,7 @@ func TestBuild_Prepare_Debug(t *testing.T) { packerConfig[DebugConfigKey] = true build := testBuild() - builder := build.builder.(*MockBuilder) + builder := build.Builder.(*MockBuilder) build.SetDebug(true) build.Prepare() @@ -139,12 +141,12 @@ func TestBuild_Prepare_Debug(t *testing.T) { t.Fatalf("bad: %#v", builder.PrepareConfig) } - coreProv := build.provisioners[0] - prov := coreProv.provisioner.(*MockProvisioner) + coreProv := build.Provisioners[0] + prov := coreProv.Provisioner.(*MockProvisioner) if !prov.PrepCalled { t.Fatal("prepare should be called") } - if !reflect.DeepEqual(prov.PrepConfigs, []interface{}{42, packerConfig}) { + if !reflect.DeepEqual(prov.PrepConfigs, []interface{}{42, packerConfig, BasicPlaceholderData()}) { t.Fatalf("bad: %#v", prov.PrepConfigs) } } @@ -156,8 +158,8 @@ func TestBuildPrepare_variables_default(t *testing.T) { } build := testBuild() - build.variables["foo"] = "bar" - builder := build.builder.(*MockBuilder) + build.Variables["foo"] = "bar" + builder := build.Builder.(*MockBuilder) warn, err := build.Prepare() if len(warn) > 0 { @@ -176,6 +178,34 @@ func TestBuildPrepare_variables_default(t *testing.T) { } } +func TestBuildPrepare_ProvisionerGetsGeneratedMap(t *testing.T) { + packerConfig := testDefaultPackerConfig() + + build := testBuild() + builder := build.Builder.(*MockBuilder) + builder.GeneratedVars = []string{"PartyVar"} + + build.Prepare() + if !builder.PrepareCalled { + t.Fatalf("should be called") + } + if !reflect.DeepEqual(builder.PrepareConfig, []interface{}{42, packerConfig}) { + t.Fatalf("bad: %#v", builder.PrepareConfig) + } + + coreProv := build.Provisioners[0] + prov := coreProv.Provisioner.(*MockProvisioner) + if !prov.PrepCalled { + t.Fatal("prepare should be called") + } + + generated := BasicPlaceholderData() + generated["PartyVar"] = "Build_PartyVar. " + common.PlaceholderMsg + if !reflect.DeepEqual(prov.PrepConfigs, []interface{}{42, packerConfig, generated}) { + t.Fatalf("bad: %#v", prov.PrepConfigs) + } +} + func TestBuild_Run(t *testing.T) { ui := testUi() @@ -191,7 +221,7 @@ func TestBuild_Run(t *testing.T) { } // Verify builder was run - builder := build.builder.(*MockBuilder) + builder := build.Builder.(*MockBuilder) if !builder.RunCalled { t.Fatal("should be called") } @@ -210,13 +240,13 @@ func TestBuild_Run(t *testing.T) { // Verify provisioners run dispatchHook.Run(ctx, HookProvision, nil, new(MockCommunicator), 42) - prov := build.provisioners[0].provisioner.(*MockProvisioner) + prov := build.Provisioners[0].Provisioner.(*MockProvisioner) if !prov.ProvCalled { t.Fatal("should be called") } // Verify post-processor was run - pp := build.postProcessors[0][0].processor.(*MockPostProcessor) + pp := build.PostProcessors[0][0].PostProcessor.(*MockPostProcessor) if !pp.PostProcessCalled { t.Fatal("should be called") } @@ -228,7 +258,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { // Test case: Test that with no post-processors, we only get the // main build. build := testBuild() - build.postProcessors = [][]coreBuildPostProcessor{} + build.PostProcessors = [][]CoreBuildPostProcessor{} build.Prepare() artifacts, err := build.Run(context.Background(), ui) @@ -249,7 +279,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { // Test case: Test that with a single post-processor that doesn't keep // inputs, only that post-processors results are returned. build = testBuild() - build.postProcessors = [][]coreBuildPostProcessor{ + build.PostProcessors = [][]CoreBuildPostProcessor{ { {&MockPostProcessor{ArtifactId: "pp"}, "pp", make(map[string]interface{}), boolPointer(false)}, }, @@ -274,7 +304,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { // Test case: Test that with multiple post-processors, as long as one // keeps the original, the original is kept. build = testBuild() - build.postProcessors = [][]coreBuildPostProcessor{ + build.PostProcessors = [][]CoreBuildPostProcessor{ { {&MockPostProcessor{ArtifactId: "pp1"}, "pp", make(map[string]interface{}), boolPointer(false)}, }, @@ -302,7 +332,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { // Test case: Test that with sequences, intermediaries are kept if they // want to be. build = testBuild() - build.postProcessors = [][]coreBuildPostProcessor{ + build.PostProcessors = [][]CoreBuildPostProcessor{ { {&MockPostProcessor{ArtifactId: "pp1a"}, "pp", make(map[string]interface{}), boolPointer(false)}, {&MockPostProcessor{ArtifactId: "pp1b"}, "pp", make(map[string]interface{}), boolPointer(true)}, @@ -332,7 +362,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { // Test case: Test that with a single post-processor that forcibly // keeps inputs, that the artifacts are kept. build = testBuild() - build.postProcessors = [][]coreBuildPostProcessor{ + build.PostProcessors = [][]CoreBuildPostProcessor{ { { &MockPostProcessor{ArtifactId: "pp", Keep: true, ForceOverride: true}, "pp", make(map[string]interface{}), boolPointer(false), @@ -360,7 +390,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { // Test case: Test that with a single post-processor that non-forcibly // keeps inputs, that the artifacts are discarded if user overrides. build = testBuild() - build.postProcessors = [][]coreBuildPostProcessor{ + build.PostProcessors = [][]CoreBuildPostProcessor{ { { &MockPostProcessor{ArtifactId: "pp", Keep: true, ForceOverride: false}, "pp", make(map[string]interface{}), boolPointer(false), @@ -387,7 +417,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { // Test case: Test that with a single post-processor that non-forcibly // keeps inputs, that the artifacts are kept if user does not have preference. build = testBuild() - build.postProcessors = [][]coreBuildPostProcessor{ + build.PostProcessors = [][]CoreBuildPostProcessor{ { { &MockPostProcessor{ArtifactId: "pp", Keep: true, ForceOverride: false}, "pp", make(map[string]interface{}), nil, @@ -434,7 +464,7 @@ func TestBuild_Cancel(t *testing.T) { topCtx, topCtxCancel := context.WithCancel(context.Background()) - builder := build.builder.(*MockBuilder) + builder := build.Builder.(*MockBuilder) builder.RunFn = func(ctx context.Context) { topCtxCancel() diff --git a/packer/builder.go b/packer/builder.go index e24073128..7c67196f1 100644 --- a/packer/builder.go +++ b/packer/builder.go @@ -1,6 +1,8 @@ package packer -import "context" +import ( + "context" +) // Implementers of Builder are responsible for actually building images // on some platform given some configuration. @@ -13,6 +15,8 @@ import "context" // parallelism is strictly disabled, so it is safe to request input from // stdin and so on. type Builder interface { + HCL2Speccer + // Prepare is responsible for configuring the builder and validating // that configuration. Any setup should be done in this method. Note that // NO side effects should take place in prepare, it is meant as a state @@ -25,9 +29,10 @@ type Builder interface { // Each of the configuration values should merge into the final // configuration. // - // Prepare should return a list of warnings along with any errors - // that occurred while preparing. - Prepare(...interface{}) ([]string, error) + // Prepare should return a list of variables that will be made accessible to + // users during the provison methods, a list of warnings along with any + // errors that occurred while preparing. + Prepare(...interface{}) ([]string, []string, error) // Run is where the actual build should take place. It takes a Build and a Ui. Run(context.Context, Ui, Hook) (Artifact, error) diff --git a/packer/builder_mock.go b/packer/builder_mock.go index 5ebd2d993..06aa87291 100644 --- a/packer/builder_mock.go +++ b/packer/builder_mock.go @@ -1,8 +1,12 @@ +//go:generate mapstructure-to-hcl2 -type MockBuilder,MockCommunicator,RemoteCmd,MockProvisioner,MockPostProcessor + package packer import ( "context" "errors" + + "github.com/hashicorp/hcl/v2/hcldec" ) // MockBuilder is an implementation of Builder that can be used for tests. @@ -21,12 +25,18 @@ type MockBuilder struct { RunUi Ui CancelCalled bool RunFn func(ctx context.Context) + + GeneratedVars []string } -func (tb *MockBuilder) Prepare(config ...interface{}) ([]string, error) { +func (tb *MockBuilder) ConfigSpec() hcldec.ObjectSpec { return tb.FlatMapstructure().HCL2Spec() } + +func (tb *MockBuilder) FlatConfig() interface{} { return tb.FlatMapstructure() } + +func (tb *MockBuilder) Prepare(config ...interface{}) ([]string, []string, error) { tb.PrepareCalled = true tb.PrepareConfig = config - return tb.PrepareWarnings, nil + return tb.GeneratedVars, tb.PrepareWarnings, nil } func (tb *MockBuilder) Run(ctx context.Context, ui Ui, h Hook) (Artifact, error) { diff --git a/packer/builder_mock.hcl2spec.go b/packer/builder_mock.hcl2spec.go new file mode 100644 index 000000000..a406ef9f0 --- /dev/null +++ b/packer/builder_mock.hcl2spec.go @@ -0,0 +1,209 @@ +// Code generated by "mapstructure-to-hcl2 -type MockBuilder,MockCommunicator,RemoteCmd,MockProvisioner,MockPostProcessor"; DO NOT EDIT. +package packer + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" + "io" +) + +// FlatMockBuilder is an auto-generated flat version of MockBuilder. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatMockBuilder struct { + ArtifactId *string `cty:"artifact_id"` + PrepareWarnings []string `cty:"prepare_warnings"` + RunErrResult *bool `cty:"run_err_result"` + RunNilResult *bool `cty:"run_nil_result"` + PrepareCalled *bool `cty:"prepare_called"` + PrepareConfig []interface{} `cty:"prepare_config"` + RunCalled *bool `cty:"run_called"` + RunHook Hook `cty:"run_hook"` + RunUi Ui `cty:"run_ui"` + CancelCalled *bool `cty:"cancel_called"` + GeneratedVars []string `cty:"generated_vars"` +} + +// FlatMapstructure returns a new FlatMockBuilder. +// FlatMockBuilder is an auto-generated flat version of MockBuilder. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*MockBuilder) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatMockBuilder) +} + +// HCL2Spec returns the hcl spec of a MockBuilder. +// This spec is used by HCL to read the fields of MockBuilder. +// The decoded values from this spec will then be applied to a FlatMockBuilder. +func (*FlatMockBuilder) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "artifact_id": &hcldec.AttrSpec{Name: "artifact_id", Type: cty.String, Required: false}, + "prepare_warnings": &hcldec.AttrSpec{Name: "prepare_warnings", Type: cty.List(cty.String), Required: false}, + "run_err_result": &hcldec.AttrSpec{Name: "run_err_result", Type: cty.Bool, Required: false}, + "run_nil_result": &hcldec.AttrSpec{Name: "run_nil_result", Type: cty.Bool, Required: false}, + "prepare_called": &hcldec.AttrSpec{Name: "prepare_called", Type: cty.Bool, Required: false}, + "prepare_config": &hcldec.AttrSpec{Name: "prepare_config", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "run_called": &hcldec.AttrSpec{Name: "run_called", Type: cty.Bool, Required: false}, + "run_hook": &hcldec.AttrSpec{Name: "run_hook", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "run_ui": &hcldec.AttrSpec{Name: "run_ui", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "cancel_called": &hcldec.AttrSpec{Name: "cancel_called", Type: cty.Bool, Required: false}, + "generated_vars": &hcldec.AttrSpec{Name: "generated_vars", Type: cty.List(cty.String), Required: false}, + } + return s +} + +// FlatMockCommunicator is an auto-generated flat version of MockCommunicator. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatMockCommunicator struct { + StartCalled *bool `cty:"start_called"` + StartCmd *FlatRemoteCmd `cty:"start_cmd"` + StartStderr *string `cty:"start_stderr"` + StartStdout *string `cty:"start_stdout"` + StartStdin *string `cty:"start_stdin"` + StartExitStatus *int `cty:"start_exit_status"` + UploadCalled *bool `cty:"upload_called"` + UploadPath *string `cty:"upload_path"` + UploadData *string `cty:"upload_data"` + UploadDirDst *string `cty:"upload_dir_dst"` + UploadDirSrc *string `cty:"upload_dir_src"` + UploadDirExclude []string `cty:"upload_dir_exclude"` + DownloadDirDst *string `cty:"download_dir_dst"` + DownloadDirSrc *string `cty:"download_dir_src"` + DownloadDirExclude []string `cty:"download_dir_exclude"` + DownloadCalled *bool `cty:"download_called"` + DownloadPath *string `cty:"download_path"` + DownloadData *string `cty:"download_data"` +} + +// FlatMapstructure returns a new FlatMockCommunicator. +// FlatMockCommunicator is an auto-generated flat version of MockCommunicator. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*MockCommunicator) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatMockCommunicator) +} + +// HCL2Spec returns the hcl spec of a MockCommunicator. +// This spec is used by HCL to read the fields of MockCommunicator. +// The decoded values from this spec will then be applied to a FlatMockCommunicator. +func (*FlatMockCommunicator) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "start_called": &hcldec.AttrSpec{Name: "start_called", Type: cty.Bool, Required: false}, + "start_cmd": &hcldec.BlockSpec{TypeName: "start_cmd", Nested: hcldec.ObjectSpec((*FlatRemoteCmd)(nil).HCL2Spec())}, + "start_stderr": &hcldec.AttrSpec{Name: "start_stderr", Type: cty.String, Required: false}, + "start_stdout": &hcldec.AttrSpec{Name: "start_stdout", Type: cty.String, Required: false}, + "start_stdin": &hcldec.AttrSpec{Name: "start_stdin", Type: cty.String, Required: false}, + "start_exit_status": &hcldec.AttrSpec{Name: "start_exit_status", Type: cty.Number, Required: false}, + "upload_called": &hcldec.AttrSpec{Name: "upload_called", Type: cty.Bool, Required: false}, + "upload_path": &hcldec.AttrSpec{Name: "upload_path", Type: cty.String, Required: false}, + "upload_data": &hcldec.AttrSpec{Name: "upload_data", Type: cty.String, Required: false}, + "upload_dir_dst": &hcldec.AttrSpec{Name: "upload_dir_dst", Type: cty.String, Required: false}, + "upload_dir_src": &hcldec.AttrSpec{Name: "upload_dir_src", Type: cty.String, Required: false}, + "upload_dir_exclude": &hcldec.AttrSpec{Name: "upload_dir_exclude", Type: cty.List(cty.String), Required: false}, + "download_dir_dst": &hcldec.AttrSpec{Name: "download_dir_dst", Type: cty.String, Required: false}, + "download_dir_src": &hcldec.AttrSpec{Name: "download_dir_src", Type: cty.String, Required: false}, + "download_dir_exclude": &hcldec.AttrSpec{Name: "download_dir_exclude", Type: cty.List(cty.String), Required: false}, + "download_called": &hcldec.AttrSpec{Name: "download_called", Type: cty.Bool, Required: false}, + "download_path": &hcldec.AttrSpec{Name: "download_path", Type: cty.String, Required: false}, + "download_data": &hcldec.AttrSpec{Name: "download_data", Type: cty.String, Required: false}, + } + return s +} + +// FlatMockPostProcessor is an auto-generated flat version of MockPostProcessor. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatMockPostProcessor struct { + ArtifactId *string `cty:"artifact_id"` + Keep *bool `cty:"keep"` + ForceOverride *bool `cty:"force_override"` + Error error `cty:"error"` + ConfigureCalled *bool `cty:"configure_called"` + ConfigureConfigs []interface{} `cty:"configure_configs"` + ConfigureError error `cty:"configure_error"` + PostProcessCalled *bool `cty:"post_process_called"` + PostProcessArtifact Artifact `cty:"post_process_artifact"` + PostProcessUi Ui `cty:"post_process_ui"` +} + +// FlatMapstructure returns a new FlatMockPostProcessor. +// FlatMockPostProcessor is an auto-generated flat version of MockPostProcessor. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*MockPostProcessor) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatMockPostProcessor) +} + +// HCL2Spec returns the hcl spec of a MockPostProcessor. +// This spec is used by HCL to read the fields of MockPostProcessor. +// The decoded values from this spec will then be applied to a FlatMockPostProcessor. +func (*FlatMockPostProcessor) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "artifact_id": &hcldec.AttrSpec{Name: "artifact_id", Type: cty.String, Required: false}, + "keep": &hcldec.AttrSpec{Name: "keep", Type: cty.Bool, Required: false}, + "force_override": &hcldec.AttrSpec{Name: "force_override", Type: cty.Bool, Required: false}, + "error": &hcldec.AttrSpec{Name: "error", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "configure_called": &hcldec.AttrSpec{Name: "configure_called", Type: cty.Bool, Required: false}, + "configure_configs": &hcldec.AttrSpec{Name: "configure_configs", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "configure_error": &hcldec.AttrSpec{Name: "configure_error", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "post_process_called": &hcldec.AttrSpec{Name: "post_process_called", Type: cty.Bool, Required: false}, + "post_process_artifact": &hcldec.AttrSpec{Name: "post_process_artifact", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "post_process_ui": &hcldec.AttrSpec{Name: "post_process_ui", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + } + return s +} + +// FlatMockProvisioner is an auto-generated flat version of MockProvisioner. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatMockProvisioner struct { + PrepCalled *bool `cty:"prep_called"` + PrepConfigs []interface{} `cty:"prep_configs"` + ProvCalled *bool `cty:"prov_called"` + ProvCommunicator Communicator `cty:"prov_communicator"` + ProvUi Ui `cty:"prov_ui"` +} + +// FlatMapstructure returns a new FlatMockProvisioner. +// FlatMockProvisioner is an auto-generated flat version of MockProvisioner. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*MockProvisioner) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatMockProvisioner) +} + +// HCL2Spec returns the hcl spec of a MockProvisioner. +// This spec is used by HCL to read the fields of MockProvisioner. +// The decoded values from this spec will then be applied to a FlatMockProvisioner. +func (*FlatMockProvisioner) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "prep_called": &hcldec.AttrSpec{Name: "prep_called", Type: cty.Bool, Required: false}, + "prep_configs": &hcldec.AttrSpec{Name: "prep_configs", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "prov_called": &hcldec.AttrSpec{Name: "prov_called", Type: cty.Bool, Required: false}, + "prov_communicator": &hcldec.AttrSpec{Name: "prov_communicator", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "prov_ui": &hcldec.AttrSpec{Name: "prov_ui", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + } + return s +} + +// FlatRemoteCmd is an auto-generated flat version of RemoteCmd. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatRemoteCmd struct { + Command *string `cty:"command"` + Stdin io.Reader `cty:"stdin"` + Stdout io.Writer `cty:"stdout"` + Stderr io.Writer `cty:"stderr"` +} + +// FlatMapstructure returns a new FlatRemoteCmd. +// FlatRemoteCmd is an auto-generated flat version of RemoteCmd. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*RemoteCmd) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatRemoteCmd) +} + +// HCL2Spec returns the hcl spec of a RemoteCmd. +// This spec is used by HCL to read the fields of RemoteCmd. +// The decoded values from this spec will then be applied to a FlatRemoteCmd. +func (*FlatRemoteCmd) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "command": &hcldec.AttrSpec{Name: "command", Type: cty.String, Required: false}, + "stdin": &hcldec.AttrSpec{Name: "stdin", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "stdout": &hcldec.AttrSpec{Name: "stdout", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + "stderr": &hcldec.AttrSpec{Name: "stderr", Type: cty.Bool, Required: false}, /* TODO(azr): could not find type */ + } + return s +} diff --git a/packer/communicator.go b/packer/communicator.go index aef1775a0..026737d4b 100644 --- a/packer/communicator.go +++ b/packer/communicator.go @@ -37,7 +37,7 @@ type RemoteCmd struct { exitStatus int // This thing is a mutex, lock when making modifications concurrently - sync.Mutex + m sync.Mutex exitChInit sync.Once exitCh chan interface{} @@ -80,6 +80,11 @@ type Communicator interface { DownloadDir(src string, dst string, exclude []string) error } +type ConfigurableCommunicator interface { + HCL2Speccer + Configure(...interface{}) ([]string, error) +} + // RunWithUi runs the remote command and streams the output to any configured // Writers for stdout/stderr, while also writing each line as it comes to a Ui. // RunWithUi will not return until the command finishes or is cancelled. @@ -95,8 +100,8 @@ func (r *RemoteCmd) RunWithUi(ctx context.Context, c Communicator, ui Ui) error originalStdout := r.Stdout originalStderr := r.Stderr defer func() { - r.Lock() - defer r.Unlock() + r.m.Lock() + defer r.m.Unlock() r.Stdout = originalStdout r.Stderr = originalStderr @@ -169,9 +174,9 @@ OutputLoop: func (r *RemoteCmd) SetExited(status int) { r.initchan() - r.Lock() + r.m.Lock() r.exitStatus = status - r.Unlock() + r.m.Unlock() close(r.exitCh) } @@ -180,8 +185,8 @@ func (r *RemoteCmd) SetExited(status int) { func (r *RemoteCmd) Wait() int { r.initchan() <-r.exitCh - r.Lock() - defer r.Unlock() + r.m.Lock() + defer r.m.Unlock() return r.exitStatus } diff --git a/packer/config_file.go b/packer/config_file.go index 173766afa..bf6c8637c 100644 --- a/packer/config_file.go +++ b/packer/config_file.go @@ -24,12 +24,10 @@ func ConfigDir() (string, error) { func homeDir() (string, error) { // Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470 if home := os.Getenv("HOME"); home != "" { - log.Printf("Detected home directory from env var: %s", home) return home, nil } if home := os.Getenv("APPDATA"); home != "" { - log.Printf("Detected home directory from env var: %s", home) return home, nil } diff --git a/packer/core.go b/packer/core.go index 45912f070..46e6ad8eb 100644 --- a/packer/core.go +++ b/packer/core.go @@ -54,14 +54,36 @@ type PostProcessorFunc func(name string) (PostProcessor, error) // The function type used to lookup Provisioner implementations. type ProvisionerFunc func(name string) (Provisioner, error) +type BasicStore interface { + Has(name string) bool + List() (names []string) +} + +type BuilderStore interface { + BasicStore + Start(name string) (Builder, error) +} + +type ProvisionerStore interface { + BasicStore + Start(name string) (Provisioner, error) +} + +type PostProcessorStore interface { + BasicStore + Start(name string) (PostProcessor, error) +} + // ComponentFinder is a struct that contains the various function // pointers necessary to look up components of Packer such as builders, // commands, etc. type ComponentFinder struct { - Builder BuilderFunc - Hook HookFunc - PostProcessor PostProcessorFunc - Provisioner ProvisionerFunc + Hook HookFunc + + // For HCL2 + BuilderStore BuilderStore + ProvisionerStore ProvisionerStore + PostProcessorStore PostProcessorStore } // NewCore creates a new Core. @@ -112,10 +134,10 @@ func (c *Core) BuildNames() []string { return r } -func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName string) (coreBuildProvisioner, error) { +func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName string) (CoreBuildProvisioner, error) { // Get the provisioner - cbp := coreBuildProvisioner{} - provisioner, err := c.components.Provisioner(rawP.Type) + cbp := CoreBuildProvisioner{} + provisioner, err := c.components.ProvisionerStore.Start(rawP.Type) if err != nil { return cbp, fmt.Errorf( "error initializing provisioner '%s': %s", @@ -146,9 +168,9 @@ func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName Provisioner: provisioner, } } - cbp = coreBuildProvisioner{ - pType: rawP.Type, - provisioner: provisioner, + cbp = CoreBuildProvisioner{ + PType: rawP.Type, + Provisioner: provisioner, config: config, } @@ -162,7 +184,7 @@ func (c *Core) Build(n string) (Build, error) { if !ok { return nil, fmt.Errorf("no such build found: %s", n) } - builder, err := c.components.Builder(configBuilder.Type) + builder, err := c.components.BuilderStore.Start(configBuilder.Type) if err != nil { return nil, fmt.Errorf( "error initializing builder '%s': %s", @@ -177,7 +199,7 @@ func (c *Core) Build(n string) (Build, error) { rawName := configBuilder.Name // Setup the provisioners for this build - provisioners := make([]coreBuildProvisioner, 0, len(c.Template.Provisioners)) + provisioners := make([]CoreBuildProvisioner, 0, len(c.Template.Provisioners)) for _, rawP := range c.Template.Provisioners { // If we're skipping this, then ignore it if rawP.OnlyExcept.Skip(rawName) { @@ -191,7 +213,7 @@ func (c *Core) Build(n string) (Build, error) { provisioners = append(provisioners, cbp) } - var cleanupProvisioner coreBuildProvisioner + var cleanupProvisioner CoreBuildProvisioner if c.Template.CleanupProvisioner != nil { // This is a special instantiation of the shell-local provisioner that // is only run on error at end of provisioning step before other step @@ -203,9 +225,9 @@ func (c *Core) Build(n string) (Build, error) { } // Setup the post-processors - postProcessors := make([][]coreBuildPostProcessor, 0, len(c.Template.PostProcessors)) + postProcessors := make([][]CoreBuildPostProcessor, 0, len(c.Template.PostProcessors)) for _, rawPs := range c.Template.PostProcessors { - current := make([]coreBuildPostProcessor, 0, len(rawPs)) + current := make([]CoreBuildPostProcessor, 0, len(rawPs)) for _, rawP := range rawPs { if rawP.Skip(rawName) { continue @@ -222,7 +244,7 @@ func (c *Core) Build(n string) (Build, error) { } // Get the post-processor - postProcessor, err := c.components.PostProcessor(rawP.Type) + postProcessor, err := c.components.PostProcessorStore.Start(rawP.Type) if err != nil { return nil, fmt.Errorf( "error initializing post-processor '%s': %s", @@ -233,9 +255,9 @@ func (c *Core) Build(n string) (Build, error) { "post-processor type not found: %s", rawP.Type) } - current = append(current, coreBuildPostProcessor{ - processor: postProcessor, - processorType: rawP.Type, + current = append(current, CoreBuildPostProcessor{ + PostProcessor: postProcessor, + PType: rawP.Type, config: rawP.Config, keepInputArtifact: rawP.KeepInputArtifact, }) @@ -251,16 +273,16 @@ func (c *Core) Build(n string) (Build, error) { // TODO hooks one day - return &coreBuild{ - name: n, - builder: builder, - builderConfig: configBuilder.Config, - builderType: configBuilder.Type, - postProcessors: postProcessors, - provisioners: provisioners, - cleanupProvisioner: cleanupProvisioner, - templatePath: c.Template.Path, - variables: c.variables, + return &CoreBuild{ + Type: n, + Builder: builder, + BuilderConfig: configBuilder.Config, + BuilderType: configBuilder.Type, + PostProcessors: postProcessors, + Provisioners: provisioners, + CleanupProvisioner: cleanupProvisioner, + TemplatePath: c.Template.Path, + Variables: c.variables, }, nil } diff --git a/packer/hcl2spec.go b/packer/hcl2spec.go new file mode 100644 index 000000000..bbba47ec3 --- /dev/null +++ b/packer/hcl2spec.go @@ -0,0 +1,12 @@ +package packer + +import "github.com/hashicorp/hcl/v2/hcldec" + +// a struct (or type) implementing HCL2Speccer is a type that can tell it's own +// hcl2 conf/layout. +type HCL2Speccer interface { + // ConfigSpec should return the hcl object spec used to configure the + // builder. It will be used to tell the HCL parsing library how to + // validate/configure a configuration. + ConfigSpec() hcldec.ObjectSpec +} diff --git a/packer/hook.go b/packer/hook.go index 25df5c855..ee8104ac9 100644 --- a/packer/hook.go +++ b/packer/hook.go @@ -34,7 +34,6 @@ type DispatchHook struct { // hooks if a mapping exists. If a mapping doesn't exist, then nothing // happens. func (h *DispatchHook) Run(ctx context.Context, name string, ui Ui, comm Communicator, data interface{}) error { - hooks, ok := h.Mapping[name] if !ok { // No hooks for that name. No problem. diff --git a/packer/maps.go b/packer/maps.go new file mode 100644 index 000000000..7cad0a014 --- /dev/null +++ b/packer/maps.go @@ -0,0 +1,74 @@ +package packer + +import ( + "fmt" +) + +type MapOfProvisioner map[string]func() (Provisioner, error) + +func (mop MapOfProvisioner) Has(provisioner string) bool { + _, res := mop[provisioner] + return res +} + +func (mop MapOfProvisioner) Start(provisioner string) (Provisioner, error) { + p, found := mop[provisioner] + if !found { + return nil, fmt.Errorf("Unknown provisioner %s", provisioner) + } + return p() +} + +func (mop MapOfProvisioner) List() []string { + res := []string{} + for k := range mop { + res = append(res, k) + } + return res +} + +type MapOfPostProcessor map[string]func() (PostProcessor, error) + +func (mopp MapOfPostProcessor) Has(postProcessor string) bool { + _, res := mopp[postProcessor] + return res +} + +func (mopp MapOfPostProcessor) Start(postProcessor string) (PostProcessor, error) { + p, found := mopp[postProcessor] + if !found { + return nil, fmt.Errorf("Unknown post-processor %s", postProcessor) + } + return p() +} + +func (mopp MapOfPostProcessor) List() []string { + res := []string{} + for k := range mopp { + res = append(res, k) + } + return res +} + +type MapOfBuilder map[string]func() (Builder, error) + +func (mob MapOfBuilder) Has(builder string) bool { + _, res := mob[builder] + return res +} + +func (mob MapOfBuilder) Start(builder string) (Builder, error) { + d, found := mob[builder] + if !found { + return nil, fmt.Errorf("Unknown builder %s", builder) + } + return d() +} + +func (mob MapOfBuilder) List() []string { + res := []string{} + for k := range mob { + res = append(res, k) + } + return res +} diff --git a/packer/plugin/builder.go b/packer/plugin/builder.go index 44af7dd67..abe508692 100644 --- a/packer/plugin/builder.go +++ b/packer/plugin/builder.go @@ -4,6 +4,7 @@ import ( "context" "log" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/packer" ) @@ -12,7 +13,16 @@ type cmdBuilder struct { client *Client } -func (b *cmdBuilder) Prepare(config ...interface{}) ([]string, error) { +func (b *cmdBuilder) ConfigSpec() hcldec.ObjectSpec { + defer func() { + r := recover() + b.checkExit(r, nil) + }() + + return b.builder.ConfigSpec() +} + +func (b *cmdBuilder) Prepare(config ...interface{}) ([]string, []string, error) { defer func() { r := recover() b.checkExit(r, nil) diff --git a/packer/plugin/client.go b/packer/plugin/client.go index 7cfcb8e21..33fcf7f75 100644 --- a/packer/plugin/client.go +++ b/packer/plugin/client.go @@ -327,8 +327,10 @@ func (c *Client) Start() (addr net.Addr, err error) { switch parts[1] { case "tcp": addr, err = net.ResolveTCPAddr("tcp", parts[2]) + log.Printf("Received tcp RPC address for %s: addr is %s", cmd.Path, addr) case "unix": addr, err = net.ResolveUnixAddr("unix", parts[2]) + log.Printf("Received unix RPC address for %s: addr is %s", cmd.Path, addr) default: err = fmt.Errorf("Unknown address type: %s", parts[1]) } @@ -339,6 +341,13 @@ func (c *Client) Start() (addr net.Addr, err error) { } func (c *Client) logStderr(r io.Reader) { + logPrefix := filepath.Base(c.config.Cmd.Path) + if logPrefix == "packer" { + // we just called the normal packer binary with the plugin arg. + // grab the last arg from the list which will match the plugin name. + logPrefix = c.config.Cmd.Args[len(c.config.Cmd.Args)-1] + } + bufR := bufio.NewReader(r) for { line, err := bufR.ReadString('\n') @@ -346,7 +355,8 @@ func (c *Client) logStderr(r io.Reader) { c.config.Stderr.Write([]byte(line)) line = strings.TrimRightFunc(line, unicode.IsSpace) - log.Printf("%s: %s", filepath.Base(c.config.Cmd.Path), line) + + log.Printf("%s plugin: %s", logPrefix, line) } if err == io.EOF { diff --git a/packer/plugin/post_processor.go b/packer/plugin/post_processor.go index a08dc586a..98a956523 100644 --- a/packer/plugin/post_processor.go +++ b/packer/plugin/post_processor.go @@ -4,6 +4,7 @@ import ( "context" "log" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/packer" ) @@ -12,6 +13,15 @@ type cmdPostProcessor struct { client *Client } +func (b *cmdPostProcessor) ConfigSpec() hcldec.ObjectSpec { + defer func() { + r := recover() + b.checkExit(r, nil) + }() + + return b.p.ConfigSpec() +} + func (c *cmdPostProcessor) Configure(config ...interface{}) error { defer func() { r := recover() diff --git a/packer/plugin/post_processor_test.go b/packer/plugin/post_processor_test.go index ee012819c..b28340773 100644 --- a/packer/plugin/post_processor_test.go +++ b/packer/plugin/post_processor_test.go @@ -5,11 +5,14 @@ import ( "os/exec" "testing" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/packer" ) type helperPostProcessor byte +func (helperPostProcessor) ConfigSpec() hcldec.ObjectSpec { return nil } + func (helperPostProcessor) Configure(...interface{}) error { return nil } diff --git a/packer/plugin/provisioner.go b/packer/plugin/provisioner.go index 9c3b8fb42..bc07d6b61 100644 --- a/packer/plugin/provisioner.go +++ b/packer/plugin/provisioner.go @@ -4,6 +4,7 @@ import ( "context" "log" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/packer" ) @@ -12,6 +13,15 @@ type cmdProvisioner struct { client *Client } +func (p *cmdProvisioner) ConfigSpec() hcldec.ObjectSpec { + defer func() { + r := recover() + p.checkExit(r, nil) + }() + + return p.p.ConfigSpec() +} + func (c *cmdProvisioner) Prepare(configs ...interface{}) error { defer func() { r := recover() @@ -21,13 +31,13 @@ func (c *cmdProvisioner) Prepare(configs ...interface{}) error { return c.p.Prepare(configs...) } -func (c *cmdProvisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (c *cmdProvisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error { defer func() { r := recover() c.checkExit(r, nil) }() - return c.p.Provision(ctx, ui, comm) + return c.p.Provision(ctx, ui, comm, generatedData) } func (c *cmdProvisioner) checkExit(p interface{}, cb func()) { diff --git a/packer/plugin/server.go b/packer/plugin/server.go index 0efefb7e5..96e2011ce 100644 --- a/packer/plugin/server.go +++ b/packer/plugin/server.go @@ -36,7 +36,7 @@ const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d69 // The APIVersion is outputted along with the RPC address. The plugin // client validates this API version and will show an error if it doesn't // know how to speak it. -const APIVersion = "4" +const APIVersion = "5" // Server waits for a connection to this plugin and returns a Packer // RPC server that you can use to register components and serve them. @@ -51,20 +51,7 @@ func Server() (*packrpc.Server, error) { runtime.GOMAXPROCS(runtime.NumCPU()) } - minPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MIN_PORT"), 10, 32) - if err != nil { - return nil, err - } - - maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32) - if err != nil { - return nil, err - } - - log.Printf("Plugin minimum port: %d\n", minPort) - log.Printf("Plugin maximum port: %d\n", maxPort) - - listener, err := serverListener(minPort, maxPort) + listener, err := serverListener() if err != nil { return nil, err } @@ -104,15 +91,27 @@ func Server() (*packrpc.Server, error) { return packrpc.NewServer(conn) } -func serverListener(minPort, maxPort int64) (net.Listener, error) { +func serverListener() (net.Listener, error) { if runtime.GOOS == "windows" { - return serverListener_tcp(minPort, maxPort) + return serverListener_tcp() } return serverListener_unix() } -func serverListener_tcp(minPort, maxPort int64) (net.Listener, error) { +func serverListener_tcp() (net.Listener, error) { + minPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MIN_PORT"), 10, 32) + if err != nil { + return nil, err + } + + maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32) + if err != nil { + return nil, err + } + + log.Printf("Plugin port range: [%d,%d]", minPort, maxPort) + for port := minPort; port <= maxPort; port++ { address := fmt.Sprintf("127.0.0.1:%d", port) listener, err := net.Listen("tcp", address) diff --git a/packer/post_processor.go b/packer/post_processor.go index c9b07e037..fd7df4e27 100644 --- a/packer/post_processor.go +++ b/packer/post_processor.go @@ -8,6 +8,8 @@ import "context" // the result of a build, compresses it, and returns a new artifact containing // a single file of the prior artifact compressed. type PostProcessor interface { + HCL2Speccer + // Configure is responsible for setting up configuration, storing // the state for later, and returning and errors, such as validation // errors. diff --git a/packer/post_processor_mock.go b/packer/post_processor_mock.go index d31fdec4b..b03abd4eb 100644 --- a/packer/post_processor_mock.go +++ b/packer/post_processor_mock.go @@ -1,6 +1,10 @@ package packer -import "context" +import ( + "context" + + "github.com/hashicorp/hcl/v2/hcldec" +) // MockPostProcessor is an implementation of PostProcessor that can be // used for tests. @@ -19,6 +23,8 @@ type MockPostProcessor struct { PostProcessUi Ui } +func (t *MockPostProcessor) ConfigSpec() hcldec.ObjectSpec { return t.FlatMapstructure().HCL2Spec() } + func (t *MockPostProcessor) Configure(configs ...interface{}) error { t.ConfigureCalled = true t.ConfigureConfigs = configs diff --git a/packer/provisioner.go b/packer/provisioner.go index ff89c0c42..68f5a2946 100644 --- a/packer/provisioner.go +++ b/packer/provisioner.go @@ -6,11 +6,16 @@ import ( "log" "sync" "time" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer/helper/common" ) // A provisioner is responsible for installing and configuring software // on a machine prior to building the actual image. type Provisioner interface { + HCL2Speccer + // Prepare is called with a set of configurations to setup the // internal state of the provisioner. The multiple configurations // should be merged in some sane way. @@ -20,7 +25,7 @@ type Provisioner interface { // given for cancellation, a UI is given to communicate with the user, and // a communicator is given that is guaranteed to be connected to some // machine so that provisioning can be done. - Provision(context.Context, Ui, Communicator) error + Provision(context.Context, Ui, Communicator, map[string]interface{}) error } // A HookedProvisioner represents a provisioner and information describing it @@ -37,6 +42,64 @@ type ProvisionHook struct { Provisioners []*HookedProvisioner } +// Provisioners interpolate most of their fields in the prepare stage; this +// placeholder map helps keep fields that are only generated at build time from +// accidentally being interpolated into empty strings at prepare time. +// This helper function generates the most basic placeholder data which should +// be accessible to the provisioners. It is used to initialize provisioners, to +// force validation using the `generated` template function. In the future, +// custom generated data could be passed into provisioners from builders to +// enable specialized builder-specific (but still validated!!) access to builder +// data. +func BasicPlaceholderData() map[string]string { + placeholderData := map[string]string{} + msg := "Build_%s. " + common.PlaceholderMsg + placeholderData["ID"] = fmt.Sprintf(msg, "ID") + // The following correspond to communicator-agnostic functions that are + // part of the SSH and WinRM communicator implementations. These functions + // are not part of the communicator interface, but are stored on the + // Communicator Config and return the appropriate values rather than + // depending on the actual communicator config values. E.g "Password" + // reprosents either WinRMPassword or SSHPassword, which makes this more + // useful if a template contains multiple builds. + placeholderData["Host"] = fmt.Sprintf(msg, "Host") + placeholderData["Port"] = fmt.Sprintf(msg, "Port") + placeholderData["User"] = fmt.Sprintf(msg, "User") + placeholderData["Password"] = fmt.Sprintf(msg, "Password") + placeholderData["ConnType"] = fmt.Sprintf(msg, "Type") + placeholderData["PackerRunUUID"] = fmt.Sprintf(msg, "PackerRunUUID") + placeholderData["SSHPublicKey"] = fmt.Sprintf(msg, "SSHPublicKey") + placeholderData["SSHPrivateKey"] = fmt.Sprintf(msg, "SSHPrivateKey") + + // Backwards-compatability: WinRM Password can get through without forcing + // the generated func validation. + placeholderData["WinRMPassword"] = "{{.WinRMPassword}}" + + return placeholderData +} + +func CastDataToMap(data interface{}) map[string]interface{} { + // Provisioners expect a map[string]interface{} in their data field, but + // it gets converted into a map[interface]interface on the way over the + // RPC. Check that data can be cast into such a form, and cast it. + cast := make(map[string]interface{}) + interMap, ok := data.(map[interface{}]interface{}) + if !ok { + log.Printf("Unable to read map[string]interface out of data."+ + "Using empty interface: %#v", data) + } else { + for key, val := range interMap { + keyString, ok := key.(string) + if ok { + cast[keyString] = val + } else { + log.Printf("Error casting generated data key to a string.") + } + } + } + return cast +} + // Runs the provisioners in order. func (h *ProvisionHook) Run(ctx context.Context, name string, ui Ui, comm Communicator, data interface{}) error { // Shortcut @@ -53,7 +116,8 @@ func (h *ProvisionHook) Run(ctx context.Context, name string, ui Ui, comm Commun for _, p := range h.Provisioners { ts := CheckpointReporter.AddSpan(p.TypeName, "provisioner", p.Config) - err := p.Provisioner.Provision(ctx, ui, comm) + cast := CastDataToMap(data) + err := p.Provisioner.Provision(ctx, ui, comm, cast) ts.End(err) if err != nil { @@ -71,11 +135,13 @@ type PausedProvisioner struct { Provisioner Provisioner } +func (p *PausedProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.ConfigSpec() } +func (p *PausedProvisioner) FlatConfig() interface{} { return p.FlatConfig() } func (p *PausedProvisioner) Prepare(raws ...interface{}) error { return p.Provisioner.Prepare(raws...) } -func (p *PausedProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator) error { +func (p *PausedProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error { // Use a select to determine if we get cancelled during the wait ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore)) @@ -85,7 +151,7 @@ func (p *PausedProvisioner) Provision(ctx context.Context, ui Ui, comm Communica return ctx.Err() } - return p.Provisioner.Provision(ctx, ui, comm) + return p.Provisioner.Provision(ctx, ui, comm, generatedData) } // DebuggedProvisioner is a Provisioner implementation that waits until a key @@ -98,11 +164,13 @@ type DebuggedProvisioner struct { lock sync.Mutex } +func (p *DebuggedProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.ConfigSpec() } +func (p *DebuggedProvisioner) FlatConfig() interface{} { return p.FlatConfig() } func (p *DebuggedProvisioner) Prepare(raws ...interface{}) error { return p.Provisioner.Prepare(raws...) } -func (p *DebuggedProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator) error { +func (p *DebuggedProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error { // Use a select to determine if we get cancelled during the wait message := "Pausing before the next provisioner . Press enter to continue." @@ -122,5 +190,5 @@ func (p *DebuggedProvisioner) Provision(ctx context.Context, ui Ui, comm Communi return ctx.Err() } - return p.Provisioner.Provision(ctx, ui, comm) + return p.Provisioner.Provision(ctx, ui, comm, generatedData) } diff --git a/packer/provisioner_mock.go b/packer/provisioner_mock.go index 9555c5f57..bfc52aa88 100644 --- a/packer/provisioner_mock.go +++ b/packer/provisioner_mock.go @@ -2,6 +2,8 @@ package packer import ( "context" + + "github.com/hashicorp/hcl/v2/hcldec" ) // MockProvisioner is an implementation of Provisioner that can be @@ -16,13 +18,17 @@ type MockProvisioner struct { ProvUi Ui } +func (tp *MockProvisioner) ConfigSpec() hcldec.ObjectSpec { return tp.FlatMapstructure().HCL2Spec() } + +func (tp *MockProvisioner) FlatConfig() interface{} { return tp.FlatMapstructure() } + func (t *MockProvisioner) Prepare(configs ...interface{}) error { t.PrepCalled = true t.PrepConfigs = configs return nil } -func (t *MockProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator) error { +func (t *MockProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error { t.ProvCalled = true t.ProvCommunicator = comm t.ProvUi = ui diff --git a/packer/provisioner_test.go b/packer/provisioner_test.go index 641702428..108dac90f 100644 --- a/packer/provisioner_test.go +++ b/packer/provisioner_test.go @@ -114,7 +114,7 @@ func TestPausedProvisionerProvision(t *testing.T) { ui := testUi() comm := new(MockCommunicator) - prov.Provision(context.Background(), ui, comm) + prov.Provision(context.Background(), ui, comm, make(map[string]interface{})) if !mock.ProvCalled { t.Fatal("prov should be called") } @@ -143,7 +143,7 @@ func TestPausedProvisionerProvision_waits(t *testing.T) { }, } - err := prov.Provision(context.Background(), testUi(), new(MockCommunicator)) + err := prov.Provision(context.Background(), testUi(), new(MockCommunicator), make(map[string]interface{})) if err != nil { t.Fatalf("prov failed: %v", err) @@ -164,7 +164,7 @@ func TestPausedProvisionerCancel(t *testing.T) { return ctx.Err() } - err := prov.Provision(topCtx, testUi(), new(MockCommunicator)) + err := prov.Provision(topCtx, testUi(), new(MockCommunicator), make(map[string]interface{})) if err == nil { t.Fatal("should have err") } @@ -198,7 +198,7 @@ func TestDebuggedProvisionerProvision(t *testing.T) { ui := testUi() comm := new(MockCommunicator) writeReader(ui, "\n") - prov.Provision(context.Background(), ui, comm) + prov.Provision(context.Background(), ui, comm, make(map[string]interface{})) if !mock.ProvCalled { t.Fatal("prov should be called") } @@ -224,7 +224,7 @@ func TestDebuggedProvisionerCancel(t *testing.T) { return ctx.Err() } - err := prov.Provision(topCtx, testUi(), new(MockCommunicator)) + err := prov.Provision(topCtx, testUi(), new(MockCommunicator), make(map[string]interface{})) if err == nil { t.Fatal("should have error") } diff --git a/packer/provisioner_timeout.go b/packer/provisioner_timeout.go index 1da02f563..a3104815c 100644 --- a/packer/provisioner_timeout.go +++ b/packer/provisioner_timeout.go @@ -13,7 +13,7 @@ type TimeoutProvisioner struct { Timeout time.Duration } -func (p *TimeoutProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator) error { +func (p *TimeoutProvisioner) Provision(ctx context.Context, ui Ui, comm Communicator, generatedData map[string]interface{}) error { ctx, cancel := context.WithTimeout(ctx, p.Timeout) defer cancel() @@ -37,7 +37,7 @@ func (p *TimeoutProvisioner) Provision(ctx context.Context, ui Ui, comm Communic } }() - err := p.Provisioner.Provision(ctx, ui, comm) + err := p.Provisioner.Provision(ctx, ui, comm, generatedData) close(errC) return err } diff --git a/packer/rpc/artifact.go b/packer/rpc/artifact.go index b1166afa7..ff64920bc 100644 --- a/packer/rpc/artifact.go +++ b/packer/rpc/artifact.go @@ -1,16 +1,13 @@ package rpc import ( - "net/rpc" - "github.com/hashicorp/packer/packer" ) // An implementation of packer.Artifact where the artifact is actually // available over an RPC connection. type artifact struct { - client *rpc.Client - endpoint string + commonClient } // ArtifactServer wraps a packer.Artifact implementation and makes it diff --git a/packer/rpc/build.go b/packer/rpc/build.go index 39b31ed67..02ac0f86d 100644 --- a/packer/rpc/build.go +++ b/packer/rpc/build.go @@ -3,7 +3,6 @@ package rpc import ( "context" "log" - "net/rpc" "github.com/hashicorp/packer/packer" ) @@ -11,8 +10,7 @@ import ( // An implementation of packer.Build where the build is actually executed // over an RPC connection. type build struct { - client *rpc.Client - mux *muxBroker + commonClient } // BuildServer wraps a packer.Build implementation and makes it exportable diff --git a/packer/rpc/builder.go b/packer/rpc/builder.go index 4b95bea3e..ae8b80a5c 100644 --- a/packer/rpc/builder.go +++ b/packer/rpc/builder.go @@ -3,7 +3,6 @@ package rpc import ( "context" "log" - "net/rpc" "github.com/hashicorp/packer/packer" ) @@ -11,8 +10,7 @@ import ( // An implementation of packer.Builder where the builder is actually executed // over an RPC connection. type builder struct { - client *rpc.Client - mux *muxBroker + commonClient } // BuilderServer wraps a packer.Builder implementation and makes it exportable @@ -21,8 +19,8 @@ type BuilderServer struct { context context.Context contextCancel func() + commonServer builder packer.Builder - mux *muxBroker } type BuilderPrepareArgs struct { @@ -30,22 +28,27 @@ type BuilderPrepareArgs struct { } type BuilderPrepareResponse struct { - Warnings []string - Error *BasicError + GeneratedVars []string + Warnings []string + Error *BasicError } -func (b *builder) Prepare(config ...interface{}) ([]string, error) { - var resp BuilderPrepareResponse - cerr := b.client.Call("Builder.Prepare", &BuilderPrepareArgs{config}, &resp) - if cerr != nil { - return nil, cerr +func (b *builder) Prepare(config ...interface{}) ([]string, []string, error) { + config, err := encodeCTYValues(config) + if err != nil { + return nil, nil, err } - var err error = nil + var resp BuilderPrepareResponse + cerr := b.client.Call(b.endpoint+".Prepare", &BuilderPrepareArgs{config}, &resp) + if cerr != nil { + return nil, nil, cerr + } + if resp.Error != nil { err = resp.Error } - return resp.Warnings, err + return resp.GeneratedVars, resp.Warnings, err } func (b *builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { @@ -61,7 +64,7 @@ func (b *builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack select { case <-ctx.Done(): log.Printf("Cancelling builder after context cancellation %v", ctx.Err()) - if err := b.client.Call("Builder.Cancel", new(interface{}), new(interface{})); err != nil { + if err := b.client.Call(b.endpoint+".Cancel", new(interface{}), new(interface{})); err != nil { log.Printf("Error cancelling builder: %s", err) } case <-done: @@ -70,7 +73,7 @@ func (b *builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack var responseId uint32 - if err := b.client.Call("Builder.Run", nextId, &responseId); err != nil { + if err := b.client.Call(b.endpoint+".Run", nextId, &responseId); err != nil { return nil, err } @@ -87,10 +90,15 @@ func (b *builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } func (b *BuilderServer) Prepare(args *BuilderPrepareArgs, reply *BuilderPrepareResponse) error { - warnings, err := b.builder.Prepare(args.Configs...) + config, err := decodeCTYValues(args.Configs) + if err != nil { + return err + } + generated, warnings, err := b.builder.Prepare(config...) *reply = BuilderPrepareResponse{ - Warnings: warnings, - Error: NewBasicError(err), + GeneratedVars: generated, + Warnings: warnings, + Error: NewBasicError(err), } return nil } diff --git a/packer/rpc/builder_test.go b/packer/rpc/builder_test.go index 9d89239b0..75d442865 100644 --- a/packer/rpc/builder_test.go +++ b/packer/rpc/builder_test.go @@ -20,7 +20,7 @@ func TestBuilderPrepare(t *testing.T) { // Test Prepare config := 42 - warnings, err := bClient.Prepare(config) + _, warnings, err := bClient.Prepare(config) if err != nil { t.Fatalf("bad: %s", err) } @@ -50,7 +50,7 @@ func TestBuilderPrepare_Warnings(t *testing.T) { b.PrepareWarnings = expected // Test Prepare - warnings, err := bClient.Prepare(nil) + _, warnings, err := bClient.Prepare(nil) if err != nil { t.Fatalf("bad: %s", err) } diff --git a/packer/rpc/client.go b/packer/rpc/client.go index 53f88d52f..e8eadfbf5 100644 --- a/packer/rpc/client.go +++ b/packer/rpc/client.go @@ -69,56 +69,79 @@ func (c *Client) Close() error { func (c *Client) Artifact() packer.Artifact { return &artifact{ - client: c.client, - endpoint: DefaultArtifactEndpoint, + commonClient: commonClient{ + endpoint: DefaultArtifactEndpoint, + client: c.client, + }, } } func (c *Client) Build() packer.Build { return &build{ - client: c.client, - mux: c.mux, + commonClient: commonClient{ + endpoint: DefaultBuildEndpoint, + client: c.client, + mux: c.mux, + }, } } func (c *Client) Builder() packer.Builder { return &builder{ - client: c.client, - mux: c.mux, + commonClient: commonClient{ + endpoint: DefaultBuilderEndpoint, + client: c.client, + mux: c.mux, + }, } } func (c *Client) Communicator() packer.Communicator { return &communicator{ - client: c.client, - mux: c.mux, + commonClient: commonClient{ + endpoint: DefaultCommunicatorEndpoint, + client: c.client, + mux: c.mux, + }, } } func (c *Client) Hook() packer.Hook { return &hook{ - client: c.client, - mux: c.mux, + commonClient: commonClient{ + endpoint: DefaultHookEndpoint, + client: c.client, + mux: c.mux, + }, } } func (c *Client) PostProcessor() packer.PostProcessor { return &postProcessor{ - client: c.client, - mux: c.mux, + commonClient: commonClient{ + endpoint: DefaultPostProcessorEndpoint, + client: c.client, + mux: c.mux, + }, } } func (c *Client) Provisioner() packer.Provisioner { return &provisioner{ - client: c.client, - mux: c.mux, + commonClient: commonClient{ + endpoint: DefaultProvisionerEndpoint, + client: c.client, + mux: c.mux, + }, } } func (c *Client) Ui() packer.Ui { return &Ui{ - client: c.client, + commonClient: commonClient{ + endpoint: DefaultUiEndpoint, + client: c.client, + }, endpoint: DefaultUiEndpoint, } } diff --git a/packer/rpc/client_test.go b/packer/rpc/client_test.go index 59a9389b9..ef91e6534 100644 --- a/packer/rpc/client_test.go +++ b/packer/rpc/client_test.go @@ -40,7 +40,10 @@ func testConn(t *testing.T) (net.Conn, net.Conn) { func testClientServer(t *testing.T) (*Client, *Server) { clientConn, serverConn := testConn(t) - server, _ := NewServer(serverConn) + server, err := NewServer(serverConn) + if err != nil { + t.Fatalf("err: %v", err) + } go server.Serve() client, err := NewClient(clientConn) diff --git a/packer/rpc/common.go b/packer/rpc/common.go new file mode 100644 index 000000000..7044ebcaf --- /dev/null +++ b/packer/rpc/common.go @@ -0,0 +1,71 @@ +package rpc + +import ( + "bytes" + "encoding/gob" + "fmt" + "net/rpc" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// commonClient allows to rpc call funcs that can be defined on the different +// build blocks of packer +type commonClient struct { + // endpoint is usually the type of build block we are connecting to. + // + // eg: Provisioner / PostProcessor / Builder / Artifact / Communicator + endpoint string + client *rpc.Client + mux *muxBroker +} + +type commonServer struct { + mux *muxBroker + selfConfigurable interface { + ConfigSpec() hcldec.ObjectSpec + } +} + +type ConfigSpecResponse struct { + ConfigSpec []byte +} + +func (p *commonClient) ConfigSpec() hcldec.ObjectSpec { + // TODO(azr): the RPC Call can fail but the ConfigSpec signature doesn't + // return an error; should we simply panic ? Logging this for now; will + // decide later. The correct approach would probably be to return an error + // in ConfigSpec but that will break a lot of things. + resp := &ConfigSpecResponse{} + cerr := p.client.Call(p.endpoint+".ConfigSpec", new(interface{}), resp) + if cerr != nil { + err := fmt.Errorf("ConfigSpec failed: %v", cerr) + panic(err.Error()) + } + + res := hcldec.ObjectSpec{} + err := gob.NewDecoder(bytes.NewReader(resp.ConfigSpec)).Decode(&res) + if err != nil { + panic("ici:" + err.Error()) + } + return res +} + +func (s *commonServer) ConfigSpec(_ interface{}, reply *ConfigSpecResponse) error { + spec := s.selfConfigurable.ConfigSpec() + b := bytes.NewBuffer(nil) + err := gob.NewEncoder(b).Encode(spec) + reply.ConfigSpec = b.Bytes() + + return err +} + +func init() { + gob.Register(new(hcldec.AttrSpec)) + gob.Register(new(hcldec.BlockSpec)) + gob.Register(new(hcldec.BlockAttrsSpec)) + gob.Register(new(hcldec.BlockListSpec)) + gob.Register(new(hcldec.BlockObjectSpec)) + gob.Register(new(cty.Value)) +} diff --git a/packer/rpc/communicator.go b/packer/rpc/communicator.go index 814dc05ca..55da58f24 100644 --- a/packer/rpc/communicator.go +++ b/packer/rpc/communicator.go @@ -15,15 +15,14 @@ import ( // An implementation of packer.Communicator where the communicator is actually // executed over an RPC connection. type communicator struct { - client *rpc.Client - mux *muxBroker + commonClient } // CommunicatorServer wraps a packer.Communicator implementation and makes // it exportable as part of a Golang RPC server. type CommunicatorServer struct { - c packer.Communicator - mux *muxBroker + commonServer + c packer.Communicator } type CommandFinished struct { @@ -62,7 +61,12 @@ type CommunicatorDownloadDirArgs struct { } func Communicator(client *rpc.Client) *communicator { - return &communicator{client: client} + return &communicator{ + commonClient: commonClient{ + client: client, + endpoint: DefaultCommunicatorEndpoint, + }, + } } func (c *communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) (err error) { @@ -123,7 +127,7 @@ func (c *communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) (err er cmd.SetExited(finished.ExitStatus) }() - err = c.client.Call("Communicator.Start", &args, new(interface{})) + err = c.client.Call(c.endpoint+".Start", &args, new(interface{})) return } @@ -141,7 +145,7 @@ func (c *communicator) Upload(path string, r io.Reader, fi *os.FileInfo) (err er args.FileInfo = NewFileInfo(*fi) } - err = c.client.Call("Communicator.Upload", &args, new(interface{})) + err = c.client.Call(c.endpoint+".Upload", &args, new(interface{})) return } @@ -153,7 +157,7 @@ func (c *communicator) UploadDir(dst string, src string, exclude []string) error } var reply error - err := c.client.Call("Communicator.UploadDir", args, &reply) + err := c.client.Call(c.endpoint+".UploadDir", args, &reply) if err == nil { err = reply } @@ -169,7 +173,7 @@ func (c *communicator) DownloadDir(src string, dst string, exclude []string) err } var reply error - err := c.client.Call("Communicator.DownloadDir", args, &reply) + err := c.client.Call(c.endpoint+".DownloadDir", args, &reply) if err == nil { err = reply } @@ -193,7 +197,7 @@ func (c *communicator) Download(path string, w io.Writer) (err error) { } // Start sending data to the RPC server - err = c.client.Call("Communicator.Download", &args, new(interface{})) + err = c.client.Call(c.endpoint+".Download", &args, new(interface{})) // Wait for the RPC server to finish receiving the data before we return <-waitServer diff --git a/packer/rpc/cty_encode.go b/packer/rpc/cty_encode.go new file mode 100644 index 000000000..570e0ee13 --- /dev/null +++ b/packer/rpc/cty_encode.go @@ -0,0 +1,35 @@ +package rpc + +import ( + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/json" +) + +// cty.Value is does not know how to encode itself through the wire so we +// transform it to bytes. +func encodeCTYValues(config []interface{}) ([]interface{}, error) { + for i := range config { + if v, ok := config[i].(cty.Value); ok { + b, err := json.Marshal(v, cty.DynamicPseudoType) + if err != nil { + return nil, err + } + config[i] = b + } + } + return config, nil +} + +// decodeCTYValues will try to decode a cty value when it finds a byte slice +func decodeCTYValues(config []interface{}) ([]interface{}, error) { + for i := range config { + if b, ok := config[i].([]byte); ok { + t, err := json.Unmarshal(b, cty.DynamicPseudoType) + if err != nil { + return nil, err + } + config[i] = t + } + } + return config, nil +} diff --git a/packer/rpc/hook.go b/packer/rpc/hook.go index c5ac329f6..12334b1d3 100644 --- a/packer/rpc/hook.go +++ b/packer/rpc/hook.go @@ -3,7 +3,6 @@ package rpc import ( "context" "log" - "net/rpc" "sync" "github.com/hashicorp/packer/packer" @@ -12,8 +11,7 @@ import ( // An implementation of packer.Hook where the hook is actually executed // over an RPC connection. type hook struct { - client *rpc.Client - mux *muxBroker + commonClient } // HookServer wraps a packer.Hook implementation and makes it exportable @@ -46,7 +44,7 @@ func (h *hook) Run(ctx context.Context, name string, ui packer.Ui, comm packer.C select { case <-ctx.Done(): log.Printf("Cancelling hook after context cancellation %v", ctx.Err()) - if err := h.client.Call("Hook.Cancel", new(interface{}), new(interface{})); err != nil { + if err := h.client.Call(h.endpoint+".Cancel", new(interface{}), new(interface{})); err != nil { log.Printf("Error cancelling builder: %s", err) } case <-done: @@ -59,7 +57,7 @@ func (h *hook) Run(ctx context.Context, name string, ui packer.Ui, comm packer.C StreamId: nextId, } - return h.client.Call("Hook.Run", &args, new(interface{})) + return h.client.Call(h.endpoint+".Run", &args, new(interface{})) } func (h *HookServer) Run(args *HookRunArgs, reply *interface{}) error { diff --git a/packer/rpc/post_processor.go b/packer/rpc/post_processor.go index 55e6363f3..4b36f2dd4 100644 --- a/packer/rpc/post_processor.go +++ b/packer/rpc/post_processor.go @@ -3,7 +3,6 @@ package rpc import ( "context" "log" - "net/rpc" "github.com/hashicorp/packer/packer" ) @@ -11,8 +10,7 @@ import ( // An implementation of packer.PostProcessor where the PostProcessor is actually // executed over an RPC connection. type postProcessor struct { - client *rpc.Client - mux *muxBroker + commonClient } // PostProcessorServer wraps a packer.PostProcessor implementation and makes it @@ -21,8 +19,8 @@ type PostProcessorServer struct { context context.Context contextCancel func() - mux *muxBroker - p packer.PostProcessor + commonServer + p packer.PostProcessor } type PostProcessorConfigureArgs struct { @@ -36,13 +34,13 @@ type PostProcessorProcessResponse struct { StreamId uint32 } -func (p *postProcessor) Configure(raw ...interface{}) (err error) { - args := &PostProcessorConfigureArgs{Configs: raw} - if cerr := p.client.Call("PostProcessor.Configure", args, new(interface{})); cerr != nil { - err = cerr +func (p *postProcessor) Configure(raw ...interface{}) error { + raw, err := encodeCTYValues(raw) + if err != nil { + return err } - - return + args := &PostProcessorConfigureArgs{Configs: raw} + return p.client.Call(p.endpoint+".Configure", args, new(interface{})) } func (p *postProcessor) PostProcess(ctx context.Context, ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, bool, error) { @@ -59,7 +57,7 @@ func (p *postProcessor) PostProcess(ctx context.Context, ui packer.Ui, a packer. select { case <-ctx.Done(): log.Printf("Cancelling post-processor after context cancellation %v", ctx.Err()) - if err := p.client.Call("PostProcessor.Cancel", new(interface{}), new(interface{})); err != nil { + if err := p.client.Call(p.endpoint+".Cancel", new(interface{}), new(interface{})); err != nil { log.Printf("Error cancelling post-processor: %s", err) } case <-done: @@ -67,7 +65,7 @@ func (p *postProcessor) PostProcess(ctx context.Context, ui packer.Ui, a packer. }() var response PostProcessorProcessResponse - if err := p.client.Call("PostProcessor.PostProcess", nextId, &response); err != nil { + if err := p.client.Call(p.endpoint+".PostProcess", nextId, &response); err != nil { return nil, false, false, err } @@ -87,8 +85,12 @@ func (p *postProcessor) PostProcess(ctx context.Context, ui packer.Ui, a packer. return client.Artifact(), response.Keep, response.ForceOverride, nil } -func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply *interface{}) error { - err := p.p.Configure(args.Configs...) +func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply *interface{}) (err error) { + config, err := decodeCTYValues(args.Configs) + if err != nil { + return err + } + err = p.p.Configure(config...) return err } diff --git a/packer/rpc/post_processor_test.go b/packer/rpc/post_processor_test.go index 3338915d2..e6f7235a1 100644 --- a/packer/rpc/post_processor_test.go +++ b/packer/rpc/post_processor_test.go @@ -5,6 +5,7 @@ import ( "reflect" "testing" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/packer" ) @@ -21,6 +22,8 @@ type TestPostProcessor struct { postProcessFn func(context.Context) error } +func (*TestPostProcessor) ConfigSpec() hcldec.ObjectSpec { return nil } + func (pp *TestPostProcessor) Configure(v ...interface{}) error { pp.configCalled = true pp.configVal = v @@ -81,7 +84,7 @@ func TestPostProcessorRPC(t *testing.T) { } if p.ppArtifactId != "ppTestId" { - t.Fatalf("unknown artifact: %s", p.ppArtifact.Id()) + t.Fatalf("unknown artifact: '%s'", p.ppArtifact.Id()) } if artifact.Id() != "id" { diff --git a/packer/rpc/provisioner.go b/packer/rpc/provisioner.go index faad6f434..2d826e0a8 100644 --- a/packer/rpc/provisioner.go +++ b/packer/rpc/provisioner.go @@ -3,7 +3,6 @@ package rpc import ( "context" "log" - "net/rpc" "github.com/hashicorp/packer/packer" ) @@ -11,8 +10,7 @@ import ( // An implementation of packer.Provisioner where the provisioner is actually // executed over an RPC connection. type provisioner struct { - client *rpc.Client - mux *muxBroker + commonClient } // ProvisionerServer wraps a packer.Provisioner implementation and makes it @@ -21,24 +19,29 @@ type ProvisionerServer struct { context context.Context contextCancel func() - p packer.Provisioner - mux *muxBroker + commonServer + p packer.Provisioner } type ProvisionerPrepareArgs struct { Configs []interface{} } -func (p *provisioner) Prepare(configs ...interface{}) (err error) { - args := &ProvisionerPrepareArgs{configs} - if cerr := p.client.Call("Provisioner.Prepare", args, new(interface{})); cerr != nil { - err = cerr +func (p *provisioner) Prepare(configs ...interface{}) error { + configs, err := encodeCTYValues(configs) + if err != nil { + return err } - - return + args := &ProvisionerPrepareArgs{configs} + return p.client.Call(p.endpoint+".Prepare", args, new(interface{})) } -func (p *provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +type ProvisionerProvisionArgs struct { + GeneratedData map[string]interface{} + StreamID uint32 +} + +func (p *provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error { nextId := p.mux.NextId() server := newServerWithMux(p.mux, nextId) server.RegisterCommunicator(comm) @@ -52,21 +55,27 @@ func (p *provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C select { case <-ctx.Done(): log.Printf("Cancelling provisioner after context cancellation %v", ctx.Err()) - if err := p.client.Call("Provisioner.Cancel", new(interface{}), new(interface{})); err != nil { + if err := p.client.Call(p.endpoint+".Cancel", new(interface{}), new(interface{})); err != nil { log.Printf("Error cancelling provisioner: %s", err) } case <-done: } }() - return p.client.Call("Provisioner.Provision", nextId, new(interface{})) + args := &ProvisionerProvisionArgs{generatedData, nextId} + return p.client.Call(p.endpoint+".Provision", args, new(interface{})) } func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *interface{}) error { - return p.p.Prepare(args.Configs...) + config, err := decodeCTYValues(args.Configs) + if err != nil { + return err + } + return p.p.Prepare(config...) } -func (p *ProvisionerServer) Provision(streamId uint32, reply *interface{}) error { +func (p *ProvisionerServer) Provision(args *ProvisionerProvisionArgs, reply *interface{}) error { + streamId := args.StreamID client, err := newClientWithMux(p.mux, streamId) if err != nil { return NewBasicError(err) @@ -76,8 +85,7 @@ func (p *ProvisionerServer) Provision(streamId uint32, reply *interface{}) error if p.context == nil { p.context, p.contextCancel = context.WithCancel(context.Background()) } - - if err := p.p.Provision(p.context, client.Ui(), client.Communicator()); err != nil { + if err := p.p.Provision(p.context, client.Ui(), client.Communicator(), args.GeneratedData); err != nil { return NewBasicError(err) } diff --git a/packer/rpc/provisioner_test.go b/packer/rpc/provisioner_test.go index bbcbc88d8..f70e09dad 100644 --- a/packer/rpc/provisioner_test.go +++ b/packer/rpc/provisioner_test.go @@ -39,7 +39,7 @@ func TestProvisionerRPC(t *testing.T) { // Test Provision ui := &testUi{} comm := &packer.MockCommunicator{} - if err := pClient.Provision(topCtx, ui, comm); err == nil { + if err := pClient.Provision(topCtx, ui, comm, make(map[string]interface{})); err == nil { t.Fatalf("Provison should have err") } if !p.ProvCalled { diff --git a/packer/rpc/server.go b/packer/rpc/server.go index e0b90d261..b583e2945 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -76,15 +76,20 @@ func (s *Server) RegisterBuild(b packer.Build) error { func (s *Server) RegisterBuilder(b packer.Builder) error { return s.server.RegisterName(DefaultBuilderEndpoint, &BuilderServer{ + commonServer: commonServer{ + selfConfigurable: b, + mux: s.mux, + }, builder: b, - mux: s.mux, }) } func (s *Server) RegisterCommunicator(c packer.Communicator) error { return s.server.RegisterName(DefaultCommunicatorEndpoint, &CommunicatorServer{ - c: c, - mux: s.mux, + c: c, + commonServer: commonServer{ + mux: s.mux, + }, }) } @@ -97,15 +102,21 @@ func (s *Server) RegisterHook(h packer.Hook) error { func (s *Server) RegisterPostProcessor(p packer.PostProcessor) error { return s.server.RegisterName(DefaultPostProcessorEndpoint, &PostProcessorServer{ - mux: s.mux, - p: p, + commonServer: commonServer{ + selfConfigurable: p, + mux: s.mux, + }, + p: p, }) } func (s *Server) RegisterProvisioner(p packer.Provisioner) error { return s.server.RegisterName(DefaultProvisionerEndpoint, &ProvisionerServer{ - mux: s.mux, - p: p, + commonServer: commonServer{ + selfConfigurable: p, + mux: s.mux, + }, + p: p, }) } diff --git a/packer/rpc/ui.go b/packer/rpc/ui.go index d86301673..adcee28b5 100644 --- a/packer/rpc/ui.go +++ b/packer/rpc/ui.go @@ -2,7 +2,6 @@ package rpc import ( "log" - "net/rpc" "github.com/hashicorp/packer/packer" ) @@ -10,7 +9,7 @@ import ( // An implementation of packer.Ui where the Ui is actually executed // over an RPC connection. type Ui struct { - client *rpc.Client + commonClient endpoint string } diff --git a/packer/testing.go b/packer/testing.go index 7217a5083..000c57e70 100644 --- a/packer/testing.go +++ b/packer/testing.go @@ -9,12 +9,8 @@ import ( func TestCoreConfig(t *testing.T) *CoreConfig { // Create some test components components := ComponentFinder{ - Builder: func(n string) (Builder, error) { - if n != "test" { - return nil, nil - } - - return &MockBuilder{}, nil + BuilderStore: MapOfBuilder{ + "test": func() (Builder, error) { return &MockBuilder{}, nil }, }, } @@ -46,12 +42,8 @@ func TestUi(t *testing.T) Ui { func TestBuilder(t *testing.T, c *CoreConfig, n string) *MockBuilder { var b MockBuilder - c.Components.Builder = func(actual string) (Builder, error) { - if actual != n { - return nil, nil - } - - return &b, nil + c.Components.BuilderStore = MapOfBuilder{ + n: func() (Builder, error) { return &b, nil }, } return &b @@ -62,12 +54,8 @@ func TestBuilder(t *testing.T, c *CoreConfig, n string) *MockBuilder { func TestProvisioner(t *testing.T, c *CoreConfig, n string) *MockProvisioner { var b MockProvisioner - c.Components.Provisioner = func(actual string) (Provisioner, error) { - if actual != n { - return nil, nil - } - - return &b, nil + c.Components.ProvisionerStore = MapOfProvisioner{ + n: func() (Provisioner, error) { return &b, nil }, } return &b @@ -78,12 +66,8 @@ func TestProvisioner(t *testing.T, c *CoreConfig, n string) *MockProvisioner { func TestPostProcessor(t *testing.T, c *CoreConfig, n string) *MockPostProcessor { var b MockPostProcessor - c.Components.PostProcessor = func(actual string) (PostProcessor, error) { - if actual != n { - return nil, nil - } - - return &b, nil + c.Components.PostProcessorStore = MapOfPostProcessor{ + n: func() (PostProcessor, error) { return &b, nil }, } return &b diff --git a/post-processor/alicloud-import/post-processor.go b/post-processor/alicloud-import/post-processor.go index 42882c6df..a38ad7b13 100644 --- a/post-processor/alicloud-import/post-processor.go +++ b/post-processor/alicloud-import/post-processor.go @@ -16,6 +16,7 @@ import ( "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" "github.com/aliyun/alibaba-cloud-sdk-go/services/ram" "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/hashicorp/hcl/v2/hcldec" packerecs "github.com/hashicorp/packer/builder/alicloud/ecs" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -82,7 +83,8 @@ type PostProcessor struct { ramClient *ram.Client } -// Entry point for configuration parsing when we've defined +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/alicloud-import/post-processor.hcl2spec.go b/post-processor/alicloud-import/post-processor.hcl2spec.go index aeeed2435..f0290a185 100644 --- a/post-processor/alicloud-import/post-processor.hcl2spec.go +++ b/post-processor/alicloud-import/post-processor.hcl2spec.go @@ -17,10 +17,13 @@ type FlatConfig struct { PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` - AlicloudAccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"` - AlicloudSecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"` - AlicloudRegion *string `mapstructure:"region" required:"true" cty:"region"` + AlicloudAccessKey *string `mapstructure:"access_key" required:"false" cty:"access_key"` + AlicloudSecretKey *string `mapstructure:"secret_key" required:"false" cty:"secret_key"` + AlicloudRegion *string `mapstructure:"region" required:"false" cty:"region"` AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"` + AlicloudSkipImageValidation *bool `mapstructure:"skip_image_validation" required:"false" cty:"skip_image_validation"` + AlicloudProfile *string `mapstructure:"profile" required:"false" cty:"profile"` + AlicloudSharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file"` SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token"` AlicloudImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"` AlicloudImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"` @@ -88,8 +91,8 @@ type FlatConfig struct { SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"` SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"` SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"` - SSHPublicKey []byte `cty:"ssh_public_key"` - SSHPrivateKey []byte `cty:"ssh_private_key"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"` WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"` WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"` WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"` @@ -112,10 +115,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -129,6 +135,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false}, "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, "skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false}, + "skip_image_validation": &hcldec.AttrSpec{Name: "skip_image_validation", Type: cty.Bool, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false}, "security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false}, "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, "image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false}, @@ -144,7 +153,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "image_ignore_data_disks": &hcldec.AttrSpec{Name: "image_ignore_data_disks", Type: cty.Bool, Required: false}, "tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false}, "system_disk_mapping": &hcldec.BlockSpec{TypeName: "system_disk_mapping", Nested: hcldec.ObjectSpec((*ecs.FlatAlicloudDiskDevice)(nil).HCL2Spec())}, - "image_disk_mappings": &hcldec.BlockListSpec{TypeName: "image_disk_mappings", Nested: &hcldec.BlockSpec{TypeName: "image_disk_mappings", Nested: hcldec.ObjectSpec((*ecs.FlatAlicloudDiskDevice)(nil).HCL2Spec())}}, + "image_disk_mappings": &hcldec.BlockListSpec{TypeName: "image_disk_mappings", Nested: hcldec.ObjectSpec((*ecs.FlatAlicloudDiskDevice)(nil).HCL2Spec())}, "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, "zone_id": &hcldec.AttrSpec{Name: "zone_id", Type: cty.String, Required: false}, "io_optimized": &hcldec.AttrSpec{Name: "io_optimized", Type: cty.Bool, Required: false}, diff --git a/post-processor/amazon-import/post-processor.go b/post-processor/amazon-import/post-processor.go index f8f055a27..f64decbe0 100644 --- a/post-processor/amazon-import/post-processor.go +++ b/post-processor/amazon-import/post-processor.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/hashicorp/hcl/v2/hcldec" awscommon "github.com/hashicorp/packer/builder/amazon/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -51,7 +52,8 @@ type PostProcessor struct { config Config } -// Entry point for configuration parsing when we've defined +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { p.config.ctx.Funcs = awscommon.TemplateFuncs err := config.Decode(&p.config, &config.DecodeOpts{ diff --git a/post-processor/amazon-import/post-processor.hcl2spec.go b/post-processor/amazon-import/post-processor.hcl2spec.go index ef2134711..60ecb8f08 100644 --- a/post-processor/amazon-import/post-processor.hcl2spec.go +++ b/post-processor/amazon-import/post-processor.hcl2spec.go @@ -49,10 +49,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/artifice/post-processor.go b/post-processor/artifice/post-processor.go index fafa915e7..3ab566dea 100644 --- a/post-processor/artifice/post-processor.go +++ b/post-processor/artifice/post-processor.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -32,6 +33,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/artifice/post-processor.hcl2spec.go b/post-processor/artifice/post-processor.hcl2spec.go index c81b3eefe..a2ae49b96 100644 --- a/post-processor/artifice/post-processor.hcl2spec.go +++ b/post-processor/artifice/post-processor.hcl2spec.go @@ -23,10 +23,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/checksum/post-processor.go b/post-processor/checksum/post-processor.go index 9b3cec76f..3495a633f 100644 --- a/post-processor/checksum/post-processor.go +++ b/post-processor/checksum/post-processor.go @@ -14,6 +14,7 @@ import ( "os" "path/filepath" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -58,6 +59,8 @@ func getHash(t string) hash.Hash { return h } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/checksum/post-processor.hcl2spec.go b/post-processor/checksum/post-processor.hcl2spec.go index 1efec6c80..b136faac0 100644 --- a/post-processor/checksum/post-processor.hcl2spec.go +++ b/post-processor/checksum/post-processor.hcl2spec.go @@ -24,10 +24,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/checksum/post-processor_test.go b/post-processor/checksum/post-processor_test.go index fce5fd1cf..e52974ec4 100644 --- a/post-processor/checksum/post-processor_test.go +++ b/post-processor/checksum/post-processor_test.go @@ -57,7 +57,7 @@ func setup(t *testing.T) (packer.Ui, packer.Artifact, error) { // Prepare the file builder builder := file.Builder{} - warnings, err := builder.Prepare(tpl.Builders["file"].Config) + _, warnings, err := builder.Prepare(tpl.Builders["file"].Config) if len(warnings) > 0 { for _, warn := range warnings { return nil, nil, fmt.Errorf("Configuration warning: %s", warn) diff --git a/post-processor/compress/post-processor.go b/post-processor/compress/post-processor.go index 0bc89b9a3..8777cab49 100644 --- a/post-processor/compress/post-processor.go +++ b/post-processor/compress/post-processor.go @@ -14,6 +14,7 @@ import ( "runtime" "github.com/biogo/hts/bgzf" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -54,6 +55,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/compress/post-processor.hcl2spec.go b/post-processor/compress/post-processor.hcl2spec.go index 4e8db0497..e1e9e55ac 100644 --- a/post-processor/compress/post-processor.hcl2spec.go +++ b/post-processor/compress/post-processor.hcl2spec.go @@ -26,10 +26,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/compress/post-processor_test.go b/post-processor/compress/post-processor_test.go index 0f5e1a19e..5b4cfcd9f 100644 --- a/post-processor/compress/post-processor_test.go +++ b/post-processor/compress/post-processor_test.go @@ -197,7 +197,7 @@ func setup(t *testing.T) (packer.Ui, packer.Artifact, error) { // Prepare the file builder builder := file.Builder{} - warnings, err := builder.Prepare(tpl.Builders["file"].Config) + _, warnings, err := builder.Prepare(tpl.Builders["file"].Config) if len(warnings) > 0 { for _, warn := range warnings { return nil, nil, fmt.Errorf("Configuration warning: %s", warn) diff --git a/post-processor/digitalocean-import/post-processor.go b/post-processor/digitalocean-import/post-processor.go index 57e82f668..50f70013a 100644 --- a/post-processor/digitalocean-import/post-processor.go +++ b/post-processor/digitalocean-import/post-processor.go @@ -19,6 +19,7 @@ import ( "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/digitalocean/godo" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/digitalocean" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -72,6 +73,8 @@ func (l logger) Log(args ...interface{}) { l.logger.Println(args...) } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/digitalocean-import/post-processor.hcl2spec.go b/post-processor/digitalocean-import/post-processor.hcl2spec.go index 00e764e83..fb2a4d8ac 100644 --- a/post-processor/digitalocean-import/post-processor.hcl2spec.go +++ b/post-processor/digitalocean-import/post-processor.hcl2spec.go @@ -34,10 +34,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/digitalocean-import/post-processor_test.go b/post-processor/digitalocean-import/post-processor_test.go index 45e7a5b32..8173d0302 100644 --- a/post-processor/digitalocean-import/post-processor_test.go +++ b/post-processor/digitalocean-import/post-processor_test.go @@ -1,32 +1,11 @@ package digitaloceanimport import ( - "bytes" "testing" "github.com/hashicorp/packer/packer" ) -func testConfig() map[string]interface{} { - return map[string]interface{}{} -} - -func testPP(t *testing.T) *PostProcessor { - var p PostProcessor - if err := p.Configure(testConfig()); err != nil { - t.Fatalf("err: %s", err) - } - - return &p -} - -func testUi() *packer.BasicUi { - return &packer.BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - } -} - func TestPostProcessor_ImplementsPostProcessor(t *testing.T) { var _ packer.PostProcessor = new(PostProcessor) } diff --git a/post-processor/docker-import/post-processor.go b/post-processor/docker-import/post-processor.go index fc0309a3c..089aed122 100644 --- a/post-processor/docker-import/post-processor.go +++ b/post-processor/docker-import/post-processor.go @@ -6,6 +6,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/docker" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -30,6 +31,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/docker-import/post-processor.hcl2spec.go b/post-processor/docker-import/post-processor.hcl2spec.go index 80a0ff36c..fc90b5adf 100644 --- a/post-processor/docker-import/post-processor.hcl2spec.go +++ b/post-processor/docker-import/post-processor.hcl2spec.go @@ -24,10 +24,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/docker-push/post-processor.go b/post-processor/docker-push/post-processor.go index cd0b950e1..a276b0fca 100644 --- a/post-processor/docker-push/post-processor.go +++ b/post-processor/docker-push/post-processor.go @@ -6,6 +6,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/docker" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -36,6 +37,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/docker-push/post-processor.hcl2spec.go b/post-processor/docker-push/post-processor.hcl2spec.go index 096aebdda..cbd5cf726 100644 --- a/post-processor/docker-push/post-processor.hcl2spec.go +++ b/post-processor/docker-push/post-processor.hcl2spec.go @@ -30,10 +30,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/docker-save/post-processor.go b/post-processor/docker-save/post-processor.go index 26198142c..8cf3306d7 100644 --- a/post-processor/docker-save/post-processor.go +++ b/post-processor/docker-save/post-processor.go @@ -7,6 +7,7 @@ import ( "fmt" "os" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/docker" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -32,6 +33,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/docker-save/post-processor.hcl2spec.go b/post-processor/docker-save/post-processor.hcl2spec.go index e7c71ed51..59bee1e9b 100644 --- a/post-processor/docker-save/post-processor.hcl2spec.go +++ b/post-processor/docker-save/post-processor.hcl2spec.go @@ -22,10 +22,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/docker-tag/post-processor.go b/post-processor/docker-tag/post-processor.go index f2ba1beb3..4c1f03054 100644 --- a/post-processor/docker-tag/post-processor.go +++ b/post-processor/docker-tag/post-processor.go @@ -6,6 +6,7 @@ import ( "context" "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/docker" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -32,6 +33,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/docker-tag/post-processor.hcl2spec.go b/post-processor/docker-tag/post-processor.hcl2spec.go index 22c20a420..2ec63f2b5 100644 --- a/post-processor/docker-tag/post-processor.hcl2spec.go +++ b/post-processor/docker-tag/post-processor.hcl2spec.go @@ -24,10 +24,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/exoscale-import/post-processor.go b/post-processor/exoscale-import/post-processor.go index 3e8cc261a..ac190191e 100644 --- a/post-processor/exoscale-import/post-processor.go +++ b/post-processor/exoscale-import/post-processor.go @@ -17,6 +17,7 @@ import ( "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/exoscale/egoscale" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/file" "github.com/hashicorp/packer/builder/qemu" "github.com/hashicorp/packer/common" @@ -57,6 +58,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { p.config.TemplateZone = defaultTemplateZone p.config.APIEndpoint = defaultAPIEndpoint diff --git a/post-processor/exoscale-import/post-processor.hcl2spec.go b/post-processor/exoscale-import/post-processor.hcl2spec.go index 3dbd4740d..243b54166 100644 --- a/post-processor/exoscale-import/post-processor.hcl2spec.go +++ b/post-processor/exoscale-import/post-processor.hcl2spec.go @@ -33,10 +33,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/googlecompute-export/post-processor.go b/post-processor/googlecompute-export/post-processor.go index 133e47dc8..16453742a 100644 --- a/post-processor/googlecompute-export/post-processor.go +++ b/post-processor/googlecompute-export/post-processor.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/googlecompute" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -41,6 +42,8 @@ type PostProcessor struct { runner multistep.Runner } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/googlecompute-export/post-processor.hcl2spec.go b/post-processor/googlecompute-export/post-processor.hcl2spec.go index 1fce9eab7..5df63b9eb 100644 --- a/post-processor/googlecompute-export/post-processor.hcl2spec.go +++ b/post-processor/googlecompute-export/post-processor.hcl2spec.go @@ -31,10 +31,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/googlecompute-import/post-processor.go b/post-processor/googlecompute-import/post-processor.go index fd949fef3..1eaf9f954 100644 --- a/post-processor/googlecompute-import/post-processor.go +++ b/post-processor/googlecompute-import/post-processor.go @@ -14,6 +14,7 @@ import ( "google.golang.org/api/compute/v1" "google.golang.org/api/storage/v1" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/googlecompute" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -46,6 +47,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/googlecompute-import/post-processor.hcl2spec.go b/post-processor/googlecompute-import/post-processor.hcl2spec.go index 39d10924a..cc9e3a5e6 100644 --- a/post-processor/googlecompute-import/post-processor.hcl2spec.go +++ b/post-processor/googlecompute-import/post-processor.hcl2spec.go @@ -32,10 +32,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/manifest/post-processor.go b/post-processor/manifest/post-processor.go index d10919d28..698c12b01 100644 --- a/post-processor/manifest/post-processor.go +++ b/post-processor/manifest/post-processor.go @@ -12,6 +12,7 @@ import ( "path/filepath" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -36,6 +37,8 @@ type ManifestFile struct { LastRunUUID string `json:"last_run_uuid"` } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/manifest/post-processor.hcl2spec.go b/post-processor/manifest/post-processor.hcl2spec.go index a7e831e30..08be1e1e9 100644 --- a/post-processor/manifest/post-processor.hcl2spec.go +++ b/post-processor/manifest/post-processor.hcl2spec.go @@ -24,10 +24,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/shell-local/post-processor.go b/post-processor/shell-local/post-processor.go index e23d31648..5cbab3fd6 100644 --- a/post-processor/shell-local/post-processor.go +++ b/post-processor/shell-local/post-processor.go @@ -3,6 +3,7 @@ package shell_local import ( "context" + "github.com/hashicorp/hcl/v2/hcldec" sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" ) @@ -16,6 +17,8 @@ type ExecuteCommandTemplate struct { Script string } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := sl.Decode(&p.config, raws...) if err != nil { @@ -41,7 +44,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact // this particular post-processor doesn't do anything with the artifact // except to return it. - success, retErr := sl.Run(ctx, ui, &p.config) + success, retErr := sl.Run(ctx, ui, &p.config, map[string]interface{}{}) if !success { return nil, false, false, retErr } diff --git a/post-processor/ucloud-import/post-processor.go b/post-processor/ucloud-import/post-processor.go index 69dded174..a0ec219ed 100644 --- a/post-processor/ucloud-import/post-processor.go +++ b/post-processor/ucloud-import/post-processor.go @@ -1,8 +1,16 @@ +//go:generate mapstructure-to-hcl2 -type Config + package ucloudimport import ( "context" "fmt" + "log" + "net/url" + "strings" + "time" + + "github.com/hashicorp/hcl/v2/hcldec" ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/retry" @@ -13,10 +21,6 @@ import ( "github.com/ucloud/ucloud-sdk-go/services/uhost" "github.com/ucloud/ucloud-sdk-go/ucloud" ufsdk "github.com/ufilesdk-dev/ufile-gosdk" - "log" - "net/url" - "strings" - "time" ) const ( @@ -57,7 +61,8 @@ type PostProcessor struct { config Config } -// Entry point for configuration parsing when we've defined +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/ucloud-import/post-processor.hcl2spec.go b/post-processor/ucloud-import/post-processor.hcl2spec.go new file mode 100644 index 000000000..ec78de713 --- /dev/null +++ b/post-processor/ucloud-import/post-processor.hcl2spec.go @@ -0,0 +1,70 @@ +// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT. +package ucloudimport + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` + PublicKey *string `mapstructure:"public_key" cty:"public_key"` + PrivateKey *string `mapstructure:"private_key" cty:"private_key"` + Region *string `mapstructure:"region" cty:"region"` + ProjectId *string `mapstructure:"project_id" cty:"project_id"` + BaseUrl *string `mapstructure:"base_url" cty:"base_url"` + UFileBucket *string `mapstructure:"ufile_bucket_name" cty:"ufile_bucket_name"` + UFileKey *string `mapstructure:"ufile_key_name" cty:"ufile_key_name"` + SkipClean *bool `mapstructure:"skip_clean" cty:"skip_clean"` + ImageName *string `mapstructure:"image_name" cty:"image_name"` + ImageDescription *string `mapstructure:"image_description" cty:"image_description"` + OSType *string `mapstructure:"image_os_type" cty:"image_os_type"` + OSName *string `mapstructure:"image_os_name" cty:"image_os_name"` + Format *string `mapstructure:"format" cty:"format"` + WaitImageReadyTimeout *int `mapstructure:"wait_image_ready_timeout" cty:"wait_image_ready_timeout"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "public_key": &hcldec.AttrSpec{Name: "public_key", Type: cty.String, Required: false}, + "private_key": &hcldec.AttrSpec{Name: "private_key", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false}, + "base_url": &hcldec.AttrSpec{Name: "base_url", Type: cty.String, Required: false}, + "ufile_bucket_name": &hcldec.AttrSpec{Name: "ufile_bucket_name", Type: cty.String, Required: false}, + "ufile_key_name": &hcldec.AttrSpec{Name: "ufile_key_name", Type: cty.String, Required: false}, + "skip_clean": &hcldec.AttrSpec{Name: "skip_clean", Type: cty.Bool, Required: false}, + "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, + "image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false}, + "image_os_type": &hcldec.AttrSpec{Name: "image_os_type", Type: cty.String, Required: false}, + "image_os_name": &hcldec.AttrSpec{Name: "image_os_name", Type: cty.String, Required: false}, + "format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false}, + "wait_image_ready_timeout": &hcldec.AttrSpec{Name: "wait_image_ready_timeout", Type: cty.Number, Required: false}, + } + return s +} diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 9401b8374..d6283e4e5 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -18,6 +18,7 @@ import ( "os" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" @@ -63,6 +64,8 @@ type PostProcessor struct { insecureSkipTLSVerify bool } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -163,7 +166,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact // Set up the state state := new(multistep.BasicStateBag) - state.Put("config", p.config) + state.Put("config", &p.config) state.Put("client", p.client) state.Put("artifact", artifact) state.Put("artifactFilePath", artifact.Files()[0]) diff --git a/post-processor/vagrant-cloud/post-processor.hcl2spec.go b/post-processor/vagrant-cloud/post-processor.hcl2spec.go index a8216aeff..515a06914 100644 --- a/post-processor/vagrant-cloud/post-processor.hcl2spec.go +++ b/post-processor/vagrant-cloud/post-processor.hcl2spec.go @@ -29,10 +29,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/vagrant-cloud/step_create_version.go b/post-processor/vagrant-cloud/step_create_version.go index 8c2459262..6830dc4a4 100644 --- a/post-processor/vagrant-cloud/step_create_version.go +++ b/post-processor/vagrant-cloud/step_create_version.go @@ -19,7 +19,7 @@ type stepCreateVersion struct { func (s *stepCreateVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*VagrantCloudClient) ui := state.Get("ui").(packer.Ui) - config := state.Get("config").(Config) + config := state.Get("config").(*Config) box := state.Get("box").(*Box) ui.Say(fmt.Sprintf("Creating version: %s", config.Version)) diff --git a/post-processor/vagrant-cloud/step_release_version.go b/post-processor/vagrant-cloud/step_release_version.go index ba3381a66..18ec9efd7 100644 --- a/post-processor/vagrant-cloud/step_release_version.go +++ b/post-processor/vagrant-cloud/step_release_version.go @@ -17,7 +17,7 @@ func (s *stepReleaseVersion) Run(ctx context.Context, state multistep.StateBag) ui := state.Get("ui").(packer.Ui) box := state.Get("box").(*Box) version := state.Get("version").(*Version) - config := state.Get("config").(Config) + config := state.Get("config").(*Config) ui.Say(fmt.Sprintf("Releasing version: %s", version.Version)) diff --git a/post-processor/vagrant-cloud/step_verify_box.go b/post-processor/vagrant-cloud/step_verify_box.go index 3580e44de..573815986 100644 --- a/post-processor/vagrant-cloud/step_verify_box.go +++ b/post-processor/vagrant-cloud/step_verify_box.go @@ -28,7 +28,7 @@ type stepVerifyBox struct { func (s *stepVerifyBox) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*VagrantCloudClient) ui := state.Get("ui").(packer.Ui) - config := state.Get("config").(Config) + config := state.Get("config").(*Config) ui.Say(fmt.Sprintf("Verifying box is accessible: %s", config.Tag)) diff --git a/post-processor/vagrant/libvirt.go b/post-processor/vagrant/libvirt.go index 60e274a57..5f14ba777 100644 --- a/post-processor/vagrant/libvirt.go +++ b/post-processor/vagrant/libvirt.go @@ -3,11 +3,61 @@ package vagrant import ( "fmt" "path/filepath" + "strconv" "strings" "github.com/hashicorp/packer/packer" ) +// Lowercase a ascii letter. +func lower(c byte) byte { + return c | ('a' - 'A') +} + +// Convert a string that represents a qemu disk image size to megabytes. +// +// Valid units (case-insensitive): +// +// B (byte) 1B +// K (kilobyte) 1024B +// M (megabyte) 1024K +// G (gigabyte) 1024M +// T (terabyte) 1024G +// P (petabyte) 1024T +// E (exabyte) 1024P +// +// The default is M. +func sizeInMegabytes(size string) uint64 { + unit := size[len(size)-1] + + if unit >= '0' && unit <= '9' { + unit = 'm' + } else { + size = size[:len(size)-1] + } + + value, _ := strconv.ParseUint(size, 10, 64) + + switch lower(unit) { + case 'b': + return value / 1024 / 1024 + case 'k': + return value / 1024 + case 'm': + return value + case 'g': + return value * 1024 + case 't': + return value * 1024 * 1024 + case 'p': + return value * 1024 * 1024 * 1024 + case 'e': + return value * 1024 * 1024 * 1024 * 1024 + default: + panic(fmt.Sprintf("Unknown size unit %c", unit)) + } +} + type LibVirtProvider struct{} func (p *LibVirtProvider) KeepInputArtifact() bool { @@ -28,7 +78,7 @@ func (p *LibVirtProvider) Process(ui packer.Ui, artifact packer.Artifact, dir st } format := artifact.State("diskType").(string) - origSize := artifact.State("diskSize").(uint64) + origSize := sizeInMegabytes(artifact.State("diskSize").(string)) size := origSize / 1024 // In MB, want GB if origSize%1024 > 0 { // Make sure we don't make the size smaller diff --git a/post-processor/vagrant/libvirt_test.go b/post-processor/vagrant/libvirt_test.go new file mode 100644 index 000000000..da03596ae --- /dev/null +++ b/post-processor/vagrant/libvirt_test.go @@ -0,0 +1,69 @@ +package vagrant + +import ( + "fmt" + "testing" +) + +func assertSizeInMegabytes(t *testing.T, size string, expected uint64) { + actual := sizeInMegabytes(size) + if actual != expected { + t.Fatalf("the size `%s` was converted to `%d` but expected `%d`", size, actual, expected) + } +} + +func Test_sizeInMegabytes_WithInvalidUnitMustPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected a panic but got none") + } + }() + + sizeInMegabytes("1234x") +} + +func Test_sizeInMegabytes_WithoutUnitMustDefaultToMegabytes(t *testing.T) { + assertSizeInMegabytes(t, "1234", 1234) +} + +func Test_sizeInMegabytes_WithBytesUnit(t *testing.T) { + assertSizeInMegabytes(t, fmt.Sprintf("%db", 1234*1024*1024), 1234) + assertSizeInMegabytes(t, fmt.Sprintf("%dB", 1234*1024*1024), 1234) + assertSizeInMegabytes(t, "1B", 0) +} + +func Test_sizeInMegabytes_WithKiloBytesUnit(t *testing.T) { + assertSizeInMegabytes(t, fmt.Sprintf("%dk", 1234*1024), 1234) + assertSizeInMegabytes(t, fmt.Sprintf("%dK", 1234*1024), 1234) + assertSizeInMegabytes(t, "1K", 0) +} + +func Test_sizeInMegabytes_WithMegabytesUnit(t *testing.T) { + assertSizeInMegabytes(t, "1234m", 1234) + assertSizeInMegabytes(t, "1234M", 1234) + assertSizeInMegabytes(t, "1M", 1) +} + +func Test_sizeInMegabytes_WithGigabytesUnit(t *testing.T) { + assertSizeInMegabytes(t, "1234g", 1234*1024) + assertSizeInMegabytes(t, "1234G", 1234*1024) + assertSizeInMegabytes(t, "1G", 1*1024) +} + +func Test_sizeInMegabytes_WithTerabytesUnit(t *testing.T) { + assertSizeInMegabytes(t, "1234t", 1234*1024*1024) + assertSizeInMegabytes(t, "1234T", 1234*1024*1024) + assertSizeInMegabytes(t, "1T", 1*1024*1024) +} + +func Test_sizeInMegabytes_WithPetabytesUnit(t *testing.T) { + assertSizeInMegabytes(t, "1234p", 1234*1024*1024*1024) + assertSizeInMegabytes(t, "1234P", 1234*1024*1024*1024) + assertSizeInMegabytes(t, "1P", 1*1024*1024*1024) +} + +func Test_sizeInMegabytes_WithExabytesUnit(t *testing.T) { + assertSizeInMegabytes(t, "1234e", 1234*1024*1024*1024*1024) + assertSizeInMegabytes(t, "1234E", 1234*1024*1024*1024*1024) + assertSizeInMegabytes(t, "1E", 1*1024*1024*1024*1024) +} diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 0a4527026..0e57829f2 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -14,6 +14,7 @@ import ( "path/filepath" "text/template" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -58,6 +59,11 @@ type PostProcessor struct { configs map[string]*Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { + panic("not implemented yet") + // return p.config.FlatMapstructure().HCL2Spec() +} + func (p *PostProcessor) Configure(raws ...interface{}) error { p.configs = make(map[string]*Config) p.configs[""] = new(Config) diff --git a/post-processor/vagrant/post-processor.hcl2spec.go b/post-processor/vagrant/post-processor.hcl2spec.go index 541c4d493..ea0b1a5af 100644 --- a/post-processor/vagrant/post-processor.hcl2spec.go +++ b/post-processor/vagrant/post-processor.hcl2spec.go @@ -27,10 +27,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/vsphere-template/post-processor.go b/post-processor/vsphere-template/post-processor.go index ca4b8393c..83f9c8865 100644 --- a/post-processor/vsphere-template/post-processor.go +++ b/post-processor/vsphere-template/post-processor.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/hashicorp/hcl/v2/hcldec" vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" @@ -45,6 +46,8 @@ type PostProcessor struct { url *url.URL } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/vsphere-template/post-processor.hcl2spec.go b/post-processor/vsphere-template/post-processor.hcl2spec.go index 0ec10a50b..c45780821 100644 --- a/post-processor/vsphere-template/post-processor.hcl2spec.go +++ b/post-processor/vsphere-template/post-processor.hcl2spec.go @@ -30,10 +30,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/post-processor/vsphere/post-processor.go b/post-processor/vsphere/post-processor.go index c4da09a8e..6bfdda5b7 100644 --- a/post-processor/vsphere/post-processor.go +++ b/post-processor/vsphere/post-processor.go @@ -15,6 +15,7 @@ import ( "runtime" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -59,6 +60,8 @@ type PostProcessor struct { config Config } +func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *PostProcessor) Configure(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, diff --git a/post-processor/vsphere/post-processor.hcl2spec.go b/post-processor/vsphere/post-processor.hcl2spec.go index ecf70776a..09373442b 100644 --- a/post-processor/vsphere/post-processor.hcl2spec.go +++ b/post-processor/vsphere/post-processor.hcl2spec.go @@ -36,10 +36,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/ansible-local/provisioner.go b/provisioner/ansible-local/provisioner.go index 4575115ac..c9ab144ea 100644 --- a/provisioner/ansible-local/provisioner.go +++ b/provisioner/ansible-local/provisioner.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/helper/config" @@ -76,6 +77,8 @@ type Provisioner struct { playbookFiles []string } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -188,7 +191,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { ui.Say("Provisioning with Ansible...") if len(p.config.PlaybookDir) > 0 { diff --git a/provisioner/ansible-local/provisioner.hcl2spec.go b/provisioner/ansible-local/provisioner.hcl2spec.go index 7424c095f..9c162d6ba 100644 --- a/provisioner/ansible-local/provisioner.hcl2spec.go +++ b/provisioner/ansible-local/provisioner.hcl2spec.go @@ -36,10 +36,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/ansible-local/provisioner_test.go b/provisioner/ansible-local/provisioner_test.go index d2af8abfb..1b8e5e34a 100644 --- a/provisioner/ansible-local/provisioner_test.go +++ b/provisioner/ansible-local/provisioner_test.go @@ -134,7 +134,7 @@ func TestProvisionerProvision_PlaybookFiles(t *testing.T) { } comm := &communicatorMock{} - if err := p.Provision(context.Background(), new(packer.NoopUi), comm); err != nil { + if err := p.Provision(context.Background(), new(packer.NoopUi), comm, make(map[string]interface{})); err != nil { t.Fatalf("err: %s", err) } @@ -168,7 +168,7 @@ func TestProvisionerProvision_PlaybookFilesWithPlaybookDir(t *testing.T) { } comm := &communicatorMock{} - if err := p.Provision(context.Background(), new(packer.NoopUi), comm); err != nil { + if err := p.Provision(context.Background(), new(packer.NoopUi), comm, make(map[string]interface{})); err != nil { t.Fatalf("err: %s", err) } @@ -343,7 +343,7 @@ func testProvisionerProvisionDockerWithPlaybookFiles(t *testing.T, templateStrin // Setup the builder builder := &docker.Builder{} - warnings, err := builder.Prepare(tpl.Builders["docker"].Config) + _, warnings, err := builder.Prepare(tpl.Builders["docker"].Config) if err != nil { t.Fatalf("Error preparing configuration %s", err) } diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 6de5bba5f..699790940 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -28,9 +28,9 @@ import ( "golang.org/x/crypto/ssh" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/adapter" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer/tmp" @@ -75,21 +75,14 @@ type Provisioner struct { done chan struct{} ansibleVersion string ansibleMajVersion uint + generatedData map[string]interface{} } -type PassthroughTemplate struct { - WinRMPassword string -} +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } func (p *Provisioner) Prepare(raws ...interface{}) error { p.done = make(chan struct{}) - // Create passthrough for winrm password so we can fill it in once we know - // it - p.config.ctx.Data = &PassthroughTemplate{ - WinRMPassword: `{{.WinRMPassword}}`, - } - err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, @@ -214,12 +207,11 @@ func (p *Provisioner) getVersion() error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error { ui.Say("Provisioning with Ansible...") - // Interpolate env vars to check for .WinRMPassword - p.config.ctx.Data = &PassthroughTemplate{ - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } + // Interpolate env vars to check for generated values like password and port + p.generatedData = generatedData + p.config.ctx.Data = generatedData for i, envVar := range p.config.AnsibleEnvVars { envVar, err := interpolate.Render(envVar, &p.config.ctx) if err != nil { @@ -227,7 +219,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C } p.config.AnsibleEnvVars[i] = envVar } - // Interpolate extra vars to check for .WinRMPassword + // Interpolate extra vars to check for generated values like password and port for i, arg := range p.config.ExtraArguments { arg, err := interpolate.Render(arg, &p.config.ctx) if err != nil { @@ -502,9 +494,10 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, pri // remove winrm password from command, if it's been added flattenedCmd := strings.Join(cmd.Args, " ") sanitized := flattenedCmd - if len(getWinRMPassword(p.config.PackerBuildName)) > 0 { + winRMPass, ok := p.generatedData["WinRMPassword"] + if ok && winRMPass != "" { sanitized = strings.Replace(sanitized, - getWinRMPassword(p.config.PackerBuildName), "*****", -1) + winRMPass.(string), "*****", -1) } ui.Say(fmt.Sprintf("Executing Ansible: %s", sanitized)) @@ -633,9 +626,3 @@ func newSigner(privKeyFile string) (*signer, error) { return signer, nil } - -func getWinRMPassword(buildName string) string { - winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) - packer.LogSecretFilter.Set(winRMPass) - return winRMPass -} diff --git a/provisioner/ansible/provisioner.hcl2spec.go b/provisioner/ansible/provisioner.hcl2spec.go index 1e659cece..dbee849a1 100644 --- a/provisioner/ansible/provisioner.hcl2spec.go +++ b/provisioner/ansible/provisioner.hcl2spec.go @@ -41,10 +41,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/ansible/provisioner_test.go b/provisioner/ansible/provisioner_test.go index 27fa27dc7..39035d4ed 100644 --- a/provisioner/ansible/provisioner_test.go +++ b/provisioner/ansible/provisioner_test.go @@ -349,7 +349,7 @@ func TestAnsibleLongMessages(t *testing.T) { Writer: new(bytes.Buffer), } - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatalf("err: %s", err) } diff --git a/provisioner/breakpoint/provisioner.go b/provisioner/breakpoint/provisioner.go index 8ff752065..fa96e544b 100644 --- a/provisioner/breakpoint/provisioner.go +++ b/provisioner/breakpoint/provisioner.go @@ -8,6 +8,7 @@ import ( "golang.org/x/sync/errgroup" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -27,6 +28,8 @@ type Provisioner struct { config Config } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -42,7 +45,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { if p.config.Disable { if p.config.Note != "" { ui.Say(fmt.Sprintf( diff --git a/provisioner/breakpoint/provisioner.hcl2spec.go b/provisioner/breakpoint/provisioner.hcl2spec.go index d22e893ae..d56a14362 100644 --- a/provisioner/breakpoint/provisioner.hcl2spec.go +++ b/provisioner/breakpoint/provisioner.hcl2spec.go @@ -23,10 +23,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 6ca75e402..cd0a32ff0 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -15,9 +15,9 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/uuid" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/provisioner" @@ -34,13 +34,13 @@ type guestOSTypeConfig struct { var guestOSTypeConfigs = map[string]guestOSTypeConfig{ provisioner.UnixOSType: { executeCommand: "{{if .Sudo}}sudo {{end}}chef-client --no-color -c {{.ConfigPath}} -j {{.JsonPath}}", - installCommand: "curl -L https://omnitruck.chef.io/install.sh | {{if .Sudo}}sudo {{end}}bash", + installCommand: "curl -L https://omnitruck.chef.io/install.sh | {{if .Sudo}}sudo {{end}}bash -s --{{if .Version}} -v {{.Version}}{{end}}", knifeCommand: "{{if .Sudo}}sudo {{end}}knife {{.Args}} {{.Flags}}", stagingDir: "/tmp/packer-chef-client", }, provisioner.WindowsOSType: { executeCommand: "c:/opscode/chef/bin/chef-client.bat --no-color -c {{.ConfigPath}} -j {{.JsonPath}}", - installCommand: "powershell.exe -Command \". { iwr -useb https://omnitruck.chef.io/install.ps1 } | iex; install\"", + installCommand: "powershell.exe -Command \". { iwr -useb https://omnitruck.chef.io/install.ps1 } | iex; Install-Project{{if .Version}} -version {{.Version}}{{end}}\"", knifeCommand: "c:/opscode/chef/bin/knife.bat {{.Args}} {{.Flags}}", stagingDir: "C:/Windows/Temp/packer-chef-client", }, @@ -77,6 +77,7 @@ type Config struct { StagingDir string `mapstructure:"staging_directory"` ValidationClientName string `mapstructure:"validation_client_name"` ValidationKeyPath string `mapstructure:"validation_key_path"` + Version string `mapstructure:"version"` ctx interpolate.Context } @@ -86,6 +87,7 @@ type Provisioner struct { communicator packer.Communicator guestOSTypeConfig guestOSTypeConfig guestCommands *provisioner.GuestCommands + generatedData map[string]interface{} } type ConfigTemplate struct { @@ -103,10 +105,6 @@ type ConfigTemplate struct { ValidationKeyPath string } -type EnvVarsTemplate struct { - WinRMPassword string -} - type ExecuteTemplate struct { ConfigPath string JsonPath string @@ -114,7 +112,8 @@ type ExecuteTemplate struct { } type InstallChefTemplate struct { - Sudo bool + Sudo bool + Version string } type KnifeTemplate struct { @@ -123,13 +122,9 @@ type KnifeTemplate struct { Args string } -func (p *Provisioner) Prepare(raws ...interface{}) error { - // Create passthrough for winrm password so we can fill it in once we know - // it - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: `{{.WinRMPassword}}`, - } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } +func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, @@ -244,8 +239,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { - +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error { + p.generatedData = generatedData p.communicator = comm nodeName := p.config.NodeName @@ -256,7 +251,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C serverUrl := p.config.ServerUrl if !p.config.SkipInstall { - if err := p.installChef(ui, comm); err != nil { + if err := p.installChef(ui, comm, p.config.Version); err != nil { return fmt.Errorf("Error installing Chef: %s", err) } } @@ -604,12 +599,13 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config return nil } -func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator, version string) error { ui.Message("Installing Chef...") ctx := context.TODO() p.config.ctx.Data = &InstallChefTemplate{ - Sudo: !p.config.PreventSudo, + Sudo: !p.config.PreventSudo, + Version: version, } command, err := interpolate.Render(p.config.InstallCommand, &p.config.ctx) if err != nil { @@ -713,12 +709,6 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) { return result, nil } -func getWinRMPassword(buildName string) string { - winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) - packer.LogSecretFilter.Set(winRMPass) - return winRMPass -} - func (p *Provisioner) Communicator() packer.Communicator { return p.communicator } @@ -729,9 +719,7 @@ func (p *Provisioner) ElevatedUser() string { func (p *Provisioner) ElevatedPassword() string { // Replace ElevatedPassword for winrm users who used this feature - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } + p.config.ctx.Data = p.generatedData elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) diff --git a/provisioner/chef-client/provisioner.hcl2spec.go b/provisioner/chef-client/provisioner.hcl2spec.go index fcc0dac58..5fb6a4716 100644 --- a/provisioner/chef-client/provisioner.hcl2spec.go +++ b/provisioner/chef-client/provisioner.hcl2spec.go @@ -43,15 +43,19 @@ type FlatConfig struct { StagingDir *string `mapstructure:"staging_directory" cty:"staging_directory"` ValidationClientName *string `mapstructure:"validation_client_name" cty:"validation_client_name"` ValidationKeyPath *string `mapstructure:"validation_key_path" cty:"validation_key_path"` + Version *string `mapstructure:"version" cty:"version"` } // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -88,6 +92,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "staging_directory": &hcldec.AttrSpec{Name: "staging_directory", Type: cty.String, Required: false}, "validation_client_name": &hcldec.AttrSpec{Name: "validation_client_name", Type: cty.String, Required: false}, "validation_key_path": &hcldec.AttrSpec{Name: "validation_key_path", Type: cty.String, Required: false}, + "version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false}, } return s } diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 39d59b36b..feb0a9505 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -15,6 +15,7 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -101,6 +102,8 @@ type InstallChefTemplate struct { Version string } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -238,7 +241,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { ui.Say("Provisioning with chef-solo") if !p.config.SkipInstall { diff --git a/provisioner/chef-solo/provisioner.hcl2spec.go b/provisioner/chef-solo/provisioner.hcl2spec.go index df801aa29..86f602b64 100644 --- a/provisioner/chef-solo/provisioner.hcl2spec.go +++ b/provisioner/chef-solo/provisioner.hcl2spec.go @@ -39,10 +39,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index c0c5cbeb4..3cfff2540 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -15,6 +15,7 @@ import ( "encoding/json" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -56,7 +57,8 @@ type Provisioner struct { config Config } -// Prepare provisioner somehow. TODO: actual docs +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode( &p.config, @@ -108,7 +110,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } // Provision node somehow. TODO: actual docs -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { ui.Say("Provisioning with Converge") // bootstrapping diff --git a/provisioner/converge/provisioner.hcl2spec.go b/provisioner/converge/provisioner.hcl2spec.go index 1594f8f14..1d35a429f 100644 --- a/provisioner/converge/provisioner.hcl2spec.go +++ b/provisioner/converge/provisioner.hcl2spec.go @@ -31,10 +31,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, @@ -48,7 +51,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false}, "bootstrap_command": &hcldec.AttrSpec{Name: "bootstrap_command", Type: cty.String, Required: false}, "prevent_bootstrap_sudo": &hcldec.AttrSpec{Name: "prevent_bootstrap_sudo", Type: cty.Bool, Required: false}, - "module_dirs": &hcldec.BlockListSpec{TypeName: "module_dirs", Nested: &hcldec.BlockSpec{TypeName: "module_dirs", Nested: hcldec.ObjectSpec((*FlatModuleDir)(nil).HCL2Spec())}}, + "module_dirs": &hcldec.BlockListSpec{TypeName: "module_dirs", Nested: hcldec.ObjectSpec((*FlatModuleDir)(nil).HCL2Spec())}, "module": &hcldec.AttrSpec{Name: "module", Type: cty.String, Required: false}, "working_directory": &hcldec.AttrSpec{Name: "working_directory", Type: cty.String, Required: false}, "params": &hcldec.BlockAttrsSpec{TypeName: "params", ElementType: cty.String, Required: false}, @@ -69,10 +72,13 @@ type FlatModuleDir struct { // FlatMapstructure returns a new FlatModuleDir. // FlatModuleDir is an auto-generated flat version of ModuleDir. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*ModuleDir) FlatMapstructure() interface{} { return new(FlatModuleDir) } +func (*ModuleDir) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatModuleDir) +} -// HCL2Spec returns the hcldec.Spec of a FlatModuleDir. -// This spec is used by HCL to read the fields of FlatModuleDir. +// HCL2Spec returns the hcl spec of a ModuleDir. +// This spec is used by HCL to read the fields of ModuleDir. +// The decoded values from this spec will then be applied to a FlatModuleDir. func (*FlatModuleDir) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false}, diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index 9fc73b950..7adf0197b 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -40,6 +41,8 @@ type Provisioner struct { config Config } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -92,7 +95,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { if p.config.Direction == "download" { return p.ProvisionDownload(ui, comm) } else { diff --git a/provisioner/file/provisioner.hcl2spec.go b/provisioner/file/provisioner.hcl2spec.go index 7a7c90d78..85a536403 100644 --- a/provisioner/file/provisioner.hcl2spec.go +++ b/provisioner/file/provisioner.hcl2spec.go @@ -26,10 +26,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/file/provisioner_test.go b/provisioner/file/provisioner_test.go index 46bccca6d..d27dfaaa7 100644 --- a/provisioner/file/provisioner_test.go +++ b/provisioner/file/provisioner_test.go @@ -127,7 +127,7 @@ func TestProvisionerProvision_SendsFile(t *testing.T) { Writer: b, } comm := &packer.MockCommunicator{} - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatalf("should successfully provision: %s", err) } diff --git a/provisioner/inspec/provisioner.go b/provisioner/inspec/provisioner.go index 0b7d047aa..50a2609ca 100644 --- a/provisioner/inspec/provisioner.go +++ b/provisioner/inspec/provisioner.go @@ -27,6 +27,7 @@ import ( "golang.org/x/crypto/ssh" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/adapter" "github.com/hashicorp/packer/helper/config" @@ -68,6 +69,8 @@ type Provisioner struct { inspecMajVersion uint } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { p.done = make(chan struct{}) @@ -186,7 +189,7 @@ func (p *Provisioner) getVersion() error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { ui.Say("Provisioning with Inspec...") for i, envVar := range p.config.InspecEnvVars { diff --git a/provisioner/inspec/provisioner.hcl2spec.go b/provisioner/inspec/provisioner.hcl2spec.go index cc6411db5..8afc07f0f 100644 --- a/provisioner/inspec/provisioner.hcl2spec.go +++ b/provisioner/inspec/provisioner.hcl2spec.go @@ -34,10 +34,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 5ff6cbe93..679f99c13 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -16,11 +16,11 @@ import ( "strings" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/common/shell" "github.com/hashicorp/packer/common/uuid" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer/tmp" @@ -73,18 +73,9 @@ type Config struct { } type Provisioner struct { - config Config - communicator packer.Communicator -} - -type ExecuteCommandTemplate struct { - Vars string - Path string - WinRMPassword string -} - -type EnvVarsTemplate struct { - WinRMPassword string + config Config + communicator packer.Communicator + generatedData map[string]interface{} } func (p *Provisioner) defaultExecuteCommand() string { @@ -97,14 +88,9 @@ func (p *Provisioner) defaultExecuteCommand() string { return fmt.Sprintf(`powershell -executionpolicy %s "%s"`, p.config.ExecutionPolicy, baseCmd) } } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } func (p *Provisioner) Prepare(raws ...interface{}) error { - // Create passthrough for winrm password so we can fill it in once we know - // it - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: `{{.WinRMPassword}}`, - } - err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, @@ -232,9 +218,10 @@ func extractScript(p *Provisioner) (string, error) { return temp.Name(), nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error { ui.Say(fmt.Sprintf("Provisioning with Powershell...")) p.communicator = comm + p.generatedData = generatedData scripts := make([]string, len(p.config.Scripts)) copy(scripts, p.config.Scripts) @@ -344,9 +331,8 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { } // interpolate environment variables - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } + p.config.ctx.Data = p.generatedData + // Split vars into key/value components for _, envVar := range p.config.Vars { envVar, err := interpolate.Render(envVar, &p.config.ctx) @@ -418,11 +404,11 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return "", err } - p.config.ctx.Data = &ExecuteCommandTemplate{ - Path: p.config.RemotePath, - Vars: p.config.RemoteEnvVarPath, - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } + ctxData := p.generatedData + ctxData["Path"] = p.config.RemotePath + ctxData["Vars"] = p.config.RemoteEnvVarPath + p.config.ctx.Data = ctxData + command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) if err != nil { @@ -433,12 +419,6 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return command, nil } -func getWinRMPassword(buildName string) string { - winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) - packer.LogSecretFilter.Set(winRMPass) - return winRMPass -} - func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { // Prepare everything needed to enable the required env vars within the // remote environment @@ -446,12 +426,11 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) if err != nil { return "", err } + ctxData := p.generatedData + ctxData["Path"] = p.config.RemotePath + ctxData["Vars"] = p.config.RemoteEnvVarPath + p.config.ctx.Data = ctxData - p.config.ctx.Data = &ExecuteCommandTemplate{ - Path: p.config.RemotePath, - Vars: p.config.RemoteEnvVarPath, - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } command, err = interpolate.Render(p.config.ElevatedExecuteCommand, &p.config.ctx) if err != nil { return "", fmt.Errorf("Error processing command: %s", err) @@ -475,10 +454,7 @@ func (p *Provisioner) ElevatedUser() string { func (p *Provisioner) ElevatedPassword() string { // Replace ElevatedPassword for winrm users who used this feature - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } - + p.config.ctx.Data = p.generatedData elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) return elevatedPassword diff --git a/provisioner/powershell/provisioner.hcl2spec.go b/provisioner/powershell/provisioner.hcl2spec.go index e1c543f62..81db6dc4f 100644 --- a/provisioner/powershell/provisioner.hcl2spec.go +++ b/provisioner/powershell/provisioner.hcl2spec.go @@ -37,10 +37,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index cef4611d1..152905bb8 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -355,7 +355,7 @@ func TestProvisionerProvision_ValidExitCodes(t *testing.T) { comm := new(packer.MockCommunicator) comm.StartExitStatus = 200 p.Prepare(config) - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -378,7 +378,7 @@ func TestProvisionerProvision_InvalidExitCodes(t *testing.T) { comm := new(packer.MockCommunicator) comm.StartExitStatus = 201 // Invalid! p.Prepare(config) - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err == nil { t.Fatal("should have error") } @@ -399,7 +399,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { p.config.PackerBuilderType = "iso" comm := new(packer.MockCommunicator) p.Prepare(config) - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -419,7 +419,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" p.Prepare(config) - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -448,7 +448,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { p := new(Provisioner) comm := new(packer.MockCommunicator) p.Prepare(config) - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -484,7 +484,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { p := new(Provisioner) comm := new(packer.MockCommunicator) p.Prepare(config) - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -511,7 +511,7 @@ func TestProvisionerProvision_UploadFails(t *testing.T) { comm := new(packer.ScriptUploadErrorMockCommunicator) p.Prepare(config) p.config.StartRetryTimeout = 1 * time.Second - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if !strings.Contains(err.Error(), packer.ScriptUploadErrorMockCommunicatorError.Error()) { t.Fatalf("expected Provision() error %q to contain %q", err.Error(), @@ -621,6 +621,7 @@ func TestProvision_createCommandText(t *testing.T) { p.config.PackerBuilderType = "iso" // Non-elevated + p.generatedData = make(map[string]interface{}) cmd, _ := p.createCommandText() re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1'; exit \$LastExitCode }"`) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index d66e54baa..4e62e842d 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -12,8 +12,8 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/provisioner" @@ -130,6 +130,7 @@ type Provisioner struct { communicator packer.Communicator guestOSTypeConfig guestOSTypeConfig guestCommands *provisioner.GuestCommands + generatedData map[string]interface{} } type ExecuteTemplate struct { @@ -150,13 +151,9 @@ type EnvVarsTemplate struct { WinRMPassword string } -func (p *Provisioner) Prepare(raws ...interface{}) error { - // Create passthrough for winrm password so we can fill it in once we know - // it - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: `{{.WinRMPassword}}`, - } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } +func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, @@ -259,9 +256,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error { ui.Say("Provisioning with Puppet...") p.communicator = comm + p.generatedData = generatedData ui.Message("Creating Puppet staging directory...") if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { return fmt.Errorf("Error creating staging directory: %s", err) @@ -484,12 +482,6 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds return comm.UploadDir(dst, src, nil) } -func getWinRMPassword(buildName string) string { - winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) - packer.LogSecretFilter.Set(winRMPass) - return winRMPass -} - func (p *Provisioner) Communicator() packer.Communicator { return p.communicator } @@ -500,9 +492,7 @@ func (p *Provisioner) ElevatedUser() string { func (p *Provisioner) ElevatedPassword() string { // Replace ElevatedPassword for winrm users who used this feature - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } + p.config.ctx.Data = p.generatedData elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) diff --git a/provisioner/puppet-masterless/provisioner.hcl2spec.go b/provisioner/puppet-masterless/provisioner.hcl2spec.go index d0deb5920..07ce05c12 100644 --- a/provisioner/puppet-masterless/provisioner.hcl2spec.go +++ b/provisioner/puppet-masterless/provisioner.hcl2spec.go @@ -37,10 +37,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/puppet-masterless/provisioner_test.go b/provisioner/puppet-masterless/provisioner_test.go index f8a0ae782..582b8d311 100644 --- a/provisioner/puppet-masterless/provisioner_test.go +++ b/provisioner/puppet-masterless/provisioner_test.go @@ -494,7 +494,7 @@ func TestProvisionerProvision_extraArguments(t *testing.T) { t.Fatalf("err: %s", err) } - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatalf("err: %s", err) } @@ -514,7 +514,7 @@ func TestProvisionerProvision_extraArguments(t *testing.T) { t.Fatalf("err: %s", err) } - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatalf("err: %s", err) } diff --git a/provisioner/puppet-server/provisioner.go b/provisioner/puppet-server/provisioner.go index e4fc2adff..71826831b 100644 --- a/provisioner/puppet-server/provisioner.go +++ b/provisioner/puppet-server/provisioner.go @@ -11,8 +11,8 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" - commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/provisioner" @@ -125,6 +125,7 @@ type Provisioner struct { communicator packer.Communicator guestOSTypeConfig guestOSTypeConfig guestCommands *provisioner.GuestCommands + generatedData map[string]interface{} } type ExecuteTemplate struct { @@ -144,13 +145,9 @@ type EnvVarsTemplate struct { WinRMPassword string } -func (p *Provisioner) Prepare(raws ...interface{}) error { - // Create passthrough for winrm password so we can fill it in once we know - // it - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: `{{.WinRMPassword}}`, - } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } +func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, @@ -229,9 +226,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error { ui.Say("Provisioning with Puppet...") p.communicator = comm + p.generatedData = generatedData ui.Message("Creating Puppet staging directory...") if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { return fmt.Errorf("Error creating staging directory: %s", err) @@ -373,12 +371,6 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds return comm.UploadDir(dst, src, nil) } -func getWinRMPassword(buildName string) string { - winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) - packer.LogSecretFilter.Set(winRMPass) - return winRMPass -} - func (p *Provisioner) Communicator() packer.Communicator { return p.communicator } @@ -389,9 +381,7 @@ func (p *Provisioner) ElevatedUser() string { func (p *Provisioner) ElevatedPassword() string { // Replace ElevatedPassword for winrm users who used this feature - p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(p.config.PackerBuildName), - } + p.config.ctx.Data = p.generatedData elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) diff --git a/provisioner/puppet-server/provisioner.hcl2spec.go b/provisioner/puppet-server/provisioner.hcl2spec.go index 3c2eb2cde..5535856c9 100644 --- a/provisioner/puppet-server/provisioner.hcl2spec.go +++ b/provisioner/puppet-server/provisioner.hcl2spec.go @@ -37,10 +37,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/salt-masterless/provisioner.go b/provisioner/salt-masterless/provisioner.go index 5531d76b9..06983cd10 100644 --- a/provisioner/salt-masterless/provisioner.go +++ b/provisioner/salt-masterless/provisioner.go @@ -13,6 +13,7 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -108,6 +109,8 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{ }, } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -222,7 +225,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { var err error var src, dst string diff --git a/provisioner/salt-masterless/provisioner.hcl2spec.go b/provisioner/salt-masterless/provisioner.hcl2spec.go index 9d918990b..4bd24fd11 100644 --- a/provisioner/salt-masterless/provisioner.hcl2spec.go +++ b/provisioner/salt-masterless/provisioner.hcl2spec.go @@ -38,10 +38,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/shell-local/provisioner.go b/provisioner/shell-local/provisioner.go index f92f93c70..991c94cea 100644 --- a/provisioner/shell-local/provisioner.go +++ b/provisioner/shell-local/provisioner.go @@ -3,6 +3,7 @@ package shell import ( "context" + "github.com/hashicorp/hcl/v2/hcldec" sl "github.com/hashicorp/packer/common/shell-local" "github.com/hashicorp/packer/packer" ) @@ -11,6 +12,8 @@ type Provisioner struct { config sl.Config } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := sl.Decode(&p.config, raws...) if err != nil { @@ -25,8 +28,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, _ packer.Communicator) error { - _, retErr := sl.Run(ctx, ui, &p.config) +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, _ packer.Communicator, generatedData map[string]interface{}) error { + _, retErr := sl.Run(ctx, ui, &p.config, generatedData) return retErr } diff --git a/provisioner/shell/provisioner.go b/provisioner/shell/provisioner.go index 824225aee..a2285a6a6 100644 --- a/provisioner/shell/provisioner.go +++ b/provisioner/shell/provisioner.go @@ -17,6 +17,7 @@ import ( "strings" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/common/shell" @@ -75,6 +76,8 @@ type ExecuteCommandTemplate struct { Path string } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -179,7 +182,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { scripts := make([]string, len(p.config.Scripts)) copy(scripts, p.config.Scripts) diff --git a/provisioner/shell/provisioner.hcl2spec.go b/provisioner/shell/provisioner.hcl2spec.go index 9b48d3c3f..6e6d88a85 100644 --- a/provisioner/shell/provisioner.hcl2spec.go +++ b/provisioner/shell/provisioner.hcl2spec.go @@ -38,10 +38,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/sleep/provisioner.go b/provisioner/sleep/provisioner.go index 646f1c4dd..f3414c961 100644 --- a/provisioner/sleep/provisioner.go +++ b/provisioner/sleep/provisioner.go @@ -6,6 +6,7 @@ import ( "context" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" ) @@ -16,11 +17,15 @@ type Provisioner struct { var _ packer.Provisioner = new(Provisioner) +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.FlatMapstructure().HCL2Spec() } + +func (p *Provisioner) FlatConfig() interface{} { return p.FlatMapstructure() } + func (p *Provisioner) Prepare(raws ...interface{}) error { return config.Decode(&p, &config.DecodeOpts{}, raws...) } -func (p *Provisioner) Provision(ctx context.Context, _ packer.Ui, _ packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, _ packer.Ui, _ packer.Communicator, _ map[string]interface{}) error { select { case <-ctx.Done(): return ctx.Err() diff --git a/provisioner/sleep/provisioner.hcl2spec.go b/provisioner/sleep/provisioner.hcl2spec.go index 60f3ed89d..fbdec1b02 100644 --- a/provisioner/sleep/provisioner.hcl2spec.go +++ b/provisioner/sleep/provisioner.hcl2spec.go @@ -15,10 +15,13 @@ type FlatProvisioner struct { // FlatMapstructure returns a new FlatProvisioner. // FlatProvisioner is an auto-generated flat version of Provisioner. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Provisioner) FlatMapstructure() interface{} { return new(FlatProvisioner) } +func (*Provisioner) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatProvisioner) +} -// HCL2Spec returns the hcldec.Spec of a FlatProvisioner. -// This spec is used by HCL to read the fields of FlatProvisioner. +// HCL2Spec returns the hcl spec of a Provisioner. +// This spec is used by HCL to read the fields of Provisioner. +// The decoded values from this spec will then be applied to a FlatProvisioner. func (*FlatProvisioner) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "duration": &hcldec.AttrSpec{Name: "duration", Type: cty.String, Required: false}, diff --git a/provisioner/sleep/provisioner_test.go b/provisioner/sleep/provisioner_test.go index 6ec26c59e..2ed66ff50 100644 --- a/provisioner/sleep/provisioner_test.go +++ b/provisioner/sleep/provisioner_test.go @@ -48,7 +48,7 @@ func TestProvisioner_Provision(t *testing.T) { p := &Provisioner{ Duration: tt.fields.Duration, } - if err := p.Provision(tt.args.ctx, nil, nil); (err != nil) != tt.wantErr { + if err := p.Provision(tt.args.ctx, nil, nil, make(map[string]interface{})); (err != nil) != tt.wantErr { t.Errorf("Provisioner.Provision() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/provisioner/windows-restart/provisioner.go b/provisioner/windows-restart/provisioner.go index f2bc01d30..f75e45ec4 100644 --- a/provisioner/windows-restart/provisioner.go +++ b/provisioner/windows-restart/provisioner.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/helper/config" @@ -63,6 +64,8 @@ type Provisioner struct { cancelLock sync.Mutex } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -96,7 +99,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { p.cancelLock.Lock() p.cancel = make(chan struct{}) p.cancelLock.Unlock() diff --git a/provisioner/windows-restart/provisioner.hcl2spec.go b/provisioner/windows-restart/provisioner.hcl2spec.go index eda8f486e..e2de58e75 100644 --- a/provisioner/windows-restart/provisioner.hcl2spec.go +++ b/provisioner/windows-restart/provisioner.hcl2spec.go @@ -26,10 +26,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/windows-restart/provisioner_test.go b/provisioner/windows-restart/provisioner_test.go index ffe9c48a2..391068457 100644 --- a/provisioner/windows-restart/provisioner_test.go +++ b/provisioner/windows-restart/provisioner_test.go @@ -104,7 +104,7 @@ func TestProvisionerProvision_Success(t *testing.T) { waitForRestart = func(context.Context, *Provisioner, packer.Communicator) error { return nil } - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -140,7 +140,7 @@ func TestProvisionerProvision_CustomCommand(t *testing.T) { waitForRestart = func(context.Context, *Provisioner, packer.Communicator) error { return nil } - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -163,7 +163,7 @@ func TestProvisionerProvision_RestartCommandFail(t *testing.T) { comm.StartExitStatus = 1 p.Prepare(config) - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err == nil { t.Fatal("should have error") } @@ -182,7 +182,7 @@ func TestProvisionerProvision_WaitForRestartFail(t *testing.T) { waitForCommunicator = func(context.Context, *Provisioner) error { return fmt.Errorf("Machine did not restart properly") } - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err == nil { t.Fatal("should have error") } @@ -216,7 +216,7 @@ func TestProvision_waitForRestartTimeout(t *testing.T) { } go func() { - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) waitDone <- true }() <-waitContinue @@ -327,7 +327,7 @@ func TestProvision_Cancel(t *testing.T) { // Create two go routines to provision and cancel in parallel // Provision will block until cancel happens go func() { - done <- p.Provision(topCtx, ui, comm) + done <- p.Provision(topCtx, ui, comm, make(map[string]interface{})) }() // Expect interrupt error diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index 2f42b145d..4a338b234 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/common/shell" @@ -51,6 +52,8 @@ type ExecuteCommandTemplate struct { Path string } +func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } + func (p *Provisioner) Prepare(raws ...interface{}) error { err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, @@ -156,7 +159,7 @@ func extractScript(p *Provisioner) (string, error) { return temp.Name(), nil } -func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator) error { +func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { ui.Say(fmt.Sprintf("Provisioning with windows-shell...")) scripts := make([]string, len(p.config.Scripts)) copy(scripts, p.config.Scripts) diff --git a/provisioner/windows-shell/provisioner.hcl2spec.go b/provisioner/windows-shell/provisioner.hcl2spec.go index ee62b6bf2..af5d7c5ec 100644 --- a/provisioner/windows-shell/provisioner.hcl2spec.go +++ b/provisioner/windows-shell/provisioner.hcl2spec.go @@ -31,10 +31,13 @@ type FlatConfig struct { // FlatMapstructure returns a new FlatConfig. // FlatConfig is an auto-generated flat version of Config. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) } +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} -// HCL2Spec returns the hcldec.Spec of a FlatConfig. -// This spec is used by HCL to read the fields of FlatConfig. +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, diff --git a/provisioner/windows-shell/provisioner_test.go b/provisioner/windows-shell/provisioner_test.go index 32ec528e1..4a5663bdb 100644 --- a/provisioner/windows-shell/provisioner_test.go +++ b/provisioner/windows-shell/provisioner_test.go @@ -292,7 +292,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { p.config.PackerBuilderType = "iso" comm := new(packer.MockCommunicator) p.Prepare(config) - err := p.Provision(context.Background(), ui, comm) + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -311,7 +311,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" p.Prepare(config) - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -342,7 +342,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { p := new(Provisioner) comm := new(packer.MockCommunicator) p.Prepare(config) - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } @@ -381,7 +381,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { p := new(Provisioner) comm := new(packer.MockCommunicator) p.Prepare(config) - err = p.Provision(context.Background(), ui, comm) + err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) if err != nil { t.Fatal("should not have error") } diff --git a/scripts/codesign_example.sh b/scripts/codesign_example.sh new file mode 100755 index 000000000..05290589c --- /dev/null +++ b/scripts/codesign_example.sh @@ -0,0 +1,150 @@ +#! /usr/bin/env bash +set -euo pipefail + +# first makes some assertions about the environment and set some shared +# variables before starting the script. +if ! command -v jq > /dev/null 2>&1; then + echo "This script requires jq to work properly." + exit 1 +fi + +PRODUCT_NAME="${PRODUCT_NAME:-""}" +if [ -z "$PRODUCT_NAME" ]; then + echo "Missing required product name: ${PRODUCT_NAME}" + exit 1 +fi + +TARGET_ZIP="${TARGET_ZIP:-""}" +if [ -z "$TARGET_ZIP" ]; then + echo "Missing required target path" + exit 1 +fi + +# Artifactory configuration +ARTIFACTORY_ENDPOINT="${ARTIFACTORY_ENDPOINT:-"https://artifactory.hashicorp.engineering/artifactory"}" +ARTIFACTORY_INPUT_REPO="${ARTIFACTORY_INPUT_REPO:-"hc-signing-input"}" +ARTIFACTORY_OUTPUT_REPO="${ARTIFACTORY_OUTPUT_REPO:-"hc-signing-output"}" + +ARTIFACTORY_TOKEN="${ARTIFACTORY_TOKEN:-""}" +ARTIFACTORY_USER="${ARTIFACTORY_USER:-""}" + +if [[ -z "$ARTIFACTORY_TOKEN" || -z "$ARTIFACTORY_USER" ]]; then + echo "Missing required Artifactory credentials" + exit 1 +fi + +# Create the sign/notarize ID "SN_ID" +if command -v uuidgen > /dev/null 2>&1; then + uuid="$(uuidgen)" +elif [ -f /proc/sys/kernel/random/uuid ]; then + uuid="$(cat /proc/sys/kernel/random/uuid)" +else + echo "This script needs some way to generate a uuid." + exit 1 +fi +SN_ID="$uuid" + +# CircleCI configuration +CIRCLE_ENDPOINT="${CIRCLE_ENDPOINT:-"https://circleci.com/api/v2"}" +CIRCLE_PROJECT="${CIRCLE_PROJECT:-"project/github/hashicorp/circle-codesign"}" + +CIRCLE_TOKEN="${CIRCLE_TOKEN:-""}" +if [ -z "$CIRCLE_TOKEN" ]; then + echo "Missing required CircleCI credentials" + exit 1 +fi + +# Next, upload an unsigned zip file to the Artifactory at +# https://artifactory.hashicorp.engineering/artifactory/hc-signing-input/{PRODUCT}/{ID}.zip +echo "Uploading unsigned zip to ${ARTIFACTORY_ENDPOINT}/${ARTIFACTORY_INPUT_REPO}/${PRODUCT_NAME}/${SN_ID}.zip" + +curl --show-error --silent --fail \ + --user "${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}" \ + --request PUT \ + "${ARTIFACTORY_ENDPOINT}/${ARTIFACTORY_INPUT_REPO}/${PRODUCT_NAME}/${SN_ID}.zip" \ + --upload-file "$TARGET_ZIP" > /dev/null + +# Next, start the CircleCI Pipeline, then wait for a Workflow +# to start. +echo "Executing CircleCI job" + +res="$(curl --show-error --silent --fail --user "${CIRCLE_TOKEN}:" \ + --request POST \ + --header 'Content-Type: application/json' \ + --header 'Accept: application/json' \ + --data "{ \"branch\": \"master\" ,\"parameters\": { \"PRODUCT\": \"${PRODUCT_NAME}\", \"PKG_NAME\": \"${SN_ID}.zip\" } }" \ + "${CIRCLE_ENDPOINT}/${CIRCLE_PROJECT}/pipeline")" +pipeline_id="$(echo "$res" | jq -r '.id')" +echo "CircleCI Pipeline $pipeline_id started" + +echo -n "Retrieving CircleCI Workflow ID" +# 24 * 5 seconds = 2 minutes +counter=12 +workflow_id="" +# wait until a Workflow ID is found +until [ "$workflow_id" != "" ]; do + echo -n "." + workflow_id=$(curl --silent --fail --user "${CIRCLE_TOKEN}:" \ + --request GET \ + --header 'Accept: application/json' \ + "${CIRCLE_ENDPOINT}/pipeline/${pipeline_id}/workflow" \ + | jq -r '.items[].id' + ) + if [ "$counter" -eq "0" ]; then + echo "Tried too many times, but Pipeline ${pipeline_id} still has no Workflows" + exit 1 + fi + counter=$((counter - 1)) + sleep 5 +done +echo "" + +echo "CircleCI Workflow $workflow_id started" + +# Next, wait for the Workflow to reach a terminal state, then fails if it isn't +# "success" +echo -n "Waiting for CircleCI Workflow ID: ${workflow_id}" +# 360 * 5 seconds = 30 minutes +counter=360 +finished="not_run" +# wait for one of the terminal states: ["success", "failed", "error", "canceled"] +until [[ "$finished" == "success" || "$finished" == "failed" || "$finished" == "error" || "$finished" == "canceled" ]]; do + echo -n "." + finished=$(curl --silent --fail --user "${CIRCLE_TOKEN}:" \ + --header 'Accept: application/json' \ + "${CIRCLE_ENDPOINT}/workflow/${workflow_id}" \ + | jq -r '.status' + ) + if [ "$counter" -eq "0" ]; then + echo "Tried too many times, but workflow is still in state ${finished}" + exit 1 + fi + counter=$((counter - 1)) + sleep 5 +done +echo "" + +if [ "$finished" != "success" ]; then + echo "Workflow ID ${workflow_id} ${finished}" + exit 1 +fi + +# Next, download the signed zip from Artifactory at +# https://artifactory.hashicorp.engineering/artifactory/hc-signing-output/{PRODUCT}/{ID}.zip +echo "Retrieving signed zip from ${ARTIFACTORY_ENDPOINT}/${ARTIFACTORY_OUTPUT_REPO}/${PRODUCT_NAME}/${SN_ID}.zip" + +curl --show-error --silent --fail --user "${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}" \ + --request GET \ + "${ARTIFACTORY_ENDPOINT}/${ARTIFACTORY_OUTPUT_REPO}/${PRODUCT_NAME}/${SN_ID}.zip" \ + --output "signed_${SN_ID}.zip" + +signed_checksum=$( + curl --silent --show-error --fail --user "${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}" \ + --head \ + "${ARTIFACTORY_ENDPOINT}/${ARTIFACTORY_OUTPUT_REPO}/${PRODUCT_NAME}/${SN_ID}.zip" \ + | grep -i "x-checksum-sha256" | awk 'gsub("[\r\n]", "", $2) {print $2;}' +) + +echo "${signed_checksum} signed_${SN_ID}.zip" | gsha256sum -c + +mv "signed_${SN_ID}.zip" "$TARGET_ZIP" \ No newline at end of file diff --git a/scripts/dist.sh b/scripts/dist.sh index 5386c95e3..7c3a25d0d 100755 --- a/scripts/dist.sh +++ b/scripts/dist.sh @@ -39,6 +39,8 @@ for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do popd >/dev/null 2>&1 done +./scripts/sign.sh + if [ -z $NOSIGN ]; then echo "==> Signing..." pushd ./pkg/dist diff --git a/scripts/prepare_changelog.sh b/scripts/prepare_changelog.sh index 5834c639f..74c8382e6 100755 --- a/scripts/prepare_changelog.sh +++ b/scripts/prepare_changelog.sh @@ -30,7 +30,7 @@ fi get_prs(){ # git log --merges v0.10.2...c3861d167533fb797b0fae0c380806625712e5f7 | git log --merges HEAD...${LAST_RELEASE} | - grep -o "Merge pull request #\(\d\+\)" | awk -F\# '{print $2}' | while read line + grep -o "Merge pull request #\([0-9]\+\)" | awk -F\# '{print $2}' | while read line do grep -q "GH-${line}" CHANGELOG.md if [ $? -ne 0 ]; then diff --git a/scripts/sign.sh b/scripts/sign.sh new file mode 100755 index 000000000..850aa5d0e --- /dev/null +++ b/scripts/sign.sh @@ -0,0 +1,31 @@ + +#!/usr/bin/env bash + +# This script uploads the Darwin builds to artifactory, then triggers the +# circle ci job that signs them. + +# ARTIFACTORY_USER="sa-circle-codesign" +# export PRODUCT_NAME="packer" +# export ARTIFACTORY_TOKEN=$ARTIFACTORY_TOKEN + +# Get the parent directory of where this script is. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done +DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" +# Change into that dir because we expect that +cd $DIR + +BIN_UUIDS=() +BUILD_NUMBERS=() +for DARWIN_BIN in $(find ./pkg/dist/*darwin_*.zip); do + echo "signing $DARWIN_BIN" + export ARTIFACTORY_USER="sa-circle-codesign" + export PRODUCT_NAME="packer" + export ARTIFACTORY_TOKEN=$ARTIFACTORY_TOKEN + export TARGET_ZIP=$DARWIN_BIN + + echo $TARGET_ZIP + ./scripts/codesign_example.sh +done + +exit 0 \ No newline at end of file diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index 609d35f4a..9dcd5061d 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -12,6 +12,7 @@ import ( consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/packer/common/uuid" + "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/version" vaultapi "github.com/hashicorp/vault/api" strftime "github.com/jehiah/go-strftime" @@ -43,6 +44,7 @@ var FuncGens = map[string]interface{}{ "consul_key": funcGenConsul, "vault": funcGenVault, "sed": funcGenSed, + "build": funcGenBuild, "replace": replace, "replace_all": replace_all, @@ -162,6 +164,44 @@ func funcGenTemplateDir(ctx *Context) interface{} { } } +func funcGenBuild(ctx *Context) interface{} { + return func(s string) (string, error) { + if data, ok := ctx.Data.(map[string]string); ok { + if heldPlace, ok := data[s]; ok { + // If we're in the first interpolation pass, the goal is to + // make sure that we pass the value through. + // TODO match against an actual string constant + if strings.Contains(heldPlace, common.PlaceholderMsg) { + return fmt.Sprintf("{{.%s}}", s), nil + } else { + return heldPlace, nil + } + } + return "", fmt.Errorf("loaded data, but couldnt find %s in it.", s) + } + if data, ok := ctx.Data.(map[interface{}]interface{}); ok { + // PlaceholderData has been passed into generator, so if the given + // key already exists in data, then we know it's an "allowed" key + if heldPlace, ok := data[s]; ok { + if hp, ok := heldPlace.(string); ok { + // If we're in the first interpolation pass, the goal is to + // make sure that we pass the value through. + // TODO match against an actual string constant + if strings.Contains(hp, common.PlaceholderMsg) { + return fmt.Sprintf("{{.%s}}", s), nil + } else { + return hp, nil + } + } + } + return "", fmt.Errorf("loaded data, but couldnt find %s in it.", s) + } + + return "", fmt.Errorf("Error validating build variable: the given "+ + "variable %s will not be passed into your plugin.", s) + } +} + func funcGenTimestamp(ctx *Context) interface{} { return func() string { return strconv.FormatInt(InitTime.Unix(), 10) diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go index f02e5678e..b9a586641 100644 --- a/template/interpolate/funcs_test.go +++ b/template/interpolate/funcs_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/version" ) @@ -318,6 +319,90 @@ func TestFuncUser(t *testing.T) { } } +func TestFuncPackerBuild(t *testing.T) { + type cases struct { + DataMap interface{} + ErrExpected bool + Template string + OutVal string + } + + testCases := []cases{ + // Data map is empty; there should be an error. + { + DataMap: nil, + ErrExpected: true, + Template: "{{ build `PartyVar` }}", + OutVal: "", + }, + // Data map is a map[string]string and contains value + { + DataMap: map[string]string{"PartyVar": "PartyVal"}, + ErrExpected: false, + Template: "{{ build `PartyVar` }}", + OutVal: "PartyVal", + }, + // Data map is a map[string]string and contains value + { + DataMap: map[string]string{"PartyVar": "PartyVal"}, + ErrExpected: false, + Template: "{{ build `PartyVar` }}", + OutVal: "PartyVal", + }, + // Data map is a map[string]string and contains value with placeholder. + { + DataMap: map[string]string{"PartyVar": "PartyVal" + common.PlaceholderMsg}, + ErrExpected: false, + Template: "{{ build `PartyVar` }}", + OutVal: "{{.PartyVar}}", + }, + // Data map is a map[interface{}]interface{} and contains value + { + DataMap: map[interface{}]interface{}{"PartyVar": "PartyVal"}, + ErrExpected: false, + Template: "{{ build `PartyVar` }}", + OutVal: "PartyVal", + }, + // Data map is a map[interface{}]interface{} and contains value + { + DataMap: map[interface{}]interface{}{"PartyVar": "PartyVal"}, + ErrExpected: false, + Template: "{{ build `PartyVar` }}", + OutVal: "PartyVal", + }, + // Data map is a map[interface{}]interface{} and contains value with placeholder. + { + DataMap: map[interface{}]interface{}{"PartyVar": "PartyVal" + common.PlaceholderMsg}, + ErrExpected: false, + Template: "{{ build `PartyVar` }}", + OutVal: "{{.PartyVar}}", + }, + // Data map is a map[interface{}]interface{} and doesn't have value. + { + DataMap: map[interface{}]interface{}{"BadVar": "PartyVal" + common.PlaceholderMsg}, + ErrExpected: true, + Template: "{{ build `MissingVar` }}", + OutVal: "", + }, + } + + for _, tc := range testCases { + ctx := &Context{} + ctx.Data = tc.DataMap + i := &I{Value: tc.Template} + + result, err := i.Render(ctx) + if (err != nil) != tc.ErrExpected { + t.Fatalf("Input: %s\n\nerr: %s", tc.Template, err) + } + + if ok := strings.Compare(result, tc.OutVal); ok != 0 { + t.Fatalf("Expected input to include: %s\n\nGot: %s", + tc.OutVal, result) + } + } +} + func TestFuncPackerVersion(t *testing.T) { template := `{{packer_version}}` diff --git a/template/template.hcl2spec.go b/template/template.hcl2spec.go index 988b6fc79..c5b7d11a5 100644 --- a/template/template.hcl2spec.go +++ b/template/template.hcl2spec.go @@ -21,10 +21,13 @@ type FlatProvisioner struct { // FlatMapstructure returns a new FlatProvisioner. // FlatProvisioner is an auto-generated flat version of Provisioner. // Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. -func (*Provisioner) FlatMapstructure() interface{} { return new(FlatProvisioner) } +func (*Provisioner) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatProvisioner) +} -// HCL2Spec returns the hcldec.Spec of a FlatProvisioner. -// This spec is used by HCL to read the fields of FlatProvisioner. +// HCL2Spec returns the hcl spec of a Provisioner. +// This spec is used by HCL to read the fields of Provisioner. +// The decoded values from this spec will then be applied to a FlatProvisioner. func (*FlatProvisioner) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "only": &hcldec.AttrSpec{Name: "only", Type: cty.List(cty.String), Required: false}, diff --git a/vendor/github.com/zclconf/go-cty/cty/convert/conversion_primitive.go b/vendor/github.com/zclconf/go-cty/cty/convert/conversion_primitive.go index e0dbf491e..0d6fae964 100644 --- a/vendor/github.com/zclconf/go-cty/cty/convert/conversion_primitive.go +++ b/vendor/github.com/zclconf/go-cty/cty/convert/conversion_primitive.go @@ -1,6 +1,8 @@ package convert import ( + "strings" + "github.com/zclconf/go-cty/cty" ) @@ -41,7 +43,14 @@ var primitiveConversionsUnsafe = map[cty.Type]map[cty.Type]conversion{ case "false", "0": return cty.False, nil default: - return cty.NilVal, path.NewErrorf("a bool is required") + switch strings.ToLower(val.AsString()) { + case "true": + return cty.NilVal, path.NewErrorf("a bool is required; to convert from string, use lowercase \"true\"") + case "false": + return cty.NilVal, path.NewErrorf("a bool is required; to convert from string, use lowercase \"false\"") + default: + return cty.NilVal, path.NewErrorf("a bool is required") + } } }, }, diff --git a/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go index 664790b46..834e9b6fc 100644 --- a/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go +++ b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go @@ -80,6 +80,7 @@ var FormatListFunc = function.New(&function.Spec{ lenChooser := -1 iterators := make([]cty.ElementIterator, len(args)) singleVals := make([]cty.Value, len(args)) + unknowns := make([]bool, len(args)) for i, arg := range args { argTy := arg.Type() switch { @@ -87,7 +88,8 @@ var FormatListFunc = function.New(&function.Spec{ if !argTy.IsTupleType() && !arg.IsKnown() { // We can't iterate this one at all yet then, so we can't // yet produce a result. - return cty.UnknownVal(retType), nil + unknowns[i] = true + continue } thisLen := arg.LengthInt() if iterLen == -1 { @@ -103,12 +105,26 @@ var FormatListFunc = function.New(&function.Spec{ ) } } + if !arg.IsKnown() { + // We allowed an unknown tuple value to fall through in + // our initial check above so that we'd be able to run + // the above error checks against it, but we still can't + // iterate it if the checks pass. + unknowns[i] = true + continue + } iterators[i] = arg.ElementIterator() default: singleVals[i] = arg } } + for _, isUnk := range unknowns { + if isUnk { + return cty.UnknownVal(retType), nil + } + } + if iterLen == 0 { // If our sequences are all empty then our result must be empty. return cty.ListValEmpty(cty.String), nil diff --git a/vendor/github.com/zclconf/go-cty/cty/gob.go b/vendor/github.com/zclconf/go-cty/cty/gob.go index a77dace27..6c972d7de 100644 --- a/vendor/github.com/zclconf/go-cty/cty/gob.go +++ b/vendor/github.com/zclconf/go-cty/cty/gob.go @@ -5,6 +5,8 @@ import ( "encoding/gob" "fmt" "math/big" + + "github.com/zclconf/go-cty/cty/set" ) // GobEncode is an implementation of the gob.GobEncoder interface, which @@ -46,11 +48,12 @@ func (val *Value) GobDecode(buf []byte) error { return fmt.Errorf("unsupported cty.Value encoding version %d; only 0 is supported", gv.Version) } - // big.Float seems to, for some reason, lose its "pointerness" when we - // round-trip it, so we'll fix that here. - if bf, ok := gv.V.(big.Float); ok { - gv.V = &bf - } + // Because big.Float.GobEncode is implemented with a pointer reciever, + // gob encoding of an interface{} containing a *big.Float value does not + // round-trip correctly, emerging instead as a non-pointer big.Float. + // The rest of cty expects all number values to be represented by + // *big.Float, so we'll fix that up here. + gv.V = gobDecodeFixNumberPtr(gv.V, gv.Ty) val.ty = gv.Ty val.v = gv.V @@ -123,3 +126,74 @@ type gobType struct { type gobCapsuleTypeImpl struct { } + +// goDecodeFixNumberPtr fixes an unfortunate quirk of round-tripping cty.Number +// values through gob: the big.Float.GobEncode method is implemented on a +// pointer receiver, and so it loses the "pointer-ness" of the value on +// encode, causing the values to emerge the other end as big.Float rather than +// *big.Float as we expect elsewhere in cty. +// +// The implementation of gobDecodeFixNumberPtr mutates the given raw value +// during its work, and may either return the same value mutated or a new +// value. Callers must no longer use whatever value they pass as "raw" after +// this function is called. +func gobDecodeFixNumberPtr(raw interface{}, ty Type) interface{} { + // Unfortunately we need to work recursively here because number values + // might be embedded in structural or collection type values. + + switch { + case ty.Equals(Number): + if bf, ok := raw.(big.Float); ok { + return &bf // wrap in pointer + } + case ty.IsMapType() && ty.ElementType().Equals(Number): + if m, ok := raw.(map[string]interface{}); ok { + for k, v := range m { + m[k] = gobDecodeFixNumberPtr(v, ty.ElementType()) + } + } + case ty.IsListType() && ty.ElementType().Equals(Number): + if s, ok := raw.([]interface{}); ok { + for i, v := range s { + s[i] = gobDecodeFixNumberPtr(v, ty.ElementType()) + } + } + case ty.IsSetType() && ty.ElementType().Equals(Number): + if s, ok := raw.(set.Set); ok { + newS := set.NewSet(s.Rules()) + for it := s.Iterator(); it.Next(); { + newV := gobDecodeFixNumberPtr(it.Value(), ty.ElementType()) + newS.Add(newV) + } + return newS + } + case ty.IsObjectType(): + if m, ok := raw.(map[string]interface{}); ok { + for k, v := range m { + aty := ty.AttributeType(k) + m[k] = gobDecodeFixNumberPtr(v, aty) + } + } + case ty.IsTupleType(): + if s, ok := raw.([]interface{}); ok { + for i, v := range s { + ety := ty.TupleElementType(i) + s[i] = gobDecodeFixNumberPtr(v, ety) + } + } + } + + return raw +} + +// gobDecodeFixNumberPtrVal is a helper wrapper around gobDecodeFixNumberPtr +// that works with already-constructed values. This is primarily for testing, +// to fix up intentionally-invalid number values for the parts of the test +// code that need them to be valid, such as calling GoString on them. +func gobDecodeFixNumberPtrVal(v Value) Value { + raw := gobDecodeFixNumberPtr(v.v, v.ty) + return Value{ + v: raw, + ty: v.ty, + } +} diff --git a/vendor/github.com/zclconf/go-cty/cty/set_internals.go b/vendor/github.com/zclconf/go-cty/cty/set_internals.go index 3fd4fb2df..f1ec98556 100644 --- a/vendor/github.com/zclconf/go-cty/cty/set_internals.go +++ b/vendor/github.com/zclconf/go-cty/cty/set_internals.go @@ -147,6 +147,17 @@ func appendSetHashBytes(val Value, buf *bytes.Buffer) { switch val.ty { case Number: + // Due to an unfortunate quirk of gob encoding for big.Float, we end up + // with non-pointer values immediately after a gob round-trip, and + // we end up in here before we've had a chance to run + // gobDecodeFixNumberPtr on the inner values of a gob-encoded set, + // and so sadly we must make a special effort to handle that situation + // here just so that we can get far enough along to fix it up for + // everything else in this package. + if bf, ok := val.v.(big.Float); ok { + buf.WriteString(bf.String()) + return + } buf.WriteString(val.v.(*big.Float).String()) return case Bool: diff --git a/vendor/modules.txt b/vendor/modules.txt index 632ccc2ba..fdb69eaed 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -618,7 +618,7 @@ github.com/yandex-cloud/go-sdk/pkg/retry github.com/yandex-cloud/go-sdk/pkg/sdkerrors github.com/yandex-cloud/go-sdk/pkg/singleflight github.com/yandex-cloud/go-sdk/sdkresolvers -# github.com/zclconf/go-cty v1.1.0 +# github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af github.com/zclconf/go-cty/cty github.com/zclconf/go-cty/cty/convert github.com/zclconf/go-cty/cty/function diff --git a/version/version.go b/version/version.go index 9755bac10..f83d80332 100644 --- a/version/version.go +++ b/version/version.go @@ -9,7 +9,7 @@ import ( var GitCommit string // The main version number that is being run at the moment. -const Version = "1.5.0" +const Version = "1.5.2" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/website/Gemfile.lock b/website/Gemfile.lock index 433998f46..efd35b165 100644 --- a/website/Gemfile.lock +++ b/website/Gemfile.lock @@ -112,7 +112,7 @@ GEM tilt (>= 1.4.1, < 3) padrino-support (0.12.9) activesupport (>= 3.1) - rack (1.6.11) + rack (1.6.12) rack-livereload (0.3.17) rack rack-test (1.1.0) diff --git a/website/config.rb b/website/config.rb index 04da1cad9..de2b97c4e 100644 --- a/website/config.rb +++ b/website/config.rb @@ -2,7 +2,7 @@ set :base_url, "https://www.packer.io/" activate :hashicorp do |h| h.name = "packer" - h.version = "1.4.5" + h.version = "1.5.1" h.github_slug = "hashicorp/packer" h.website_root = "website" end diff --git a/website/source/docs/builders/alicloud-ecs.html.md.erb b/website/source/docs/builders/alicloud-ecs.html.md.erb index 5c7b3ce58..7f067f87a 100644 --- a/website/source/docs/builders/alicloud-ecs.html.md.erb +++ b/website/source/docs/builders/alicloud-ecs.html.md.erb @@ -23,7 +23,6 @@ builder. ### Required: -<%= partial "partials/builder/alicloud/ecs/AlicloudAccessConfig-required" %> <%= partial "partials/builder/alicloud/ecs/RunConfig-required" %> <%= partial "partials/builder/alicloud/ecs/AlicloudImageConfig-required" %> diff --git a/website/source/docs/builders/amazon-chroot.html.md.erb b/website/source/docs/builders/amazon-chroot.html.md.erb index 311595fe5..a09f791ab 100644 --- a/website/source/docs/builders/amazon-chroot.html.md.erb +++ b/website/source/docs/builders/amazon-chroot.html.md.erb @@ -287,4 +287,6 @@ variables are available: - `SourceAMIName` - The source AMI Name (for example `ubuntu/images/ebs-ssd/ubuntu-xenial-16.04-amd64-server-20180306`) used to build the AMI. +- `SourceAMIOwner` - The source AMI owner ID. +- `SourceAMIOwnerName` - The source AMI owner alias/name (for example `amazon`). - `SourceAMITags` - The source AMI Tags, as a `map[string]string` object. diff --git a/website/source/docs/builders/amazon-ebs.html.md.erb b/website/source/docs/builders/amazon-ebs.html.md.erb index bf079ee48..0e3c96041 100644 --- a/website/source/docs/builders/amazon-ebs.html.md.erb +++ b/website/source/docs/builders/amazon-ebs.html.md.erb @@ -194,6 +194,8 @@ variables are available: - `SourceAMIName` - The source AMI Name (for example `ubuntu/images/ebs-ssd/ubuntu-xenial-16.04-amd64-server-20180306`) used to build the AMI. +- `SourceAMIOwner` - The source AMI owner ID. +- `SourceAMIOwnerName` - The source AMI owner alias/name (for example `amazon`). - `SourceAMITags` - The source AMI Tags, as a `map[string]string` object. ## Tag Example diff --git a/website/source/docs/builders/amazon-ebssurrogate.html.md.erb b/website/source/docs/builders/amazon-ebssurrogate.html.md.erb index d04fb127b..5aebb0192 100644 --- a/website/source/docs/builders/amazon-ebssurrogate.html.md.erb +++ b/website/source/docs/builders/amazon-ebssurrogate.html.md.erb @@ -82,7 +82,7 @@ Block devices can be nested in the <%= partial "partials/builder/amazon/common/BlockDevice" %> -#### Optional only for [launch_block_device_mappings](#launch_block_device_mappings) +#### Optional only for [launch_block_device_mappings](#launch_block_device_mappings) <%= partial "partials/builder/amazon/ebssurrogate/BlockDevice-not-required" %> @@ -97,7 +97,6 @@ Block devices can be nested in the <%= partial "partials/helper/communicator/Config-not-required" %> <%= partial "partials/helper/communicator/SSH-not-required" %> -<%= partial "partials/helper/communicator/SSHInterface-not-required" %> ## Basic Example @@ -157,6 +156,8 @@ variables are available: - `SourceAMIName` - The source AMI Name (for example `ubuntu/images/ebs-ssd/ubuntu-xenial-16.04-amd64-server-20180306`) used to build the AMI. +- `SourceAMIOwner` - The source AMI owner ID. +- `SourceAMIOwnerName` - The source AMI owner alias/name (for example `amazon`). - `SourceAMITags` - The source AMI Tags, as a `map[string]string` object. -> **Note:** Packer uses pre-built AMIs as the source for building images. diff --git a/website/source/docs/builders/amazon-ebsvolume.html.md.erb b/website/source/docs/builders/amazon-ebsvolume.html.md.erb index 00ab2639b..20c0dfa4f 100644 --- a/website/source/docs/builders/amazon-ebsvolume.html.md.erb +++ b/website/source/docs/builders/amazon-ebsvolume.html.md.erb @@ -99,7 +99,6 @@ Block devices can be nested in the <%= partial "partials/helper/communicator/Config-not-required" %> <%= partial "partials/helper/communicator/SSH-not-required" %> -<%= partial "partials/helper/communicator/SSHInterface-not-required" %> ## Basic Example @@ -176,6 +175,8 @@ variables are available: - `SourceAMIName` - The source AMI Name (for example `ubuntu/images/ebs-ssd/ubuntu-xenial-16.04-amd64-server-20180306`) used to build the AMI. +- `SourceAMIOwner` - The source AMI owner ID. +- `SourceAMIOwnerName` - The source AMI owner alias/name (for example `amazon`). - `SourceAMITags` - The source AMI Tags, as a `map[string]string` object. -> **Note:** Packer uses pre-built AMIs as the source for building images. diff --git a/website/source/docs/builders/amazon-instance.html.md.erb b/website/source/docs/builders/amazon-instance.html.md.erb index 7b740186d..6cceb0b3b 100644 --- a/website/source/docs/builders/amazon-instance.html.md.erb +++ b/website/source/docs/builders/amazon-instance.html.md.erb @@ -95,7 +95,7 @@ necessary for this build to succeed and can be found further down the page. ### Block Devices Configuration Block devices can be nested in the -[ami_block_device_mappings](#ami_block_device_mappings) or the +[ami_block_device_mappings](#ami_block_device_mappings) or the [launch_block_device_mappings](#launch_block_device_mappings) array. <%= partial "partials/builder/amazon/common/BlockDevice" %> @@ -110,7 +110,6 @@ Block devices can be nested in the <%= partial "partials/helper/communicator/Config-not-required" %> <%= partial "partials/helper/communicator/SSH-not-required" %> -<%= partial "partials/helper/communicator/SSHInterface-not-required" %> ## Basic Example @@ -159,6 +158,8 @@ variables are available: - `SourceAMIName` - The source AMI Name (for example `ubuntu/images/ebs-ssd/ubuntu-xenial-16.04-amd64-server-20180306`) used to build the AMI. +- `SourceAMIOwner` - The source AMI owner ID. +- `SourceAMIOwnerName` - The source AMI owner alias/name (for example `amazon`). - `SourceAMITags` - The source AMI Tags, as a `map[string]string` object. ## Custom Bundle Commands diff --git a/website/source/docs/builders/amazon.html.md b/website/source/docs/builders/amazon.html.md index 07e4ce23c..15ac7a98a 100644 --- a/website/source/docs/builders/amazon.html.md +++ b/website/source/docs/builders/amazon.html.md @@ -202,7 +202,10 @@ work, but specifics will depend on your use-case. { "Sid": "PackerIAMPassRole", "Effect": "Allow", - "Action": "iam:PassRole", + "Action": [ + "iam:PassRole", + "iam:GetInstanceProfile" + ], "Resource": [ "*" ] @@ -314,4 +317,4 @@ generally during image copy/encryption. Possible reasons for the error include: - Your KMS key is invalid, possibly because of a typo - Your KMS key is valid but does not have the necessary permissions (see above for the necessary key permissions) - - Your KMS key is valid, but not in the region you've told us to use it in. \ No newline at end of file + - Your KMS key is valid, but not in the region you've told us to use it in. diff --git a/website/source/docs/builders/azure-arm.html.md.erb b/website/source/docs/builders/azure-arm.html.md.erb index b08e8506e..77d01a294 100644 --- a/website/source/docs/builders/azure-arm.html.md.erb +++ b/website/source/docs/builders/azure-arm.html.md.erb @@ -114,8 +114,6 @@ a Shared Image Gallery**. When publishing to a Shared Image Gallery the followin Following is an example. - - "shared_image_gallery_destination": { "resource_group": "ResourceGroup", "gallery_name": "GalleryName", diff --git a/website/source/docs/configuration/from-1.5/expressions.html.md b/website/source/docs/configuration/from-1.5/expressions.html.md new file mode 100644 index 000000000..8c2069a26 --- /dev/null +++ b/website/source/docs/configuration/from-1.5/expressions.html.md @@ -0,0 +1,222 @@ +--- +layout: "docs" +page_title: "Expressions - Configuration Language" +sidebar_current: configuration-expressions +description: |- + HCL allows the use of expressions to access data exported + by resources and to transform and combine that data to produce other values. +--- + +# Expressions + +_Expressions_ are used to refer to or compute values within a configuration. +The simplest expressions are just literal values, like `"hello"` or `5`, but +HCL also allows more complex expressions such as references to data exported by +resources, arithmetic, conditional evaluation, and a number of built-in +functions. + +Expressions can be used in a number of places in HCL, but some contexts limit +which expression constructs are allowed, such as requiring a literal value of a +particular type or forbidding. Each language feature's documentation describes +any restrictions it places on expressions. + +The rest of this page describes all of the features of Packer's +expression syntax. + +## Types and Values + +The result of an expression is a _value_. All values have a _type_, which +dictates where that value can be used and what transformations can be +applied to it. + +HCL uses the following types for its values: + +* `string`: a sequence of Unicode characters representing some text, like + `"hello"`. +* `number`: a numeric value. The `number` type can represent both whole + numbers like `15` and fractional values like `6.283185`. +* `bool`: either `true` or `false`. `bool` values can be used in conditional + logic. +* `list` (or `tuple`): a sequence of values, like + `["us-west-1a", "us-west-1c"]`. Elements in a list or tuple are identified by + consecutive whole numbers, starting with zero. +* `map` (or `object`): a group of values identified by named labels, like + `{name = "Mabel", age = 52}`. + +Strings, numbers, and bools are sometimes called _primitive types._ +Lists/tuples and maps/objects are sometimes called _complex types,_ _structural +types,_ or _collection types._ + +Finally, there is one special value that has _no_ type: + +* `null`: a value that represents _absence_ or _omission._ If you set an + argument of a source or module to `null`, Packer behaves as though you + had completely omitted it — it will use the argument's default value if it has + one, or raise an error if the argument is mandatory. `null` is most useful in + conditional expressions, so you can dynamically omit an argument if a + condition isn't met. + +### Advanced Type Details + +In most situations, lists and tuples behave identically, as do maps and objects. +Whenever the distinction isn't relevant, the Packer documentation uses each +pair of terms interchangeably (with a historical preference for "list" and +"map"). + +However, module authors and provider developers should understand the +differences between these similar types (and the related `set` type), since they +offer different ways to restrict the allowed values for input variables and +source arguments. + +### Type Conversion + +Expressions are most often used to set values for the arguments of resources and +child modules. In these cases, the argument has an expected type and the given +expression must produce a value of that type. + +Where possible, Packer automatically converts values from one type to +another in order to produce the expected type. If this isn't possible, Packer +will produce a type mismatch error and you must update the configuration with a +more suitable expression. + +Packer automatically converts number and bool values to strings when needed. +It also converts strings to numbers or bools, as long as the string contains a +valid representation of a number or bool value. + +* `true` converts to `"true"`, and vice-versa +* `false` converts to `"false"`, and vice-versa +* `15` converts to `"15"`, and vice-versa + +## Literal Expressions + +A _literal expression_ is an expression that directly represents a particular +constant value. Packer has a literal expression syntax for each of the value +types described above: + +* Strings are usually represented by a double-quoted sequence of Unicode + characters, `"like this"`. There is also a "heredoc" syntax for more complex + strings. String literals are the most complex kind of literal expression in + Packer, and have additional documentation on this page: + * See [String Literals](#string-literals) below for information about escape + sequences and the heredoc syntax. + * See [String Templates](#string-templates) below for information about + interpolation and template directives. +* Numbers are represented by unquoted sequences of digits with or without a + decimal point, like `15` or `6.283185`. +* Bools are represented by the unquoted symbols `true` and `false`. +* The null value is represented by the unquoted symbol `null`. +* Lists/tuples are represented by a pair of square brackets containing a + comma-separated sequence of values, like `["a", 15, true]`. + + List literals can be split into multiple lines for readability, but always + require a comma between values. A comma after the final value is allowed, + but not required. Values in a list can be arbitrary expressions. +* Maps/objects are represented by a pair of curly braces containing a series of + ` = ` pairs: + + ```hcl + { + name = "John" + age = 52 + } + ``` + + Key/value pairs can be separated by either a comma or a line break. Values + can be arbitrary expressions. Keys are strings; they can be left unquoted if + they are a valid [identifier](./syntax.html#identifiers), but must be quoted + otherwise. You can use a non-literal expression as a key by wrapping it in + parentheses, like `(var.business_unit_tag_name) = "SRE"`. + +## References to Named Values + +Packer makes one named values available. + +The following named values are available: + +* `source..` is an object representing a + [source](./sources.html) of the given type + and name. + +## String Literals + +HCL has two different syntaxes for string literals. The +most common is to delimit the string with quote characters (`"`), like +`"hello"`. In quoted strings, the backslash character serves as an escape +sequence, with the following characters selecting the escape behavior: + +| Sequence | Replacement | +| ------------ | ----------------------------------------------------------------------------- | +| `\n` | Newline | +| `\r` | Carriage Return | +| `\t` | Tab | +| `\"` | Literal quote (without terminating the string) | +| `\\` | Literal backslash | +| `\uNNNN` | Unicode character from the basic multilingual plane (NNNN is four hex digits) | +| `\UNNNNNNNN` | Unicode character from supplementary planes (NNNNNNNN is eight hex digits) | + +The alternative syntax for string literals is the so-called Here Documents or +"heredoc" style, inspired by Unix shell languages. This style allows multi-line +strings to be expressed more clearly by using a custom delimiter word on a line +of its own to close the string: + +```hcl +< "" "" { + # Block body + = # Argument +} +``` + +- _Blocks_ are containers for other content and usually represent the + configuration of some kind of object, like a source. Blocks have a + _block type,_ can have zero or more _labels,_ and have a _body_ that contains + any number of arguments and nested blocks. Most of Packer's features are + controlled by top-level blocks in a configuration file. +- _Arguments_ assign a value to a name. They appear within blocks. +- _Expressions_ represent a value, either literally or by referencing and + combining other values. They appear as values for arguments, or within other + expressions. + +For full details about Packer's syntax, see: + +- [Configuration Syntax](./syntax.html) +- [Expressions](./expressions.html) + +## Code Organization + +The HCL language uses configuration files that are named with the `.pkr.hcl` +file extension. There is also [a JSON-based variant of the +language](./syntax-json.html) that is named with the `.pkr.json` file +extension. + +Configuration files must always use UTF-8 encoding, and by convention are +usually maintained with Unix-style line endings (LF) rather than Windows-style +line endings (CRLF), though both are accepted. + +## Configuration Ordering + +The ordering of root blocks is not significant. The order of `provisioner` or +`post-processor` blocks within a `build` is the only major feature where block +order matters. diff --git a/website/source/docs/configuration/from-1.5/syntax-json.html.md b/website/source/docs/configuration/from-1.5/syntax-json.html.md new file mode 100644 index 000000000..a264b5f12 --- /dev/null +++ b/website/source/docs/configuration/from-1.5/syntax-json.html.md @@ -0,0 +1,276 @@ +--- +layout: "docs" +page_title: "JSON Configuration Syntax - Configuration Language" +sidebar_current: configuration-json-syntax +description: |- + In addition to the native syntax that is most commonly used with Packer, + the HCL language can also be expressed in a JSON-compatible syntax. +--- + +# JSON Configuration Syntax + + +Most Packer configurations are written in [the native HCL +syntax](./syntax.html), which is designed to be easy for humans to read and +update. + +Packer also supports an alternative syntax that is JSON-compatible. This +syntax is useful when generating portions of a configuration programmatically, +since existing JSON libraries can be used to prepare the generated +configuration files. + +The JSON syntax is defined in terms of the native syntax. Everything that can +be expressed in native syntax can also be expressed in JSON syntax, but some +constructs are more complex to represent in JSON due to limitations of the +JSON grammar. + +Packer expects native syntax for files named with a `.pkr.hcl` suffix, and JSON +syntax for files named with a `.pkr.json` suffix. + +The low-level JSON syntax, just as with the native syntax, is defined in terms +of a specification called _HCL_. It is not necessary to know all of the details +of HCL syntax or its JSON mapping in order to use Packer, and so this page +summarizes the most important differences between native and JSON syntax. If +you are interested, you can find a full definition of HCL's JSON syntax in [its +specification](https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md). + +## JSON File Structure + +At the root of any JSON-based Packer configuration is a JSON object. The +properties of this object correspond to the top-level block types of the +Packer language. For example: + +```json +{ + "variables": { + "example": "value" + } +} +``` + +Each top-level object property must match the name of one of the expected +top-level block types. Block types that expect labels, such as `variable` shown +above, are represented by one nested object value for each level of label. +`source` blocks expect two labels, so two levels of nesting are required: + +```json +{ + "source": { + "amazon-ebs": { + "example": { + "instance_type": "t2.micro", + "ami_name": "ami-abc123" + } + } + } +} +``` + +After any nested objects representing the labels, finally one more nested +object represents the body of the block itself. In the above example the +`instance_type` and `ami_name` arguments for `source "amazon-ebs" "example"` +are specified. + +Taken together, the above two configuration files are equivalent to the +following blocks in the native syntax: + +```hcl +variables { + example = "value" +} + +source "amazon-ebs" "example" { + instance_type = "t2.micro" + ami_name = "ami-abc123" +} +``` + +Within each top-level block type the rules for mapping to JSON are slightly +different (see [Block-type-specific Exceptions][inpage-exceptions] below), but the following general rules apply in most cases: + +* The JSON object representing the block body contains properties that + correspond either to argument names or to nested block type names. + +* Where a property corresponds to an argument that accepts + [arbitrary expressions](./expressions.html) in the native syntax, the + property value is mapped to an expression as described under + [_Expression Mapping_](#expression-mapping) below. For arguments that + do _not_ accept arbitrary expressions, the interpretation of the property + value depends on the argument, as described in the + [block-type-specific exceptions](#block-type-specific-exceptions) + given later in this page. + +* Where a property name corresponds to an expected nested block type name, + the value is interpreted as described under + [_Nested Block Mapping_](#nested-block-mapping) below, unless otherwise + stated in [the block-type-specific exceptions](#block-type-specific-exceptions) + given later in this page. + +## Expression Mapping + +Since JSON grammar is not able to represent all of the Packer language +[expression syntax](./expressions.html), JSON values interpreted as expressions +are mapped as follows: + +| JSON | Packer Language Interpretation | +| ------- | ------------------------------------------------------------------------------------------------------------- | +| Boolean | A literal `bool` value. | +| Number | A literal `number` value. | +| String | Parsed as a [string template](./expressions.html#string-templates) and then evaluated as described below. | +| Object | Each property value is mapped per this table, producing an `object(...)` value with suitable attribute types. | +| Array | Each element is mapped per this table, producing a `tuple(...)` value with suitable element types. | +| Null | A literal `null`. | + +When a JSON string is encountered in a location where arbitrary expressions are +expected, its value is first parsed as a [string template](./expressions.html#string-templates) +and then it is evaluated to produce the final result. + +If the given template consists _only_ of a single interpolation sequence, +the result of its expression is taken directly, without first converting it +to a string. This allows non-string expressions to be used within the +JSON syntax. + +## Nested Block Mapping + +When a JSON object property is named after a nested block type, the value +of this property represents one or more blocks of that type. The value of +the property must be either a JSON object or a JSON array. + +The simplest situation is representing only a single block of the given type +when that type expects no labels, as with the `tags` nested block used +within `source` blocks: + +```json +{ + "source": { + "amazon-ebs": { + "example": { + "tags": { + "key": "value" + } + } + } + } +} +``` + +The above is equivalent to the following native syntax configuration: + +```hcl +source "amazon-ebs" "example" { + tags { + key = "value" + } +} +``` + +When the nested block type requires one or more labels, or when multiple +blocks of the same type can be given, the mapping gets a little more +complicated. For example, the `provisioner` nested block type used +within `source` blocks expects a label giving the provisioner to use, +and the ordering of provisioner blocks is significant to decide the order +of operations. + +The following native syntax example shows a `source` block with a number +of provisioners of different types: + +```hcl +source "amazon-ebs" "example" { + # (source configuration omitted for brevity) + + provisioner "shell-local" { + inline = ["echo 'Hello World' >example.txt"] + } + provisioner "file" { + source = "example.txt" + destination = "/tmp/example.txt" + } + provisioner "shell" { + inline = [ + "sudo install-something -f /tmp/example.txt", + ] + } +} +``` + +In order to preserve the order of these blocks, you must use a JSON array +as the direct value of the property representing this block type, as in +this JSON equivalent of the above: + +```json +{ + "source": { + "amazon-ebs": { + "example": { + "provisioner": [ + { + "shell-local": { + "inline": ["echo 'Hello World' >example.txt"] + } + }, + { + "file": { + "source": "example.txt", + "destination": "/tmp/example.txt" + } + }, + { + "shell": { + "inline": ["sudo install-something -f /tmp/example.txt"] + } + } + ] + } + } + } +} +``` + +Each element of the `provisioner` array is an object with a single property +whose name represents the label for each `provisioner` block. For block types +that expect multiple labels, this pattern of alternating array and object +nesting can be used for each additional level. + +If a nested block type requires labels but the order does _not_ matter, you +may omit the array and provide just a single object whose property names +correspond to unique block labels. This is allowed as a shorthand for the above +for simple cases, but the alternating array and object approach is the most +general. We recommend using the most general form if systematically converting +from native syntax to JSON, to ensure that the meaning of the configuration is +preserved exactly. + +### Comment Properties + +Although we do not recommend hand-editing of JSON syntax configuration files +-- this format is primarily intended for programmatic generation and consumption -- +a limited form of _comments_ are allowed inside JSON objects that represent +block bodies using a special property name: + +```json +{ + "source": { + "amazon-ebs": { + "example": { + "//": "This instance runs the scheduled tasks for backup", + + "instance_type": "t2.micro", + "ami_name": "ami-abc123" + } + } + } +} +``` + +In any object that represents a block body, properties named `"//"` are +ignored by Packer entirely. This exception does _not_ apply to objects +that are being [interpreted as expressions](#expression-mapping), where this +would be interpreted as an object type attribute named `"//"`. + +This special property name can also be used at the root of a JSON-based +configuration file. This can be useful to note which program created the file. + +```json +{ + "//": "This file is generated by generate-outputs.py. DO NOT HAND-EDIT!", +} +``` diff --git a/website/source/docs/configuration/from-1.5/syntax.html.md b/website/source/docs/configuration/from-1.5/syntax.html.md new file mode 100644 index 000000000..5bc4c4690 --- /dev/null +++ b/website/source/docs/configuration/from-1.5/syntax.html.md @@ -0,0 +1,119 @@ +--- +layout: "docs" +page_title: "Syntax - Configuration Language" +sidebar_current: configuration-syntax +description: |- + HCL has its own syntax, intended to combine declarative + structure with expressions in a way that is easy for humans to read and + understand. +--- + +# HCL Configuration Syntax + +Other pages in this section have described various configuration constructs +that can appear in HCL. This page describes the lower-level syntax of the +language in more detail, revealing the building blocks that those constructs +are built from. + +This page describes the _native syntax_ of HCL, which is a rich language +designed to be easy for humans to read and write. The constructs in HCL can +also be expressed in [JSON syntax](./syntax-json.html), which is harder for +humans to read and edit but easier to generate and parse programmatically. + +This low-level syntax of HCL is defined in terms of a syntax called _HCL_, +which is also used by configuration languages in other applications, and in +particular other HashiCorp products. It is not necessary to know all of the +details of HCL in order to use Packer, and so this page summarizes the most +important details. If you are interested, you can find a full definition of HCL +syntax in [the HCL native syntax +specification](https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md). + +## Arguments and Blocks + +HCL syntax is built around two key syntax constructs: +arguments and blocks. + +### Arguments + +An _argument_ assigns a value to a particular name: + +```hcl +image_id = "abc123" +``` + +The identifier before the equals sign is the _argument name_, and the expression +after the equals sign is the argument's value. + +The context where the argument appears determines what value types are valid +(for example, each source type has a schema that defines the types of its +arguments), but many arguments accept arbitrary +[expressions](./expressions.html), which allow the value to +either be specified literally or generated from other values programmatically. + +### Blocks + +A _block_ is a container for other content: + +```hcl +source "amazon-ebs" "example" { + ami_name = "abc123" + + tags { + # ... + } +} +``` + +A block has a _type_ (`source` in this example). Each block type defines +how many _labels_ must follow the type keyword. The `source` block type +expects two labels, which are `amazon-ebs` and `example` in the example above. +A particular block type may have any number of required labels, or it may +require none as with the nested `tags` block type. + +After the block type keyword and any labels, the block _body_ is delimited +by the `{` and `}` characters. Within the block body, further arguments +and blocks may be nested, creating a hierarchy of blocks and their associated +arguments. + +HCL uses a limited number of _top-level block types,_ which +are blocks that can appear outside of any other block in a configuration file. +Most of Packer's features (including resources, input variables, output +values, data sources, etc.) are implemented as top-level blocks. + +## Identifiers + +Argument names, block type names, and the names of most Packer-specific +constructs like resources, input variables, etc. are all _identifiers_. + +Identifiers can contain letters, digits, underscores (`_`), and hyphens (`-`). +The first character of an identifier must not be a digit, to avoid ambiguity +with literal numbers. + +For complete identifier rules, Packer implements +[the Unicode identifier syntax](http://unicode.org/reports/tr31/), extended to +include the ASCII hyphen character `-`. + +## Comments + +HCL supports three different syntaxes for comments: + +* `#` begins a single-line comment, ending at the end of the line. +* `//` also begins a single-line comment, as an alternative to `#`. +* `/*` and `*/` are start and end delimiters for a comment that might span + over multiple lines. + +The `#` single-line comment style is the default comment style and should be +used in most cases. Automatic configuration formatting tools may automatically +transform `//` comments into `#` comments, since the double-slash style is +not idiomatic. + +## Character Encoding and Line Endings + +Packer configuration files must always be UTF-8 encoded. While the +delimiters of the language are all ASCII characters, Packer accepts +non-ASCII characters in identifiers, comments, and string values. + +Packer accepts configuration files with either Unix-style line endings +(LF only) or Windows-style line endings (CR then LF), but the idiomatic style +is to use the Unix convention, and so automatic configuration formatting tools +may automatically transform CRLF endings to LF. diff --git a/website/source/docs/extending/custom-builders.html.md b/website/source/docs/extending/custom-builders.html.md index 63cfdb40b..d23eb1bfc 100644 --- a/website/source/docs/extending/custom-builders.html.md +++ b/website/source/docs/extending/custom-builders.html.md @@ -31,10 +31,17 @@ method should do. ``` go type Builder interface { - Prepare(...interface{}) error + ConfigSpec() hcldec.ObjectSpec + Prepare(...interface{}) ([]string, []string, error) Run(context.Context, ui Ui, hook Hook) (Artifact, error) } ``` +### The "ConfigSpec" Method + +This method returns a hcldec.ObjectSpec, which is a spec necessary for using +HCL2 templates with Packer. For information on how to use and implement this +function, check our +[object spec docs](https://www.packer.io/guides/hcl/component-object-spec) ### The "Prepare" Method diff --git a/website/source/docs/extending/custom-post-processors.html.md b/website/source/docs/extending/custom-post-processors.html.md index e940047b3..4a66f5469 100644 --- a/website/source/docs/extending/custom-post-processors.html.md +++ b/website/source/docs/extending/custom-post-processors.html.md @@ -36,11 +36,19 @@ explaining what each method should do. ``` go type PostProcessor interface { + ConfigSpec() hcldec.ObjectSpec Configure(interface{}) error PostProcess(context.Context, Ui, Artifact) (a Artifact, keep, mustKeep bool, err error) } ``` +### The "ConfigSpec" Method + +This method returns a hcldec.ObjectSpec, which is a spec necessary for using +HCL2 templates with Packer. For information on how to use and implement this +function, check our +[object spec docs](https://www.packer.io/guides/hcl/component-object-spec) + ### The "Configure" Method The `Configure` method for each post-processor is called early in the build @@ -91,6 +99,6 @@ return value is explained below: keep the artifact around. - `bool` - If forceOverride is true, then any user input for keep_input_artifact is ignored and the artifact is either kept or discarded - according to the value set in `keep`. + according to the value set in `keep`. - `error` - Non-nil if there was an error in any way. If this is the case, the other two return values are ignored. diff --git a/website/source/docs/extending/custom-provisioners.html.md b/website/source/docs/extending/custom-provisioners.html.md index 3d3a1721d..03b73efbf 100644 --- a/website/source/docs/extending/custom-provisioners.html.md +++ b/website/source/docs/extending/custom-provisioners.html.md @@ -35,8 +35,9 @@ explaining what each method should do. ``` go type Provisioner interface { + ConfigSpec() hcldec.ObjectSpec Prepare(...interface{}) error - Provision(Ui, Communicator) error + Provision(context.Context, Ui, Communicator, map[string]interface{}) error } ``` @@ -66,6 +67,13 @@ validate the configuration. The `Prepare` method is called very early in the build process so that errors may be displayed to the user before anything actually happens. +### The "ConfigSpec" Method + +This method returns a hcldec.ObjectSpec, which is a spec necessary for using +HCL2 templates with Packer. For information on how to use and implement this +function, check our +[object spec docs](https://www.packer.io/guides/hcl/component-object-spec) + ### The "Provision" Method The `Provision` method is called when a machine is running and ready to be @@ -78,6 +86,10 @@ connected at this point. The provision method should not return until provisioning is complete. +The map[string]interface{} provides users with build-specific information, +like host and IP, provided by the `build` template engine. Provisioners may use +this information however they please, or not use it. + ## Using the Communicator The `packer.Communicator` parameter and interface is used to communicate with diff --git a/website/source/docs/provisioners/ansible.html.md.erb b/website/source/docs/provisioners/ansible.html.md.erb index 4f4a481c1..ff2608cca 100644 --- a/website/source/docs/provisioners/ansible.html.md.erb +++ b/website/source/docs/provisioners/ansible.html.md.erb @@ -25,6 +25,7 @@ provisioner. This is a fully functional template that will provision an image on DigitalOcean. Replace the mock `api_token` value with your own. +Example Packer template: ``` json { "provisioners": [ @@ -45,6 +46,23 @@ DigitalOcean. Replace the mock `api_token` value with your own. } ``` +Example playbook: + +``` +--- +# playbook.yml +- name: "Provision Image" + hosts: default + become: true + + tasks: + - name: install Apache + package: + name: "httpd" + state: present + +``` + ## Configuration Reference Required Parameters: @@ -451,4 +469,4 @@ Example playbook: - name: install Apache yum: name: httpd -``` \ No newline at end of file +``` diff --git a/website/source/docs/provisioners/chef-client.html.md.erb b/website/source/docs/provisioners/chef-client.html.md.erb index 341f19da0..264432c8c 100644 --- a/website/source/docs/provisioners/chef-client.html.md.erb +++ b/website/source/docs/provisioners/chef-client.html.md.erb @@ -149,6 +149,9 @@ configuration is actually required. machine. If this is NOT set, then it is your responsibility via other means (shell provisioner, etc.) to get a validation key to where Chef expects it. +- `version` (string) - The version of Chef to be installed. By default this + is empty which will install the latest version of Chef. + <%= partial "partials/provisioners/common-config" %> ## Chef Configuration diff --git a/website/source/docs/templates/engine.html.md b/website/source/docs/templates/engine.html.md index 4c7883c88..7232f0b12 100644 --- a/website/source/docs/templates/engine.html.md +++ b/website/source/docs/templates/engine.html.md @@ -62,6 +62,36 @@ Here is a full list of the available functions for reference. each function will behave. - `env` - Returns environment variables. See example in [using home variable](/docs/templates/user-variables.html#using-home-variable) +- `build` - This engine will allow you to access special variables that + provide connection information and basic instance state information. + Usage example: + ```json + { + "type": "shell-local", + "environment_vars": ["TESTVAR={{ build `PackerRunUUID`}}"], + "inline": ["echo $TESTVAR"] + }, + ``` + Valid variables to request are: "ID", "Host", + "Port", "User", "Password", "ConnType", + "PackerRunUUID", "SSHPublicKey", and "SSHPrivateKey". + Depending on which communicator you are using, some of these values may be + empty -- for example, the public and private keys are unique to the SSH + communicator. InstanceID represents the vm being provisioned. For example, + in Amazon it is the instance id; in digitalocean, it is the droplet id; in + Vmware, it is the vm name. + + For backwards compatability, `WinRMPassword` is also available through this + engine, though it is no different than using the more general `Password`. + + This function is only for use within specific options inside of + _provisioners_ -- these options will be listed as being template engines + in the provisioner documentation. This feature does not yet work + if the provisioners are being used in conjunction with our chroot builders + or with lxc/lxd builders. + + This engine is in beta; please report any issues or requests on the Packer + issue tracker on GitHub. - `isotime [FORMAT]` - UTC time, which can be [formatted](https://golang.org/pkg/time/#example_Time_Format). See more examples below in [the `isotime` format diff --git a/website/source/guides/hcl/component-object-spec/index.html.md.erb b/website/source/guides/hcl/component-object-spec/index.html.md.erb new file mode 100644 index 000000000..f548abdc8 --- /dev/null +++ b/website/source/guides/hcl/component-object-spec/index.html.md.erb @@ -0,0 +1,44 @@ +--- +layout: guides +page_title: Generating code for config spec. +sidebar_current: hcl-component-object-spec +description: |- + Learn how to generate the HCL2 configuration of your component easily. +--- + +# Auto Generate the HCL2 code of a plugin + +From v1.5, Packer can be configured using HCL2. Because Packer has so many +builders, provisioner & post-processors, we relied on code generation to +iterate more easily. The good new is that you can benefit from this code +generator to get the HCL2 spec code of your component simply. It's a Go binary +package and is located in [`cmd/mapstructure-to-hcl2`](https://github.com/hashicorp/packer/tree/master/cmd/mapstructure-to-hcl2). + +Say you want to configure the `Config` struct of a `Builder` in a package +located in `my/example-plugin/config.go`. Here are some simple steps you can +follow to make it HCL2 enabled: + +* run `go install github.com/hashicorp/packer/cmd/mapstructure-to-hcl2` + +* Add `//go:generate mapstructure-to-hcl2 -type Config` at the top of +`config.go` + +* run `go generate ./my/example-plugin/...` + + This will generate a `my/example-plugin/config.hcl2spec.go` file containing + the configuration fields of `Config`. + +* Make sure that all the nested structs of `Config` are also auto generated the + same way. + +* Now we only need to make Builder implement the interface by adding the +following snippet: + + ```go + func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } + ``` + + From now on every time you add or change a field of Config you will need to + run the `go generate` command again. + +A good example of this is the [Config struct of the amazon-ebs builder](https://github.com/hashicorp/packer/blob/master/builder/amazon/ebs/builder.go) \ No newline at end of file diff --git a/website/source/guides/hcl/from-json-v1/index.html.md.erb b/website/source/guides/hcl/from-json-v1/index.html.md.erb new file mode 100644 index 000000000..cee1b0ac9 --- /dev/null +++ b/website/source/guides/hcl/from-json-v1/index.html.md.erb @@ -0,0 +1,179 @@ +--- +layout: guides +page_title: Transforming Packer v1 files for Packer v1.5.0 +sidebar_current: hcl-from-json-v1 +description: |- + Learn how to manually move from a Packer v1 working JSON build file to a + working v1.5.0 HCL file. +--- + +# Transforming Packer v1 config files to HCL2 for Packer v1.5 + +-> **Note:** Starting from version **1.5.0** Packer can read HCL2 files. + +We will soon provide a programatic way to transpose a v1 buildfile to a v1.5 +HCL file. In the meantime we will show how to manually do it. + +The following file : + +```json +{ + "builders": [ + { + "ami_name": "packer-test", + "region": "us-east-1", + "instance_type": "t2.micro", + + "source_ami_filter": { + "filters": { + "virtualization-type": "hvm", + "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*", + "root-device-type": "ebs" + }, + "owners": ["amazon"], + "most_recent": true + }, + + "ssh_username": "ubuntu", + "type": "amazon-ebs" + } + ], + "provisioners": [ + { + "type": "shell", + "inline": [ + "sleep 5" + ] + } + ] +} +``` + +Becomes: + +```hcl +# the source block is what was defined in the builders section and represents a +# reusable way to start a machine. You build your images from that source. All +# sources have a 1:1 correspondance to what currently is a builder. The +# argument name (ie: ami_name) must be unquoted and can be set using the equal +# sign operator (=). +source "amazon-ebs" "example" { + ami_name = "packer-test" + region = "us-east-1" + instance_type = "t2.micro" + + source_ami_filter { + filters { + virtualization-type = "hvm" + name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*" + root-device-type = "ebs" + } + owners = ["amazon"] + most_recent = true + } + + communicator = "ssh" + ssh_username = "ubuntu" +} + +# A build starts sources and runs provisioning steps on those sources. +build { + sources = [ + # there can be multiple sources per build + "source.amazon-ebs.example" + ] + + # All provisioners and post-processors have a 1:1 correspondence to their + # current layout. The argument name (ie: inline) must to be unquoted + # and can be set using the equal sign operator (=). + provisioner "shell" { + inline = ["sleep 5"] + } + + # post-processors work too, example: `post-processor "shell-local" {}`. +} + +``` + +### 1:1 correspondence of components ... except : + +All fields of builders, provisioners and post-processors have a 1:1 +correspondance except for the following: + +* builders: + * aws ami_block_device_mappings + * aws launch_block_device_mappings + * aws run_volume_tags + * alicloud image_disk_mappings + * osc omi_block_device_mappings + * osc launch_block_device_mappings + * proxmox network_adapters + * proxmox disks + * tencentcloud data_disks + * ucloud image_copy_to_mappings + + +* provisioner: + * converge module_dirs + +* post-processor: + * alicloud-import image_disk_mappings + +One could think that these are defined as "arrays of blocks" - they are in fact +repeatable blocks with the same identifier. For example: + +```json +"builders": [ + { + "type": "amazon-ebs", + "launch_block_device_mappings": [ + { + "device_name": "/dev/xvda", + "volume_size": "20", + "volume_type": "gp2", + "delete_on_termination": "true" + }, + { + "device_name": "/dev/xvdf", + "volume_size": "500", + "volume_type": "gp2", + "delete_on_termination": "true", + "encrypted": true + } + ], + } +``` + +Becomes: + +```hcl +source "amazon-ebs" "example" { + launch_block_device_mappings { + device_name = "/dev/xvda" + volume_size = 20 + volume_type = "gp2" + delete_on_termination = true + } + launch_block_device_mappings { + device_name = "/dev/xvdf" + volume_size = 500 + volume_type = "gp2" + delete_on_termination = true + encrypted = true + } +``` + +There is soon going to be a PR to drop the `s` at the end of these fields. + +### Deprecation + +The current layout of buildfiles will be supported until we and the community +love the new format. Only then the v1 format will be carefully deprecated. + +-> **Note:** The HCL parsing library can read JSON and if it is your +configuration format of predilection, you will still be able to do it. You will +have to tweak a few things in order to use future versions of Packer that have +deprecated the current format. Sorry about that! Because the HCL reading code +is generated from the JSON parsing settings; every builder, provisioner and +post-processor setting should look and work the same. A config file transposer +is currently in the making. diff --git a/website/source/guides/hcl/index.html.md.erb b/website/source/guides/hcl/index.html.md.erb new file mode 100644 index 000000000..555d06c94 --- /dev/null +++ b/website/source/guides/hcl/index.html.md.erb @@ -0,0 +1,90 @@ +--- +layout: guides +sidebar_current: hcl +page_title: Getting started configuring Packer with HCL2 files +--- + +# Introduction to Packer HCL2 + +-> **Note:** Starting from version **1.5.0** Packer can read HCL2 files. + +It is not necessary to know all of the details of the HCL syntax in order to +use Packer, and so this page summarizes the most important details to get you +started. If you are interested, you can find a [full definition of HCL +syntax](https://github.com/hashicorp/hcl2/blob/master/hcl/hclsyntax/spec.md) in +the HCL native syntax specification. + +## Arguments and Blocks + +The HCL syntax is built around two key syntax constructs: arguments and blocks. + +```hcl +# block +source "amazon-ebs" "example" { + + # argument + ami_name = "abc123" +} +``` + +## Comments + +The HCL language supports three different syntaxes for comments: + +* ```#``` begins a single-line comment, ending at the end of the line. +* ```//``` also begins a single-line comment, as an alternative to ```#```. +* ```/*``` and ```*/``` are start and end delimiters for a comment that might + span over multiple lines. + +## Multi-line strings + +A multi-line string value can be provided using heredoc syntax. + +```hcl +variable "long_key" { + type = "string" + default = < +/install/vmlinuz noapic +... +EOF +} +``` + +```hcl +# folder/build.pkr.hcl +build { + sources = [ + "source.amazon-ebs.example-1", + "source.virtualbox-iso.example-2" + ] + + provisioner "shell" { + inline = [ + "echo it's alive !" + ] + } +} +``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index c6f13a3e3..6f833a900 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -1,9 +1,24 @@ <% wrap_layout :inner do %> <% content_for :sidebar do %>