github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/terraform/node_resource_plan_instance.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "sort" 7 8 "github.com/muratcelep/terraform/not-internal/plans" 9 "github.com/muratcelep/terraform/not-internal/states" 10 "github.com/muratcelep/terraform/not-internal/tfdiags" 11 12 "github.com/muratcelep/terraform/not-internal/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. 88 diags = diags.Append(n.writeResourceInstanceState(ctx, state, prevRunState)) 89 if diags.HasErrors() { 90 return diags 91 } 92 93 diags = diags.Append(validateSelfRef(addr.Resource, config.Config, providerSchema)) 94 if diags.HasErrors() { 95 return diags 96 } 97 98 change, state, planDiags := n.planDataSource(ctx, state) 99 diags = diags.Append(planDiags) 100 if diags.HasErrors() { 101 return diags 102 } 103 104 // write the data source into both the refresh state and the 105 // working state 106 diags = diags.Append(n.writeResourceInstanceState(ctx, state, refreshState)) 107 if diags.HasErrors() { 108 return diags 109 } 110 diags = diags.Append(n.writeResourceInstanceState(ctx, state, workingState)) 111 if diags.HasErrors() { 112 return diags 113 } 114 115 diags = diags.Append(n.writeChange(ctx, change, "")) 116 return diags 117 } 118 119 func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) { 120 config := n.Config 121 addr := n.ResourceInstanceAddr() 122 123 var change *plans.ResourceInstanceChange 124 var instanceRefreshState *states.ResourceInstanceObject 125 126 _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 127 diags = diags.Append(err) 128 if diags.HasErrors() { 129 return diags 130 } 131 132 diags = diags.Append(validateSelfRef(addr.Resource, config.Config, providerSchema)) 133 if diags.HasErrors() { 134 return diags 135 } 136 137 instanceRefreshState, readDiags := n.readResourceInstanceState(ctx, addr) 138 diags = diags.Append(readDiags) 139 if diags.HasErrors() { 140 return diags 141 } 142 143 // We'll save a snapshot of what we just read from the state into the 144 // prevRunState before we do anything else, since this will capture the 145 // result of any schema upgrading that readResourceInstanceState just did, 146 // but not include any out-of-band changes we might detect in in the 147 // refresh step below. 148 diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, prevRunState)) 149 if diags.HasErrors() { 150 return diags 151 } 152 // Also the refreshState, because that should still reflect schema upgrades 153 // even if it doesn't reflect upstream changes. 154 diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, refreshState)) 155 if diags.HasErrors() { 156 return diags 157 } 158 159 // In 0.13 we could be refreshing a resource with no config. 160 // We should be operating on managed resource, but check here to be certain 161 if n.Config == nil || n.Config.Managed == nil { 162 log.Printf("[WARN] managedResourceExecute: no Managed config value found in instance state for %q", n.Addr) 163 } else { 164 if instanceRefreshState != nil { 165 instanceRefreshState.CreateBeforeDestroy = n.Config.Managed.CreateBeforeDestroy || n.ForceCreateBeforeDestroy 166 } 167 } 168 169 // Refresh, maybe 170 if !n.skipRefresh { 171 s, refreshDiags := n.refresh(ctx, states.NotDeposed, instanceRefreshState) 172 diags = diags.Append(refreshDiags) 173 if diags.HasErrors() { 174 return diags 175 } 176 177 instanceRefreshState = s 178 179 if instanceRefreshState != nil { 180 // When refreshing we start by merging the stored dependencies and 181 // the configured dependencies. The configured dependencies will be 182 // stored to state once the changes are applied. If the plan 183 // results in no changes, we will re-write these dependencies 184 // below. 185 instanceRefreshState.Dependencies = mergeDeps(n.Dependencies, instanceRefreshState.Dependencies) 186 } 187 188 diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, refreshState)) 189 if diags.HasErrors() { 190 return diags 191 } 192 } 193 194 // Plan the instance, unless we're in the refresh-only mode 195 if !n.skipPlanChanges { 196 change, instancePlanState, planDiags := n.plan( 197 ctx, change, instanceRefreshState, n.ForceCreateBeforeDestroy, n.forceReplace, 198 ) 199 diags = diags.Append(planDiags) 200 if diags.HasErrors() { 201 return diags 202 } 203 204 diags = diags.Append(n.checkPreventDestroy(change)) 205 if diags.HasErrors() { 206 return diags 207 } 208 209 diags = diags.Append(n.writeResourceInstanceState(ctx, instancePlanState, workingState)) 210 if diags.HasErrors() { 211 return diags 212 } 213 214 // If this plan resulted in a NoOp, then apply won't have a chance to make 215 // any changes to the stored dependencies. Since this is a NoOp we know 216 // that the stored dependencies will have no effect during apply, and we can 217 // write them out now. 218 if change.Action == plans.NoOp && !depsEqual(instanceRefreshState.Dependencies, n.Dependencies) { 219 // the refresh state will be the final state for this resource, so 220 // finalize the dependencies here if they need to be updated. 221 instanceRefreshState.Dependencies = n.Dependencies 222 diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, refreshState)) 223 if diags.HasErrors() { 224 return diags 225 } 226 } 227 228 diags = diags.Append(n.writeChange(ctx, change, "")) 229 } else { 230 // Even if we don't plan changes, we do still need to at least update 231 // the working state to reflect the refresh result. If not, then e.g. 232 // any output values refering to this will not react to the drift. 233 // (Even if we didn't actually refresh above, this will still save 234 // the result of any schema upgrading we did in readResourceInstanceState.) 235 diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, workingState)) 236 if diags.HasErrors() { 237 return diags 238 } 239 } 240 241 return diags 242 } 243 244 // mergeDeps returns the union of 2 sets of dependencies 245 func mergeDeps(a, b []addrs.ConfigResource) []addrs.ConfigResource { 246 switch { 247 case len(a) == 0: 248 return b 249 case len(b) == 0: 250 return a 251 } 252 253 set := make(map[string]addrs.ConfigResource) 254 255 for _, dep := range a { 256 set[dep.String()] = dep 257 } 258 259 for _, dep := range b { 260 set[dep.String()] = dep 261 } 262 263 newDeps := make([]addrs.ConfigResource, 0, len(set)) 264 for _, dep := range set { 265 newDeps = append(newDeps, dep) 266 } 267 268 return newDeps 269 } 270 271 func depsEqual(a, b []addrs.ConfigResource) bool { 272 if len(a) != len(b) { 273 return false 274 } 275 276 less := func(s []addrs.ConfigResource) func(i, j int) bool { 277 return func(i, j int) bool { 278 return s[i].String() < s[j].String() 279 } 280 } 281 282 sort.Slice(a, less(a)) 283 sort.Slice(b, less(b)) 284 285 for i := range a { 286 if !a[i].Equal(b[i]) { 287 return false 288 } 289 } 290 return true 291 }