]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/eval_variable.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_variable.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
4 "fmt"
5 "log"
6 "reflect"
bae9f6d2
JC
7 "strings"
8
107c1cdb
ND
9 "github.com/hashicorp/hcl2/hcl"
10 "github.com/hashicorp/terraform/configs"
11
12 "github.com/hashicorp/terraform/addrs"
13
bae9f6d2
JC
14 "github.com/hashicorp/terraform/config"
15 "github.com/hashicorp/terraform/config/module"
107c1cdb
ND
16 "github.com/zclconf/go-cty/cty"
17 "github.com/zclconf/go-cty/cty/convert"
bae9f6d2
JC
18)
19
20// EvalTypeCheckVariable is an EvalNode which ensures that the variable
21// values which are assigned as inputs to a module (including the root)
22// match the types which are either declared for the variables explicitly
23// or inferred from the default values.
24//
25// In order to achieve this three things are required:
26// - a map of the proposed variable values
27// - the configuration tree of the module in which the variable is
28// declared
29// - the path to the module (so we know which part of the tree to
30// compare the values against).
31type EvalTypeCheckVariable struct {
32 Variables map[string]interface{}
33 ModulePath []string
34 ModuleTree *module.Tree
35}
36
37func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
38 currentTree := n.ModuleTree
39 for _, pathComponent := range n.ModulePath[1:] {
40 currentTree = currentTree.Children()[pathComponent]
41 }
42 targetConfig := currentTree.Config()
43
44 prototypes := make(map[string]config.VariableType)
45 for _, variable := range targetConfig.Variables {
46 prototypes[variable.Name] = variable.Type()
47 }
48
49 // Only display a module in an error message if we are not in the root module
50 modulePathDescription := fmt.Sprintf(" in module %s", strings.Join(n.ModulePath[1:], "."))
51 if len(n.ModulePath) == 1 {
52 modulePathDescription = ""
53 }
54
55 for name, declaredType := range prototypes {
56 proposedValue, ok := n.Variables[name]
57 if !ok {
58 // This means the default value should be used as no overriding value
59 // has been set. Therefore we should continue as no check is necessary.
60 continue
61 }
62
63 if proposedValue == config.UnknownVariableValue {
64 continue
65 }
66
67 switch declaredType {
68 case config.VariableTypeString:
69 switch proposedValue.(type) {
70 case string:
71 continue
72 default:
73 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
74 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
75 }
76 case config.VariableTypeMap:
77 switch proposedValue.(type) {
78 case map[string]interface{}:
79 continue
80 default:
81 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
82 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
83 }
84 case config.VariableTypeList:
85 switch proposedValue.(type) {
86 case []interface{}:
87 continue
88 default:
89 return nil, fmt.Errorf("variable %s%s should be type %s, got %s",
90 name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue))
91 }
92 default:
93 return nil, fmt.Errorf("variable %s%s should be type %s, got type string",
94 name, modulePathDescription, declaredType.Printable())
95 }
96 }
97
98 return nil, nil
99}
100
107c1cdb
ND
101// EvalSetModuleCallArguments is an EvalNode implementation that sets values
102// for arguments of a child module call, for later retrieval during
103// expression evaluation.
104type EvalSetModuleCallArguments struct {
105 Module addrs.ModuleCallInstance
106 Values map[string]cty.Value
bae9f6d2
JC
107}
108
109// TODO: test
107c1cdb
ND
110func (n *EvalSetModuleCallArguments) Eval(ctx EvalContext) (interface{}, error) {
111 ctx.SetModuleCallArguments(n.Module, n.Values)
bae9f6d2
JC
112 return nil, nil
113}
114
107c1cdb
ND
115// EvalModuleCallArgument is an EvalNode implementation that produces the value
116// for a particular variable as will be used by a child module instance.
117//
118// The result is written into the map given in Values, with its key
119// set to the local name of the variable, disregarding the module instance
120// address. Any existing values in that map are deleted first. This weird
121// interface is a result of trying to be convenient for use with
122// EvalContext.SetModuleCallArguments, which expects a map to merge in with
123// any existing arguments.
124type EvalModuleCallArgument struct {
125 Addr addrs.InputVariable
126 Config *configs.Variable
127 Expr hcl.Expression
128
129 // If this flag is set, any diagnostics are discarded and this operation
130 // will always succeed, though may produce an unknown value in the
131 // event of an error.
132 IgnoreDiagnostics bool
133
134 Values map[string]cty.Value
bae9f6d2
JC
135}
136
107c1cdb 137func (n *EvalModuleCallArgument) Eval(ctx EvalContext) (interface{}, error) {
bae9f6d2 138 // Clear out the existing mapping
107c1cdb
ND
139 for k := range n.Values {
140 delete(n.Values, k)
bae9f6d2
JC
141 }
142
107c1cdb
ND
143 wantType := n.Config.Type
144 name := n.Addr.Name
145 expr := n.Expr
146
147 if expr == nil {
148 // Should never happen, but we'll bail out early here rather than
149 // crash in case it does. We set no value at all in this case,
150 // making a subsequent call to EvalContext.SetModuleCallArguments
151 // a no-op.
152 log.Printf("[ERROR] attempt to evaluate %s with nil expression", n.Addr.String())
153 return nil, nil
bae9f6d2
JC
154 }
155
107c1cdb
ND
156 val, diags := ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil)
157
158 // We intentionally passed DynamicPseudoType to EvaluateExpr above because
159 // now we can do our own local type conversion and produce an error message
160 // with better context if it fails.
161 var convErr error
162 val, convErr = convert.Convert(val, wantType)
163 if convErr != nil {
164 diags = diags.Append(&hcl.Diagnostic{
165 Severity: hcl.DiagError,
166 Summary: "Invalid value for module argument",
167 Detail: fmt.Sprintf(
168 "The given value is not suitable for child module variable %q defined at %s: %s.",
169 name, n.Config.DeclRange.String(), convErr,
170 ),
171 Subject: expr.Range().Ptr(),
172 })
173 // We'll return a placeholder unknown value to avoid producing
174 // redundant downstream errors.
175 val = cty.UnknownVal(wantType)
bae9f6d2
JC
176 }
177
107c1cdb
ND
178 n.Values[name] = val
179 if n.IgnoreDiagnostics {
180 return nil, nil
bae9f6d2 181 }
107c1cdb 182 return nil, diags.ErrWithWarnings()
bae9f6d2
JC
183}
184
185// hclTypeName returns the name of the type that would represent this value in
186// a config file, or falls back to the Go type name if there's no corresponding
187// HCL type. This is used for formatted output, not for comparing types.
188func hclTypeName(i interface{}) string {
189 switch k := reflect.Indirect(reflect.ValueOf(i)).Kind(); k {
190 case reflect.Bool:
191 return "boolean"
192 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
193 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
194 reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64:
195 return "number"
196 case reflect.Array, reflect.Slice:
197 return "list"
198 case reflect.Map:
199 return "map"
200 case reflect.String:
201 return "string"
202 default:
203 // fall back to the Go type if there's no match
204 return k.String()
205 }
206}