github.com/aspring/terraform@v0.8.2-0.20161216122603-6a8619a5db2e/command/plan.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/go-multierror"
    10  	"github.com/hashicorp/terraform/terraform"
    11  )
    12  
    13  // PlanCommand is a Command implementation that compares a Terraform
    14  // configuration to an actual infrastructure and shows the differences.
    15  type PlanCommand struct {
    16  	Meta
    17  }
    18  
    19  func (c *PlanCommand) Run(args []string) int {
    20  	var destroy, refresh, detailed bool
    21  	var outPath string
    22  	var moduleDepth int
    23  
    24  	args = c.Meta.process(args, true)
    25  
    26  	cmdFlags := c.Meta.flagSet("plan")
    27  	cmdFlags.BoolVar(&destroy, "destroy", false, "destroy")
    28  	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
    29  	c.addModuleDepthFlag(cmdFlags, &moduleDepth)
    30  	cmdFlags.StringVar(&outPath, "out", "", "path")
    31  	cmdFlags.IntVar(
    32  		&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
    33  	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    34  	cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode")
    35  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    36  	if err := cmdFlags.Parse(args); err != nil {
    37  		return 1
    38  	}
    39  
    40  	var path string
    41  	args = cmdFlags.Args()
    42  	if len(args) > 1 {
    43  		c.Ui.Error(
    44  			"The plan command expects at most one argument with the path\n" +
    45  				"to a Terraform configuration.\n")
    46  		cmdFlags.Usage()
    47  		return 1
    48  	} else if len(args) == 1 {
    49  		path = args[0]
    50  	} else {
    51  		var err error
    52  		path, err = os.Getwd()
    53  		if err != nil {
    54  			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    55  		}
    56  	}
    57  
    58  	countHook := new(CountHook)
    59  	c.Meta.extraHooks = []terraform.Hook{countHook}
    60  
    61  	// This is going to keep track of shadow errors
    62  	var shadowErr error
    63  
    64  	ctx, planned, err := c.Context(contextOpts{
    65  		Destroy:     destroy,
    66  		Path:        path,
    67  		StatePath:   c.Meta.statePath,
    68  		Parallelism: c.Meta.parallelism,
    69  	})
    70  	if err != nil {
    71  		c.Ui.Error(err.Error())
    72  		return 1
    73  	}
    74  	if planned {
    75  		c.Ui.Output(c.Colorize().Color(
    76  			"[reset][bold][yellow]" +
    77  				"The plan command received a saved plan file as input. This command\n" +
    78  				"will output the saved plan. This will not modify the already-existing\n" +
    79  				"plan. If you wish to generate a new plan, please pass in a configuration\n" +
    80  				"directory as an argument.\n\n"))
    81  
    82  		// Disable refreshing no matter what since we only want to show the plan
    83  		refresh = false
    84  	}
    85  
    86  	err = terraform.SetDebugInfo(DefaultDataDir)
    87  	if err != nil {
    88  		c.Ui.Error(err.Error())
    89  		return 1
    90  	}
    91  
    92  	if err := ctx.Input(c.InputMode()); err != nil {
    93  		c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
    94  		return 1
    95  	}
    96  
    97  	// Record any shadow errors for later
    98  	if err := ctx.ShadowError(); err != nil {
    99  		shadowErr = multierror.Append(shadowErr, multierror.Prefix(
   100  			err, "input operation:"))
   101  	}
   102  
   103  	if !validateContext(ctx, c.Ui) {
   104  		return 1
   105  	}
   106  
   107  	if refresh {
   108  		c.Ui.Output("Refreshing Terraform state in-memory prior to plan...")
   109  		c.Ui.Output("The refreshed state will be used to calculate this plan, but")
   110  		c.Ui.Output("will not be persisted to local or remote state storage.\n")
   111  		_, err := ctx.Refresh()
   112  		if err != nil {
   113  			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
   114  			return 1
   115  		}
   116  		c.Ui.Output("")
   117  	}
   118  
   119  	plan, err := ctx.Plan()
   120  	if err != nil {
   121  		c.Ui.Error(fmt.Sprintf("Error running plan: %s", err))
   122  		return 1
   123  	}
   124  
   125  	if outPath != "" {
   126  		log.Printf("[INFO] Writing plan output to: %s", outPath)
   127  		f, err := os.Create(outPath)
   128  		if err == nil {
   129  			defer f.Close()
   130  			err = terraform.WritePlan(plan, f)
   131  		}
   132  		if err != nil {
   133  			c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err))
   134  			return 1
   135  		}
   136  	}
   137  
   138  	if plan.Diff.Empty() {
   139  		c.Ui.Output(
   140  			"No changes. Infrastructure is up-to-date. This means that Terraform\n" +
   141  				"could not detect any differences between your configuration and\n" +
   142  				"the real physical resources that exist. As a result, Terraform\n" +
   143  				"doesn't need to do anything.")
   144  		return 0
   145  	}
   146  
   147  	if outPath == "" {
   148  		c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
   149  	} else {
   150  		c.Ui.Output(fmt.Sprintf(
   151  			strings.TrimSpace(planHeaderYesOutput)+"\n",
   152  			outPath))
   153  	}
   154  
   155  	c.Ui.Output(FormatPlan(&FormatPlanOpts{
   156  		Plan:        plan,
   157  		Color:       c.Colorize(),
   158  		ModuleDepth: moduleDepth,
   159  	}))
   160  
   161  	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   162  		"[reset][bold]Plan:[reset] "+
   163  			"%d to add, %d to change, %d to destroy.",
   164  		countHook.ToAdd+countHook.ToRemoveAndAdd,
   165  		countHook.ToChange,
   166  		countHook.ToRemove+countHook.ToRemoveAndAdd)))
   167  
   168  	// Record any shadow errors for later
   169  	if err := ctx.ShadowError(); err != nil {
   170  		shadowErr = multierror.Append(shadowErr, multierror.Prefix(
   171  			err, "plan operation:"))
   172  	}
   173  
   174  	// If we have an error in the shadow graph, let the user know.
   175  	c.outputShadowError(shadowErr, true)
   176  
   177  	if detailed {
   178  		return 2
   179  	}
   180  	return 0
   181  }
   182  
   183  func (c *PlanCommand) Help() string {
   184  	helpText := `
   185  Usage: terraform plan [options] [DIR-OR-PLAN]
   186  
   187    Generates an execution plan for Terraform.
   188  
   189    This execution plan can be reviewed prior to running apply to get a
   190    sense for what Terraform will do. Optionally, the plan can be saved to
   191    a Terraform plan file, and apply can take this plan file to execute
   192    this plan exactly.
   193  
   194    If a saved plan is passed as an argument, this command will output
   195    the saved plan contents. It will not modify the given plan.
   196  
   197  Options:
   198  
   199    -destroy            If set, a plan will be generated to destroy all resources
   200                        managed by the given configuration and state.
   201  
   202    -detailed-exitcode  Return detailed exit codes when the command exits. This
   203                        will change the meaning of exit codes to:
   204                        0 - Succeeded, diff is empty (no changes)
   205                        1 - Errored
   206                        2 - Succeeded, there is a diff
   207  
   208    -input=true         Ask for input for variables if not directly set.
   209  
   210    -module-depth=n     Specifies the depth of modules to show in the output.
   211                        This does not affect the plan itself, only the output
   212                        shown. By default, this is -1, which will expand all.
   213  
   214    -no-color           If specified, output won't contain any color.
   215  
   216    -out=path           Write a plan file to the given path. This can be used as
   217                        input to the "apply" command.
   218  
   219    -parallelism=n      Limit the number of concurrent operations. Defaults to 10.
   220  
   221    -refresh=true       Update state prior to checking for differences.
   222  
   223    -state=statefile    Path to a Terraform state file to use to look
   224                        up Terraform-managed resources. By default it will
   225                        use the state "terraform.tfstate" if it exists.
   226  
   227    -target=resource    Resource to target. Operation will be limited to this
   228                        resource and its dependencies. This flag can be used
   229                        multiple times.
   230  
   231    -var 'foo=bar'      Set a variable in the Terraform configuration. This
   232                        flag can be set multiple times.
   233  
   234    -var-file=foo       Set variables in the Terraform configuration from
   235                        a file. If "terraform.tfvars" is present, it will be
   236                        automatically loaded if this flag is not specified.
   237  `
   238  	return strings.TrimSpace(helpText)
   239  }
   240  
   241  func (c *PlanCommand) Synopsis() string {
   242  	return "Generate and show an execution plan"
   243  }
   244  
   245  const planHeaderNoOutput = `
   246  The Terraform execution plan has been generated and is shown below.
   247  Resources are shown in alphabetical order for quick scanning. Green resources
   248  will be created (or destroyed and then created if an existing resource
   249  exists), yellow resources are being changed in-place, and red resources
   250  will be destroyed. Cyan entries are data sources to be read.
   251  
   252  Note: You didn't specify an "-out" parameter to save this plan, so when
   253  "apply" is called, Terraform can't guarantee this is what will execute.
   254  `
   255  
   256  const planHeaderYesOutput = `
   257  The Terraform execution plan has been generated and is shown below.
   258  Resources are shown in alphabetical order for quick scanning. Green resources
   259  will be created (or destroyed and then created if an existing resource
   260  exists), yellow resources are being changed in-place, and red resources
   261  will be destroyed. Cyan entries are data sources to be read.
   262  
   263  Your plan was also saved to the path below. Call the "apply" subcommand
   264  with this plan file and Terraform will exactly execute this execution
   265  plan.
   266  
   267  Path: %s
   268  `