github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_walk.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"log"
    10  	"time"
    11  
    12  	"github.com/opentofu/opentofu/internal/checks"
    13  	"github.com/opentofu/opentofu/internal/configs"
    14  	"github.com/opentofu/opentofu/internal/instances"
    15  	"github.com/opentofu/opentofu/internal/plans"
    16  	"github.com/opentofu/opentofu/internal/refactoring"
    17  	"github.com/opentofu/opentofu/internal/states"
    18  	"github.com/opentofu/opentofu/internal/tfdiags"
    19  )
    20  
    21  // graphWalkOpts captures some transient values we use (and possibly mutate)
    22  // during a graph walk.
    23  //
    24  // The way these options get used unfortunately varies between the different
    25  // walkOperation types. This is a historical design wart that dates back to
    26  // us using the same graph structure for all operations; hopefully we'll
    27  // make the necessary differences between the walk types more explicit someday.
    28  type graphWalkOpts struct {
    29  	InputState *states.State
    30  	Changes    *plans.Changes
    31  	Config     *configs.Config
    32  
    33  	// PlanTimeCheckResults should be populated during the apply phase with
    34  	// the snapshot of check results that was generated during the plan step.
    35  	//
    36  	// This then propagates the decisions about which checkable objects exist
    37  	// from the plan phase into the apply phase without having to re-compute
    38  	// the module and resource expansion.
    39  	PlanTimeCheckResults *states.CheckResults
    40  
    41  	// PlanTimeTimestamp should be populated during the plan phase by retrieving
    42  	// the current UTC timestamp, and should be read from the plan file during
    43  	// the apply phase.
    44  	PlanTimeTimestamp time.Time
    45  
    46  	MoveResults refactoring.MoveResults
    47  }
    48  
    49  func (c *Context) walk(graph *Graph, operation walkOperation, opts *graphWalkOpts) (*ContextGraphWalker, tfdiags.Diagnostics) {
    50  	log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
    51  
    52  	walker := c.graphWalker(operation, opts)
    53  
    54  	// Watch for a stop so we can call the provider Stop() API.
    55  	watchStop, watchWait := c.watchStop(walker)
    56  
    57  	// Walk the real graph, this will block until it completes
    58  	diags := graph.Walk(walker)
    59  
    60  	// Close the channel so the watcher stops, and wait for it to return.
    61  	close(watchStop)
    62  	<-watchWait
    63  
    64  	return walker, diags
    65  }
    66  
    67  func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *ContextGraphWalker {
    68  	var state *states.SyncState
    69  	var refreshState *states.SyncState
    70  	var prevRunState *states.SyncState
    71  
    72  	// NOTE: None of the SyncState objects must directly wrap opts.InputState,
    73  	// because we use those to mutate the state object and opts.InputState
    74  	// belongs to our caller and thus we must treat it as immutable.
    75  	//
    76  	// To account for that, most of our SyncState values created below end up
    77  	// wrapping a _deep copy_ of opts.InputState instead.
    78  	inputState := opts.InputState
    79  	if inputState == nil {
    80  		// Lots of callers use nil to represent the "empty" case where we've
    81  		// not run Apply yet, so we tolerate that.
    82  		inputState = states.NewState()
    83  	}
    84  
    85  	switch operation {
    86  	case walkValidate:
    87  		// validate should not use any state
    88  		state = states.NewState().SyncWrapper()
    89  
    90  		// validate currently uses the plan graph, so we have to populate the
    91  		// refreshState and the prevRunState.
    92  		refreshState = states.NewState().SyncWrapper()
    93  		prevRunState = states.NewState().SyncWrapper()
    94  
    95  	case walkPlan, walkPlanDestroy, walkImport:
    96  		state = inputState.DeepCopy().SyncWrapper()
    97  		refreshState = inputState.DeepCopy().SyncWrapper()
    98  		prevRunState = inputState.DeepCopy().SyncWrapper()
    99  
   100  		// For both of our new states we'll discard the previous run's
   101  		// check results, since we can still refer to them from the
   102  		// prevRunState object if we need to.
   103  		state.DiscardCheckResults()
   104  		refreshState.DiscardCheckResults()
   105  
   106  	default:
   107  		state = inputState.DeepCopy().SyncWrapper()
   108  		// Only plan-like walks use refreshState and prevRunState
   109  
   110  		// Discard the input state's check results, because we should create
   111  		// a new set as a result of the graph walk.
   112  		state.DiscardCheckResults()
   113  	}
   114  
   115  	changes := opts.Changes
   116  	if changes == nil {
   117  		// Several of our non-plan walks end up sharing codepaths with the
   118  		// plan walk and thus expect to generate planned changes even though
   119  		// we don't care about them. To avoid those crashing, we'll just
   120  		// insert a placeholder changes object which'll get discarded
   121  		// afterwards.
   122  		changes = plans.NewChanges()
   123  	}
   124  
   125  	if opts.Config == nil {
   126  		panic("Context.graphWalker call without Config")
   127  	}
   128  
   129  	checkState := checks.NewState(opts.Config)
   130  	if opts.PlanTimeCheckResults != nil {
   131  		// We'll re-report all of the same objects we determined during the
   132  		// plan phase so that we can repeat the checks during the apply
   133  		// phase to finalize them.
   134  		for _, configElem := range opts.PlanTimeCheckResults.ConfigResults.Elems {
   135  			if configElem.Value.ObjectAddrsKnown() {
   136  				configAddr := configElem.Key
   137  				checkState.ReportCheckableObjects(configAddr, configElem.Value.ObjectResults.Keys())
   138  			}
   139  		}
   140  	}
   141  
   142  	return &ContextGraphWalker{
   143  		Context:          c,
   144  		State:            state,
   145  		Config:           opts.Config,
   146  		RefreshState:     refreshState,
   147  		PrevRunState:     prevRunState,
   148  		Changes:          changes.SyncWrapper(),
   149  		Checks:           checkState,
   150  		InstanceExpander: instances.NewExpander(),
   151  		MoveResults:      opts.MoveResults,
   152  		ImportResolver:   NewImportResolver(),
   153  		Operation:        operation,
   154  		StopContext:      c.runContext,
   155  		PlanTimestamp:    opts.PlanTimeTimestamp,
   156  		Encryption:       c.encryption,
   157  	}
   158  }