github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/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  	graph, operation, diags := c.applyGraph(plan, config, true)
    29  	if diags.HasErrors() {
    30  		return nil, diags
    31  	}
    32  
    33  	workingState := plan.PriorState.DeepCopy()
    34  	walker, walkDiags := c.walk(graph, operation, &graphWalkOpts{
    35  		Config:     config,
    36  		InputState: workingState,
    37  		Changes:    plan.Changes,
    38  
    39  		// We need to propagate the check results from the plan phase,
    40  		// because that will tell us which checkable objects we're expecting
    41  		// to see updated results from during the apply step.
    42  		PlanTimeCheckResults: plan.Checks,
    43  	})
    44  	diags = diags.Append(walker.NonFatalDiagnostics)
    45  	diags = diags.Append(walkDiags)
    46  
    47  	// After the walk is finished, we capture a simplified snapshot of the
    48  	// check result data as part of the new state.
    49  	walker.State.RecordCheckResults(walker.Checks)
    50  
    51  	newState := walker.State.Close()
    52  	if plan.UIMode == plans.DestroyMode && !diags.HasErrors() {
    53  		// NOTE: This is a vestigial violation of the rule that we mustn't
    54  		// use plan.UIMode to affect apply-time behavior.
    55  		// We ideally ought to just call newState.PruneResourceHusks
    56  		// unconditionally here, but we historically didn't and haven't yet
    57  		// verified that it'd be safe to do so.
    58  		newState.PruneResourceHusks()
    59  	}
    60  
    61  	if len(plan.TargetAddrs) > 0 {
    62  		diags = diags.Append(tfdiags.Sourceless(
    63  			tfdiags.Warning,
    64  			"Applied changes may be incomplete",
    65  			`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:
    66      terraform plan
    67  	
    68  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.`,
    69  		))
    70  	}
    71  
    72  	return newState, diags
    73  }
    74  
    75  func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, validate bool) (*Graph, walkOperation, tfdiags.Diagnostics) {
    76  	var diags tfdiags.Diagnostics
    77  
    78  	variables := InputValues{}
    79  	for name, dyVal := range plan.VariableValues {
    80  		val, err := dyVal.Decode(cty.DynamicPseudoType)
    81  		if err != nil {
    82  			diags = diags.Append(tfdiags.Sourceless(
    83  				tfdiags.Error,
    84  				"Invalid variable value in plan",
    85  				fmt.Sprintf("Invalid value for variable %q recorded in plan file: %s.", name, err),
    86  			))
    87  			continue
    88  		}
    89  
    90  		variables[name] = &InputValue{
    91  			Value:      val,
    92  			SourceType: ValueFromPlan,
    93  		}
    94  	}
    95  	if diags.HasErrors() {
    96  		return nil, walkApply, diags
    97  	}
    98  
    99  	// The plan.VariableValues field only records variables that were actually
   100  	// set by the caller in the PlanOpts, so we may need to provide
   101  	// placeholders for any other variables that the user didn't set, in
   102  	// which case Terraform will once again use the default value from the
   103  	// configuration when we visit these variables during the graph walk.
   104  	for name := range config.Module.Variables {
   105  		if _, ok := variables[name]; ok {
   106  			continue
   107  		}
   108  		variables[name] = &InputValue{
   109  			Value:      cty.NilVal,
   110  			SourceType: ValueFromPlan,
   111  		}
   112  	}
   113  
   114  	graph, moreDiags := (&ApplyGraphBuilder{
   115  		Config:             config,
   116  		Changes:            plan.Changes,
   117  		State:              plan.PriorState,
   118  		RootVariableValues: variables,
   119  		Plugins:            c.plugins,
   120  		Targets:            plan.TargetAddrs,
   121  		ForceReplace:       plan.ForceReplaceAddrs,
   122  	}).Build(addrs.RootModuleInstance)
   123  	diags = diags.Append(moreDiags)
   124  	if moreDiags.HasErrors() {
   125  		return nil, walkApply, diags
   126  	}
   127  
   128  	operation := walkApply
   129  	if plan.UIMode == plans.DestroyMode {
   130  		// NOTE: This is a vestigial violation of the rule that we mustn't
   131  		// use plan.UIMode to affect apply-time behavior. It's a design error
   132  		// if anything downstream switches behavior when operation is set
   133  		// to walkDestroy, but we've not yet fully audited that.
   134  		// TODO: Audit that and remove walkDestroy as an operation mode.
   135  		operation = walkDestroy
   136  	}
   137  
   138  	return graph, operation, diags
   139  }
   140  
   141  // ApplyGraphForUI is a last vestage of graphs in the public interface of
   142  // Context (as opposed to graphs as an implementation detail) intended only for
   143  // use by the "terraform graph" command when asked to render an apply-time
   144  // graph.
   145  //
   146  // The result of this is intended only for rendering ot the user as a dot
   147  // graph, and so may change in future in order to make the result more useful
   148  // in that context, even if drifts away from the physical graph that Terraform
   149  // Core currently uses as an implementation detail of planning.
   150  func (c *Context) ApplyGraphForUI(plan *plans.Plan, config *configs.Config) (*Graph, tfdiags.Diagnostics) {
   151  	// For now though, this really is just the internal graph, confusing
   152  	// implementation details and all.
   153  
   154  	var diags tfdiags.Diagnostics
   155  
   156  	graph, _, moreDiags := c.applyGraph(plan, config, false)
   157  	diags = diags.Append(moreDiags)
   158  	return graph, diags
   159  }