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