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