Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2362 from hashicorp/feature/var-env
Browse files Browse the repository at this point in the history
Support specifying custom environment variables for input variable default values
  • Loading branch information
mitchellh authored Sep 28, 2021
2 parents 37973d7 + 41e7264 commit 6aa435d
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 6 deletions.
4 changes: 4 additions & 0 deletions .changelog/2362.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:improvement
config: input variables (`variable`) can now use an `env` key to specify
alternate environment variable names to source variable values from
```
8 changes: 7 additions & 1 deletion internal/config/variables/testdata/valid.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ variable "is_good" {
variable "whatdoesittaketobenumber" {
default = 1
type = number
}
}

variable "envs" {
default = 1
type = number
env = ["foo", "bar"]
}
51 changes: 51 additions & 0 deletions internal/config/variables/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ var (
{
Name: "description",
},
{
Name: "env",
},
},
}
)
Expand All @@ -70,6 +73,10 @@ type Variable struct {
// The default value in the variable definition
Default *Value

// A list of environment variables that will be sourced to satisfy
// the value of this variable.
Env []string

// Cty Type of the variable. If the default value or a collected value is
// not of this type nor can be converted to this type an error diagnostic
// will show up. This allows us to assume that values are valid.
Expand All @@ -95,6 +102,7 @@ type HclVariable struct {
Default cty.Value `hcl:"default,optional"`
Type hcl.Expression `hcl:"type,optional"`
Description string `hcl:"description,optional"`
Env []string `hcl:"env,optional"`
}

// Values are used to store values collected from various sources.
Expand Down Expand Up @@ -177,6 +185,14 @@ func decodeVariableBlock(block *hcl.Block) (*Variable, hcl.Diagnostics) {
v.Type = t
}

if attr, exists := content.Attributes["env"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Env)
diags = append(diags, valDiags...)
if diags.HasErrors() {
return nil, diags
}
}

if attr, exists := content.Attributes["default"]; exists {
val, valDiags := attr.Expr.Value(nil)
diags = append(diags, valDiags...)
Expand Down Expand Up @@ -278,6 +294,41 @@ func LoadVariableValues(vars map[string]string, files []string) ([]*pb.Variable,
return ret, diags
}

// LoadEnvValues loads the variable values from environment variables
// specified via the `env` field on the `variable` stanza.
func LoadEnvValues(vars map[string]*Variable) ([]*pb.Variable, hcl.Diagnostics) {
var diags hcl.Diagnostics
var ret []*pb.Variable

for _, variable := range vars {
// First we check for the WP_VAR_ value cause that always wins.
v := os.Getenv(varEnvPrefix + variable.Name)

// If we didn't find one and we have other sources, check those.
if v == "" && len(variable.Env) > 0 {
for _, env := range variable.Env {
v = os.Getenv(env)
if v != "" {
break
}
}
}

// If we still have no value, then we set nothing.
if v == "" {
continue
}

ret = append(ret, &pb.Variable{
Name: variable.Name,
Value: &pb.Variable_Str{Str: v},
Source: &pb.Variable_Env{},
})
}

return ret, diags
}

// EvaluateVariables evaluates the provided variable values and validates their
// types per the type declared in the waypoint.hcl for that variable name.
// The order in which values are evaluated corresponds to their precedence, with
Expand Down
93 changes: 92 additions & 1 deletion internal/config/variables/variables_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package variables

import (
"os"
"path/filepath"
"testing"

Expand Down Expand Up @@ -63,7 +64,7 @@ func TestVariables_DecodeVariableBlock(t *testing.T) {
}

if tt.err == "" {
require.False(diags.HasErrors())
require.False(diags.HasErrors(), diags.Error())
return
}

Expand Down Expand Up @@ -407,6 +408,96 @@ func TestVariables_SetJobInputVariables(t *testing.T) {
}
}

func TestLoadEnvValues(t *testing.T) {
cases := []struct {
name string
vars map[string]*Variable
env map[string]string
expected map[string]string
}{
{
"WP_VAR_ always wins",
map[string]*Variable{
"foo": {
Name: "foo",
Env: []string{"one", "two"},
},
},
map[string]string{"WP_VAR_foo": "x", "one": "1", "two": "2"},
map[string]string{"foo": "x"},
},

{
"first match takes priority",
map[string]*Variable{
"foo": {
Name: "foo",
Env: []string{"one", "two"},
},
},
map[string]string{"one": "1", "two": "2"},
map[string]string{"foo": "1"},
},

{
"first match takes priority (second set)",
map[string]*Variable{
"foo": {
Name: "foo",
Env: []string{"one", "two"},
},
},
map[string]string{"two": "2"},
map[string]string{"foo": "2"},
},

{
"none set",
map[string]*Variable{
"foo": {
Name: "foo",
Env: []string{"one", "two"},
},
},
map[string]string{},
map[string]string{},
},

{
"env key not set",
map[string]*Variable{
"foo": {
Name: "foo",
},
},
map[string]string{"one": "1", "two": "2"},
map[string]string{},
},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)

// Set our env vars
for k, v := range tt.env {
defer os.Setenv(k, os.Getenv(k))
require.NoError(os.Setenv(k, v))
}

actual, diags := LoadEnvValues(tt.vars)
require.False(diags.HasErrors(), diags.Error())

actualMap := map[string]string{}
for _, v := range actual {
actualMap[v.Name] = v.Value.(*pb.Variable_Str).Str
}

require.Equal(tt.expected, actualMap)
})
}
}

// helper functions
var ctyValueComparer = cmp.Comparer(func(x, y cty.Value) bool {
return x.RawEquals(y)
Expand Down
7 changes: 7 additions & 0 deletions internal/runner/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ func (r *Runner) executeJob(
return nil, err
}

// Load variable values from the environment.
envVars, diags := variables.LoadEnvValues(cfg.InputVariables)
if diags.HasErrors() {
return nil, diags
}

// Here we'll load our values from auto vars files and the server/UI, and
// combine them with any values set on the job
// The order values are added to our final pbVars slice is the order
Expand All @@ -159,6 +165,7 @@ func (r *Runner) executeJob(
}

pbVars := resp.Project.GetVariables()
pbVars = append(pbVars, envVars...)
pbVars = append(pbVars, vcsVars...)
pbVars = append(pbVars, job.Variables...)

Expand Down
5 changes: 5 additions & 0 deletions website/content/docs/waypoint-hcl/variable.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ variable "tag" {
Supply a value to a specified `variable` with one of the following methods:

- the [`default`][inpage-default] value in the definition block
- an environment variable on the runner specified by `env` in the definition block
- the parameter `-var key=value`
- the parameter `-var-file=filename`, where `filename` is the name of a file
with variable values
Expand All @@ -57,4 +58,8 @@ Supply a value to a specified `variable` with one of the following methods:
- `description` `(string: "")` - A short summary documenting what the variable
is and its purpose.

- `env` `(list of string: [])` - Sets a set of environment variables to source
a default value from if a value is not set. The environment variables are read
only on the runner. See [environment variables](/docs/waypoint-hcl/variables/input#environment-variables) for more information.

[expression]: /docs/waypoint-hcl/syntax/expressions#types-and-values 'Expressions: Types and Values'
32 changes: 28 additions & 4 deletions website/content/docs/waypoint-hcl/variables/input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
layout: docs
page_title: Input Variables - Variables - waypoint.hcl
description: |-
Input variables allow you to customize Waypoint configuration according to
Input variables allow you to customize Waypoint configuration according to
set parameters. This section documents input variable syntax, including how to
define, reference, and use input variables.
---
Expand All @@ -29,6 +29,7 @@ blocks include:
variable "port" {
type = number
default = 8080
env = ["PORT"]
}
variable "image_name" {
Expand Down Expand Up @@ -62,6 +63,7 @@ Waypoint CLI defines the following optional arguments for variable declarations:
- [`default`][inpage-default] - A default value.
- [`type`][inpage-type] - This specifies what value types are accepted for the variable.
- [`description`][inpage-description] - This specifies the input variable's documentation.
- [`env`][inpage-env] - Default value sourced from an environment variable.

### Default values

Expand Down Expand Up @@ -273,9 +275,26 @@ the root object properties corresponding to variable names:

### Environment Variables

As a fallback for the other ways of defining variables, Waypoint searches
the environment of its own process for environment variables named `WP_VAR_`
followed by the name of a declared variable.
[inpage-env]: #environment-variables

If a variable specifies a value for the `env` field, environment variables
of those names are searched to provide a value for the variable. The variables
are searched in order, and the first non-empty environment variable is
used. The special environment variable named `WP_VAR_<name>` (where `<name>`
is the name of the variable) always works regardless of if `env` is set or not.

For example, given the following variable:

```hcl
variable "port" {
type = number
default = 8080
env = ["PORT"]
}
```

Waypoint will search for a value in `WP_VAR_port` followed by `PORT`.
If neither are set, the default `8080` is used as specified in the stanza.

This can be useful when running Waypoint in automation, or when running a
sequence of Waypoint commands in succession with the same variables.
Expand All @@ -292,6 +311,11 @@ Waypoint matches the variable name exactly as given in configuration, and
so the required environment variable name will usually have a mix of upper
and lower case letters as in the above example.

-> **Note:** Environment variables specified with `env` are only loaded
as default values on the Waypoint [runner](/docs/runner). For local operations
this is the same machine as the CLI but for production use cases this might
be a remote machine.

### Variables in the UI

To assign variable values in the UI, navigate to the Project Settings page and
Expand Down

0 comments on commit 6aa435d

Please sign in to comment.