diff --git a/command/inspect_test.go b/command/inspect_test.go index 5fda6c0d1..4bb594d07 100644 --- a/command/inspect_test.go +++ b/command/inspect_test.go @@ -78,7 +78,9 @@ local.fruit: "banana" > input-variables: +var.default_from_env: "" var.fruit: "peach" +var.other_default_from_env: "" var.unknown_list_of_string: "[\n \"first_peach\",\n \"second_peach\",\n]" var.unknown_string: "also_peach" var.unknown_unknown: "[\"peach_too\"]" @@ -89,11 +91,13 @@ var.unknown_unknown: "[\"peach_too\"]" > builds: `}, - {[]string{"inspect", "-var=fruit=peach", filepath.Join(testFixture("hcl"), "inspect")}, nil, `Packer Inspect: HCL2 mode + {[]string{"inspect", "-var=fruit=peach", "-var=other_default_from_env=apple", filepath.Join(testFixture("hcl"), "inspect")}, []string{"DEFAULT_FROM_ENV=cherry"}, `Packer Inspect: HCL2 mode > input-variables: +var.default_from_env: "cherry" var.fruit: "peach" +var.other_default_from_env: "apple" var.unknown_list_of_string: "" var.unknown_string: "" var.unknown_unknown: "" diff --git a/command/test-fixtures/hcl/inspect/fruit_string.pkr.hcl b/command/test-fixtures/hcl/inspect/fruit_string.pkr.hcl index fb904c368..51bc427f7 100644 --- a/command/test-fixtures/hcl/inspect/fruit_string.pkr.hcl +++ b/command/test-fixtures/hcl/inspect/fruit_string.pkr.hcl @@ -15,3 +15,11 @@ variable "unknown_list_of_string" { variable "unknown_unknown" { } + +variable "default_from_env" { + default = env("DEFAULT_FROM_ENV") +} + +variable "other_default_from_env" { + default = env("OTHER_DEFAULT_FROM_ENV") +} diff --git a/hcl2template/function/env.go b/hcl2template/function/env.go new file mode 100644 index 000000000..98eac11cc --- /dev/null +++ b/hcl2template/function/env.go @@ -0,0 +1,32 @@ +package function + +import ( + "os" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// EnvFunc constructs a function that returns a string representation of the +// env var behind a value +var EnvFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "key", + Type: cty.String, + AllowNull: false, + AllowUnknown: false, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + key := args[0].AsString() + value := os.Getenv(key) + return cty.StringVal(value), nil + }, +}) + +// Env returns a string representation of the env var behind key. +func Env(key cty.Value) (cty.Value, error) { + return EnvFunc.Call([]cty.Value{key}) +} diff --git a/hcl2template/types.packer_config.go b/hcl2template/types.packer_config.go index daf80a209..b642b826a 100644 --- a/hcl2template/types.packer_config.go +++ b/hcl2template/types.packer_config.go @@ -8,9 +8,11 @@ import ( "github.com/gobwas/glob" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + pkrfunction "github.com/hashicorp/packer/hcl2template/function" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/version" "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" ) // PackerConfig represents a loaded Packer HCL config. It will contain @@ -107,16 +109,23 @@ func (c *PackerConfig) decodeInputVariables(f *hcl.File) hcl.Diagnostics { content, moreDiags := f.Body.Content(configSchema) diags = append(diags, moreDiags...) + // for input variables we allow to use env in the default value section. + ectx := &hcl.EvalContext{ + Functions: map[string]function.Function{ + "env": pkrfunction.EnvFunc, + }, + } + for _, block := range content.Blocks { switch block.Type { case variableLabel: - moreDiags := c.InputVariables.decodeVariableBlock(block, nil) + moreDiags := c.InputVariables.decodeVariableBlock(block, ectx) diags = append(diags, moreDiags...) case variablesLabel: attrs, moreDiags := block.Body.JustAttributes() diags = append(diags, moreDiags...) for key, attr := range attrs { - moreDiags = c.InputVariables.decodeVariable(key, attr, nil) + moreDiags = c.InputVariables.decodeVariable(key, attr, ectx) diags = append(diags, moreDiags...) } } diff --git a/hcl2template/types.variables.go b/hcl2template/types.variables.go index 6e09893ef..ede4a014c 100644 --- a/hcl2template/types.variables.go +++ b/hcl2template/types.variables.go @@ -246,7 +246,8 @@ var variableBlockSchema = &hcl.BodySchema{ }, } -// decodeVariableBlock decodes a "variables" section the way packer 1 used to +// decodeVariableBlock decodes a "variable" block +// ectx is passed only in the evaluation of the default value. func (variables *Variables) decodeVariableBlock(block *hcl.Block, ectx *hcl.EvalContext) hcl.Diagnostics { if (*variables) == nil { (*variables) = Variables{} @@ -473,6 +474,7 @@ func decodeVariableValidationBlock(varName string, block *hcl.Block) (*VariableV // Packer's specific style, rather than that they are going to try to work // around these rules to write a lower-quality message. func looksLikeSentences(s string) bool { + s = strings.TrimSpace(s) if len(s) < 1 { return false } diff --git a/website/data/docs-navigation.js b/website/data/docs-navigation.js index 8400ff6ed..efc9e70ff 100644 --- a/website/data/docs-navigation.js +++ b/website/data/docs-navigation.js @@ -35,6 +35,7 @@ export default [ content: [ 'aws_secretsmanager', 'consul', + 'env', 'vault', ], }, diff --git a/website/pages/docs/from-1.5/functions/contextual/env.mdx b/website/pages/docs/from-1.5/functions/contextual/env.mdx new file mode 100644 index 000000000..347ea3374 --- /dev/null +++ b/website/pages/docs/from-1.5/functions/contextual/env.mdx @@ -0,0 +1,52 @@ +--- +layout: docs +page_title: env - Functions - Configuration Language +sidebar_title: env +description: The env function retrieves environment values for input variables. +--- + +# `env` Function + +```hcl +variable "aws_region" { + default = env("AWS_DEFAULT_REGION") +} +``` + +`env` allows you to get the value for an environment variable inside input +variables _only_. This is the only function that is callable from a variable +block and it can only be used in the default input. `env` cannot be called from +other places. + +In the previous example, the value of `aws_region` will be what's stored in the +`AWS_DEFAULT_REGION` env var, unless aws_region is also set in a [manner that takes +precedence](/docs/from-1.5/variables#variable-definition-precedence). + + +-> **Why can't I use environment variables elsewhere?** User variables are the +single source of configurable input. We felt that having environment variables +used _anywhere_ in a configuration would confuse the user about the possible inputs +to a template. By allowing environment variables only within default values for +input variables, input variables remain as the single source of input to a +template that a user can easily discover using `packer inspect`. + +When the environment variable is not set at all -- not even with the empty +string -- the value returned by `env` will be an an empty string. It will still +be possible to set it using other means but you could use [custom validation +rules](/docs/from-1.5/variables#custom-validation-rules) to error in that case +to make sure it is set, for example: + +```hcl +variable "aws_region" { + default = env("AWS_DEFAULT_REGION") + + validation { + condition = length(var.aws_region) > 0 + error_message = <