github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/backend/local/backend_plan.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/errwrap"
    11  	"github.com/hashicorp/go-multierror"
    12  	"github.com/hashicorp/terraform/backend"
    13  	"github.com/hashicorp/terraform/command/format"
    14  	clistate "github.com/hashicorp/terraform/command/state"
    15  	"github.com/hashicorp/terraform/config/module"
    16  	"github.com/hashicorp/terraform/terraform"
    17  )
    18  
    19  func (b *Local) opPlan(
    20  	ctx 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  	// If we're locking state, unlock when we're done
    63  	if op.LockState {
    64  		defer func() {
    65  			if err := clistate.Unlock(opState, b.CLI, b.Colorize()); err != nil {
    66  				runningOp.Err = multierror.Append(runningOp.Err, err)
    67  			}
    68  		}()
    69  	}
    70  
    71  	// Setup the state
    72  	runningOp.State = tfCtx.State()
    73  
    74  	// If we're refreshing before plan, perform that
    75  	if op.PlanRefresh {
    76  		log.Printf("[INFO] backend/local: plan calling Refresh")
    77  
    78  		if b.CLI != nil {
    79  			b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n"))
    80  		}
    81  
    82  		_, err := tfCtx.Refresh()
    83  		if err != nil {
    84  			runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
    85  			return
    86  		}
    87  	}
    88  
    89  	// Perform the plan
    90  	log.Printf("[INFO] backend/local: plan calling Plan")
    91  	plan, err := tfCtx.Plan()
    92  	if err != nil {
    93  		runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err)
    94  		return
    95  	}
    96  
    97  	// Record state
    98  	runningOp.PlanEmpty = plan.Diff.Empty()
    99  
   100  	// Save the plan to disk
   101  	if path := op.PlanOutPath; path != "" {
   102  		// Write the backend if we have one
   103  		plan.Backend = op.PlanOutBackend
   104  
   105  		log.Printf("[INFO] backend/local: writing plan output to: %s", path)
   106  		f, err := os.Create(path)
   107  		if err == nil {
   108  			err = terraform.WritePlan(plan, f)
   109  		}
   110  		f.Close()
   111  		if err != nil {
   112  			runningOp.Err = fmt.Errorf("Error writing plan file: %s", err)
   113  			return
   114  		}
   115  	}
   116  
   117  	// Perform some output tasks if we have a CLI to output to.
   118  	if b.CLI != nil {
   119  		if plan.Diff.Empty() {
   120  			b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges)))
   121  			return
   122  		}
   123  
   124  		if path := op.PlanOutPath; path == "" {
   125  			b.CLI.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
   126  		} else {
   127  			b.CLI.Output(fmt.Sprintf(
   128  				strings.TrimSpace(planHeaderYesOutput)+"\n",
   129  				path))
   130  		}
   131  
   132  		b.CLI.Output(format.Plan(&format.PlanOpts{
   133  			Plan:        plan,
   134  			Color:       b.Colorize(),
   135  			ModuleDepth: -1,
   136  		}))
   137  
   138  		b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   139  			"[reset][bold]Plan:[reset] "+
   140  				"%d to add, %d to change, %d to destroy.",
   141  			countHook.ToAdd+countHook.ToRemoveAndAdd,
   142  			countHook.ToChange,
   143  			countHook.ToRemove+countHook.ToRemoveAndAdd)))
   144  	}
   145  }
   146  
   147  const planErrNoConfig = `
   148  No configuration files found!
   149  
   150  Plan requires configuration to be present. Planning without a configuration
   151  would mark everything for destruction, which is normally not what is desired.
   152  If you would like to destroy everything, please run plan with the "-destroy"
   153  flag or create a single empty configuration file. Otherwise, please create
   154  a Terraform configuration file in the path being executed and try again.
   155  `
   156  
   157  const planHeaderNoOutput = `
   158  The Terraform execution plan has been generated and is shown below.
   159  Resources are shown in alphabetical order for quick scanning. Green resources
   160  will be created (or destroyed and then created if an existing resource
   161  exists), yellow resources are being changed in-place, and red resources
   162  will be destroyed. Cyan entries are data sources to be read.
   163  
   164  Note: You didn't specify an "-out" parameter to save this plan, so when
   165  "apply" is called, Terraform can't guarantee this is what will execute.
   166  `
   167  
   168  const planHeaderYesOutput = `
   169  The Terraform execution plan has been generated and is shown below.
   170  Resources are shown in alphabetical order for quick scanning. Green resources
   171  will be created (or destroyed and then created if an existing resource
   172  exists), yellow resources are being changed in-place, and red resources
   173  will be destroyed. Cyan entries are data sources to be read.
   174  
   175  Your plan was also saved to the path below. Call the "apply" subcommand
   176  with this plan file and Terraform will exactly execute this execution
   177  plan.
   178  
   179  Path: %s
   180  `
   181  
   182  const planNoChanges = `
   183  [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green]
   184  
   185  This means that Terraform did not detect any differences between your
   186  configuration and real physical resources that exist. As a result, Terraform
   187  doesn't need to do anything.
   188  `
   189  
   190  const planRefreshing = `
   191  [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset]
   192  The refreshed state will be used to calculate this plan, but will not be
   193  persisted to local or remote state storage.
   194  `