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