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