github.com/sfdevops1/terrra4orm@v0.11.12-beta1/config/raw_config.go (about) 1 package config 2 3 import ( 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. 24 const 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. 35 type 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. 64 func 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. 87 func NewRawConfigHCL2(body hcl2.Body) *RawConfig { 88 return &RawConfig{ 89 Body: body, 90 } 91 } 92 93 // RawMap returns a copy of the RawConfig.Raw map. 94 func (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. 106 func (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. 134 func (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. 157 func (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. 170 func (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. 190 func (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 237 func (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 271 func (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 295 func (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. 334 func (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. 394 func (r *RawConfig) UnknownKeys() []string { 395 r.lock.Lock() 396 defer r.lock.Unlock() 397 return r.unknownKeys 398 } 399 400 // See GobEncode 401 func (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. 418 func (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 435 type gobRawConfig struct { 436 Key string 437 Raw map[string]interface{} 438 } 439 440 // langEvalConfig returns the evaluation configuration we use to execute. 441 func 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 }