github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_apply.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 "fmt" 10 "log" 11 12 "github.com/zclconf/go-cty/cty" 13 14 "github.com/opentofu/opentofu/internal/addrs" 15 "github.com/opentofu/opentofu/internal/configs" 16 "github.com/opentofu/opentofu/internal/plans" 17 "github.com/opentofu/opentofu/internal/states" 18 "github.com/opentofu/opentofu/internal/tfdiags" 19 ) 20 21 // Apply performs the actions described by the given Plan object and returns 22 // the resulting updated state. 23 // 24 // The given configuration *must* be the same configuration that was passed 25 // earlier to Context.Plan in order to create this plan. 26 // 27 // Even if the returned diagnostics contains errors, Apply always returns the 28 // resulting state which is likely to have been partially-updated. 29 func (c *Context) Apply(plan *plans.Plan, config *configs.Config) (*states.State, tfdiags.Diagnostics) { 30 defer c.acquireRun("apply")() 31 32 log.Printf("[DEBUG] Building and walking apply graph for %s plan", plan.UIMode) 33 34 if plan.Errored { 35 var diags tfdiags.Diagnostics 36 diags = diags.Append(tfdiags.Sourceless( 37 tfdiags.Error, 38 "Cannot apply failed plan", 39 `The given plan is incomplete due to errors during planning, and so it cannot be applied.`, 40 )) 41 return nil, diags 42 } 43 44 for _, rc := range plan.Changes.Resources { 45 // Import is a no-op change during an apply (all the real action happens during the plan) but we'd 46 // like to show some helpful output that mirrors the way we show other changes. 47 if rc.Importing != nil { 48 for _, h := range c.hooks { 49 // In future, we may need to call PostApplyImport separately elsewhere in the apply 50 // operation. For now, though, we'll call Pre and Post hooks together. 51 h.PreApplyImport(rc.Addr, *rc.Importing) 52 h.PostApplyImport(rc.Addr, *rc.Importing) 53 } 54 } 55 } 56 57 graph, operation, diags := c.applyGraph(plan, config, true) 58 if diags.HasErrors() { 59 return nil, diags 60 } 61 62 workingState := plan.PriorState.DeepCopy() 63 walker, walkDiags := c.walk(graph, operation, &graphWalkOpts{ 64 Config: config, 65 InputState: workingState, 66 Changes: plan.Changes, 67 68 // We need to propagate the check results from the plan phase, 69 // because that will tell us which checkable objects we're expecting 70 // to see updated results from during the apply step. 71 PlanTimeCheckResults: plan.Checks, 72 73 // We also want to propagate the timestamp from the plan file. 74 PlanTimeTimestamp: plan.Timestamp, 75 }) 76 diags = diags.Append(walker.NonFatalDiagnostics) 77 diags = diags.Append(walkDiags) 78 79 // After the walk is finished, we capture a simplified snapshot of the 80 // check result data as part of the new state. 81 walker.State.RecordCheckResults(walker.Checks) 82 83 newState := walker.State.Close() 84 if plan.UIMode == plans.DestroyMode && !diags.HasErrors() { 85 // NOTE: This is a vestigial violation of the rule that we mustn't 86 // use plan.UIMode to affect apply-time behavior. 87 // We ideally ought to just call newState.PruneResourceHusks 88 // unconditionally here, but we historically didn't and haven't yet 89 // verified that it'd be safe to do so. 90 newState.PruneResourceHusks() 91 } 92 93 if len(plan.TargetAddrs) > 0 { 94 diags = diags.Append(tfdiags.Sourceless( 95 tfdiags.Warning, 96 "Applied changes may be incomplete", 97 `The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may not be fully updated. Run the following command to verify that no other changes are pending: 98 tofu plan 99 100 Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`, 101 )) 102 } 103 104 // FIXME: we cannot check for an empty plan for refresh-only, because root 105 // outputs are always stored as changes. The final condition of the state 106 // also depends on some cleanup which happens during the apply walk. It 107 // would probably make more sense if applying a refresh-only plan were 108 // simply just returning the planned state and checks, but some extra 109 // cleanup is going to be needed to make the plan state match what apply 110 // would do. For now we can copy the checks over which were overwritten 111 // during the apply walk. 112 // Despite the intent of UIMode, it must still be used for apply-time 113 // differences in destroy plans too, so we can make use of that here as 114 // well. 115 if plan.UIMode == plans.RefreshOnlyMode { 116 newState.CheckResults = plan.Checks.DeepCopy() 117 } 118 119 return newState, diags 120 } 121 122 func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, validate bool) (*Graph, walkOperation, tfdiags.Diagnostics) { 123 var diags tfdiags.Diagnostics 124 125 variables := InputValues{} 126 for name, dyVal := range plan.VariableValues { 127 val, err := dyVal.Decode(cty.DynamicPseudoType) 128 if err != nil { 129 diags = diags.Append(tfdiags.Sourceless( 130 tfdiags.Error, 131 "Invalid variable value in plan", 132 fmt.Sprintf("Invalid value for variable %q recorded in plan file: %s.", name, err), 133 )) 134 continue 135 } 136 137 variables[name] = &InputValue{ 138 Value: val, 139 SourceType: ValueFromPlan, 140 } 141 } 142 if diags.HasErrors() { 143 return nil, walkApply, diags 144 } 145 146 // The plan.VariableValues field only records variables that were actually 147 // set by the caller in the PlanOpts, so we may need to provide 148 // placeholders for any other variables that the user didn't set, in 149 // which case OpenTofu will once again use the default value from the 150 // configuration when we visit these variables during the graph walk. 151 for name := range config.Module.Variables { 152 if _, ok := variables[name]; ok { 153 continue 154 } 155 variables[name] = &InputValue{ 156 Value: cty.NilVal, 157 SourceType: ValueFromPlan, 158 } 159 } 160 161 operation := walkApply 162 if plan.UIMode == plans.DestroyMode { 163 // FIXME: Due to differences in how objects must be handled in the 164 // graph and evaluated during a complete destroy, we must continue to 165 // use plans.DestroyMode to switch on this behavior. If all objects 166 // which require special destroy handling can be tracked in the plan, 167 // then this switch will no longer be needed and we can remove the 168 // walkDestroy operation mode. 169 // TODO: Audit that and remove walkDestroy as an operation mode. 170 operation = walkDestroy 171 } 172 173 graph, moreDiags := (&ApplyGraphBuilder{ 174 Config: config, 175 Changes: plan.Changes, 176 State: plan.PriorState, 177 RootVariableValues: variables, 178 Plugins: c.plugins, 179 Targets: plan.TargetAddrs, 180 ForceReplace: plan.ForceReplaceAddrs, 181 Operation: operation, 182 ExternalReferences: plan.ExternalReferences, 183 }).Build(addrs.RootModuleInstance) 184 diags = diags.Append(moreDiags) 185 if moreDiags.HasErrors() { 186 return nil, walkApply, diags 187 } 188 189 return graph, operation, diags 190 } 191 192 // ApplyGraphForUI is a last vestage of graphs in the public interface of 193 // Context (as opposed to graphs as an implementation detail) intended only for 194 // use by the "tofu graph" command when asked to render an apply-time 195 // graph. 196 // 197 // The result of this is intended only for rendering ot the user as a dot 198 // graph, and so may change in future in order to make the result more useful 199 // in that context, even if drifts away from the physical graph that OpenTofu 200 // Core currently uses as an implementation detail of planning. 201 func (c *Context) ApplyGraphForUI(plan *plans.Plan, config *configs.Config) (*Graph, tfdiags.Diagnostics) { 202 // For now though, this really is just the internal graph, confusing 203 // implementation details and all. 204 205 var diags tfdiags.Diagnostics 206 207 graph, _, moreDiags := c.applyGraph(plan, config, false) 208 diags = diags.Append(moreDiags) 209 return graph, diags 210 }