github.com/serbaut/terraform@v0.6.12-0.20160607213102-ac2d195cc560/command/plan.go (about)

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