github.com/jhixson74/hashicorp-terraform@v0.11.12-beta1/backend/local/backend_plan.go (about)

     1  package local
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/errwrap"
    12  	"github.com/hashicorp/terraform/backend"
    13  	"github.com/hashicorp/terraform/command/format"
    14  	"github.com/hashicorp/terraform/config/module"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  func (b *Local) opPlan(
    19  	stopCtx context.Context,
    20  	cancelCtx context.Context,
    21  	op *backend.Operation,
    22  	runningOp *backend.RunningOperation) {
    23  	log.Printf("[INFO] backend/local: starting Plan operation")
    24  
    25  	if b.CLI != nil && op.Plan != nil {
    26  		b.CLI.Output(b.Colorize().Color(
    27  			"[reset][bold][yellow]" +
    28  				"The plan command received a saved plan file as input. This command\n" +
    29  				"will output the saved plan. This will not modify the already-existing\n" +
    30  				"plan. If you wish to generate a new plan, please pass in a configuration\n" +
    31  				"directory as an argument.\n\n"))
    32  	}
    33  
    34  	// A local plan requires either a plan or a module
    35  	if op.Plan == nil && op.Module == nil && !op.Destroy {
    36  		runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrNoConfig))
    37  		return
    38  	}
    39  
    40  	// If we have a nil module at this point, then set it to an empty tree
    41  	// to avoid any potential crashes.
    42  	if op.Module == nil {
    43  		op.Module = module.NewEmptyTree()
    44  	}
    45  
    46  	// Setup our count hook that keeps track of resource changes
    47  	countHook := new(CountHook)
    48  	if b.ContextOpts == nil {
    49  		b.ContextOpts = new(terraform.ContextOpts)
    50  	}
    51  	old := b.ContextOpts.Hooks
    52  	defer func() { b.ContextOpts.Hooks = old }()
    53  	b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook)
    54  
    55  	// Get our context
    56  	tfCtx, opState, err := b.context(op)
    57  	if err != nil {
    58  		runningOp.Err = err
    59  		return
    60  	}
    61  
    62  	// Setup the state
    63  	runningOp.State = tfCtx.State()
    64  
    65  	// If we're refreshing before plan, perform that
    66  	if op.PlanRefresh {
    67  		log.Printf("[INFO] backend/local: plan calling Refresh")
    68  
    69  		if b.CLI != nil {
    70  			b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n"))
    71  		}
    72  
    73  		_, err := tfCtx.Refresh()
    74  		if err != nil {
    75  			runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
    76  			return
    77  		}
    78  		if b.CLI != nil {
    79  			b.CLI.Output("\n------------------------------------------------------------------------")
    80  		}
    81  	}
    82  
    83  	// Perform the plan in a goroutine so we can be interrupted
    84  	var plan *terraform.Plan
    85  	var planErr error
    86  	doneCh := make(chan struct{})
    87  	go func() {
    88  		defer close(doneCh)
    89  		log.Printf("[INFO] backend/local: plan calling Plan")
    90  		plan, planErr = tfCtx.Plan()
    91  	}()
    92  
    93  	if b.opWait(doneCh, stopCtx, cancelCtx, tfCtx, opState) {
    94  		return
    95  	}
    96  
    97  	if planErr != nil {
    98  		runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", planErr)
    99  		return
   100  	}
   101  	// Record state
   102  	runningOp.PlanEmpty = plan.Diff.Empty()
   103  
   104  	// Save the plan to disk
   105  	if path := op.PlanOutPath; path != "" {
   106  		// Write the backend if we have one
   107  		plan.Backend = op.PlanOutBackend
   108  
   109  		// This works around a bug (#12871) which is no longer possible to
   110  		// trigger but will exist for already corrupted upgrades.
   111  		if plan.Backend != nil && plan.State != nil {
   112  			plan.State.Remote = nil
   113  		}
   114  
   115  		log.Printf("[INFO] backend/local: writing plan output to: %s", path)
   116  		f, err := os.Create(path)
   117  		if err == nil {
   118  			err = terraform.WritePlan(plan, f)
   119  		}
   120  		f.Close()
   121  		if err != nil {
   122  			runningOp.Err = fmt.Errorf("Error writing plan file: %s", err)
   123  			return
   124  		}
   125  	}
   126  
   127  	// Perform some output tasks if we have a CLI to output to.
   128  	if b.CLI != nil {
   129  		dispPlan := format.NewPlan(plan)
   130  		if dispPlan.Empty() {
   131  			b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges)))
   132  			return
   133  		}
   134  
   135  		b.renderPlan(dispPlan)
   136  
   137  		// Give the user some next-steps, unless we're running in an automation
   138  		// tool which is presumed to provide its own UI for further actions.
   139  		if !b.RunningInAutomation {
   140  
   141  			b.CLI.Output("\n------------------------------------------------------------------------")
   142  
   143  			if path := op.PlanOutPath; path == "" {
   144  				b.CLI.Output(fmt.Sprintf(
   145  					"\n" + strings.TrimSpace(planHeaderNoOutput) + "\n",
   146  				))
   147  			} else {
   148  				b.CLI.Output(fmt.Sprintf(
   149  					"\n"+strings.TrimSpace(planHeaderYesOutput)+"\n",
   150  					path, path,
   151  				))
   152  			}
   153  
   154  		}
   155  	}
   156  }
   157  
   158  func (b *Local) renderPlan(dispPlan *format.Plan) {
   159  
   160  	headerBuf := &bytes.Buffer{}
   161  	fmt.Fprintf(headerBuf, "\n%s\n", strings.TrimSpace(planHeaderIntro))
   162  	counts := dispPlan.ActionCounts()
   163  	if counts[terraform.DiffCreate] > 0 {
   164  		fmt.Fprintf(headerBuf, "%s create\n", format.DiffActionSymbol(terraform.DiffCreate))
   165  	}
   166  	if counts[terraform.DiffUpdate] > 0 {
   167  		fmt.Fprintf(headerBuf, "%s update in-place\n", format.DiffActionSymbol(terraform.DiffUpdate))
   168  	}
   169  	if counts[terraform.DiffDestroy] > 0 {
   170  		fmt.Fprintf(headerBuf, "%s destroy\n", format.DiffActionSymbol(terraform.DiffDestroy))
   171  	}
   172  	if counts[terraform.DiffDestroyCreate] > 0 {
   173  		fmt.Fprintf(headerBuf, "%s destroy and then create replacement\n", format.DiffActionSymbol(terraform.DiffDestroyCreate))
   174  	}
   175  	if counts[terraform.DiffRefresh] > 0 {
   176  		fmt.Fprintf(headerBuf, "%s read (data resources)\n", format.DiffActionSymbol(terraform.DiffRefresh))
   177  	}
   178  
   179  	b.CLI.Output(b.Colorize().Color(headerBuf.String()))
   180  
   181  	b.CLI.Output("Terraform will perform the following actions:\n")
   182  
   183  	b.CLI.Output(dispPlan.Format(b.Colorize()))
   184  
   185  	stats := dispPlan.Stats()
   186  	b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   187  		"[reset][bold]Plan:[reset] "+
   188  			"%d to add, %d to change, %d to destroy.",
   189  		stats.ToAdd, stats.ToChange, stats.ToDestroy,
   190  	)))
   191  }
   192  
   193  const planErrNoConfig = `
   194  No configuration files found!
   195  
   196  Plan requires configuration to be present. Planning without a configuration
   197  would mark everything for destruction, which is normally not what is desired.
   198  If you would like to destroy everything, please run plan with the "-destroy"
   199  flag or create a single empty configuration file. Otherwise, please create
   200  a Terraform configuration file in the path being executed and try again.
   201  `
   202  
   203  const planHeaderIntro = `
   204  An execution plan has been generated and is shown below.
   205  Resource actions are indicated with the following symbols:
   206  `
   207  
   208  const planHeaderNoOutput = `
   209  Note: You didn't specify an "-out" parameter to save this plan, so Terraform
   210  can't guarantee that exactly these actions will be performed if
   211  "terraform apply" is subsequently run.
   212  `
   213  
   214  const planHeaderYesOutput = `
   215  This plan was saved to: %s
   216  
   217  To perform exactly these actions, run the following command to apply:
   218      terraform apply %q
   219  `
   220  
   221  const planNoChanges = `
   222  [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green]
   223  
   224  This means that Terraform did not detect any differences between your
   225  configuration and real physical resources that exist. As a result, no
   226  actions need to be performed.
   227  `
   228  
   229  const planRefreshing = `
   230  [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset]
   231  The refreshed state will be used to calculate this plan, but will not be
   232  persisted to local or remote state storage.
   233  `