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