1package plans 2 3import ( 4 "sort" 5 6 "github.com/hashicorp/terraform/internal/addrs" 7 "github.com/hashicorp/terraform/internal/configs/configschema" 8 "github.com/hashicorp/terraform/internal/states" 9 "github.com/zclconf/go-cty/cty" 10) 11 12// Plan is the top-level type representing a planned set of changes. 13// 14// A plan is a summary of the set of changes required to move from a current 15// state to a goal state derived from configuration. The described changes 16// are not applied directly, but contain an approximation of the final 17// result that will be completed during apply by resolving any values that 18// cannot be predicted. 19// 20// A plan must always be accompanied by the configuration it was built from, 21// since the plan does not itself include all of the information required to 22// make the changes indicated. 23type Plan struct { 24 // Mode is the mode under which this plan was created. 25 // 26 // This is only recorded to allow for UI differences when presenting plans 27 // to the end-user, and so it must not be used to influence apply-time 28 // behavior. The actions during apply must be described entirely by 29 // the Changes field, regardless of how the plan was created. 30 UIMode Mode 31 32 VariableValues map[string]DynamicValue 33 Changes *Changes 34 TargetAddrs []addrs.Targetable 35 ForceReplaceAddrs []addrs.AbsResourceInstance 36 ProviderSHA256s map[string][]byte 37 Backend Backend 38 39 // PrevRunState and PriorState both describe the situation that the plan 40 // was derived from: 41 // 42 // PrevRunState is a representation of the outcome of the previous 43 // Terraform operation, without any updates from the remote system but 44 // potentially including some changes that resulted from state upgrade 45 // actions. 46 // 47 // PriorState is a representation of the current state of remote objects, 48 // which will differ from PrevRunState if the "refresh" step returned 49 // different data, which might reflect drift. 50 // 51 // PriorState is the main snapshot we use for actions during apply. 52 // PrevRunState is only here so that we can diff PriorState against it in 53 // order to report to the user any out-of-band changes we've detected. 54 PrevRunState *states.State 55 PriorState *states.State 56} 57 58// CanApply returns true if and only if the recieving plan includes content 59// that would make sense to apply. If it returns false, the plan operation 60// should indicate that there's nothing to do and Terraform should exit 61// without prompting the user to confirm the changes. 62// 63// This function represents our main business logic for making the decision 64// about whether a given plan represents meaningful "changes", and so its 65// exact definition may change over time; the intent is just to centralize the 66// rules for that rather than duplicating different versions of it at various 67// locations in the UI code. 68func (p *Plan) CanApply() bool { 69 switch { 70 case !p.Changes.Empty(): 71 // "Empty" means that everything in the changes is a "NoOp", so if 72 // not empty then there's at least one non-NoOp change. 73 return true 74 75 case !p.PriorState.ManagedResourcesEqual(p.PrevRunState): 76 // If there are no changes planned but we detected some 77 // outside-Terraform changes while refreshing then we consider 78 // that applyable in isolation only if this was a refresh-only 79 // plan where we expect updating the state to include these 80 // changes was the intended goal. 81 // 82 // (We don't treat a "refresh only" plan as applyable in normal 83 // planning mode because historically the refresh result wasn't 84 // considered part of a plan at all, and so it would be 85 // a disruptive breaking change if refreshing alone suddenly 86 // became applyable in the normal case and an existing configuration 87 // was relying on ignore_changes in order to be convergent in spite 88 // of intentional out-of-band operations.) 89 return p.UIMode == RefreshOnlyMode 90 91 default: 92 // Otherwise, there are either no changes to apply or they are changes 93 // our cases above don't consider as worthy of applying in isolation. 94 return false 95 } 96} 97 98// ProviderAddrs returns a list of all of the provider configuration addresses 99// referenced throughout the receiving plan. 100// 101// The result is de-duplicated so that each distinct address appears only once. 102func (p *Plan) ProviderAddrs() []addrs.AbsProviderConfig { 103 if p == nil || p.Changes == nil { 104 return nil 105 } 106 107 m := map[string]addrs.AbsProviderConfig{} 108 for _, rc := range p.Changes.Resources { 109 m[rc.ProviderAddr.String()] = rc.ProviderAddr 110 } 111 if len(m) == 0 { 112 return nil 113 } 114 115 // This is mainly just so we'll get stable results for testing purposes. 116 keys := make([]string, 0, len(m)) 117 for k := range m { 118 keys = append(keys, k) 119 } 120 sort.Strings(keys) 121 122 ret := make([]addrs.AbsProviderConfig, len(keys)) 123 for i, key := range keys { 124 ret[i] = m[key] 125 } 126 127 return ret 128} 129 130// Backend represents the backend-related configuration and other data as it 131// existed when a plan was created. 132type Backend struct { 133 // Type is the type of backend that the plan will apply against. 134 Type string 135 136 // Config is the configuration of the backend, whose schema is decided by 137 // the backend Type. 138 Config DynamicValue 139 140 // Workspace is the name of the workspace that was active when the plan 141 // was created. It is illegal to apply a plan created for one workspace 142 // to the state of another workspace. 143 // (This constraint is already enforced by the statefile lineage mechanism, 144 // but storing this explicitly allows us to return a better error message 145 // in the situation where the user has the wrong workspace selected.) 146 Workspace string 147} 148 149func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) { 150 dv, err := NewDynamicValue(config, configSchema.ImpliedType()) 151 if err != nil { 152 return nil, err 153 } 154 155 return &Backend{ 156 Type: typeName, 157 Config: dv, 158 Workspace: workspaceName, 159 }, nil 160} 161