github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/terraform/node_resource_plan_instance.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sort"
     7  
     8  	"github.com/cycloidio/terraform/plans"
     9  	"github.com/cycloidio/terraform/states"
    10  	"github.com/cycloidio/terraform/tfdiags"
    11  
    12  	"github.com/cycloidio/terraform/addrs"
    13  )
    14  
    15  // NodePlannableResourceInstance represents a _single_ resource
    16  // instance that is plannable. This means this represents a single
    17  // count index, for example.
    18  type NodePlannableResourceInstance struct {
    19  	*NodeAbstractResourceInstance
    20  	ForceCreateBeforeDestroy bool
    21  
    22  	// skipRefresh indicates that we should skip refreshing individual instances
    23  	skipRefresh bool
    24  
    25  	// skipPlanChanges indicates we should skip trying to plan change actions
    26  	// for any instances.
    27  	skipPlanChanges bool
    28  
    29  	// forceReplace are resource instance addresses where the user wants to
    30  	// force generating a replace action. This set isn't pre-filtered, so
    31  	// it might contain addresses that have nothing to do with the resource
    32  	// that this node represents, which the node itself must therefore ignore.
    33  	forceReplace []addrs.AbsResourceInstance
    34  }
    35  
    36  var (
    37  	_ GraphNodeModuleInstance       = (*NodePlannableResourceInstance)(nil)
    38  	_ GraphNodeReferenceable        = (*NodePlannableResourceInstance)(nil)
    39  	_ GraphNodeReferencer           = (*NodePlannableResourceInstance)(nil)
    40  	_ GraphNodeConfigResource       = (*NodePlannableResourceInstance)(nil)
    41  	_ GraphNodeResourceInstance     = (*NodePlannableResourceInstance)(nil)
    42  	_ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
    43  	_ GraphNodeAttachResourceState  = (*NodePlannableResourceInstance)(nil)
    44  	_ GraphNodeExecutable           = (*NodePlannableResourceInstance)(nil)
    45  )
    46  
    47  // GraphNodeEvalable
    48  func (n *NodePlannableResourceInstance) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
    49  	addr := n.ResourceInstanceAddr()
    50  
    51  	// Eval info is different depending on what kind of resource this is
    52  	switch addr.Resource.Resource.Mode {
    53  	case addrs.ManagedResourceMode:
    54  		return n.managedResourceExecute(ctx)
    55  	case addrs.DataResourceMode:
    56  		return n.dataResourceExecute(ctx)
    57  	default:
    58  		panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
    59  	}
    60  }
    61  
    62  func (n *NodePlannableResourceInstance) dataResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) {
    63  	config := n.Config
    64  	addr := n.ResourceInstanceAddr()
    65  
    66  	var change *plans.ResourceInstanceChange
    67  
    68  	_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
    69  	diags = diags.Append(err)
    70  	if diags.HasErrors() {
    71  		return diags
    72  	}
    73  
    74  	state, readDiags := n.readResourceInstanceState(ctx, addr)
    75  	diags = diags.Append(readDiags)
    76  	if diags.HasErrors() {
    77  		return diags
    78  	}
    79  
    80  	// We'll save a snapshot of what we just read from the state into the
    81  	// prevRunState which will capture the result read in the previous
    82  	// run, possibly tweaked by any upgrade steps that
    83  	// readResourceInstanceState might've made.
    84  	// However, note that we don't have any explicit mechanism for upgrading
    85  	// data resource results as we do for managed resources, and so the
    86  	// prevRunState might not conform to the current schema if the
    87  	// previous run was with a different provider version. In that case the
    88  	// snapshot will be null if we could not decode it at all.
    89  	diags = diags.Append(n.writeResourceInstanceState(ctx, state, prevRunState))
    90  	if diags.HasErrors() {
    91  		return diags
    92  	}
    93  
    94  	diags = diags.Append(validateSelfRef(addr.Resource, config.Config, providerSchema))
    95  	if diags.HasErrors() {
    96  		return diags
    97  	}
    98  
    99  	change, state, planDiags := n.planDataSource(ctx, state)
   100  	diags = diags.Append(planDiags)
   101  	if diags.HasErrors() {
   102  		return diags
   103  	}
   104  
   105  	// write the data source into both the refresh state and the
   106  	// working state
   107  	diags = diags.Append(n.writeResourceInstanceState(ctx, state, refreshState))
   108  	if diags.HasErrors() {
   109  		return diags
   110  	}
   111  	diags = diags.Append(n.writeResourceInstanceState(ctx, state, workingState))
   112  	if diags.HasErrors() {
   113  		return diags
   114  	}
   115  
   116  	diags = diags.Append(n.writeChange(ctx, change, ""))
   117  	return diags
   118  }
   119  
   120  func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) {
   121  	config := n.Config
   122  	addr := n.ResourceInstanceAddr()
   123  
   124  	var change *plans.ResourceInstanceChange
   125  	var instanceRefreshState *states.ResourceInstanceObject
   126  
   127  	_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
   128  	diags = diags.Append(err)
   129  	if diags.HasErrors() {
   130  		return diags
   131  	}
   132  
   133  	diags = diags.Append(validateSelfRef(addr.Resource, config.Config, providerSchema))
   134  	if diags.HasErrors() {
   135  		return diags
   136  	}
   137  
   138  	instanceRefreshState, readDiags := n.readResourceInstanceState(ctx, addr)
   139  	diags = diags.Append(readDiags)
   140  	if diags.HasErrors() {
   141  		return diags
   142  	}
   143  
   144  	// We'll save a snapshot of what we just read from the state into the
   145  	// prevRunState before we do anything else, since this will capture the
   146  	// result of any schema upgrading that readResourceInstanceState just did,
   147  	// but not include any out-of-band changes we might detect in in the
   148  	// refresh step below.
   149  	diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, prevRunState))
   150  	if diags.HasErrors() {
   151  		return diags
   152  	}
   153  	// Also the refreshState, because that should still reflect schema upgrades
   154  	// even if it doesn't reflect upstream changes.
   155  	diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, refreshState))
   156  	if diags.HasErrors() {
   157  		return diags
   158  	}
   159  
   160  	// In 0.13 we could be refreshing a resource with no config.
   161  	// We should be operating on managed resource, but check here to be certain
   162  	if n.Config == nil || n.Config.Managed == nil {
   163  		log.Printf("[WARN] managedResourceExecute: no Managed config value found in instance state for %q", n.Addr)
   164  	} else {
   165  		if instanceRefreshState != nil {
   166  			instanceRefreshState.CreateBeforeDestroy = n.Config.Managed.CreateBeforeDestroy || n.ForceCreateBeforeDestroy
   167  		}
   168  	}
   169  
   170  	// Refresh, maybe
   171  	if !n.skipRefresh {
   172  		s, refreshDiags := n.refresh(ctx, states.NotDeposed, instanceRefreshState)
   173  		diags = diags.Append(refreshDiags)
   174  		if diags.HasErrors() {
   175  			return diags
   176  		}
   177  
   178  		instanceRefreshState = s
   179  
   180  		if instanceRefreshState != nil {
   181  			// When refreshing we start by merging the stored dependencies and
   182  			// the configured dependencies. The configured dependencies will be
   183  			// stored to state once the changes are applied. If the plan
   184  			// results in no changes, we will re-write these dependencies
   185  			// below.
   186  			instanceRefreshState.Dependencies = mergeDeps(n.Dependencies, instanceRefreshState.Dependencies)
   187  		}
   188  
   189  		diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, refreshState))
   190  		if diags.HasErrors() {
   191  			return diags
   192  		}
   193  	}
   194  
   195  	// Plan the instance, unless we're in the refresh-only mode
   196  	if !n.skipPlanChanges {
   197  		change, instancePlanState, planDiags := n.plan(
   198  			ctx, change, instanceRefreshState, n.ForceCreateBeforeDestroy, n.forceReplace,
   199  		)
   200  		diags = diags.Append(planDiags)
   201  		if diags.HasErrors() {
   202  			return diags
   203  		}
   204  
   205  		diags = diags.Append(n.checkPreventDestroy(change))
   206  		if diags.HasErrors() {
   207  			return diags
   208  		}
   209  
   210  		diags = diags.Append(n.writeResourceInstanceState(ctx, instancePlanState, workingState))
   211  		if diags.HasErrors() {
   212  			return diags
   213  		}
   214  
   215  		// If this plan resulted in a NoOp, then apply won't have a chance to make
   216  		// any changes to the stored dependencies. Since this is a NoOp we know
   217  		// that the stored dependencies will have no effect during apply, and we can
   218  		// write them out now.
   219  		if change.Action == plans.NoOp && !depsEqual(instanceRefreshState.Dependencies, n.Dependencies) {
   220  			// the refresh state will be the final state for this resource, so
   221  			// finalize the dependencies here if they need to be updated.
   222  			instanceRefreshState.Dependencies = n.Dependencies
   223  			diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, refreshState))
   224  			if diags.HasErrors() {
   225  				return diags
   226  			}
   227  		}
   228  
   229  		diags = diags.Append(n.writeChange(ctx, change, ""))
   230  	} else {
   231  		// Even if we don't plan changes, we do still need to at least update
   232  		// the working state to reflect the refresh result. If not, then e.g.
   233  		// any output values refering to this will not react to the drift.
   234  		// (Even if we didn't actually refresh above, this will still save
   235  		// the result of any schema upgrading we did in readResourceInstanceState.)
   236  		diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, workingState))
   237  		if diags.HasErrors() {
   238  			return diags
   239  		}
   240  	}
   241  
   242  	return diags
   243  }
   244  
   245  // mergeDeps returns the union of 2 sets of dependencies
   246  func mergeDeps(a, b []addrs.ConfigResource) []addrs.ConfigResource {
   247  	switch {
   248  	case len(a) == 0:
   249  		return b
   250  	case len(b) == 0:
   251  		return a
   252  	}
   253  
   254  	set := make(map[string]addrs.ConfigResource)
   255  
   256  	for _, dep := range a {
   257  		set[dep.String()] = dep
   258  	}
   259  
   260  	for _, dep := range b {
   261  		set[dep.String()] = dep
   262  	}
   263  
   264  	newDeps := make([]addrs.ConfigResource, 0, len(set))
   265  	for _, dep := range set {
   266  		newDeps = append(newDeps, dep)
   267  	}
   268  
   269  	return newDeps
   270  }
   271  
   272  func depsEqual(a, b []addrs.ConfigResource) bool {
   273  	if len(a) != len(b) {
   274  		return false
   275  	}
   276  
   277  	less := func(s []addrs.ConfigResource) func(i, j int) bool {
   278  		return func(i, j int) bool {
   279  			return s[i].String() < s[j].String()
   280  		}
   281  	}
   282  
   283  	sort.Slice(a, less(a))
   284  	sort.Slice(b, less(b))
   285  
   286  	for i := range a {
   287  		if !a[i].Equal(b[i]) {
   288  			return false
   289  		}
   290  	}
   291  	return true
   292  }