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  }