github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/terraform/eval_context_builtin.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package terraform 5 6 import ( 7 "context" 8 "fmt" 9 "log" 10 "sync" 11 12 "github.com/hashicorp/hcl/v2" 13 "github.com/zclconf/go-cty/cty" 14 15 "github.com/terramate-io/tf/addrs" 16 "github.com/terramate-io/tf/checks" 17 "github.com/terramate-io/tf/configs/configschema" 18 "github.com/terramate-io/tf/instances" 19 "github.com/terramate-io/tf/lang" 20 "github.com/terramate-io/tf/plans" 21 "github.com/terramate-io/tf/providers" 22 "github.com/terramate-io/tf/provisioners" 23 "github.com/terramate-io/tf/refactoring" 24 "github.com/terramate-io/tf/states" 25 "github.com/terramate-io/tf/tfdiags" 26 "github.com/terramate-io/tf/version" 27 ) 28 29 // BuiltinEvalContext is an EvalContext implementation that is used by 30 // Terraform by default. 31 type BuiltinEvalContext struct { 32 // StopContext is the context used to track whether we're complete 33 StopContext context.Context 34 35 // PathValue is the Path that this context is operating within. 36 PathValue addrs.ModuleInstance 37 38 // pathSet indicates that this context was explicitly created for a 39 // specific path, and can be safely used for evaluation. This lets us 40 // differentiate between PathValue being unset, and the zero value which is 41 // equivalent to RootModuleInstance. Path and Evaluation methods will 42 // panic if this is not set. 43 pathSet bool 44 45 // Evaluator is used for evaluating expressions within the scope of this 46 // eval context. 47 Evaluator *Evaluator 48 49 // VariableValues contains the variable values across all modules. This 50 // structure is shared across the entire containing context, and so it 51 // may be accessed only when holding VariableValuesLock. 52 // The keys of the first level of VariableValues are the string 53 // representations of addrs.ModuleInstance values. The second-level keys 54 // are variable names within each module instance. 55 VariableValues map[string]map[string]cty.Value 56 VariableValuesLock *sync.Mutex 57 58 // Plugins is a library of plugin components (providers and provisioners) 59 // available for use during a graph walk. 60 Plugins *contextPlugins 61 62 Hooks []Hook 63 InputValue UIInput 64 ProviderCache map[string]providers.Interface 65 ProviderInputConfig map[string]map[string]cty.Value 66 ProviderLock *sync.Mutex 67 ProvisionerCache map[string]provisioners.Interface 68 ProvisionerLock *sync.Mutex 69 ChangesValue *plans.ChangesSync 70 StateValue *states.SyncState 71 ChecksValue *checks.State 72 RefreshStateValue *states.SyncState 73 PrevRunStateValue *states.SyncState 74 InstanceExpanderValue *instances.Expander 75 MoveResultsValue refactoring.MoveResults 76 } 77 78 // BuiltinEvalContext implements EvalContext 79 var _ EvalContext = (*BuiltinEvalContext)(nil) 80 81 func (ctx *BuiltinEvalContext) WithPath(path addrs.ModuleInstance) EvalContext { 82 newCtx := *ctx 83 newCtx.pathSet = true 84 newCtx.PathValue = path 85 return &newCtx 86 } 87 88 func (ctx *BuiltinEvalContext) Stopped() <-chan struct{} { 89 // This can happen during tests. During tests, we just block forever. 90 if ctx.StopContext == nil { 91 return nil 92 } 93 94 return ctx.StopContext.Done() 95 } 96 97 func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error { 98 for _, h := range ctx.Hooks { 99 action, err := fn(h) 100 if err != nil { 101 return err 102 } 103 104 switch action { 105 case HookActionContinue: 106 continue 107 case HookActionHalt: 108 // Return an early exit error to trigger an early exit 109 log.Printf("[WARN] Early exit triggered by hook: %T", h) 110 return nil 111 } 112 } 113 114 return nil 115 } 116 117 func (ctx *BuiltinEvalContext) Input() UIInput { 118 return ctx.InputValue 119 } 120 121 func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig) (providers.Interface, error) { 122 // If we already initialized, it is an error 123 if p := ctx.Provider(addr); p != nil { 124 return nil, fmt.Errorf("%s is already initialized", addr) 125 } 126 127 // Warning: make sure to acquire these locks AFTER the call to Provider 128 // above, since it also acquires locks. 129 ctx.ProviderLock.Lock() 130 defer ctx.ProviderLock.Unlock() 131 132 key := addr.String() 133 134 p, err := ctx.Plugins.NewProviderInstance(addr.Provider) 135 if err != nil { 136 return nil, err 137 } 138 139 log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", addr.String(), addr) 140 ctx.ProviderCache[key] = p 141 142 return p, nil 143 } 144 145 func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) providers.Interface { 146 ctx.ProviderLock.Lock() 147 defer ctx.ProviderLock.Unlock() 148 149 return ctx.ProviderCache[addr.String()] 150 } 151 152 func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) { 153 // first see if we have already have an initialized provider to avoid 154 // re-loading it only for the schema 155 p := ctx.Provider(addr) 156 if p != nil { 157 resp := p.GetProviderSchema() 158 // convert any diagnostics here in case this is the first call 159 // FIXME: better control provider instantiation so we can be sure this 160 // won't be the first call to ProviderSchema 161 var err error 162 if resp.Diagnostics.HasErrors() { 163 err = resp.Diagnostics.ErrWithWarnings() 164 } 165 return resp, err 166 } 167 168 return ctx.Plugins.ProviderSchema(addr.Provider) 169 } 170 171 func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.AbsProviderConfig) error { 172 ctx.ProviderLock.Lock() 173 defer ctx.ProviderLock.Unlock() 174 175 key := addr.String() 176 provider := ctx.ProviderCache[key] 177 if provider != nil { 178 delete(ctx.ProviderCache, key) 179 return provider.Close() 180 } 181 182 return nil 183 } 184 185 func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, cfg cty.Value) tfdiags.Diagnostics { 186 var diags tfdiags.Diagnostics 187 if !addr.Module.Equal(ctx.Path().Module()) { 188 // This indicates incorrect use of ConfigureProvider: it should be used 189 // only from the module that the provider configuration belongs to. 190 panic(fmt.Sprintf("%s configured by wrong module %s", addr, ctx.Path())) 191 } 192 193 p := ctx.Provider(addr) 194 if p == nil { 195 diags = diags.Append(fmt.Errorf("%s not initialized", addr)) 196 return diags 197 } 198 199 req := providers.ConfigureProviderRequest{ 200 TerraformVersion: version.String(), 201 Config: cfg, 202 } 203 204 resp := p.ConfigureProvider(req) 205 return resp.Diagnostics 206 } 207 208 func (ctx *BuiltinEvalContext) ProviderInput(pc addrs.AbsProviderConfig) map[string]cty.Value { 209 ctx.ProviderLock.Lock() 210 defer ctx.ProviderLock.Unlock() 211 212 if !pc.Module.Equal(ctx.Path().Module()) { 213 // This indicates incorrect use of InitProvider: it should be used 214 // only from the module that the provider configuration belongs to. 215 panic(fmt.Sprintf("%s initialized by wrong module %s", pc, ctx.Path())) 216 } 217 218 if !ctx.Path().IsRoot() { 219 // Only root module provider configurations can have input. 220 return nil 221 } 222 223 return ctx.ProviderInputConfig[pc.String()] 224 } 225 226 func (ctx *BuiltinEvalContext) SetProviderInput(pc addrs.AbsProviderConfig, c map[string]cty.Value) { 227 absProvider := pc 228 if !pc.Module.IsRoot() { 229 // Only root module provider configurations can have input. 230 log.Printf("[WARN] BuiltinEvalContext: attempt to SetProviderInput for non-root module") 231 return 232 } 233 234 // Save the configuration 235 ctx.ProviderLock.Lock() 236 ctx.ProviderInputConfig[absProvider.String()] = c 237 ctx.ProviderLock.Unlock() 238 } 239 240 func (ctx *BuiltinEvalContext) Provisioner(n string) (provisioners.Interface, error) { 241 ctx.ProvisionerLock.Lock() 242 defer ctx.ProvisionerLock.Unlock() 243 244 p, ok := ctx.ProvisionerCache[n] 245 if !ok { 246 var err error 247 p, err = ctx.Plugins.NewProvisionerInstance(n) 248 if err != nil { 249 return nil, err 250 } 251 252 ctx.ProvisionerCache[n] = p 253 } 254 255 return p, nil 256 } 257 258 func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) (*configschema.Block, error) { 259 return ctx.Plugins.ProvisionerSchema(n) 260 } 261 262 func (ctx *BuiltinEvalContext) CloseProvisioners() error { 263 var diags tfdiags.Diagnostics 264 ctx.ProvisionerLock.Lock() 265 defer ctx.ProvisionerLock.Unlock() 266 267 for name, prov := range ctx.ProvisionerCache { 268 err := prov.Close() 269 if err != nil { 270 diags = diags.Append(fmt.Errorf("provisioner.Close %s: %s", name, err)) 271 } 272 } 273 274 return diags.Err() 275 } 276 277 func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) { 278 var diags tfdiags.Diagnostics 279 scope := ctx.EvaluationScope(self, nil, keyData) 280 body, evalDiags := scope.ExpandBlock(body, schema) 281 diags = diags.Append(evalDiags) 282 val, evalDiags := scope.EvalBlock(body, schema) 283 diags = diags.Append(evalDiags) 284 return val, body, diags 285 } 286 287 func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) { 288 scope := ctx.EvaluationScope(self, nil, EvalDataForNoInstanceKey) 289 return scope.EvalExpr(expr, wantType) 290 } 291 292 func (ctx *BuiltinEvalContext) EvaluateReplaceTriggeredBy(expr hcl.Expression, repData instances.RepetitionData) (*addrs.Reference, bool, tfdiags.Diagnostics) { 293 294 // get the reference to lookup changes in the plan 295 ref, diags := evalReplaceTriggeredByExpr(expr, repData) 296 if diags.HasErrors() { 297 return nil, false, diags 298 } 299 300 var changes []*plans.ResourceInstanceChangeSrc 301 // store the address once we get it for validation 302 var resourceAddr addrs.Resource 303 304 // The reference is either a resource or resource instance 305 switch sub := ref.Subject.(type) { 306 case addrs.Resource: 307 resourceAddr = sub 308 rc := sub.Absolute(ctx.Path()) 309 changes = ctx.Changes().GetChangesForAbsResource(rc) 310 case addrs.ResourceInstance: 311 resourceAddr = sub.ContainingResource() 312 rc := sub.Absolute(ctx.Path()) 313 change := ctx.Changes().GetResourceInstanceChange(rc, states.CurrentGen) 314 if change != nil { 315 // we'll generate an error below if there was no change 316 changes = append(changes, change) 317 } 318 } 319 320 // Do some validation to make sure we are expecting a change at all 321 cfg := ctx.Evaluator.Config.Descendent(ctx.Path().Module()) 322 resCfg := cfg.Module.ResourceByAddr(resourceAddr) 323 if resCfg == nil { 324 diags = diags.Append(&hcl.Diagnostic{ 325 Severity: hcl.DiagError, 326 Summary: `Reference to undeclared resource`, 327 Detail: fmt.Sprintf(`A resource %s has not been declared in %s`, ref.Subject, moduleDisplayAddr(ctx.Path())), 328 Subject: expr.Range().Ptr(), 329 }) 330 return nil, false, diags 331 } 332 333 if len(changes) == 0 { 334 // If the resource is valid there should always be at least one change. 335 diags = diags.Append(fmt.Errorf("no change found for %s in %s", ref.Subject, moduleDisplayAddr(ctx.Path()))) 336 return nil, false, diags 337 } 338 339 // If we don't have a traversal beyond the resource, then we can just look 340 // for any change. 341 if len(ref.Remaining) == 0 { 342 for _, c := range changes { 343 switch c.ChangeSrc.Action { 344 // Only immediate changes to the resource will trigger replacement. 345 case plans.Update, plans.DeleteThenCreate, plans.CreateThenDelete: 346 return ref, true, diags 347 } 348 } 349 350 // no change triggered 351 return nil, false, diags 352 } 353 354 // This must be an instances to have a remaining traversal, which means a 355 // single change. 356 change := changes[0] 357 358 // Make sure the change is actionable. A create or delete action will have 359 // a change in value, but are not valid for our purposes here. 360 switch change.ChangeSrc.Action { 361 case plans.Update, plans.DeleteThenCreate, plans.CreateThenDelete: 362 // OK 363 default: 364 return nil, false, diags 365 } 366 367 // Since we have a traversal after the resource reference, we will need to 368 // decode the changes, which means we need a schema. 369 providerAddr := change.ProviderAddr 370 schema, err := ctx.ProviderSchema(providerAddr) 371 if err != nil { 372 diags = diags.Append(err) 373 return nil, false, diags 374 } 375 376 resAddr := change.Addr.ContainingResource().Resource 377 resSchema, _ := schema.SchemaForResourceType(resAddr.Mode, resAddr.Type) 378 ty := resSchema.ImpliedType() 379 380 before, err := change.ChangeSrc.Before.Decode(ty) 381 if err != nil { 382 diags = diags.Append(err) 383 return nil, false, diags 384 } 385 386 after, err := change.ChangeSrc.After.Decode(ty) 387 if err != nil { 388 diags = diags.Append(err) 389 return nil, false, diags 390 } 391 392 path := traversalToPath(ref.Remaining) 393 attrBefore, _ := path.Apply(before) 394 attrAfter, _ := path.Apply(after) 395 396 if attrBefore == cty.NilVal || attrAfter == cty.NilVal { 397 replace := attrBefore != attrAfter 398 return ref, replace, diags 399 } 400 401 replace := !attrBefore.RawEquals(attrAfter) 402 403 return ref, replace, diags 404 } 405 406 func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope { 407 if !ctx.pathSet { 408 panic("context path not set") 409 } 410 data := &evaluationStateData{ 411 Evaluator: ctx.Evaluator, 412 ModulePath: ctx.PathValue, 413 InstanceKeyData: keyData, 414 Operation: ctx.Evaluator.Operation, 415 } 416 scope := ctx.Evaluator.Scope(data, self, source) 417 418 // ctx.PathValue is the path of the module that contains whatever 419 // expression the caller will be trying to evaluate, so this will 420 // activate only the experiments from that particular module, to 421 // be consistent with how experiment checking in the "configs" 422 // package itself works. The nil check here is for robustness in 423 // incompletely-mocked testing situations; mc should never be nil in 424 // real situations. 425 if mc := ctx.Evaluator.Config.DescendentForInstance(ctx.PathValue); mc != nil { 426 scope.SetActiveExperiments(mc.Module.ActiveExperiments) 427 } 428 return scope 429 } 430 431 func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance { 432 if !ctx.pathSet { 433 panic("context path not set") 434 } 435 return ctx.PathValue 436 } 437 438 func (ctx *BuiltinEvalContext) SetRootModuleArgument(addr addrs.InputVariable, v cty.Value) { 439 ctx.VariableValuesLock.Lock() 440 defer ctx.VariableValuesLock.Unlock() 441 442 log.Printf("[TRACE] BuiltinEvalContext: Storing final value for variable %s", addr.Absolute(addrs.RootModuleInstance)) 443 key := addrs.RootModuleInstance.String() 444 args := ctx.VariableValues[key] 445 if args == nil { 446 args = make(map[string]cty.Value) 447 ctx.VariableValues[key] = args 448 } 449 args[addr.Name] = v 450 } 451 452 func (ctx *BuiltinEvalContext) SetModuleCallArgument(callAddr addrs.ModuleCallInstance, varAddr addrs.InputVariable, v cty.Value) { 453 ctx.VariableValuesLock.Lock() 454 defer ctx.VariableValuesLock.Unlock() 455 456 if !ctx.pathSet { 457 panic("context path not set") 458 } 459 460 childPath := callAddr.ModuleInstance(ctx.PathValue) 461 log.Printf("[TRACE] BuiltinEvalContext: Storing final value for variable %s", varAddr.Absolute(childPath)) 462 key := childPath.String() 463 args := ctx.VariableValues[key] 464 if args == nil { 465 args = make(map[string]cty.Value) 466 ctx.VariableValues[key] = args 467 } 468 args[varAddr.Name] = v 469 } 470 471 func (ctx *BuiltinEvalContext) GetVariableValue(addr addrs.AbsInputVariableInstance) cty.Value { 472 ctx.VariableValuesLock.Lock() 473 defer ctx.VariableValuesLock.Unlock() 474 475 modKey := addr.Module.String() 476 modVars := ctx.VariableValues[modKey] 477 val, ok := modVars[addr.Variable.Name] 478 if !ok { 479 return cty.DynamicVal 480 } 481 return val 482 } 483 484 func (ctx *BuiltinEvalContext) Changes() *plans.ChangesSync { 485 return ctx.ChangesValue 486 } 487 488 func (ctx *BuiltinEvalContext) State() *states.SyncState { 489 return ctx.StateValue 490 } 491 492 func (ctx *BuiltinEvalContext) Checks() *checks.State { 493 return ctx.ChecksValue 494 } 495 496 func (ctx *BuiltinEvalContext) RefreshState() *states.SyncState { 497 return ctx.RefreshStateValue 498 } 499 500 func (ctx *BuiltinEvalContext) PrevRunState() *states.SyncState { 501 return ctx.PrevRunStateValue 502 } 503 504 func (ctx *BuiltinEvalContext) InstanceExpander() *instances.Expander { 505 return ctx.InstanceExpanderValue 506 } 507 508 func (ctx *BuiltinEvalContext) MoveResults() refactoring.MoveResults { 509 return ctx.MoveResultsValue 510 }