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