From 3522e13b1e394d7ab044ea47e236605238e4fa06 Mon Sep 17 00:00:00 2001 From: nywilken Date: Thu, 31 Oct 2019 14:41:48 -0400 Subject: [PATCH] provisioner/shell: Add `env_var_format` support to shell provisioner * Move setting of default EnvVarFormat to the Prepare function * Add generated hcl2 structure for the updated Shell provisioner config * Move `EnvVarFormat` field to shared common shell type * Add description for the `env_var_format` parameter * Update `env_var_format` when used with `use_env_var_file` --- common/shell/shell.go | 4 + provisioner/powershell/provisioner.go | 4 - .../powershell/provisioner.hcl2spec.go | 4 +- provisioner/shell/provisioner.go | 27 ++++-- provisioner/shell/provisioner.hcl2spec.go | 2 + provisioner/shell/provisioner_test.go | 89 +++++++++++++++++++ provisioner/windows-shell/provisioner.go | 4 - .../windows-shell/provisioner.hcl2spec.go | 4 +- .../docs/provisioners/shell.html.md.erb | 5 ++ 9 files changed, 123 insertions(+), 20 deletions(-) diff --git a/common/shell/shell.go b/common/shell/shell.go index 39a89fa5e..b698f4805 100644 --- a/common/shell/shell.go +++ b/common/shell/shell.go @@ -39,4 +39,8 @@ type Provisioner struct { // An array of environment variables that will be injected before // your command(s) are executed. Vars []string `mapstructure:"environment_vars"` + + // This is used in the template generation to format environment variables + // inside the `ExecuteCommand` template. + EnvVarFormat string `mapstructure:"env_var_format"` } diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index e541a782b..69704233d 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -55,10 +55,6 @@ type Config struct { // can be set high to allow for reboots. StartRetryTimeout time.Duration `mapstructure:"start_retry_timeout"` - // This is used in the template generation to format environment variables - // inside the `ExecuteCommand` template. - EnvVarFormat string - // This is used in the template generation to format environment variables // inside the `ElevatedExecuteCommand` template. ElevatedEnvVarFormat string `mapstructure:"elevated_env_var_format"` diff --git a/provisioner/powershell/provisioner.hcl2spec.go b/provisioner/powershell/provisioner.hcl2spec.go index 42f2e39ef..8350ebc8d 100644 --- a/provisioner/powershell/provisioner.hcl2spec.go +++ b/provisioner/powershell/provisioner.hcl2spec.go @@ -24,10 +24,10 @@ type FlatConfig struct { Scripts []string `cty:"scripts"` ValidExitCodes []int `mapstructure:"valid_exit_codes" cty:"valid_exit_codes"` Vars []string `mapstructure:"environment_vars" cty:"environment_vars"` + EnvVarFormat *string `mapstructure:"env_var_format" cty:"env_var_format"` RemoteEnvVarPath *string `mapstructure:"remote_env_var_path" cty:"remote_env_var_path"` ElevatedExecuteCommand *string `mapstructure:"elevated_execute_command" cty:"elevated_execute_command"` StartRetryTimeout *string `mapstructure:"start_retry_timeout" cty:"start_retry_timeout"` - EnvVarFormat *string `cty:"env_var_format"` ElevatedEnvVarFormat *string `mapstructure:"elevated_env_var_format" cty:"elevated_env_var_format"` ElevatedUser *string `mapstructure:"elevated_user" cty:"elevated_user"` ElevatedPassword *string `mapstructure:"elevated_password" cty:"elevated_password"` @@ -58,10 +58,10 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "scripts": &hcldec.AttrSpec{Name: "scripts", Type: cty.List(cty.String), Required: false}, "valid_exit_codes": &hcldec.AttrSpec{Name: "valid_exit_codes", Type: cty.List(cty.Number), Required: false}, "environment_vars": &hcldec.AttrSpec{Name: "environment_vars", Type: cty.List(cty.String), Required: false}, + "env_var_format": &hcldec.AttrSpec{Name: "env_var_format", Type: cty.String, Required: false}, "remote_env_var_path": &hcldec.AttrSpec{Name: "remote_env_var_path", Type: cty.String, Required: false}, "elevated_execute_command": &hcldec.AttrSpec{Name: "elevated_execute_command", Type: cty.String, Required: false}, "start_retry_timeout": &hcldec.AttrSpec{Name: "start_retry_timeout", Type: cty.String, Required: false}, - "env_var_format": &hcldec.AttrSpec{Name: "env_var_format", Type: cty.String, Required: false}, "elevated_env_var_format": &hcldec.AttrSpec{Name: "elevated_env_var_format", Type: cty.String, Required: false}, "elevated_user": &hcldec.AttrSpec{Name: "elevated_user", Type: cty.String, Required: false}, "elevated_password": &hcldec.AttrSpec{Name: "elevated_password", Type: cty.String, Required: false}, diff --git a/provisioner/shell/provisioner.go b/provisioner/shell/provisioner.go index 685b514ec..7c0bce800 100644 --- a/provisioner/shell/provisioner.go +++ b/provisioner/shell/provisioner.go @@ -57,9 +57,10 @@ type Config struct { ExpectDisconnect bool `mapstructure:"expect_disconnect"` - ctx interpolate.Context // name of the tmp environment variable file, if UseEnvVarFile is true envVarFile string + + ctx interpolate.Context } type Provisioner struct { @@ -82,10 +83,19 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { }, }, }, raws...) + if err != nil { return err } + if p.config.EnvVarFormat == "" { + p.config.EnvVarFormat = "%s='%s' " + + if p.config.UseEnvVarFile == true { + p.config.EnvVarFormat = "export %s='%s'\n" + } + } + if p.config.ExecuteCommand == "" { p.config.ExecuteCommand = "chmod +x {{.Path}}; {{.Vars}} {{.Path}}" if p.config.UseEnvVarFile == true { @@ -430,21 +440,22 @@ func (p *Provisioner) escapeEnvVars() ([]string, map[string]string) { func (p *Provisioner) createEnvVarFileContent() string { keys, envVars := p.escapeEnvVars() - flattened := "" - // Re-assemble vars surrounding value with single quotes and flatten + var flattened string for _, key := range keys { - flattened += fmt.Sprintf("export %s='%s'\n", key, envVars[key]) + flattened += fmt.Sprintf(p.config.EnvVarFormat, key, envVars[key]) } return flattened } -func (p *Provisioner) createFlattenedEnvVars() (flattened string) { +func (p *Provisioner) createFlattenedEnvVars() string { keys, envVars := p.escapeEnvVars() - // Re-assemble vars surrounding value with single quotes and flatten + // Re-assemble vars into specified format and flatten + var flattened string for _, key := range keys { - flattened += fmt.Sprintf("%s='%s' ", key, envVars[key]) + flattened += fmt.Sprintf(p.config.EnvVarFormat, key, envVars[key]) } - return + + return flattened } diff --git a/provisioner/shell/provisioner.hcl2spec.go b/provisioner/shell/provisioner.hcl2spec.go index 9f1fbc75c..2b59e6436 100644 --- a/provisioner/shell/provisioner.hcl2spec.go +++ b/provisioner/shell/provisioner.hcl2spec.go @@ -24,6 +24,7 @@ type FlatConfig struct { Scripts []string `cty:"scripts"` ValidExitCodes []int `mapstructure:"valid_exit_codes" cty:"valid_exit_codes"` Vars []string `mapstructure:"environment_vars" cty:"environment_vars"` + EnvVarFormat *string `mapstructure:"env_var_format" cty:"env_var_format"` InlineShebang *string `mapstructure:"inline_shebang" cty:"inline_shebang"` PauseAfter *string `mapstructure:"pause_after" cty:"pause_after"` UseEnvVarFile *bool `mapstructure:"use_env_var_file" cty:"use_env_var_file"` @@ -58,6 +59,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "scripts": &hcldec.AttrSpec{Name: "scripts", Type: cty.List(cty.String), Required: false}, "valid_exit_codes": &hcldec.AttrSpec{Name: "valid_exit_codes", Type: cty.List(cty.Number), Required: false}, "environment_vars": &hcldec.AttrSpec{Name: "environment_vars", Type: cty.List(cty.String), Required: false}, + "env_var_format": &hcldec.AttrSpec{Name: "env_var_format", Type: cty.String, Required: false}, "inline_shebang": &hcldec.AttrSpec{Name: "inline_shebang", Type: cty.String, Required: false}, "pause_after": &hcldec.AttrSpec{Name: "pause_after", Type: cty.String, Required: false}, "use_env_var_file": &hcldec.AttrSpec{Name: "use_env_var_file", Type: cty.Bool, Required: false}, diff --git a/provisioner/shell/provisioner_test.go b/provisioner/shell/provisioner_test.go index fd3914383..9d4a8847c 100644 --- a/provisioner/shell/provisioner_test.go +++ b/provisioner/shell/provisioner_test.go @@ -286,6 +286,45 @@ func TestProvisioner_createFlattenedEnvVars(t *testing.T) { } } +func TestProvisioner_createFlattenedEnvVars_withEnvVarFormat(t *testing.T) { + var flattenedEnvVars string + config := testConfig() + + userEnvVarTests := [][]string{ + {}, // No user env var + {"FOO=bar"}, // Single user env var + {"FOO=bar's"}, // User env var with single quote in value + {"FOO=bar", "BAZ=qux"}, // Multiple user env vars + {"FOO=bar=baz"}, // User env var with value containing equals + {"FOO==bar"}, // User env var with value starting with equals + } + expected := []string{ + `PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, + `FOO=bar PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, + `FOO=bar'"'"'s PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, + `BAZ=qux FOO=bar PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, + `FOO=bar=baz PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, + `FOO==bar PACKER_BUILDER_TYPE=iso PACKER_BUILD_NAME=vmware `, + } + + p := new(Provisioner) + + p.config.EnvVarFormat = "%s=%s " + p.Prepare(config) + + // Defaults provided by Packer + p.config.PackerBuildName = "vmware" + p.config.PackerBuilderType = "iso" + + for i, expectedValue := range expected { + p.config.Vars = userEnvVarTests[i] + flattenedEnvVars = p.createFlattenedEnvVars() + if flattenedEnvVars != expectedValue { + t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars) + } + } +} + func TestProvisioner_createEnvVarFileContent(t *testing.T) { var flattenedEnvVars string config := testConfig() @@ -326,6 +365,7 @@ export PACKER_BUILD_NAME='vmware' } p := new(Provisioner) + p.config.UseEnvVarFile = true p.Prepare(config) // Defaults provided by Packer @@ -341,6 +381,55 @@ export PACKER_BUILD_NAME='vmware' } } +func TestProvisioner_createEnvVarFileContent_withEnvVarFormat(t *testing.T) { + var flattenedEnvVars string + config := testConfig() + + userEnvVarTests := [][]string{ + {}, // No user env var + {"FOO=bar", "BAZ=qux"}, // Multiple user env vars + {"FOO=bar=baz"}, // User env var with value containing equals + {"FOO==bar"}, // User env var with value starting with equals + } + expected := []string{ + `PACKER_BUILDER_TYPE=iso +PACKER_BUILD_NAME=vmware +`, + `BAZ=qux +FOO=bar +PACKER_BUILDER_TYPE=iso +PACKER_BUILD_NAME=vmware +`, + `FOO=bar=baz +PACKER_BUILDER_TYPE=iso +PACKER_BUILD_NAME=vmware +`, + `FOO==bar +PACKER_BUILDER_TYPE=iso +PACKER_BUILD_NAME=vmware +`, + } + + p := new(Provisioner) + + p.config.UseEnvVarFile = true + //User provided env_var_format without export prefix + p.config.EnvVarFormat = "%s=%s\n" + p.Prepare(config) + + // Defaults provided by Packer + p.config.PackerBuildName = "vmware" + p.config.PackerBuilderType = "iso" + + for i, expectedValue := range expected { + p.config.Vars = userEnvVarTests[i] + flattenedEnvVars = p.createEnvVarFileContent() + if flattenedEnvVars != expectedValue { + t.Fatalf("expected flattened env vars to be: %q, got %q.", expectedValue, flattenedEnvVars) + } + } +} + func TestProvisioner_RemoteFolderSetSuccessfully(t *testing.T) { config := testConfig() diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index a2927017c..8397c3491 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -37,10 +37,6 @@ type Config struct { // This can be set high to allow for reboots. StartRetryTimeout time.Duration `mapstructure:"start_retry_timeout"` - // This is used in the template generation to format environment variables - // inside the `ExecuteCommand` template. - EnvVarFormat string `mapstructure:"env_var_format"` - ctx interpolate.Context } diff --git a/provisioner/windows-shell/provisioner.hcl2spec.go b/provisioner/windows-shell/provisioner.hcl2spec.go index 3258045c9..6bfd990d3 100644 --- a/provisioner/windows-shell/provisioner.hcl2spec.go +++ b/provisioner/windows-shell/provisioner.hcl2spec.go @@ -24,8 +24,8 @@ type FlatConfig struct { Scripts []string `cty:"scripts"` ValidExitCodes []int `mapstructure:"valid_exit_codes" cty:"valid_exit_codes"` Vars []string `mapstructure:"environment_vars" cty:"environment_vars"` - StartRetryTimeout *string `mapstructure:"start_retry_timeout" cty:"start_retry_timeout"` EnvVarFormat *string `mapstructure:"env_var_format" cty:"env_var_format"` + StartRetryTimeout *string `mapstructure:"start_retry_timeout" cty:"start_retry_timeout"` } // FlatMapstructure returns a new FlatConfig. @@ -52,8 +52,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "scripts": &hcldec.AttrSpec{Name: "scripts", Type: cty.List(cty.String), Required: false}, "valid_exit_codes": &hcldec.AttrSpec{Name: "valid_exit_codes", Type: cty.List(cty.Number), Required: false}, "environment_vars": &hcldec.AttrSpec{Name: "environment_vars", Type: cty.List(cty.String), Required: false}, - "start_retry_timeout": &hcldec.AttrSpec{Name: "start_retry_timeout", Type: cty.String, Required: false}, "env_var_format": &hcldec.AttrSpec{Name: "env_var_format", Type: cty.String, Required: false}, + "start_retry_timeout": &hcldec.AttrSpec{Name: "start_retry_timeout", Type: cty.String, Required: false}, } return s } diff --git a/website/source/docs/provisioners/shell.html.md.erb b/website/source/docs/provisioners/shell.html.md.erb index a9eed5300..7c17f4437 100644 --- a/website/source/docs/provisioners/shell.html.md.erb +++ b/website/source/docs/provisioners/shell.html.md.erb @@ -40,6 +40,11 @@ The example below is fully functional. Packer injects some environmental variables by default into the environment, as well, which are covered in the section below. +- `env_var_format` (string) - When we parse the environment\_vars that you + provide, this gives us a string template to use in order to make sure that + we are setting the environment vars correctly. By default it is `"%s='%s' "`. + When used in conjunction with `use_env_var_file` the default is `"export %s='%s'\n"` + - `use_env_var_file` (boolean) - If true, Packer will write your environment variables to a tempfile and source them from that file, rather than declaring them inline in our execute\_command. The default