github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/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    -destroy            If set, a plan will be generated to destroy all resources
   161                        managed by the given configuration and state.
   162  
   163    -detailed-exitcode  Return detailed exit codes when the command exits. This
   164                        will change the meaning of exit codes to:
   165                        0 - Succeeded, diff is empty (no changes)
   166                        1 - Errored
   167                        2 - Succeeded, there is a diff
   168  
   169    -input=true         Ask for input for variables if not directly set.
   170  
   171    -module-depth=n     Specifies the depth of modules to show in the output.
   172                        This does not affect the plan itself, only the output
   173                        shown. By default, this is -1, which will expand all.
   174  
   175    -no-color           If specified, output won't contain any color.
   176  
   177    -out=path           Write a plan file to the given path. This can be used as
   178                        input to the "apply" command.
   179  
   180    -parallelism=n      Limit the number of concurrent operations. Defaults to 10.
   181  
   182    -refresh=true       Update state prior to checking for differences.
   183  
   184    -state=statefile    Path to a Terraform state file to use to look
   185                        up Terraform-managed resources. By default it will
   186                        use the state "terraform.tfstate" if it exists.
   187  
   188    -target=resource    Resource to target. Operation will be limited to this
   189                        resource and its dependencies. This flag can be used
   190                        multiple times.
   191  
   192    -var 'foo=bar'      Set a variable in the Terraform configuration. This
   193                        flag can be set multiple times.
   194  
   195    -var-file=foo       Set variables in the Terraform configuration from
   196                        a file. If "terraform.tfvars" is present, it will be
   197                        automatically loaded if this flag is not specified.
   198  `
   199  	return strings.TrimSpace(helpText)
   200  }
   201  
   202  func (c *PlanCommand) Synopsis() string {
   203  	return "Generate and show an execution plan"
   204  }
   205  
   206  const planHeaderNoOutput = `
   207  The Terraform execution plan has been generated and is shown below.
   208  Resources are shown in alphabetical order for quick scanning. Green resources
   209  will be created (or destroyed and then created if an existing resource
   210  exists), yellow resources are being changed in-place, and red resources
   211  will be destroyed. Cyan entries are data sources to be read.
   212  
   213  Note: You didn't specify an "-out" parameter to save this plan, so when
   214  "apply" is called, Terraform can't guarantee this is what will execute.
   215  `
   216  
   217  const planHeaderYesOutput = `
   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. Cyan entries are data sources to be read.
   223  
   224  Your plan was also saved to the path below. Call the "apply" subcommand
   225  with this plan file and Terraform will exactly execute this execution
   226  plan.
   227  
   228  Path: %s
   229  `