github.com/simt2/terraform@v0.7.10/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, _, 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  
    75  	err = terraform.SetDebugInfo(DefaultDataDir)
    76  	if err != nil {
    77  		c.Ui.Error(err.Error())
    78  		return 1
    79  	}
    80  
    81  	if err := ctx.Input(c.InputMode()); err != nil {
    82  		c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
    83  		return 1
    84  	}
    85  
    86  	// Record any shadow errors for later
    87  	if err := ctx.ShadowError(); err != nil {
    88  		shadowErr = multierror.Append(shadowErr, multierror.Prefix(
    89  			err, "input operation:"))
    90  	}
    91  
    92  	if !validateContext(ctx, c.Ui) {
    93  		return 1
    94  	}
    95  
    96  	if refresh {
    97  		c.Ui.Output("Refreshing Terraform state in-memory prior to plan...")
    98  		c.Ui.Output("The refreshed state will be used to calculate this plan, but")
    99  		c.Ui.Output("will not be persisted to local or remote state storage.\n")
   100  		_, err := ctx.Refresh()
   101  		if err != nil {
   102  			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
   103  			return 1
   104  		}
   105  		c.Ui.Output("")
   106  	}
   107  
   108  	plan, err := ctx.Plan()
   109  	if err != nil {
   110  		c.Ui.Error(fmt.Sprintf("Error running plan: %s", err))
   111  		return 1
   112  	}
   113  
   114  	if outPath != "" {
   115  		log.Printf("[INFO] Writing plan output to: %s", outPath)
   116  		f, err := os.Create(outPath)
   117  		if err == nil {
   118  			defer f.Close()
   119  			err = terraform.WritePlan(plan, f)
   120  		}
   121  		if err != nil {
   122  			c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err))
   123  			return 1
   124  		}
   125  	}
   126  
   127  	if plan.Diff.Empty() {
   128  		c.Ui.Output(
   129  			"No changes. Infrastructure is up-to-date. This means that Terraform\n" +
   130  				"could not detect any differences between your configuration and\n" +
   131  				"the real physical resources that exist. As a result, Terraform\n" +
   132  				"doesn't need to do anything.")
   133  		return 0
   134  	}
   135  
   136  	if outPath == "" {
   137  		c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
   138  	} else {
   139  		c.Ui.Output(fmt.Sprintf(
   140  			strings.TrimSpace(planHeaderYesOutput)+"\n",
   141  			outPath))
   142  	}
   143  
   144  	c.Ui.Output(FormatPlan(&FormatPlanOpts{
   145  		Plan:        plan,
   146  		Color:       c.Colorize(),
   147  		ModuleDepth: moduleDepth,
   148  	}))
   149  
   150  	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   151  		"[reset][bold]Plan:[reset] "+
   152  			"%d to add, %d to change, %d to destroy.",
   153  		countHook.ToAdd+countHook.ToRemoveAndAdd,
   154  		countHook.ToChange,
   155  		countHook.ToRemove+countHook.ToRemoveAndAdd)))
   156  
   157  	// Record any shadow errors for later
   158  	if err := ctx.ShadowError(); err != nil {
   159  		shadowErr = multierror.Append(shadowErr, multierror.Prefix(
   160  			err, "plan operation:"))
   161  	}
   162  
   163  	// If we have an error in the shadow graph, let the user know.
   164  	c.outputShadowError(shadowErr, true)
   165  
   166  	if detailed {
   167  		return 2
   168  	}
   169  	return 0
   170  }
   171  
   172  func (c *PlanCommand) Help() string {
   173  	helpText := `
   174  Usage: terraform plan [options] [dir]
   175  
   176    Generates an execution plan for Terraform.
   177  
   178    This execution plan can be reviewed prior to running apply to get a
   179    sense for what Terraform will do. Optionally, the plan can be saved to
   180    a Terraform plan file, and apply can take this plan file to execute
   181    this plan exactly.
   182  
   183  Options:
   184  
   185    -destroy            If set, a plan will be generated to destroy all resources
   186                        managed by the given configuration and state.
   187  
   188    -detailed-exitcode  Return detailed exit codes when the command exits. This
   189                        will change the meaning of exit codes to:
   190                        0 - Succeeded, diff is empty (no changes)
   191                        1 - Errored
   192                        2 - Succeeded, there is a diff
   193  
   194    -input=true         Ask for input for variables if not directly set.
   195  
   196    -module-depth=n     Specifies the depth of modules to show in the output.
   197                        This does not affect the plan itself, only the output
   198                        shown. By default, this is -1, which will expand all.
   199  
   200    -no-color           If specified, output won't contain any color.
   201  
   202    -out=path           Write a plan file to the given path. This can be used as
   203                        input to the "apply" command.
   204  
   205    -parallelism=n      Limit the number of concurrent operations. Defaults to 10.
   206  
   207    -refresh=true       Update state prior to checking for differences.
   208  
   209    -state=statefile    Path to a Terraform state file to use to look
   210                        up Terraform-managed resources. By default it will
   211                        use the state "terraform.tfstate" if it exists.
   212  
   213    -target=resource    Resource to target. Operation will be limited to this
   214                        resource and its dependencies. This flag can be used
   215                        multiple times.
   216  
   217    -var 'foo=bar'      Set a variable in the Terraform configuration. This
   218                        flag can be set multiple times.
   219  
   220    -var-file=foo       Set variables in the Terraform configuration from
   221                        a file. If "terraform.tfvars" is present, it will be
   222                        automatically loaded if this flag is not specified.
   223  `
   224  	return strings.TrimSpace(helpText)
   225  }
   226  
   227  func (c *PlanCommand) Synopsis() string {
   228  	return "Generate and show an execution plan"
   229  }
   230  
   231  const planHeaderNoOutput = `
   232  The Terraform execution plan has been generated and is shown below.
   233  Resources are shown in alphabetical order for quick scanning. Green resources
   234  will be created (or destroyed and then created if an existing resource
   235  exists), yellow resources are being changed in-place, and red resources
   236  will be destroyed. Cyan entries are data sources to be read.
   237  
   238  Note: You didn't specify an "-out" parameter to save this plan, so when
   239  "apply" is called, Terraform can't guarantee this is what will execute.
   240  `
   241  
   242  const planHeaderYesOutput = `
   243  The Terraform execution plan has been generated and is shown below.
   244  Resources are shown in alphabetical order for quick scanning. Green resources
   245  will be created (or destroyed and then created if an existing resource
   246  exists), yellow resources are being changed in-place, and red resources
   247  will be destroyed. Cyan entries are data sources to be read.
   248  
   249  Your plan was also saved to the path below. Call the "apply" subcommand
   250  with this plan file and Terraform will exactly execute this execution
   251  plan.
   252  
   253  Path: %s
   254  `