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