github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/node_output.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform/internal/addrs" 11 "github.com/hashicorp/terraform/internal/configs" 12 "github.com/hashicorp/terraform/internal/dag" 13 "github.com/hashicorp/terraform/internal/lang" 14 "github.com/hashicorp/terraform/internal/lang/marks" 15 "github.com/hashicorp/terraform/internal/plans" 16 "github.com/hashicorp/terraform/internal/states" 17 "github.com/hashicorp/terraform/internal/tfdiags" 18 ) 19 20 // nodeExpandOutput is the placeholder for a non-root module output that has 21 // not yet had its module path expanded. 22 type nodeExpandOutput struct { 23 Addr addrs.OutputValue 24 Module addrs.Module 25 Config *configs.Output 26 Destroy bool 27 RefreshOnly bool 28 } 29 30 var ( 31 _ GraphNodeReferenceable = (*nodeExpandOutput)(nil) 32 _ GraphNodeReferencer = (*nodeExpandOutput)(nil) 33 _ GraphNodeReferenceOutside = (*nodeExpandOutput)(nil) 34 _ GraphNodeDynamicExpandable = (*nodeExpandOutput)(nil) 35 _ graphNodeTemporaryValue = (*nodeExpandOutput)(nil) 36 _ graphNodeExpandsInstances = (*nodeExpandOutput)(nil) 37 ) 38 39 func (n *nodeExpandOutput) expandsInstances() {} 40 41 func (n *nodeExpandOutput) temporaryValue() bool { 42 // non root outputs are temporary 43 return !n.Module.IsRoot() 44 } 45 46 func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, error) { 47 if n.Destroy { 48 // if we're planning a destroy, we only need to handle the root outputs. 49 // The destroy plan doesn't evaluate any other config, so we can skip 50 // the rest of the outputs. 51 return n.planDestroyRootOutput(ctx) 52 } 53 54 expander := ctx.InstanceExpander() 55 changes := ctx.Changes() 56 57 // If this is an output value that participates in custom condition checks 58 // (i.e. it has preconditions or postconditions) then the check state 59 // wants to know the addresses of the checkable objects so that it can 60 // treat them as unknown status if we encounter an error before actually 61 // visiting the checks. 62 var checkableAddrs addrs.Set[addrs.Checkable] 63 if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.Addr.InModule(n.Module)) { 64 checkableAddrs = addrs.MakeSet[addrs.Checkable]() 65 } 66 67 var g Graph 68 for _, module := range expander.ExpandModule(n.Module) { 69 absAddr := n.Addr.Absolute(module) 70 if checkableAddrs != nil { 71 checkableAddrs.Add(absAddr) 72 } 73 74 // Find any recorded change for this output 75 var change *plans.OutputChangeSrc 76 var outputChanges []*plans.OutputChangeSrc 77 if module.IsRoot() { 78 outputChanges = changes.GetRootOutputChanges() 79 } else { 80 parent, call := module.Call() 81 outputChanges = changes.GetOutputChanges(parent, call) 82 } 83 for _, c := range outputChanges { 84 if c.Addr.String() == absAddr.String() { 85 change = c 86 break 87 } 88 } 89 90 o := &NodeApplyableOutput{ 91 Addr: absAddr, 92 Config: n.Config, 93 Change: change, 94 RefreshOnly: n.RefreshOnly, 95 } 96 log.Printf("[TRACE] Expanding output: adding %s as %T", o.Addr.String(), o) 97 g.Add(o) 98 } 99 100 if checkableAddrs != nil { 101 checkState := ctx.Checks() 102 checkState.ReportCheckableObjects(n.Addr.InModule(n.Module), checkableAddrs) 103 } 104 105 return &g, nil 106 } 107 108 // if we're planing a destroy operation, add a destroy node for any root output 109 func (n *nodeExpandOutput) planDestroyRootOutput(ctx EvalContext) (*Graph, error) { 110 if !n.Module.IsRoot() { 111 return nil, nil 112 } 113 state := ctx.State() 114 if state == nil { 115 return nil, nil 116 } 117 118 var g Graph 119 o := &NodeDestroyableOutput{ 120 Addr: n.Addr.Absolute(addrs.RootModuleInstance), 121 Config: n.Config, 122 } 123 log.Printf("[TRACE] Expanding output: adding %s as %T", o.Addr.String(), o) 124 g.Add(o) 125 126 return &g, nil 127 } 128 129 func (n *nodeExpandOutput) Name() string { 130 path := n.Module.String() 131 addr := n.Addr.String() + " (expand)" 132 if path != "" { 133 return path + "." + addr 134 } 135 return addr 136 } 137 138 // GraphNodeModulePath 139 func (n *nodeExpandOutput) ModulePath() addrs.Module { 140 return n.Module 141 } 142 143 // GraphNodeReferenceable 144 func (n *nodeExpandOutput) ReferenceableAddrs() []addrs.Referenceable { 145 // An output in the root module can't be referenced at all. 146 if n.Module.IsRoot() { 147 return nil 148 } 149 150 // the output is referenced through the module call, and via the 151 // module itself. 152 _, call := n.Module.Call() 153 callOutput := addrs.ModuleCallOutput{ 154 Call: call, 155 Name: n.Addr.Name, 156 } 157 158 // Otherwise, we can reference the output via the 159 // module call itself 160 return []addrs.Referenceable{call, callOutput} 161 } 162 163 // GraphNodeReferenceOutside implementation 164 func (n *nodeExpandOutput) ReferenceOutside() (selfPath, referencePath addrs.Module) { 165 // Output values have their expressions resolved in the context of the 166 // module where they are defined. 167 referencePath = n.Module 168 169 // ...but they are referenced in the context of their calling module. 170 selfPath = referencePath.Parent() 171 172 return // uses named return values 173 } 174 175 // GraphNodeReferencer 176 func (n *nodeExpandOutput) References() []*addrs.Reference { 177 // root outputs might be destroyable, and may not reference anything in 178 // that case 179 return referencesForOutput(n.Config) 180 } 181 182 // NodeApplyableOutput represents an output that is "applyable": 183 // it is ready to be applied. 184 type NodeApplyableOutput struct { 185 Addr addrs.AbsOutputValue 186 Config *configs.Output // Config is the output in the config 187 // If this is being evaluated during apply, we may have a change recorded already 188 Change *plans.OutputChangeSrc 189 190 // Refresh-only mode means that any failing output preconditions are 191 // reported as warnings rather than errors 192 RefreshOnly bool 193 } 194 195 var ( 196 _ GraphNodeModuleInstance = (*NodeApplyableOutput)(nil) 197 _ GraphNodeReferenceable = (*NodeApplyableOutput)(nil) 198 _ GraphNodeReferencer = (*NodeApplyableOutput)(nil) 199 _ GraphNodeReferenceOutside = (*NodeApplyableOutput)(nil) 200 _ GraphNodeExecutable = (*NodeApplyableOutput)(nil) 201 _ graphNodeTemporaryValue = (*NodeApplyableOutput)(nil) 202 _ dag.GraphNodeDotter = (*NodeApplyableOutput)(nil) 203 ) 204 205 func (n *NodeApplyableOutput) temporaryValue() bool { 206 // this must always be evaluated if it is a root module output 207 return !n.Addr.Module.IsRoot() 208 } 209 210 func (n *NodeApplyableOutput) Name() string { 211 return n.Addr.String() 212 } 213 214 // GraphNodeModuleInstance 215 func (n *NodeApplyableOutput) Path() addrs.ModuleInstance { 216 return n.Addr.Module 217 } 218 219 // GraphNodeModulePath 220 func (n *NodeApplyableOutput) ModulePath() addrs.Module { 221 return n.Addr.Module.Module() 222 } 223 224 func referenceOutsideForOutput(addr addrs.AbsOutputValue) (selfPath, referencePath addrs.Module) { 225 // Output values have their expressions resolved in the context of the 226 // module where they are defined. 227 referencePath = addr.Module.Module() 228 229 // ...but they are referenced in the context of their calling module. 230 selfPath = addr.Module.Parent().Module() 231 232 return // uses named return values 233 } 234 235 // GraphNodeReferenceOutside implementation 236 func (n *NodeApplyableOutput) ReferenceOutside() (selfPath, referencePath addrs.Module) { 237 return referenceOutsideForOutput(n.Addr) 238 } 239 240 func referenceableAddrsForOutput(addr addrs.AbsOutputValue) []addrs.Referenceable { 241 // An output in the root module can't be referenced at all. 242 if addr.Module.IsRoot() { 243 return nil 244 } 245 246 // Otherwise, we can be referenced via a reference to our output name 247 // on the parent module's call, or via a reference to the entire call. 248 // e.g. module.foo.bar or just module.foo . 249 // Note that our ReferenceOutside method causes these addresses to be 250 // relative to the calling module, not the module where the output 251 // was declared. 252 _, outp := addr.ModuleCallOutput() 253 _, call := addr.Module.CallInstance() 254 255 return []addrs.Referenceable{outp, call} 256 } 257 258 // GraphNodeReferenceable 259 func (n *NodeApplyableOutput) ReferenceableAddrs() []addrs.Referenceable { 260 return referenceableAddrsForOutput(n.Addr) 261 } 262 263 func referencesForOutput(c *configs.Output) []*addrs.Reference { 264 impRefs, _ := lang.ReferencesInExpr(c.Expr) 265 expRefs, _ := lang.References(c.DependsOn) 266 l := len(impRefs) + len(expRefs) 267 if l == 0 { 268 return nil 269 } 270 refs := make([]*addrs.Reference, 0, l) 271 refs = append(refs, impRefs...) 272 refs = append(refs, expRefs...) 273 for _, check := range c.Preconditions { 274 checkRefs, _ := lang.ReferencesInExpr(check.Condition) 275 refs = append(refs, checkRefs...) 276 } 277 return refs 278 } 279 280 // GraphNodeReferencer 281 func (n *NodeApplyableOutput) References() []*addrs.Reference { 282 return referencesForOutput(n.Config) 283 } 284 285 // GraphNodeExecutable 286 func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { 287 state := ctx.State() 288 if state == nil { 289 return 290 } 291 292 changes := ctx.Changes() // may be nil, if we're not working on a changeset 293 294 val := cty.UnknownVal(cty.DynamicPseudoType) 295 changeRecorded := n.Change != nil 296 // we we have a change recorded, we don't need to re-evaluate if the value 297 // was known 298 if changeRecorded { 299 change, err := n.Change.Decode() 300 diags = diags.Append(err) 301 if err == nil { 302 val = change.After 303 } 304 } 305 306 checkRuleSeverity := tfdiags.Error 307 if n.RefreshOnly { 308 checkRuleSeverity = tfdiags.Warning 309 } 310 checkDiags := evalCheckRules( 311 addrs.OutputPrecondition, 312 n.Config.Preconditions, 313 ctx, n.Addr, EvalDataForNoInstanceKey, 314 checkRuleSeverity, 315 ) 316 diags = diags.Append(checkDiags) 317 if diags.HasErrors() { 318 return diags // failed preconditions prevent further evaluation 319 } 320 321 // If there was no change recorded, or the recorded change was not wholly 322 // known, then we need to re-evaluate the output 323 if !changeRecorded || !val.IsWhollyKnown() { 324 // This has to run before we have a state lock, since evaluation also 325 // reads the state 326 var evalDiags tfdiags.Diagnostics 327 val, evalDiags = ctx.EvaluateExpr(n.Config.Expr, cty.DynamicPseudoType, nil) 328 diags = diags.Append(evalDiags) 329 330 // We'll handle errors below, after we have loaded the module. 331 // Outputs don't have a separate mode for validation, so validate 332 // depends_on expressions here too 333 diags = diags.Append(validateDependsOn(ctx, n.Config.DependsOn)) 334 335 // For root module outputs in particular, an output value must be 336 // statically declared as sensitive in order to dynamically return 337 // a sensitive result, to help avoid accidental exposure in the state 338 // of a sensitive value that the user doesn't want to include there. 339 if n.Addr.Module.IsRoot() { 340 if !n.Config.Sensitive && marks.Contains(val, marks.Sensitive) { 341 diags = diags.Append(&hcl.Diagnostic{ 342 Severity: hcl.DiagError, 343 Summary: "Output refers to sensitive values", 344 Detail: `To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires that any root module output containing sensitive data be explicitly marked as sensitive, to confirm your intent. 345 346 If you do intend to export this data, annotate the output value as sensitive by adding the following argument: 347 sensitive = true`, 348 Subject: n.Config.DeclRange.Ptr(), 349 }) 350 } 351 } 352 } 353 354 // handling the interpolation error 355 if diags.HasErrors() { 356 if flagWarnOutputErrors { 357 log.Printf("[ERROR] Output interpolation %q failed: %s", n.Addr, diags.Err()) 358 // if we're continuing, make sure the output is included, and 359 // marked as unknown. If the evaluator was able to find a type 360 // for the value in spite of the error then we'll use it. 361 n.setValue(state, changes, cty.UnknownVal(val.Type())) 362 363 // Keep existing warnings, while converting errors to warnings. 364 // This is not meant to be the normal path, so there no need to 365 // make the errors pretty. 366 var warnings tfdiags.Diagnostics 367 for _, d := range diags { 368 switch d.Severity() { 369 case tfdiags.Warning: 370 warnings = warnings.Append(d) 371 case tfdiags.Error: 372 desc := d.Description() 373 warnings = warnings.Append(tfdiags.SimpleWarning(fmt.Sprintf("%s:%s", desc.Summary, desc.Detail))) 374 } 375 } 376 377 return warnings 378 } 379 return diags 380 } 381 n.setValue(state, changes, val) 382 383 // If we were able to evaluate a new value, we can update that in the 384 // refreshed state as well. 385 if state = ctx.RefreshState(); state != nil && val.IsWhollyKnown() { 386 n.setValue(state, changes, val) 387 } 388 389 return diags 390 } 391 392 // dag.GraphNodeDotter impl. 393 func (n *NodeApplyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { 394 return &dag.DotNode{ 395 Name: name, 396 Attrs: map[string]string{ 397 "label": n.Name(), 398 "shape": "note", 399 }, 400 } 401 } 402 403 // NodeDestroyableOutput represents an output that is "destroyable": 404 // its application will remove the output from the state. 405 type NodeDestroyableOutput struct { 406 Addr addrs.AbsOutputValue 407 Config *configs.Output // Config is the output in the config 408 } 409 410 var ( 411 _ GraphNodeExecutable = (*NodeDestroyableOutput)(nil) 412 _ dag.GraphNodeDotter = (*NodeDestroyableOutput)(nil) 413 ) 414 415 func (n *NodeDestroyableOutput) Name() string { 416 return fmt.Sprintf("%s (destroy)", n.Addr.String()) 417 } 418 419 // GraphNodeModulePath 420 func (n *NodeDestroyableOutput) ModulePath() addrs.Module { 421 return n.Addr.Module.Module() 422 } 423 424 func (n *NodeDestroyableOutput) temporaryValue() bool { 425 // this must always be evaluated if it is a root module output 426 return !n.Addr.Module.IsRoot() 427 } 428 429 // GraphNodeExecutable 430 func (n *NodeDestroyableOutput) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics { 431 state := ctx.State() 432 if state == nil { 433 return nil 434 } 435 436 // if this is a root module, try to get a before value from the state for 437 // the diff 438 sensitiveBefore := false 439 before := cty.NullVal(cty.DynamicPseudoType) 440 mod := state.Module(n.Addr.Module) 441 if n.Addr.Module.IsRoot() && mod != nil { 442 if o, ok := mod.OutputValues[n.Addr.OutputValue.Name]; ok { 443 sensitiveBefore = o.Sensitive 444 before = o.Value 445 } else { 446 // If the output was not in state, a delete change would 447 // be meaningless, so exit early. 448 return nil 449 450 } 451 } 452 453 changes := ctx.Changes() 454 if changes != nil { 455 change := &plans.OutputChange{ 456 Addr: n.Addr, 457 Sensitive: sensitiveBefore, 458 Change: plans.Change{ 459 Action: plans.Delete, 460 Before: before, 461 After: cty.NullVal(cty.DynamicPseudoType), 462 }, 463 } 464 465 cs, err := change.Encode() 466 if err != nil { 467 // Should never happen, since we just constructed this right above 468 panic(fmt.Sprintf("planned change for %s could not be encoded: %s", n.Addr, err)) 469 } 470 log.Printf("[TRACE] NodeDestroyableOutput: Saving %s change for %s in changeset", change.Action, n.Addr) 471 changes.RemoveOutputChange(n.Addr) // remove any existing planned change, if present 472 changes.AppendOutputChange(cs) // add the new planned change 473 } 474 475 state.RemoveOutputValue(n.Addr) 476 return nil 477 } 478 479 // dag.GraphNodeDotter impl. 480 func (n *NodeDestroyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { 481 return &dag.DotNode{ 482 Name: name, 483 Attrs: map[string]string{ 484 "label": n.Name(), 485 "shape": "note", 486 }, 487 } 488 } 489 490 func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.ChangesSync, val cty.Value) { 491 // If we have an active changeset then we'll first replicate the value in 492 // there and lookup the prior value in the state. This is used in 493 // preference to the state where present, since it *is* able to represent 494 // unknowns, while the state cannot. 495 if changes != nil { 496 // if this is a root module, try to get a before value from the state for 497 // the diff 498 sensitiveBefore := false 499 before := cty.NullVal(cty.DynamicPseudoType) 500 501 // is this output new to our state? 502 newOutput := true 503 504 mod := state.Module(n.Addr.Module) 505 if n.Addr.Module.IsRoot() && mod != nil { 506 for name, o := range mod.OutputValues { 507 if name == n.Addr.OutputValue.Name { 508 before = o.Value 509 sensitiveBefore = o.Sensitive 510 newOutput = false 511 break 512 } 513 } 514 } 515 516 // We will not show the value is either the before or after are marked 517 // as sensitivity. We can show the value again once sensitivity is 518 // removed from both the config and the state. 519 sensitiveChange := sensitiveBefore || n.Config.Sensitive 520 521 // strip any marks here just to be sure we don't panic on the True comparison 522 unmarkedVal, _ := val.UnmarkDeep() 523 524 action := plans.Update 525 switch { 526 case val.IsNull() && before.IsNull(): 527 // This is separate from the NoOp case below, since we can ignore 528 // sensitivity here when there are only null values. 529 action = plans.NoOp 530 531 case newOutput: 532 // This output was just added to the configuration 533 action = plans.Create 534 535 case val.IsWhollyKnown() && 536 unmarkedVal.Equals(before).True() && 537 n.Config.Sensitive == sensitiveBefore: 538 // Sensitivity must also match to be a NoOp. 539 // Theoretically marks may not match here, but sensitivity is the 540 // only one we can act on, and the state will have been loaded 541 // without any marks to consider. 542 action = plans.NoOp 543 } 544 545 change := &plans.OutputChange{ 546 Addr: n.Addr, 547 Sensitive: sensitiveChange, 548 Change: plans.Change{ 549 Action: action, 550 Before: before, 551 After: val, 552 }, 553 } 554 555 cs, err := change.Encode() 556 if err != nil { 557 // Should never happen, since we just constructed this right above 558 panic(fmt.Sprintf("planned change for %s could not be encoded: %s", n.Addr, err)) 559 } 560 log.Printf("[TRACE] setValue: Saving %s change for %s in changeset", change.Action, n.Addr) 561 changes.RemoveOutputChange(n.Addr) // remove any existing planned change, if present 562 changes.AppendOutputChange(cs) // add the new planned change 563 } 564 565 if val.IsKnown() && !val.IsNull() { 566 // The state itself doesn't represent unknown values, so we null them 567 // out here and then we'll save the real unknown value in the planned 568 // changeset below, if we have one on this graph walk. 569 log.Printf("[TRACE] setValue: Saving value for %s in state", n.Addr) 570 unmarkedVal, _ := val.UnmarkDeep() 571 stateVal := cty.UnknownAsNull(unmarkedVal) 572 state.SetOutputValue(n.Addr, stateVal, n.Config.Sensitive) 573 } else { 574 log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr) 575 state.RemoveOutputValue(n.Addr) 576 } 577 578 }