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 }