github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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/state"
    17  	"github.com/hashicorp/terraform/terraform"
    18  )
    19  
    20  func (b *Local) opPlan(
    21  	ctx context.Context,
    22  	op *backend.Operation,
    23  	runningOp *backend.RunningOperation) {
    24  	log.Printf("[INFO] backend/local: starting Plan operation")
    25  
    26  	if b.CLI != nil && op.Plan != nil {
    27  		b.CLI.Output(b.Colorize().Color(
    28  			"[reset][bold][yellow]" +
    29  				"The plan command received a saved plan file as input. This command\n" +
    30  				"will output the saved plan. This will not modify the already-existing\n" +
    31  				"plan. If you wish to generate a new plan, please pass in a configuration\n" +
    32  				"directory as an argument.\n\n"))
    33  	}
    34  
    35  	// A local plan requires either a plan or a module
    36  	if op.Plan == nil && op.Module == nil && !op.Destroy {
    37  		runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrNoConfig))
    38  		return
    39  	}
    40  
    41  	// If we have a nil module at this point, then set it to an empty tree
    42  	// to avoid any potential crashes.
    43  	if op.Module == nil {
    44  		op.Module = module.NewEmptyTree()
    45  	}
    46  
    47  	// Setup our count hook that keeps track of resource changes
    48  	countHook := new(CountHook)
    49  	if b.ContextOpts == nil {
    50  		b.ContextOpts = new(terraform.ContextOpts)
    51  	}
    52  	old := b.ContextOpts.Hooks
    53  	defer func() { b.ContextOpts.Hooks = old }()
    54  	b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook)
    55  
    56  	// Get our context
    57  	tfCtx, opState, err := b.context(op)
    58  	if err != nil {
    59  		runningOp.Err = err
    60  		return
    61  	}
    62  
    63  	if op.LockState {
    64  		lockInfo := state.NewLockInfo()
    65  		lockInfo.Operation = op.Type.String()
    66  		lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
    67  		if err != nil {
    68  			runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
    69  			return
    70  		}
    71  
    72  		defer func() {
    73  			if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil {
    74  				runningOp.Err = multierror.Append(runningOp.Err, err)
    75  			}
    76  		}()
    77  	}
    78  
    79  	// Setup the state
    80  	runningOp.State = tfCtx.State()
    81  
    82  	// If we're refreshing before plan, perform that
    83  	if op.PlanRefresh {
    84  		log.Printf("[INFO] backend/local: plan calling Refresh")
    85  
    86  		if b.CLI != nil {
    87  			b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n"))
    88  		}
    89  
    90  		_, err := tfCtx.Refresh()
    91  		if err != nil {
    92  			runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
    93  			return
    94  		}
    95  	}
    96  
    97  	// Perform the plan
    98  	log.Printf("[INFO] backend/local: plan calling Plan")
    99  	plan, err := tfCtx.Plan()
   100  	if err != nil {
   101  		runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err)
   102  		return
   103  	}
   104  
   105  	// Record state
   106  	runningOp.PlanEmpty = plan.Diff.Empty()
   107  
   108  	// Save the plan to disk
   109  	if path := op.PlanOutPath; path != "" {
   110  		// Write the backend if we have one
   111  		plan.Backend = op.PlanOutBackend
   112  
   113  		// This works around a bug (#12871) which is no longer possible to
   114  		// trigger but will exist for already corrupted upgrades.
   115  		if plan.Backend != nil && plan.State != nil {
   116  			plan.State.Remote = nil
   117  		}
   118  
   119  		log.Printf("[INFO] backend/local: writing plan output to: %s", path)
   120  		f, err := os.Create(path)
   121  		if err == nil {
   122  			err = terraform.WritePlan(plan, f)
   123  		}
   124  		f.Close()
   125  		if err != nil {
   126  			runningOp.Err = fmt.Errorf("Error writing plan file: %s", err)
   127  			return
   128  		}
   129  	}
   130  
   131  	// Perform some output tasks if we have a CLI to output to.
   132  	if b.CLI != nil {
   133  		if plan.Diff.Empty() {
   134  			b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges)))
   135  			return
   136  		}
   137  
   138  		if path := op.PlanOutPath; path == "" {
   139  			b.CLI.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
   140  		} else {
   141  			b.CLI.Output(fmt.Sprintf(
   142  				strings.TrimSpace(planHeaderYesOutput)+"\n",
   143  				path))
   144  		}
   145  
   146  		b.CLI.Output(format.Plan(&format.PlanOpts{
   147  			Plan:        plan,
   148  			Color:       b.Colorize(),
   149  			ModuleDepth: -1,
   150  		}))
   151  
   152  		b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   153  			"[reset][bold]Plan:[reset] "+
   154  				"%d to add, %d to change, %d to destroy.",
   155  			countHook.ToAdd+countHook.ToRemoveAndAdd,
   156  			countHook.ToChange,
   157  			countHook.ToRemove+countHook.ToRemoveAndAdd)))
   158  	}
   159  }
   160  
   161  const planErrNoConfig = `
   162  No configuration files found!
   163  
   164  Plan requires configuration to be present. Planning without a configuration
   165  would mark everything for destruction, which is normally not what is desired.
   166  If you would like to destroy everything, please run plan with the "-destroy"
   167  flag or create a single empty configuration file. Otherwise, please create
   168  a Terraform configuration file in the path being executed and try again.
   169  `
   170  
   171  const planHeaderNoOutput = `
   172  The Terraform execution plan has been generated and is shown below.
   173  Resources are shown in alphabetical order for quick scanning. Green resources
   174  will be created (or destroyed and then created if an existing resource
   175  exists), yellow resources are being changed in-place, and red resources
   176  will be destroyed. Cyan entries are data sources to be read.
   177  
   178  Note: You didn't specify an "-out" parameter to save this plan, so when
   179  "apply" is called, Terraform can't guarantee this is what will execute.
   180  `
   181  
   182  const planHeaderYesOutput = `
   183  The Terraform execution plan has been generated and is shown below.
   184  Resources are shown in alphabetical order for quick scanning. Green resources
   185  will be created (or destroyed and then created if an existing resource
   186  exists), yellow resources are being changed in-place, and red resources
   187  will be destroyed. Cyan entries are data sources to be read.
   188  
   189  Your plan was also saved to the path below. Call the "apply" subcommand
   190  with this plan file and Terraform will exactly execute this execution
   191  plan.
   192  
   193  Path: %s
   194  `
   195  
   196  const planNoChanges = `
   197  [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green]
   198  
   199  This means that Terraform did not detect any differences between your
   200  configuration and real physical resources that exist. As a result, Terraform
   201  doesn't need to do anything.
   202  `
   203  
   204  const planRefreshing = `
   205  [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset]
   206  The refreshed state will be used to calculate this plan, but will not be
   207  persisted to local or remote state storage.
   208  `