HCL2: allow calling env as input var default value (#10240)

* HCL2: allow to use env in default value of input variables
This commit is contained in:
Adrien Delorme 2020-11-11 11:27:32 +01:00 committed by GitHub
parent 17ec88246f
commit deba1484ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 4 deletions

View File

@ -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: "<unknown>"
var.unknown_string: "<unknown>"
var.unknown_unknown: "<unknown>"

View File

@ -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")
}

View File

@ -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})
}

View File

@ -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...)
}
}

View File

@ -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
}

View File

@ -35,6 +35,7 @@ export default [
content: [
'aws_secretsmanager',
'consul',
'env',
'vault',
],
},

View File

@ -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 = <<EOF
The aws_region var is not set: make sure to at least set the AWS_DEFAULT_REGION env var.
To fix this you could also set the aws_region variable from the arguments, for example:
$ packer build -var=aws_region=us-something-1...
EOF
}
}
```