github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/apply.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "sort" 8 "strings" 9 10 "github.com/hashicorp/terraform/tfdiags" 11 12 "github.com/hashicorp/go-getter" 13 "github.com/hashicorp/terraform/backend" 14 "github.com/hashicorp/terraform/config" 15 "github.com/hashicorp/terraform/config/module" 16 "github.com/hashicorp/terraform/terraform" 17 ) 18 19 // ApplyCommand is a Command implementation that applies a Terraform 20 // configuration and actually builds or changes infrastructure. 21 type ApplyCommand struct { 22 Meta 23 24 // If true, then this apply command will become the "destroy" 25 // command. It is just like apply but only processes a destroy. 26 Destroy bool 27 } 28 29 func (c *ApplyCommand) Run(args []string) int { 30 var destroyForce, refresh, autoApprove bool 31 args, err := c.Meta.process(args, true) 32 if err != nil { 33 return 1 34 } 35 36 cmdName := "apply" 37 if c.Destroy { 38 cmdName = "destroy" 39 } 40 41 cmdFlags := c.Meta.flagSet(cmdName) 42 if c.Destroy { 43 cmdFlags.BoolVar(&destroyForce, "force", false, "force") 44 } 45 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 46 if !c.Destroy { 47 cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of plan before applying") 48 } 49 cmdFlags.IntVar( 50 &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 51 cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") 52 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 53 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 54 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 55 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 56 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 57 if err := cmdFlags.Parse(args); err != nil { 58 return 1 59 } 60 61 // Get the args. The "maybeInit" flag tracks whether we may need to 62 // initialize the configuration from a remote path. This is true as long 63 // as we have an argument. 64 args = cmdFlags.Args() 65 maybeInit := len(args) == 1 66 configPath, err := ModulePath(args) 67 if err != nil { 68 c.Ui.Error(err.Error()) 69 return 1 70 } 71 72 // Check for user-supplied plugin path 73 if c.pluginPath, err = c.loadPluginPath(); err != nil { 74 c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err)) 75 return 1 76 } 77 78 if !c.Destroy && maybeInit { 79 // We need the pwd for the getter operation below 80 pwd, err := os.Getwd() 81 if err != nil { 82 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 83 return 1 84 } 85 86 // Do a detect to determine if we need to do an init + apply. 87 if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { 88 c.Ui.Error(fmt.Sprintf( 89 "Invalid path: %s", err)) 90 return 1 91 } else if !strings.HasPrefix(detected, "file") { 92 // If this isn't a file URL then we're doing an init + 93 // apply. 94 var init InitCommand 95 init.Meta = c.Meta 96 if code := init.Run([]string{detected}); code != 0 { 97 return code 98 } 99 100 // Change the config path to be the cwd 101 configPath = pwd 102 } 103 } 104 105 // Check if the path is a plan 106 plan, err := c.Plan(configPath) 107 if err != nil { 108 c.Ui.Error(err.Error()) 109 return 1 110 } 111 if c.Destroy && plan != nil { 112 c.Ui.Error(fmt.Sprintf( 113 "Destroy can't be called with a plan file.")) 114 return 1 115 } 116 if plan != nil { 117 // Reset the config path for backend loading 118 configPath = "" 119 } 120 121 var diags tfdiags.Diagnostics 122 123 // Load the module if we don't have one yet (not running from plan) 124 var mod *module.Tree 125 if plan == nil { 126 var modDiags tfdiags.Diagnostics 127 mod, modDiags = c.Module(configPath) 128 diags = diags.Append(modDiags) 129 if modDiags.HasErrors() { 130 c.showDiagnostics(diags) 131 return 1 132 } 133 } 134 135 var conf *config.Config 136 if mod != nil { 137 conf = mod.Config() 138 } 139 140 // Load the backend 141 b, err := c.Backend(&BackendOpts{ 142 Config: conf, 143 Plan: plan, 144 }) 145 if err != nil { 146 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 147 return 1 148 } 149 150 // Build the operation 151 opReq := c.Operation() 152 opReq.Destroy = c.Destroy 153 opReq.Module = mod 154 opReq.Plan = plan 155 opReq.PlanRefresh = refresh 156 opReq.Type = backend.OperationTypeApply 157 opReq.AutoApprove = autoApprove 158 opReq.DestroyForce = destroyForce 159 160 op, err := c.RunOperation(b, opReq) 161 if err != nil { 162 diags = diags.Append(err) 163 } 164 165 c.showDiagnostics(diags) 166 if diags.HasErrors() { 167 return 1 168 } 169 170 if !c.Destroy { 171 // Get the right module that we used. If we ran a plan, then use 172 // that module. 173 if plan != nil { 174 mod = plan.Module 175 } 176 177 if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" { 178 c.Ui.Output(c.Colorize().Color(outputs)) 179 } 180 } 181 182 return 0 183 } 184 185 func (c *ApplyCommand) Help() string { 186 if c.Destroy { 187 return c.helpDestroy() 188 } 189 190 return c.helpApply() 191 } 192 193 func (c *ApplyCommand) Synopsis() string { 194 if c.Destroy { 195 return "Destroy Terraform-managed infrastructure" 196 } 197 198 return "Builds or changes infrastructure" 199 } 200 201 func (c *ApplyCommand) helpApply() string { 202 helpText := ` 203 Usage: terraform apply [options] [DIR-OR-PLAN] 204 205 Builds or changes infrastructure according to Terraform configuration 206 files in DIR. 207 208 By default, apply scans the current directory for the configuration 209 and applies the changes appropriately. However, a path to another 210 configuration or an execution plan can be provided. Execution plans can be 211 used to only execute a pre-determined set of actions. 212 213 Options: 214 215 -backup=path Path to backup the existing state file before 216 modifying. Defaults to the "-state-out" path with 217 ".backup" extension. Set to "-" to disable backup. 218 219 -lock=true Lock the state file when locking is supported. 220 221 -lock-timeout=0s Duration to retry a state lock. 222 223 -auto-approve Skip interactive approval of plan before applying. 224 225 -input=true Ask for input for variables if not directly set. 226 227 -no-color If specified, output won't contain any color. 228 229 -parallelism=n Limit the number of parallel resource operations. 230 Defaults to 10. 231 232 -refresh=true Update state prior to checking for differences. This 233 has no effect if a plan file is given to apply. 234 235 -state=path Path to read and save state (unless state-out 236 is specified). Defaults to "terraform.tfstate". 237 238 -state-out=path Path to write state to that is different than 239 "-state". This can be used to preserve the old 240 state. 241 242 -target=resource Resource to target. Operation will be limited to this 243 resource and its dependencies. This flag can be used 244 multiple times. 245 246 -var 'foo=bar' Set a variable in the Terraform configuration. This 247 flag can be set multiple times. 248 249 -var-file=foo Set variables in the Terraform configuration from 250 a file. If "terraform.tfvars" or any ".auto.tfvars" 251 files are present, they will be automatically loaded. 252 253 254 ` 255 return strings.TrimSpace(helpText) 256 } 257 258 func (c *ApplyCommand) helpDestroy() string { 259 helpText := ` 260 Usage: terraform destroy [options] [DIR] 261 262 Destroy Terraform-managed infrastructure. 263 264 Options: 265 266 -backup=path Path to backup the existing state file before 267 modifying. Defaults to the "-state-out" path with 268 ".backup" extension. Set to "-" to disable backup. 269 270 -force Don't ask for input for destroy confirmation. 271 272 -lock=true Lock the state file when locking is supported. 273 274 -lock-timeout=0s Duration to retry a state lock. 275 276 -no-color If specified, output won't contain any color. 277 278 -parallelism=n Limit the number of concurrent operations. 279 Defaults to 10. 280 281 -refresh=true Update state prior to checking for differences. This 282 has no effect if a plan file is given to apply. 283 284 -state=path Path to read and save state (unless state-out 285 is specified). Defaults to "terraform.tfstate". 286 287 -state-out=path Path to write state to that is different than 288 "-state". This can be used to preserve the old 289 state. 290 291 -target=resource Resource to target. Operation will be limited to this 292 resource and its dependencies. This flag can be used 293 multiple times. 294 295 -var 'foo=bar' Set a variable in the Terraform configuration. This 296 flag can be set multiple times. 297 298 -var-file=foo Set variables in the Terraform configuration from 299 a file. If "terraform.tfvars" or any ".auto.tfvars" 300 files are present, they will be automatically loaded. 301 302 303 ` 304 return strings.TrimSpace(helpText) 305 } 306 307 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 308 if state == nil { 309 return "" 310 } 311 312 ms := state.ModuleByPath(modPath) 313 if ms == nil { 314 return "" 315 } 316 317 outputs := ms.Outputs 318 outputBuf := new(bytes.Buffer) 319 if len(outputs) > 0 { 320 schemaMap := make(map[string]*config.Output) 321 if schema != nil { 322 for _, s := range schema { 323 schemaMap[s.Name] = s 324 } 325 } 326 327 if includeHeader { 328 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 329 } 330 331 // Output the outputs in alphabetical order 332 keyLen := 0 333 ks := make([]string, 0, len(outputs)) 334 for key, _ := range outputs { 335 ks = append(ks, key) 336 if len(key) > keyLen { 337 keyLen = len(key) 338 } 339 } 340 sort.Strings(ks) 341 342 for _, k := range ks { 343 schema, ok := schemaMap[k] 344 if ok && schema.Sensitive { 345 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 346 continue 347 } 348 349 v := outputs[k] 350 switch typedV := v.Value.(type) { 351 case string: 352 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 353 case []interface{}: 354 outputBuf.WriteString(formatListOutput("", k, typedV)) 355 outputBuf.WriteString("\n") 356 case map[string]interface{}: 357 outputBuf.WriteString(formatMapOutput("", k, typedV)) 358 outputBuf.WriteString("\n") 359 } 360 } 361 } 362 363 return strings.TrimSpace(outputBuf.String()) 364 } 365 366 const outputInterrupt = `Interrupt received. 367 Please wait for Terraform to exit or data loss may occur. 368 Gracefully shutting down...`