github.com/maheshbr/terraform@v0.3.1-0.20141020033300-deec7194a3ea/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 bool
    20  	var outPath, statePath, backupPath 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  	cmdFlags.IntVar(&moduleDepth, "module-depth", 0, "module-depth")
    29  	cmdFlags.StringVar(&outPath, "out", "", "path")
    30  	cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path")
    31  	cmdFlags.StringVar(&backupPath, "backup", "", "path")
    32  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    33  	if err := cmdFlags.Parse(args); err != nil {
    34  		return 1
    35  	}
    36  
    37  	var path string
    38  	args = cmdFlags.Args()
    39  	if len(args) > 1 {
    40  		c.Ui.Error(
    41  			"The plan command expects at most one argument with the path\n" +
    42  				"to a Terraform configuration.\n")
    43  		cmdFlags.Usage()
    44  		return 1
    45  	} else if len(args) == 1 {
    46  		path = args[0]
    47  	} else {
    48  		var err error
    49  		path, err = os.Getwd()
    50  		if err != nil {
    51  			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    52  		}
    53  	}
    54  
    55  	// If the default state path doesn't exist, ignore it.
    56  	if statePath != "" {
    57  		if _, err := os.Stat(statePath); err != nil {
    58  			if os.IsNotExist(err) && statePath == DefaultStateFilename {
    59  				statePath = ""
    60  			}
    61  		}
    62  	}
    63  
    64  	// If we don't specify a backup path, default to state out with
    65  	// the extension
    66  	if backupPath == "" {
    67  		backupPath = statePath + DefaultBackupExtention
    68  	}
    69  
    70  	ctx, _, err := c.Context(contextOpts{
    71  		Path:      path,
    72  		StatePath: statePath,
    73  	})
    74  	if err != nil {
    75  		c.Ui.Error(err.Error())
    76  		return 1
    77  	}
    78  	if err := ctx.Input(c.InputMode()); err != nil {
    79  		c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
    80  		return 1
    81  	}
    82  	if !validateContext(ctx, c.Ui) {
    83  		return 1
    84  	}
    85  
    86  	if refresh {
    87  		// Create a backup of the state before updating
    88  		if backupPath != "-" && c.state != nil {
    89  			log.Printf("[INFO] Writing backup state to: %s", backupPath)
    90  			f, err := os.Create(backupPath)
    91  			if err == nil {
    92  				err = terraform.WriteState(c.state, f)
    93  				f.Close()
    94  			}
    95  			if err != nil {
    96  				c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
    97  				return 1
    98  			}
    99  		}
   100  
   101  		c.Ui.Output("Refreshing Terraform state prior to plan...\n")
   102  		if _, err := ctx.Refresh(); err != nil {
   103  			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
   104  			return 1
   105  		}
   106  		c.Ui.Output("")
   107  	}
   108  
   109  	plan, err := ctx.Plan(&terraform.PlanOpts{Destroy: destroy})
   110  	if err != nil {
   111  		c.Ui.Error(fmt.Sprintf("Error running plan: %s", err))
   112  		return 1
   113  	}
   114  
   115  	if plan.Diff.Empty() {
   116  		c.Ui.Output(
   117  			"No changes. Infrastructure is up-to-date. This means that Terraform\n" +
   118  				"could not detect any differences between your configuration and\n" +
   119  				"the real physical resources that exist. As a result, Terraform\n" +
   120  				"doesn't need to do anything.")
   121  		return 0
   122  	}
   123  
   124  	if outPath != "" {
   125  		log.Printf("[INFO] Writing plan output to: %s", outPath)
   126  		f, err := os.Create(outPath)
   127  		if err == nil {
   128  			defer f.Close()
   129  			err = terraform.WritePlan(plan, f)
   130  		}
   131  		if err != nil {
   132  			c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err))
   133  			return 1
   134  		}
   135  	}
   136  
   137  	if outPath == "" {
   138  		c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
   139  	} else {
   140  		c.Ui.Output(fmt.Sprintf(
   141  			strings.TrimSpace(planHeaderYesOutput)+"\n",
   142  			outPath))
   143  	}
   144  
   145  	c.Ui.Output(FormatPlan(&FormatPlanOpts{
   146  		Plan:        plan,
   147  		Color:       c.Colorize(),
   148  		ModuleDepth: moduleDepth,
   149  	}))
   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    -input=true         Ask for input for variables if not directly set.
   175  
   176    -module-depth=n     Specifies the depth of modules to show in the output.
   177                        This does not affect the plan itself, only the output
   178                        shown. By default, this is zero. -1 will expand all.
   179  
   180    -no-color           If specified, output won't contain any color.
   181  
   182    -out=path           Write a plan file to the given path. This can be used as
   183                        input to the "apply" command.
   184  
   185    -refresh=true       Update state prior to checking for differences.
   186  
   187    -state=statefile    Path to a Terraform state file to use to look
   188                        up Terraform-managed resources. By default it will
   189                        use the state "terraform.tfstate" if it exists.
   190  
   191    -var 'foo=bar'      Set a variable in the Terraform configuration. This
   192                        flag can be set multiple times.
   193  
   194    -var-file=foo       Set variables in the Terraform configuration from
   195                        a file. If "terraform.tfvars" is present, it will be
   196                        automatically loaded if this flag is not specified.
   197  
   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.
   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.
   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  `