github.com/sylr/terraform@v0.11.12-beta1/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 cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of plan before applying") 43 if c.Destroy { 44 cmdFlags.BoolVar(&destroyForce, "force", false, "deprecated: same as auto-approve") 45 } 46 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 47 cmdFlags.IntVar( 48 &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 49 cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") 50 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 51 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 52 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 53 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 54 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 55 if err := cmdFlags.Parse(args); err != nil { 56 return 1 57 } 58 59 // Get the args. The "maybeInit" flag tracks whether we may need to 60 // initialize the configuration from a remote path. This is true as long 61 // as we have an argument. 62 args = cmdFlags.Args() 63 maybeInit := len(args) == 1 64 configPath, err := ModulePath(args) 65 if err != nil { 66 c.Ui.Error(err.Error()) 67 return 1 68 } 69 70 // Check for user-supplied plugin path 71 if c.pluginPath, err = c.loadPluginPath(); err != nil { 72 c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err)) 73 return 1 74 } 75 76 if !c.Destroy && maybeInit { 77 // We need the pwd for the getter operation below 78 pwd, err := os.Getwd() 79 if err != nil { 80 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 81 return 1 82 } 83 84 // Do a detect to determine if we need to do an init + apply. 85 if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { 86 c.Ui.Error(fmt.Sprintf( 87 "Invalid path: %s", err)) 88 return 1 89 } else if !strings.HasPrefix(detected, "file") { 90 // If this isn't a file URL then we're doing an init + 91 // apply. 92 var init InitCommand 93 init.Meta = c.Meta 94 if code := init.Run([]string{detected}); code != 0 { 95 return code 96 } 97 98 // Change the config path to be the cwd 99 configPath = pwd 100 } 101 } 102 103 // Check if the path is a plan 104 plan, err := c.Plan(configPath) 105 if err != nil { 106 c.Ui.Error(err.Error()) 107 return 1 108 } 109 if c.Destroy && plan != nil { 110 c.Ui.Error(fmt.Sprintf( 111 "Destroy can't be called with a plan file.")) 112 return 1 113 } 114 if plan != nil { 115 // Reset the config path for backend loading 116 configPath = "" 117 } 118 119 var diags tfdiags.Diagnostics 120 121 // Load the module if we don't have one yet (not running from plan) 122 var mod *module.Tree 123 if plan == nil { 124 var modDiags tfdiags.Diagnostics 125 mod, modDiags = c.Module(configPath) 126 diags = diags.Append(modDiags) 127 if modDiags.HasErrors() { 128 c.showDiagnostics(diags) 129 return 1 130 } 131 } 132 133 var conf *config.Config 134 if mod != nil { 135 conf = mod.Config() 136 } 137 138 // Load the backend 139 b, err := c.Backend(&BackendOpts{ 140 Config: conf, 141 Plan: plan, 142 }) 143 if err != nil { 144 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 145 return 1 146 } 147 148 // Build the operation 149 opReq := c.Operation() 150 opReq.AutoApprove = autoApprove 151 opReq.Destroy = c.Destroy 152 opReq.DestroyForce = destroyForce 153 opReq.Module = mod 154 opReq.Plan = plan 155 opReq.PlanRefresh = refresh 156 opReq.Type = backend.OperationTypeApply 157 158 op, err := c.RunOperation(b, opReq) 159 if err != nil { 160 diags = diags.Append(err) 161 } 162 163 c.showDiagnostics(diags) 164 if diags.HasErrors() { 165 return 1 166 } 167 168 if !c.Destroy { 169 // Get the right module that we used. If we ran a plan, then use 170 // that module. 171 if plan != nil { 172 mod = plan.Module 173 } 174 175 if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" { 176 c.Ui.Output(c.Colorize().Color(outputs)) 177 } 178 } 179 180 return op.ExitCode 181 } 182 183 func (c *ApplyCommand) Help() string { 184 if c.Destroy { 185 return c.helpDestroy() 186 } 187 188 return c.helpApply() 189 } 190 191 func (c *ApplyCommand) Synopsis() string { 192 if c.Destroy { 193 return "Destroy Terraform-managed infrastructure" 194 } 195 196 return "Builds or changes infrastructure" 197 } 198 199 func (c *ApplyCommand) helpApply() string { 200 helpText := ` 201 Usage: terraform apply [options] [DIR-OR-PLAN] 202 203 Builds or changes infrastructure according to Terraform configuration 204 files in DIR. 205 206 By default, apply scans the current directory for the configuration 207 and applies the changes appropriately. However, a path to another 208 configuration or an execution plan can be provided. Execution plans can be 209 used to only execute a pre-determined set of actions. 210 211 Options: 212 213 -backup=path Path to backup the existing state file before 214 modifying. Defaults to the "-state-out" path with 215 ".backup" extension. Set to "-" to disable backup. 216 217 -auto-approve Skip interactive approval of plan before applying. 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 -input=true Ask for input for variables if not directly set. 224 225 -no-color If specified, output won't contain any color. 226 227 -parallelism=n Limit the number of parallel resource operations. 228 Defaults to 10. 229 230 -refresh=true Update state prior to checking for differences. This 231 has no effect if a plan file is given to apply. 232 233 -state=path Path to read and save state (unless state-out 234 is specified). Defaults to "terraform.tfstate". 235 236 -state-out=path Path to write state to that is different than 237 "-state". This can be used to preserve the old 238 state. 239 240 -target=resource Resource to target. Operation will be limited to this 241 resource and its dependencies. This flag can be used 242 multiple times. 243 244 -var 'foo=bar' Set a variable in the Terraform configuration. This 245 flag can be set multiple times. 246 247 -var-file=foo Set variables in the Terraform configuration from 248 a file. If "terraform.tfvars" or any ".auto.tfvars" 249 files are present, they will be automatically loaded. 250 251 252 ` 253 return strings.TrimSpace(helpText) 254 } 255 256 func (c *ApplyCommand) helpDestroy() string { 257 helpText := ` 258 Usage: terraform destroy [options] [DIR] 259 260 Destroy Terraform-managed infrastructure. 261 262 Options: 263 264 -backup=path Path to backup the existing state file before 265 modifying. Defaults to the "-state-out" path with 266 ".backup" extension. Set to "-" to disable backup. 267 268 -auto-approve Skip interactive approval before destroying. 269 270 -force Deprecated: same as auto-approve. 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...`