diff --git a/CHANGELOG.md b/CHANGELOG.md index fa02033dd..9decaef09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ FEATURES: an existing OVF or OVA. [GH-201] * **New builder:** "vmware-vmx" can build VMware images from an existing VMX. [GH-201] +* Environmental variables can now be accessed as default values for + user variables using the "env" function. See the documentation for more + information. * "description" field in templates: write a human-readable description of what a template does. This will be shown in `packer inspect`. * Vagrant post-processor now accepts a list of files to include in the diff --git a/packer/config_template.go b/packer/config_template.go index c60afdd1a..d76e3e672 100644 --- a/packer/config_template.go +++ b/packer/config_template.go @@ -37,6 +37,7 @@ func NewConfigTemplate() (*ConfigTemplate, error) { result.root = template.New("configTemplateRoot") result.root.Funcs(template.FuncMap{ + "env": templateDisableEnv, "pwd": templatePwd, "isotime": templateISOTime, "timestamp": templateTimestamp, @@ -95,6 +96,20 @@ func (t *ConfigTemplate) templateUser(n string) (string, error) { return result, nil } +func templateDisableEnv(n string) (string, error) { + return "", fmt.Errorf( + "Environmental variables can only be used as default values for user variables.") +} + +func templateDisableUser(n string) (string, error) { + return "", fmt.Errorf( + "User variable can't be used within a default value for a user variable: %s", n) +} + +func templateEnv(n string) string { + return os.Getenv(n) +} + func templateISOTime() string { return time.Now().UTC().Format(time.RFC3339) } diff --git a/packer/config_template_test.go b/packer/config_template_test.go index f5e7394fb..8604f2c43 100644 --- a/packer/config_template_test.go +++ b/packer/config_template_test.go @@ -8,6 +8,18 @@ import ( "time" ) +func TestConfigTemplateProcess_env(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + _, err = tpl.Process(`{{env "foo"}}`, nil) + if err == nil { + t.Fatal("should error") + } +} + func TestConfigTemplateProcess_isotime(t *testing.T) { tpl, err := NewConfigTemplate() if err != nil { diff --git a/packer/template.go b/packer/template.go index 4a3170427..3ba9ad483 100644 --- a/packer/template.go +++ b/packer/template.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "os" "sort" + "text/template" "time" ) @@ -450,6 +451,18 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err return } + // Prepare the variable template processor, which is a bit unique + // because we don't allow user variable usage and we add a function + // to read from the environment. + varTpl, err := NewConfigTemplate() + if err != nil { + return nil, err + } + varTpl.Funcs(template.FuncMap{ + "env": templateEnv, + "user": templateDisableUser, + }) + // Prepare the variables var varErrors []error variables := make(map[string]string) @@ -459,9 +472,15 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err fmt.Errorf("Required user variable '%s' not set", k)) } - var val string = v.Default + var val string if v.HasValue { val = v.Value + } else { + val, err = varTpl.Process(v.Default, nil) + if err != nil { + varErrors = append(varErrors, + fmt.Errorf("Error processing user variable '%s': %s'", k, err)) + } } variables[k] = val diff --git a/packer/template_test.go b/packer/template_test.go index 2d17bb3e7..eff68612d 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -688,6 +688,47 @@ func TestTemplate_BuildUnknownBuilder(t *testing.T) { } } +func TestTemplateBuild_envInVars(t *testing.T) { + data := ` + { + "variables": { + "foo": "{{env \"foo\"}}" + }, + + "builders": [ + { + "name": "test1", + "type": "test-builder" + } + ] + } + ` + + defer os.Setenv("foo", os.Getenv("foo")) + if err := os.Setenv("foo", "bar"); err != nil { + t.Fatalf("err: %s", err) + } + + template, err := ParseTemplate([]byte(data), map[string]string{}) + if err != nil { + t.Fatalf("err: %s", err) + } + + b, err := template.Build("test1", testComponentFinder()) + if err != nil { + t.Fatalf("err: %s", err) + } + + coreBuild, ok := b.(*coreBuild) + if !ok { + t.Fatal("should be ok") + } + + if coreBuild.variables["foo"] != "bar" { + t.Fatalf("bad: %#v", coreBuild.variables) + } +} + func TestTemplateBuild_names(t *testing.T) { data := ` { diff --git a/website/source/docs/templates/user-variables.html.markdown b/website/source/docs/templates/user-variables.html.markdown index 0eba9de95..f1ab19c5c 100644 --- a/website/source/docs/templates/user-variables.html.markdown +++ b/website/source/docs/templates/user-variables.html.markdown @@ -58,6 +58,36 @@ This function can be used in _any value_ within the template, in builders, provisioners, _anything_. The user variable is available globally within the template. +## Environmental Variables + +Environmental variables can be used within your template using user +variables. The `env` function is available _only_ within the default value +of a user variable, allowing you to default a user variable to an +environmental variable. An example is shown below: + +
+{ + "variables": { + "my_secret": "{{env `MY_SECRET`}}", + }, + + ... +} ++ +This will default "my\_secret" to be the value of the "MY\_SECRET" +environmental variable (or the empty string if it does not exist). + +
packer inspect
.
+