/
input_value.go
161 lines (137 loc) · 4.61 KB
/
input_value.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package terraform
import (
"fmt"
"log"
"os"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
type InputValue struct {
Value cty.Value
}
type InputValues map[string]*InputValue
func (vv InputValues) Override(others ...InputValues) InputValues {
ret := make(InputValues)
for k, v := range vv {
ret[k] = v
}
for _, other := range others {
for k, v := range other {
ret[k] = v
}
}
return ret
}
// DefaultVariableValues returns InputValues using the default values
// of variables declared in the configuration.
func DefaultVariableValues(configs map[string]*Variable) InputValues {
ret := make(InputValues)
for k, c := range configs {
val := c.Default
// cty.NilVal means no default declared in the variable. Terraform collects this value interactively,
// while TFLint marks it as unknown and continues inspection.
if c.Default == cty.NilVal {
val = cty.UnknownVal(c.Type)
}
ret[k] = &InputValue{
Value: val,
}
}
return ret
}
// EnvironmentVariableValues looks up `TF_VAR_*` env variables and returns InputValues.
// Declared variables are required because the parsing mode of the variable value is type-dependent.
// However, in the case of environment variables, no error is returned even if the variable is not declared.
func EnvironmentVariableValues(declVars map[string]*Variable) (InputValues, hcl.Diagnostics) {
envVariables := make(InputValues)
var diags hcl.Diagnostics
for _, e := range os.Environ() {
idx := strings.Index(e, "=")
envKey := e[:idx]
envVal := e[idx+1:]
if strings.HasPrefix(envKey, "TF_VAR_") {
log.Printf("[INFO] TF_VAR_* environment variable found: key=%s", envKey)
varName := strings.Replace(envKey, "TF_VAR_", "", 1)
var mode VariableParsingMode
declVar, declared := declVars[varName]
if declared {
mode = declVar.ParsingMode
} else {
mode = VariableParseLiteral
}
val, parseDiags := mode.Parse(varName, envVal)
if parseDiags.HasErrors() {
diags = diags.Extend(parseDiags)
continue
}
envVariables[varName] = &InputValue{
Value: val,
}
}
}
return envVariables, diags
}
// ParseVariableValues parses the variable values passed as CLI flags and returns InputValues.
// Declared variables are required because the parsing mode of the variable value is type-dependent.
// Return an error if the variable is not declared.
func ParseVariableValues(vars []string, declVars map[string]*Variable) (InputValues, hcl.Diagnostics) {
variables := make(InputValues)
var diags hcl.Diagnostics
for _, raw := range vars {
idx := strings.Index(raw, "=")
if idx == -1 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Subject: &hcl.Range{Filename: "<input-value>", Start: hcl.InitialPos, End: hcl.InitialPos},
Summary: "invalid variable value format",
Detail: fmt.Sprintf(`"%s" is invalid. Variables must be "key=value" format`, raw),
})
continue
}
name := raw[:idx]
rawVal := raw[idx+1:]
var mode VariableParsingMode
declVar, declared := declVars[name]
if declared {
mode = declVar.ParsingMode
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Subject: &hcl.Range{Filename: fmt.Sprintf("<value for var.%s>", name), Start: hcl.InitialPos, End: hcl.InitialPos},
Summary: "Value for undeclared variable",
Detail: fmt.Sprintf("A variable named %q was assigned, but the root module does not declare a variable of that name.", name),
})
continue
}
val, parseDiags := mode.Parse(name, rawVal)
if parseDiags.HasErrors() {
diags = diags.Extend(parseDiags)
continue
}
variables[name] = &InputValue{
Value: val,
}
}
return variables, diags
}
// VariableValues returns a value map based on configuration, environment variables,
// and external input values. External input values take precedence over configuration defaults,
// environment variables, and the last one passed takes precedence.
func VariableValues(config *Config, values ...InputValues) (map[string]map[string]cty.Value, hcl.Diagnostics) {
moduleKey := config.Path.UnkeyedInstanceShim().String()
variableValues := make(map[string]map[string]cty.Value)
variableValues[moduleKey] = make(map[string]cty.Value)
variables := DefaultVariableValues(config.Module.Variables)
envVars, diags := EnvironmentVariableValues(config.Module.Variables)
if diags.HasErrors() {
return variableValues, diags
}
overrideVariables := variables.Override(envVars).Override(values...)
for k, iv := range overrideVariables {
variableValues[moduleKey][k] = iv.Value
}
return variableValues, nil
}