]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame_incremental - vendor/github.com/hashicorp/terraform/config/raw_config.go
Merge branch 'fix_read_test' of github.com:alexandreFre/terraform-provider-statuscake
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / config / raw_config.go
... / ...
CommitLineData
1package config
2
3import (
4 "bytes"
5 "encoding/gob"
6 "errors"
7 "strconv"
8 "sync"
9
10 "github.com/zclconf/go-cty/cty"
11 "github.com/zclconf/go-cty/cty/convert"
12
13 hcl2 "github.com/hashicorp/hcl2/hcl"
14 "github.com/hashicorp/hil"
15 "github.com/hashicorp/hil/ast"
16 "github.com/mitchellh/copystructure"
17 "github.com/mitchellh/reflectwalk"
18)
19
20// UnknownVariableValue is a sentinel value that can be used
21// to denote that the value of a variable is unknown at this time.
22// RawConfig uses this information to build up data about
23// unknown keys.
24const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
25
26// RawConfig is a structure that holds a piece of configuration
27// where the overall structure is unknown since it will be used
28// to configure a plugin or some other similar external component.
29//
30// RawConfigs can be interpolated with variables that come from
31// other resources, user variables, etc.
32//
33// RawConfig supports a query-like interface to request
34// information from deep within the structure.
35type RawConfig struct {
36 Key string
37
38 // Only _one_ of Raw and Body may be populated at a time.
39 //
40 // In the normal case, Raw is populated and Body is nil.
41 //
42 // When the experimental HCL2 parsing mode is enabled, "Body"
43 // is populated and RawConfig serves only to transport the hcl2.Body
44 // through the rest of Terraform core so we can ultimately decode it
45 // once its schema is known.
46 //
47 // Once we transition to HCL2 as the primary representation, RawConfig
48 // should be removed altogether and the hcl2.Body should be passed
49 // around directly.
50
51 Raw map[string]interface{}
52 Body hcl2.Body
53
54 Interpolations []ast.Node
55 Variables map[string]InterpolatedVariable
56
57 lock sync.Mutex
58 config map[string]interface{}
59 unknownKeys []string
60}
61
62// NewRawConfig creates a new RawConfig structure and populates the
63// publicly readable struct fields.
64func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) {
65 result := &RawConfig{Raw: raw}
66 if err := result.init(); err != nil {
67 return nil, err
68 }
69
70 return result, nil
71}
72
73// NewRawConfigHCL2 creates a new RawConfig that is serving as a capsule
74// to transport a hcl2.Body. In this mode, the publicly-readable struct
75// fields are not populated since all operations should instead be diverted
76// to the HCL2 body.
77//
78// For a RawConfig object constructed with this function, the only valid use
79// is to later retrieve the Body value and call its own methods. Callers
80// may choose to set and then later handle the Key field, in a manner
81// consistent with how it is handled by the Value method, but the Value
82// method itself must not be used.
83//
84// This is an experimental codepath to be used only by the HCL2 config loader.
85// Non-experimental parsing should _always_ use NewRawConfig to produce a
86// fully-functional RawConfig object.
87func NewRawConfigHCL2(body hcl2.Body) *RawConfig {
88 return &RawConfig{
89 Body: body,
90 }
91}
92
93// RawMap returns a copy of the RawConfig.Raw map.
94func (r *RawConfig) RawMap() map[string]interface{} {
95 r.lock.Lock()
96 defer r.lock.Unlock()
97
98 m := make(map[string]interface{})
99 for k, v := range r.Raw {
100 m[k] = v
101 }
102 return m
103}
104
105// Copy returns a copy of this RawConfig, uninterpolated.
106func (r *RawConfig) Copy() *RawConfig {
107 if r == nil {
108 return nil
109 }
110
111 r.lock.Lock()
112 defer r.lock.Unlock()
113
114 if r.Body != nil {
115 return NewRawConfigHCL2(r.Body)
116 }
117
118 newRaw := make(map[string]interface{})
119 for k, v := range r.Raw {
120 newRaw[k] = v
121 }
122
123 result, err := NewRawConfig(newRaw)
124 if err != nil {
125 panic("copy failed: " + err.Error())
126 }
127
128 result.Key = r.Key
129 return result
130}
131
132// Value returns the value of the configuration if this configuration
133// has a Key set. If this does not have a Key set, nil will be returned.
134func (r *RawConfig) Value() interface{} {
135 if c := r.Config(); c != nil {
136 if v, ok := c[r.Key]; ok {
137 return v
138 }
139 }
140
141 r.lock.Lock()
142 defer r.lock.Unlock()
143 return r.Raw[r.Key]
144}
145
146// Config returns the entire configuration with the variables
147// interpolated from any call to Interpolate.
148//
149// If any interpolated variables are unknown (value set to
150// UnknownVariableValue), the first non-container (map, slice, etc.) element
151// will be removed from the config. The keys of unknown variables
152// can be found using the UnknownKeys function.
153//
154// By pruning out unknown keys from the configuration, the raw
155// structure will always successfully decode into its ultimate
156// structure using something like mapstructure.
157func (r *RawConfig) Config() map[string]interface{} {
158 r.lock.Lock()
159 defer r.lock.Unlock()
160 return r.config
161}
162
163// Interpolate uses the given mapping of variable values and uses
164// those as the values to replace any variables in this raw
165// configuration.
166//
167// Any prior calls to Interpolate are replaced with this one.
168//
169// If a variable key is missing, this will panic.
170func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
171 r.lock.Lock()
172 defer r.lock.Unlock()
173
174 config := langEvalConfig(vs)
175 return r.interpolate(func(root ast.Node) (interface{}, error) {
176 // None of the variables we need are computed, meaning we should
177 // be able to properly evaluate.
178 result, err := hil.Eval(root, config)
179 if err != nil {
180 return "", err
181 }
182
183 return result.Value, nil
184 })
185}
186
187// Merge merges another RawConfig into this one (overriding any conflicting
188// values in this config) and returns a new config. The original config
189// is not modified.
190func (r *RawConfig) Merge(other *RawConfig) *RawConfig {
191 r.lock.Lock()
192 defer r.lock.Unlock()
193
194 // Merge the raw configurations
195 raw := make(map[string]interface{})
196 for k, v := range r.Raw {
197 raw[k] = v
198 }
199 for k, v := range other.Raw {
200 raw[k] = v
201 }
202
203 // Create the result
204 result, err := NewRawConfig(raw)
205 if err != nil {
206 panic(err)
207 }
208
209 // Merge the interpolated results
210 result.config = make(map[string]interface{})
211 for k, v := range r.config {
212 result.config[k] = v
213 }
214 for k, v := range other.config {
215 result.config[k] = v
216 }
217
218 // Build the unknown keys
219 if len(r.unknownKeys) > 0 || len(other.unknownKeys) > 0 {
220 unknownKeys := make(map[string]struct{})
221 for _, k := range r.unknownKeys {
222 unknownKeys[k] = struct{}{}
223 }
224 for _, k := range other.unknownKeys {
225 unknownKeys[k] = struct{}{}
226 }
227
228 result.unknownKeys = make([]string, 0, len(unknownKeys))
229 for k, _ := range unknownKeys {
230 result.unknownKeys = append(result.unknownKeys, k)
231 }
232 }
233
234 return result
235}
236
237func (r *RawConfig) init() error {
238 r.lock.Lock()
239 defer r.lock.Unlock()
240
241 r.config = r.Raw
242 r.Interpolations = nil
243 r.Variables = nil
244
245 fn := func(node ast.Node) (interface{}, error) {
246 r.Interpolations = append(r.Interpolations, node)
247 vars, err := DetectVariables(node)
248 if err != nil {
249 return "", err
250 }
251
252 for _, v := range vars {
253 if r.Variables == nil {
254 r.Variables = make(map[string]InterpolatedVariable)
255 }
256
257 r.Variables[v.FullKey()] = v
258 }
259
260 return "", nil
261 }
262
263 walker := &interpolationWalker{F: fn}
264 if err := reflectwalk.Walk(r.Raw, walker); err != nil {
265 return err
266 }
267
268 return nil
269}
270
271func (r *RawConfig) interpolate(fn interpolationWalkerFunc) error {
272 if r.Body != nil {
273 // For RawConfigs created for the HCL2 experiement, callers must
274 // use the HCL2 Body API directly rather than interpolating via
275 // the RawConfig.
276 return errors.New("this feature is not yet supported under the HCL2 experiment")
277 }
278
279 config, err := copystructure.Copy(r.Raw)
280 if err != nil {
281 return err
282 }
283 r.config = config.(map[string]interface{})
284
285 w := &interpolationWalker{F: fn, Replace: true}
286 err = reflectwalk.Walk(r.config, w)
287 if err != nil {
288 return err
289 }
290
291 r.unknownKeys = w.unknownKeys
292 return nil
293}
294
295func (r *RawConfig) merge(r2 *RawConfig) *RawConfig {
296 if r == nil && r2 == nil {
297 return nil
298 }
299
300 if r == nil {
301 r = &RawConfig{}
302 }
303
304 rawRaw, err := copystructure.Copy(r.Raw)
305 if err != nil {
306 panic(err)
307 }
308
309 raw := rawRaw.(map[string]interface{})
310 if r2 != nil {
311 for k, v := range r2.Raw {
312 raw[k] = v
313 }
314 }
315
316 result, err := NewRawConfig(raw)
317 if err != nil {
318 panic(err)
319 }
320
321 return result
322}
323
324// couldBeInteger is a helper that determines if the represented value could
325// result in an integer.
326//
327// This function only works for RawConfigs that have "Key" set, meaning that
328// a single result can be produced. Calling this function will overwrite
329// the Config and Value results to be a test value.
330//
331// This function is conservative. If there is some doubt about whether the
332// result could be an integer -- for example, if it depends on a variable
333// whose type we don't know yet -- it will still return true.
334func (r *RawConfig) couldBeInteger() bool {
335 if r.Key == "" {
336 // un-keyed RawConfigs can never produce numbers
337 return false
338 }
339 if r.Body == nil {
340 // Normal path: using the interpolator in this package
341 // Interpolate with a fixed number to verify that its a number.
342 r.interpolate(func(root ast.Node) (interface{}, error) {
343 // Execute the node but transform the AST so that it returns
344 // a fixed value of "5" for all interpolations.
345 result, err := hil.Eval(
346 hil.FixedValueTransform(
347 root, &ast.LiteralNode{Value: "5", Typex: ast.TypeString}),
348 nil)
349 if err != nil {
350 return "", err
351 }
352
353 return result.Value, nil
354 })
355 _, err := strconv.ParseInt(r.Value().(string), 0, 0)
356 return err == nil
357 } else {
358 // HCL2 experiment path: using the HCL2 API via shims
359 //
360 // This path catches fewer situations because we have to assume all
361 // variables are entirely unknown in HCL2, rather than the assumption
362 // above that all variables can be numbers because names like "var.foo"
363 // are considered a single variable rather than an attribute access.
364 // This is fine in practice, because we get a definitive answer
365 // during the graph walk when we have real values to work with.
366 attrs, diags := r.Body.JustAttributes()
367 if diags.HasErrors() {
368 // This body is not just a single attribute with a value, so
369 // this can't be a number.
370 return false
371 }
372 attr, hasAttr := attrs[r.Key]
373 if !hasAttr {
374 return false
375 }
376 result, diags := hcl2EvalWithUnknownVars(attr.Expr)
377 if diags.HasErrors() {
378 // We'll conservatively assume that this error is a result of
379 // us not being ready to fully-populate the scope, and catch
380 // any further problems during the main graph walk.
381 return true
382 }
383
384 // If the result is convertable to number then we'll allow it.
385 // We do this because an unknown string is optimistically convertable
386 // to number (might be "5") but a _known_ string "hello" is not.
387 _, err := convert.Convert(result, cty.Number)
388 return err == nil
389 }
390}
391
392// UnknownKeys returns the keys of the configuration that are unknown
393// because they had interpolated variables that must be computed.
394func (r *RawConfig) UnknownKeys() []string {
395 r.lock.Lock()
396 defer r.lock.Unlock()
397 return r.unknownKeys
398}
399
400// See GobEncode
401func (r *RawConfig) GobDecode(b []byte) error {
402 var data gobRawConfig
403 err := gob.NewDecoder(bytes.NewReader(b)).Decode(&data)
404 if err != nil {
405 return err
406 }
407
408 r.Key = data.Key
409 r.Raw = data.Raw
410
411 return r.init()
412}
413
414// GobEncode is a custom Gob encoder to use so that we only include the
415// raw configuration. Interpolated variables and such are lost and the
416// tree of interpolated variables is recomputed on decode, since it is
417// referentially transparent.
418func (r *RawConfig) GobEncode() ([]byte, error) {
419 r.lock.Lock()
420 defer r.lock.Unlock()
421
422 data := gobRawConfig{
423 Key: r.Key,
424 Raw: r.Raw,
425 }
426
427 var buf bytes.Buffer
428 if err := gob.NewEncoder(&buf).Encode(data); err != nil {
429 return nil, err
430 }
431
432 return buf.Bytes(), nil
433}
434
435type gobRawConfig struct {
436 Key string
437 Raw map[string]interface{}
438}
439
440// langEvalConfig returns the evaluation configuration we use to execute.
441func langEvalConfig(vs map[string]ast.Variable) *hil.EvalConfig {
442 funcMap := make(map[string]ast.Function)
443 for k, v := range Funcs() {
444 funcMap[k] = v
445 }
446 funcMap["lookup"] = interpolationFuncLookup(vs)
447 funcMap["keys"] = interpolationFuncKeys(vs)
448 funcMap["values"] = interpolationFuncValues(vs)
449
450 return &hil.EvalConfig{
451 GlobalScope: &ast.BasicScope{
452 VarMap: vs,
453 FuncMap: funcMap,
454 },
455 }
456}