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  }