github.com/opentofu/opentofu@v1.7.1/internal/plans/plan.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 plans 7 8 import ( 9 "sort" 10 "time" 11 12 "github.com/zclconf/go-cty/cty" 13 14 "github.com/opentofu/opentofu/internal/addrs" 15 "github.com/opentofu/opentofu/internal/configs/configschema" 16 "github.com/opentofu/opentofu/internal/lang/globalref" 17 "github.com/opentofu/opentofu/internal/states" 18 ) 19 20 // Plan is the top-level type representing a planned set of changes. 21 // 22 // A plan is a summary of the set of changes required to move from a current 23 // state to a goal state derived from configuration. The described changes 24 // are not applied directly, but contain an approximation of the final 25 // result that will be completed during apply by resolving any values that 26 // cannot be predicted. 27 // 28 // A plan must always be accompanied by the configuration it was built from, 29 // since the plan does not itself include all of the information required to 30 // make the changes indicated. 31 type Plan struct { 32 // Mode is the mode under which this plan was created. 33 // 34 // This is only recorded to allow for UI differences when presenting plans 35 // to the end-user, and so it must not be used to influence apply-time 36 // behavior. The actions during apply must be described entirely by 37 // the Changes field, regardless of how the plan was created. 38 // 39 // FIXME: destroy operations still rely on DestroyMode being set, because 40 // there is no other source of this information in the plan. New behavior 41 // should not be added based on this flag, and changing the flag should be 42 // checked carefully against existing destroy behaviors. 43 UIMode Mode 44 45 VariableValues map[string]DynamicValue 46 Changes *Changes 47 DriftedResources []*ResourceInstanceChangeSrc 48 TargetAddrs []addrs.Targetable 49 ForceReplaceAddrs []addrs.AbsResourceInstance 50 Backend Backend 51 52 // Errored is true if the Changes information is incomplete because 53 // the planning operation failed. An errored plan cannot be applied, 54 // but can be cautiously inspected for debugging purposes. 55 Errored bool 56 57 // Checks captures a snapshot of the (probably-incomplete) check results 58 // at the end of the planning process. 59 // 60 // If this plan is applyable (that is, if the planning process completed 61 // without errors) then the set of checks here should be complete even 62 // though some of them will likely have StatusUnknown where the check 63 // condition depends on values we won't know until the apply step. 64 Checks *states.CheckResults 65 66 // RelevantAttributes is a set of resource instance addresses and 67 // attributes that are either directly affected by proposed changes or may 68 // have indirectly contributed to them via references in expressions. 69 // 70 // This is the result of a heuristic and is intended only as a hint to 71 // the UI layer in case it wants to emphasize or de-emphasize certain 72 // resources. Don't use this to drive any non-cosmetic behavior, especially 73 // including anything that would be subject to compatibility constraints. 74 RelevantAttributes []globalref.ResourceAttr 75 76 // PrevRunState and PriorState both describe the situation that the plan 77 // was derived from: 78 // 79 // PrevRunState is a representation of the outcome of the previous 80 // OpenTofu operation, without any updates from the remote system but 81 // potentially including some changes that resulted from state upgrade 82 // actions. 83 // 84 // PriorState is a representation of the current state of remote objects, 85 // which will differ from PrevRunState if the "refresh" step returned 86 // different data, which might reflect drift. 87 // 88 // PriorState is the main snapshot we use for actions during apply. 89 // PrevRunState is only here so that we can diff PriorState against it in 90 // order to report to the user any out-of-band changes we've detected. 91 PrevRunState *states.State 92 PriorState *states.State 93 94 // PlannedState is the temporary planned state that was created during the 95 // graph walk that generated this plan. 96 // 97 // This is required by the testing framework when evaluating run blocks 98 // executing in plan mode. The graph updates the state with certain values 99 // that are difficult to retrieve later, such as local values that reference 100 // updated resources. It is easier to build the testing scope with access 101 // to same temporary state the plan used/built. 102 // 103 // This is never recorded outside of OpenTofu. It is not written into the 104 // binary plan file, and it is not written into the JSON structured outputs. 105 // The testing framework never writes the plans out but holds everything in 106 // memory as it executes, so there is no need to add any kind of 107 // serialization for this field. This does mean that you shouldn't rely on 108 // this field existing unless you have just generated the plan. 109 PlannedState *states.State 110 111 // ExternalReferences are references that are being made to resources within 112 // the plan from external sources. As with PlannedState this is used by the 113 // OpenTofu testing framework, and so isn't written into any external 114 // representation of the plan. 115 ExternalReferences []*addrs.Reference 116 117 // Timestamp is the record of truth for when the plan happened. 118 Timestamp time.Time 119 } 120 121 // CanApply returns true if and only if the recieving plan includes content 122 // that would make sense to apply. If it returns false, the plan operation 123 // should indicate that there's nothing to do and OpenTofu should exit 124 // without prompting the user to confirm the changes. 125 // 126 // This function represents our main business logic for making the decision 127 // about whether a given plan represents meaningful "changes", and so its 128 // exact definition may change over time; the intent is just to centralize the 129 // rules for that rather than duplicating different versions of it at various 130 // locations in the UI code. 131 func (p *Plan) CanApply() bool { 132 switch { 133 case p.Errored: 134 // An errored plan can never be applied, because it is incomplete. 135 // Such a plan is only useful for describing the subset of actions 136 // planned so far in case they are useful for understanding the 137 // causes of the errors. 138 return false 139 140 case !p.Changes.Empty(): 141 // "Empty" means that everything in the changes is a "NoOp", so if 142 // not empty then there's at least one non-NoOp change. 143 return true 144 145 case !p.PriorState.ManagedResourcesEqual(p.PrevRunState): 146 // If there are no changes planned but we detected some 147 // outside-OpenTofu changes while refreshing then we consider 148 // that applyable in isolation only if this was a refresh-only 149 // plan where we expect updating the state to include these 150 // changes was the intended goal. 151 // 152 // (We don't treat a "refresh only" plan as applyable in normal 153 // planning mode because historically the refresh result wasn't 154 // considered part of a plan at all, and so it would be 155 // a disruptive breaking change if refreshing alone suddenly 156 // became applyable in the normal case and an existing configuration 157 // was relying on ignore_changes in order to be convergent in spite 158 // of intentional out-of-band operations.) 159 return p.UIMode == RefreshOnlyMode 160 161 default: 162 // Otherwise, there are either no changes to apply or they are changes 163 // our cases above don't consider as worthy of applying in isolation. 164 return false 165 } 166 } 167 168 // ProviderAddrs returns a list of all of the provider configuration addresses 169 // referenced throughout the receiving plan. 170 // 171 // The result is de-duplicated so that each distinct address appears only once. 172 func (p *Plan) ProviderAddrs() []addrs.AbsProviderConfig { 173 if p == nil || p.Changes == nil { 174 return nil 175 } 176 177 m := map[string]addrs.AbsProviderConfig{} 178 for _, rc := range p.Changes.Resources { 179 m[rc.ProviderAddr.String()] = rc.ProviderAddr 180 } 181 if len(m) == 0 { 182 return nil 183 } 184 185 // This is mainly just so we'll get stable results for testing purposes. 186 keys := make([]string, 0, len(m)) 187 for k := range m { 188 keys = append(keys, k) 189 } 190 sort.Strings(keys) 191 192 ret := make([]addrs.AbsProviderConfig, len(keys)) 193 for i, key := range keys { 194 ret[i] = m[key] 195 } 196 197 return ret 198 } 199 200 // Backend represents the backend-related configuration and other data as it 201 // existed when a plan was created. 202 type Backend struct { 203 // Type is the type of backend that the plan will apply against. 204 Type string 205 206 // Config is the configuration of the backend, whose schema is decided by 207 // the backend Type. 208 Config DynamicValue 209 210 // Workspace is the name of the workspace that was active when the plan 211 // was created. It is illegal to apply a plan created for one workspace 212 // to the state of another workspace. 213 // (This constraint is already enforced by the statefile lineage mechanism, 214 // but storing this explicitly allows us to return a better error message 215 // in the situation where the user has the wrong workspace selected.) 216 Workspace string 217 } 218 219 func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) { 220 dv, err := NewDynamicValue(config, configSchema.ImpliedType()) 221 if err != nil { 222 return nil, err 223 } 224 225 return &Backend{ 226 Type: typeName, 227 Config: dv, 228 Workspace: workspaceName, 229 }, nil 230 }