github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/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 `