github.com/blacked/terraform@v0.6.2-0.20150806163846-669c4ad71586/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.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    31  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    32  	cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode")
    33  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    34  	if err := cmdFlags.Parse(args); err != nil {
    35  		return 1
    36  	}
    37  
    38  	var path string
    39  	args = cmdFlags.Args()
    40  	if len(args) > 1 {
    41  		c.Ui.Error(
    42  			"The plan command expects at most one argument with the path\n" +
    43  				"to a Terraform configuration.\n")
    44  		cmdFlags.Usage()
    45  		return 1
    46  	} else if len(args) == 1 {
    47  		path = args[0]
    48  	} else {
    49  		var err error
    50  		path, err = os.Getwd()
    51  		if err != nil {
    52  			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    53  		}
    54  	}
    55  
    56  	countHook := new(CountHook)
    57  	c.Meta.extraHooks = []terraform.Hook{countHook}
    58  
    59  	ctx, _, err := c.Context(contextOpts{
    60  		Destroy:   destroy,
    61  		Path:      path,
    62  		StatePath: c.Meta.statePath,
    63  	})
    64  	if err != nil {
    65  		c.Ui.Error(err.Error())
    66  		return 1
    67  	}
    68  	if !validateContext(ctx, c.Ui) {
    69  		return 1
    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 refresh {
    77  		c.Ui.Output("Refreshing Terraform state prior to plan...\n")
    78  		state, err := ctx.Refresh()
    79  		if err != nil {
    80  			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
    81  			return 1
    82  		}
    83  		c.Ui.Output("")
    84  
    85  		if state != nil {
    86  			log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
    87  			if err := c.Meta.PersistState(state); err != nil {
    88  				c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
    89  				return 1
    90  			}
    91  		}
    92  	}
    93  
    94  	plan, err := ctx.Plan()
    95  	if err != nil {
    96  		c.Ui.Error(fmt.Sprintf("Error running plan: %s", err))
    97  		return 1
    98  	}
    99  
   100  	if plan.Diff.Empty() {
   101  		c.Ui.Output(
   102  			"No changes. Infrastructure is up-to-date. This means that Terraform\n" +
   103  				"could not detect any differences between your configuration and\n" +
   104  				"the real physical resources that exist. As a result, Terraform\n" +
   105  				"doesn't need to do anything.")
   106  		return 0
   107  	}
   108  
   109  	if outPath != "" {
   110  		log.Printf("[INFO] Writing plan output to: %s", outPath)
   111  		f, err := os.Create(outPath)
   112  		if err == nil {
   113  			defer f.Close()
   114  			err = terraform.WritePlan(plan, f)
   115  		}
   116  		if err != nil {
   117  			c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err))
   118  			return 1
   119  		}
   120  	}
   121  
   122  	if outPath == "" {
   123  		c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
   124  	} else {
   125  		c.Ui.Output(fmt.Sprintf(
   126  			strings.TrimSpace(planHeaderYesOutput)+"\n",
   127  			outPath))
   128  	}
   129  
   130  	c.Ui.Output(FormatPlan(&FormatPlanOpts{
   131  		Plan:        plan,
   132  		Color:       c.Colorize(),
   133  		ModuleDepth: moduleDepth,
   134  	}))
   135  
   136  	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   137  		"[reset][bold]Plan:[reset] "+
   138  			"%d to add, %d to change, %d to destroy.",
   139  		countHook.ToAdd,
   140  		(countHook.ToChange + countHook.ToRemoveAndAdd),
   141  		countHook.ToRemove)))
   142  
   143  	if detailed {
   144  		return 2
   145  	}
   146  	return 0
   147  }
   148  
   149  func (c *PlanCommand) Help() string {
   150  	helpText := `
   151  Usage: terraform plan [options] [dir]
   152  
   153    Generates an execution plan for Terraform.
   154  
   155    This execution plan can be reviewed prior to running apply to get a
   156    sense for what Terraform will do. Optionally, the plan can be saved to
   157    a Terraform plan file, and apply can take this plan file to execute
   158    this plan exactly.
   159  
   160  Options:
   161  
   162    -backup=path        Path to backup the existing state file before
   163                        modifying. Defaults to the "-state-out" path with
   164                        ".backup" extension. Set to "-" to disable backup.
   165  
   166    -destroy            If set, a plan will be generated to destroy all resources
   167                        managed by the given configuration and state.
   168  
   169    -detailed-exitcode  Return detailed exit codes when the command exits. This
   170                        will change the meaning of exit codes to:
   171                        0 - Succeeded, diff is empty (no changes)
   172                        1 - Errored
   173                        2 - Succeeded, there is a diff
   174  
   175    -input=true         Ask for input for variables if not directly set.
   176  
   177    -module-depth=n     Specifies the depth of modules to show in the output.
   178                        This does not affect the plan itself, only the output
   179                        shown. By default, this is zero. -1 will expand all.
   180  
   181    -no-color           If specified, output won't contain any color.
   182  
   183    -out=path           Write a plan file to the given path. This can be used as
   184                        input to the "apply" command.
   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.
   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.
   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  `