github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  		log.Printf("[INFO] backend/local: writing plan output to: %s", path)
   114  		f, err := os.Create(path)
   115  		if err == nil {
   116  			err = terraform.WritePlan(plan, f)
   117  		}
   118  		f.Close()
   119  		if err != nil {
   120  			runningOp.Err = fmt.Errorf("Error writing plan file: %s", err)
   121  			return
   122  		}
   123  	}
   124  
   125  	// Perform some output tasks if we have a CLI to output to.
   126  	if b.CLI != nil {
   127  		if plan.Diff.Empty() {
   128  			b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges)))
   129  			return
   130  		}
   131  
   132  		if path := op.PlanOutPath; path == "" {
   133  			b.CLI.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
   134  		} else {
   135  			b.CLI.Output(fmt.Sprintf(
   136  				strings.TrimSpace(planHeaderYesOutput)+"\n",
   137  				path))
   138  		}
   139  
   140  		b.CLI.Output(format.Plan(&format.PlanOpts{
   141  			Plan:        plan,
   142  			Color:       b.Colorize(),
   143  			ModuleDepth: -1,
   144  		}))
   145  
   146  		b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   147  			"[reset][bold]Plan:[reset] "+
   148  				"%d to add, %d to change, %d to destroy.",
   149  			countHook.ToAdd+countHook.ToRemoveAndAdd,
   150  			countHook.ToChange,
   151  			countHook.ToRemove+countHook.ToRemoveAndAdd)))
   152  	}
   153  }
   154  
   155  const planErrNoConfig = `
   156  No configuration files found!
   157  
   158  Plan requires configuration to be present. Planning without a configuration
   159  would mark everything for destruction, which is normally not what is desired.
   160  If you would like to destroy everything, please run plan with the "-destroy"
   161  flag or create a single empty configuration file. Otherwise, please create
   162  a Terraform configuration file in the path being executed and try again.
   163  `
   164  
   165  const planHeaderNoOutput = `
   166  The Terraform execution plan has been generated and is shown below.
   167  Resources are shown in alphabetical order for quick scanning. Green resources
   168  will be created (or destroyed and then created if an existing resource
   169  exists), yellow resources are being changed in-place, and red resources
   170  will be destroyed. Cyan entries are data sources to be read.
   171  
   172  Note: You didn't specify an "-out" parameter to save this plan, so when
   173  "apply" is called, Terraform can't guarantee this is what will execute.
   174  `
   175  
   176  const planHeaderYesOutput = `
   177  The Terraform execution plan has been generated and is shown below.
   178  Resources are shown in alphabetical order for quick scanning. Green resources
   179  will be created (or destroyed and then created if an existing resource
   180  exists), yellow resources are being changed in-place, and red resources
   181  will be destroyed. Cyan entries are data sources to be read.
   182  
   183  Your plan was also saved to the path below. Call the "apply" subcommand
   184  with this plan file and Terraform will exactly execute this execution
   185  plan.
   186  
   187  Path: %s
   188  `
   189  
   190  const planNoChanges = `
   191  [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green]
   192  
   193  This means that Terraform did not detect any differences between your
   194  configuration and real physical resources that exist. As a result, Terraform
   195  doesn't need to do anything.
   196  `
   197  
   198  const planRefreshing = `
   199  [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset]
   200  The refreshed state will be used to calculate this plan, but will not be
   201  persisted to local or remote state storage.
   202  `