github.com/ticketmaster/terraform@v0.10.0-beta2.0.20170711045249-a12daf5aba4f/command/init.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "sort" 8 "strings" 9 10 multierror "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/terraform/backend" 12 "github.com/hashicorp/terraform/config" 13 "github.com/hashicorp/terraform/config/module" 14 "github.com/hashicorp/terraform/helper/variables" 15 "github.com/hashicorp/terraform/plugin" 16 "github.com/hashicorp/terraform/plugin/discovery" 17 "github.com/hashicorp/terraform/terraform" 18 ) 19 20 // InitCommand is a Command implementation that takes a Terraform 21 // module and clones it to the working directory. 22 type InitCommand struct { 23 Meta 24 25 // getPlugins is for the -get-plugins flag 26 getPlugins bool 27 28 // providerInstaller is used to download and install providers that 29 // aren't found locally. This uses a discovery.ProviderInstaller instance 30 // by default, but it can be overridden here as a way to mock fetching 31 // providers for tests. 32 providerInstaller discovery.Installer 33 } 34 35 func (c *InitCommand) Run(args []string) int { 36 var flagBackend, flagGet, flagUpgrade bool 37 var flagConfigExtra map[string]interface{} 38 var flagPluginPath FlagStringSlice 39 var flagVerifyPlugins bool 40 41 args, err := c.Meta.process(args, false) 42 if err != nil { 43 return 1 44 } 45 cmdFlags := c.flagSet("init") 46 cmdFlags.BoolVar(&flagBackend, "backend", true, "") 47 cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "") 48 cmdFlags.BoolVar(&flagGet, "get", true, "") 49 cmdFlags.BoolVar(&c.getPlugins, "get-plugins", true, "") 50 cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") 51 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 52 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 53 cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure") 54 cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "") 55 cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory") 56 cmdFlags.BoolVar(&flagVerifyPlugins, "verify-plugins", true, "verify plugins") 57 58 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 59 if err := cmdFlags.Parse(args); err != nil { 60 return 1 61 } 62 63 if len(flagPluginPath) > 0 { 64 c.pluginPath = flagPluginPath 65 c.getPlugins = false 66 } 67 68 // set getProvider if we don't have a test version already 69 if c.providerInstaller == nil { 70 c.providerInstaller = &discovery.ProviderInstaller{ 71 Dir: c.pluginDir(), 72 PluginProtocolVersion: plugin.Handshake.ProtocolVersion, 73 SkipVerify: !flagVerifyPlugins, 74 } 75 } 76 77 // Validate the arg count 78 args = cmdFlags.Args() 79 if len(args) > 1 { 80 c.Ui.Error("The init command expects at most one argument.\n") 81 cmdFlags.Usage() 82 return 1 83 } 84 85 if err := c.storePluginPath(c.pluginPath); err != nil { 86 c.Ui.Error(fmt.Sprintf("Error saving -plugin-path values: %s", err)) 87 return 1 88 } 89 90 // Get our pwd. We don't always need it but always getting it is easier 91 // than the logic to determine if it is or isn't needed. 92 pwd, err := os.Getwd() 93 if err != nil { 94 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 95 return 1 96 } 97 98 // Get the path and source module to copy 99 path := pwd 100 if len(args) == 1 { 101 path = args[0] 102 } 103 104 // This will track whether we outputted anything so that we know whether 105 // to output a newline before the success message 106 var header bool 107 108 // If our directory is empty, then we're done. We can't get or setup 109 // the backend with an empty directory. 110 if empty, err := config.IsEmptyDir(path); err != nil { 111 c.Ui.Error(fmt.Sprintf( 112 "Error checking configuration: %s", err)) 113 return 1 114 } else if empty { 115 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty))) 116 return 0 117 } 118 119 var back backend.Backend 120 121 // If we're performing a get or loading the backend, then we perform 122 // some extra tasks. 123 if flagGet || flagBackend { 124 conf, err := c.Config(path) 125 if err != nil { 126 c.Ui.Error(fmt.Sprintf( 127 "Error loading configuration: %s", err)) 128 return 1 129 } 130 131 // If we requested downloading modules and have modules in the config 132 if flagGet && len(conf.Modules) > 0 { 133 header = true 134 135 getMode := module.GetModeGet 136 if flagUpgrade { 137 getMode = module.GetModeUpdate 138 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 139 "[reset][bold]Upgrading modules..."))) 140 } else { 141 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 142 "[reset][bold]Downloading modules..."))) 143 } 144 145 if err := getModules(&c.Meta, path, getMode); err != nil { 146 c.Ui.Error(fmt.Sprintf( 147 "Error downloading modules: %s", err)) 148 return 1 149 } 150 151 } 152 153 // If we're requesting backend configuration or looking for required 154 // plugins, load the backend 155 if flagBackend { 156 header = true 157 158 // Only output that we're initializing a backend if we have 159 // something in the config. We can be UNSETTING a backend as well 160 // in which case we choose not to show this. 161 if conf.Terraform != nil && conf.Terraform.Backend != nil { 162 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 163 "\n[reset][bold]Initializing the backend..."))) 164 } 165 166 opts := &BackendOpts{ 167 Config: conf, 168 ConfigExtra: flagConfigExtra, 169 Init: true, 170 } 171 if back, err = c.Backend(opts); err != nil { 172 c.Ui.Error(err.Error()) 173 return 1 174 } 175 } 176 } 177 178 if back == nil { 179 // If we didn't initialize a backend then we'll try to at least 180 // instantiate one. This might fail if it wasn't already initalized 181 // by a previous run, so we must still expect that "back" may be nil 182 // in code that follows. 183 back, err = c.Backend(nil) 184 if err != nil { 185 // This is fine. We'll proceed with no backend, then. 186 back = nil 187 } 188 } 189 190 var state *terraform.State 191 192 // If we have a functional backend (either just initialized or initialized 193 // on a previous run) we'll use the current state as a potential source 194 // of provider dependencies. 195 if back != nil { 196 sMgr, err := back.State(c.Workspace()) 197 if err != nil { 198 c.Ui.Error(fmt.Sprintf( 199 "Error loading state: %s", err)) 200 return 1 201 } 202 203 if err := sMgr.RefreshState(); err != nil { 204 c.Ui.Error(fmt.Sprintf( 205 "Error refreshing state: %s", err)) 206 return 1 207 } 208 209 state = sMgr.State() 210 } 211 212 if v := os.Getenv(ProviderSkipVerifyEnvVar); v != "" { 213 c.ignorePluginChecksum = true 214 } 215 216 // Now that we have loaded all modules, check the module tree for missing providers. 217 err = c.getProviders(path, state, flagUpgrade) 218 if err != nil { 219 // this function provides its own output 220 log.Printf("[ERROR] %s", err) 221 return 1 222 } 223 224 // If we outputted information, then we need to output a newline 225 // so that our success message is nicely spaced out from prior text. 226 if header { 227 c.Ui.Output("") 228 } 229 230 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess))) 231 232 return 0 233 } 234 235 // Load the complete module tree, and fetch any missing providers. 236 // This method outputs its own Ui. 237 func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) error { 238 mod, err := c.Module(path) 239 if err != nil { 240 c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err)) 241 return err 242 } 243 244 if err := mod.Validate(); err != nil { 245 c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err)) 246 return err 247 } 248 249 var available discovery.PluginMetaSet 250 if upgrade { 251 // If we're in upgrade mode, we ignore any auto-installed plugins 252 // in "available", causing us to reinstall and possibly upgrade them. 253 available = c.providerPluginManuallyInstalledSet() 254 } else { 255 available = c.providerPluginSet() 256 } 257 258 requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() 259 if len(requirements) == 0 { 260 // nothing to initialize 261 return nil 262 } 263 264 c.Ui.Output(c.Colorize().Color( 265 "\n[reset][bold]Initializing provider plugins...", 266 )) 267 268 missing := c.missingPlugins(available, requirements) 269 270 var errs error 271 if c.getPlugins { 272 for provider, reqd := range missing { 273 c.Ui.Output(fmt.Sprintf("- Downloading plugin for provider %q...", provider)) 274 _, err := c.providerInstaller.Get(provider, reqd.Versions) 275 276 if err != nil { 277 switch err { 278 case discovery.ErrorNoSuchProvider: 279 c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir)) 280 case discovery.ErrorNoSuitableVersion: 281 if reqd.Versions.Unconstrained() { 282 // This should never happen, but might crop up if we catch 283 // the releases server in a weird state where the provider's 284 // directory is present but does not yet contain any 285 // versions. We'll treat it like ErrorNoSuchProvider, then. 286 c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir)) 287 } else { 288 c.Ui.Error(fmt.Sprintf(errProviderVersionsUnsuitable, provider, reqd.Versions)) 289 } 290 case discovery.ErrorNoVersionCompatible: 291 // FIXME: This error message is sub-awesome because we don't 292 // have enough information here to tell the user which versions 293 // we considered and which versions might be compatible. 294 constraint := reqd.Versions.String() 295 if constraint == "" { 296 constraint = "(any version)" 297 } 298 c.Ui.Error(fmt.Sprintf(errProviderIncompatible, provider, constraint)) 299 default: 300 c.Ui.Error(fmt.Sprintf(errProviderInstallError, provider, err.Error(), DefaultPluginVendorDir)) 301 } 302 303 errs = multierror.Append(errs, err) 304 } 305 } 306 307 if errs != nil { 308 return errs 309 } 310 } else if len(missing) > 0 { 311 // we have missing providers, but aren't going to try and download them 312 var lines []string 313 for provider, reqd := range missing { 314 if reqd.Versions.Unconstrained() { 315 lines = append(lines, fmt.Sprintf("* %s (any version)\n", provider)) 316 } else { 317 lines = append(lines, fmt.Sprintf("* %s (%s)\n", provider, reqd.Versions)) 318 } 319 errs = multierror.Append(errs, fmt.Errorf("missing provider %q", provider)) 320 } 321 sort.Strings(lines) 322 c.Ui.Error(fmt.Sprintf(errMissingProvidersNoInstall, strings.Join(lines, ""), DefaultPluginVendorDir)) 323 return errs 324 } 325 326 // With all the providers downloaded, we'll generate our lock file 327 // that ensures the provider binaries remain unchanged until we init 328 // again. If anything changes, other commands that use providers will 329 // fail with an error instructing the user to re-run this command. 330 available = c.providerPluginSet() // re-discover to see newly-installed plugins 331 chosen := choosePlugins(available, requirements) 332 digests := map[string][]byte{} 333 for name, meta := range chosen { 334 digest, err := meta.SHA256() 335 if err != nil { 336 c.Ui.Error(fmt.Sprintf("failed to read provider plugin %s: %s", meta.Path, err)) 337 return err 338 } 339 digests[name] = digest 340 if c.ignorePluginChecksum { 341 digests[name] = nil 342 } 343 } 344 err = c.providerPluginsLock().Write(digests) 345 if err != nil { 346 c.Ui.Error(fmt.Sprintf("failed to save provider manifest: %s", err)) 347 return err 348 } 349 350 { 351 // Purge any auto-installed plugins that aren't being used. 352 purged, err := c.providerInstaller.PurgeUnused(chosen) 353 if err != nil { 354 // Failure to purge old plugins is not a fatal error 355 c.Ui.Warn(fmt.Sprintf("failed to purge unused plugins: %s", err)) 356 } 357 if purged != nil { 358 for meta := range purged { 359 log.Printf("[DEBUG] Purged unused %s plugin %s", meta.Name, meta.Path) 360 } 361 } 362 } 363 364 // If any providers have "floating" versions (completely unconstrained) 365 // we'll suggest the user constrain with a pessimistic constraint to 366 // avoid implicitly adopting a later major release. 367 constraintSuggestions := make(map[string]discovery.ConstraintStr) 368 for name, meta := range chosen { 369 req := requirements[name] 370 if req == nil { 371 // should never happen, but we don't want to crash here, so we'll 372 // be cautious. 373 continue 374 } 375 376 if req.Versions.Unconstrained() && meta.Version != discovery.VersionZero { 377 // meta.Version.MustParse is safe here because our "chosen" metas 378 // were already filtered for validity of versions. 379 constraintSuggestions[name] = meta.Version.MustParse().MinorUpgradeConstraintStr() 380 } 381 } 382 if len(constraintSuggestions) != 0 { 383 names := make([]string, 0, len(constraintSuggestions)) 384 for name := range constraintSuggestions { 385 names = append(names, name) 386 } 387 sort.Strings(names) 388 389 c.Ui.Output(outputInitProvidersUnconstrained) 390 for _, name := range names { 391 c.Ui.Output(fmt.Sprintf("* provider.%s: version = %q", name, constraintSuggestions[name])) 392 } 393 } 394 395 return nil 396 } 397 398 func (c *InitCommand) Help() string { 399 helpText := ` 400 Usage: terraform init [options] [DIR] 401 402 Initialize a new or existing Terraform working directory by creating 403 initial files, loading any remote state, downloading modules, etc. 404 405 This is the first command that should be run for any new or existing 406 Terraform configuration per machine. This sets up all the local data 407 necessary to run Terraform that is typically not committed to version 408 control. 409 410 This command is always safe to run multiple times. Though subsequent runs 411 may give errors, this command will never delete your configuration or 412 state. Even so, if you have important information, please back it up prior 413 to running this command, just in case. 414 415 If no arguments are given, the configuration in this working directory 416 is initialized. 417 418 Options: 419 420 -backend=true Configure the backend for this configuration. 421 422 -backend-config=path This can be either a path to an HCL file with key/value 423 assignments (same format as terraform.tfvars) or a 424 'key=value' format. This is merged with what is in the 425 configuration file. This can be specified multiple 426 times. The backend type must be in the configuration 427 itself. 428 429 -force-copy Suppress prompts about copying state data. This is 430 equivalent to providing a "yes" to all confirmation 431 prompts. 432 433 -get=true Download any modules for this configuration. 434 435 -get-plugins=true Download any missing plugins for this configuration. 436 437 -input=true Ask for input if necessary. If false, will error if 438 input was required. 439 440 -lock=true Lock the state file when locking is supported. 441 442 -lock-timeout=0s Duration to retry a state lock. 443 444 -no-color If specified, output won't contain any color. 445 446 -plugin-dir Directory containing plugin binaries. This overrides all 447 default search paths for plugins, and prevents the 448 automatic installation of plugins. This flag can be used 449 multiple times. 450 451 -reconfigure Reconfigure the backend, ignoring any saved 452 configuration. 453 454 -upgrade=false If installing modules (-get) or plugins (-get-plugins), 455 ignore previously-downloaded objects and install the 456 latest version allowed within configured constraints. 457 458 -verify-plugins=true Verify the authenticity and integrity of automatically 459 downloaded plugins. 460 ` 461 return strings.TrimSpace(helpText) 462 } 463 464 func (c *InitCommand) Synopsis() string { 465 return "Initialize a new or existing Terraform configuration" 466 } 467 468 const errInitCopyNotEmpty = ` 469 The destination path contains Terraform configuration files. The init command 470 with a SOURCE parameter can only be used on a directory without existing 471 Terraform files. 472 473 Please resolve this issue and try again. 474 ` 475 476 const outputInitEmpty = ` 477 [reset][bold]Terraform initialized in an empty directory![reset] 478 479 The directory has no Terraform configuration files. You may begin working 480 with Terraform immediately by creating Terraform configuration files. 481 ` 482 483 const outputInitSuccess = ` 484 [reset][bold][green]Terraform has been successfully initialized![reset][green] 485 486 You may now begin working with Terraform. Try running "terraform plan" to see 487 any changes that are required for your infrastructure. All Terraform commands 488 should now work. 489 490 If you ever set or change modules or backend configuration for Terraform, 491 rerun this command to reinitialize your working directory. If you forget, other 492 commands will detect it and remind you to do so if necessary. 493 ` 494 495 const outputInitProvidersUnconstrained = ` 496 The following providers do not have any version constraints in configuration, 497 so the latest version was installed. 498 499 To prevent automatic upgrades to new major versions that may contain breaking 500 changes, it is recommended to add version = "..." constraints to the 501 corresponding provider blocks in configuration, with the constraint strings 502 suggested below. 503 ` 504 505 const errProviderNotFound = ` 506 [reset][bold][red]Provider %[1]q not available for installation.[reset][red] 507 508 A provider named %[1]q could not be found in the official repository. 509 510 This may result from mistyping the provider name, or the given provider may 511 be a third-party provider that cannot be installed automatically. 512 513 In the latter case, the plugin must be installed manually by locating and 514 downloading a suitable distribution package and placing the plugin's executable 515 file in the following directory: 516 %[2]s 517 518 Terraform detects necessary plugins by inspecting the configuration and state. 519 To view the provider versions requested by each module, run 520 "terraform providers". 521 ` 522 523 const errProviderVersionsUnsuitable = ` 524 [reset][bold][red]No provider %[1]q plugins meet the constraint %[2]q.[reset][red] 525 526 The version constraint is derived from the "version" argument within the 527 provider %[1]q block in configuration. Child modules may also apply 528 provider version constraints. To view the provider versions requested by each 529 module in the current configuration, run "terraform providers". 530 531 To proceed, the version constraints for this provider must be relaxed by 532 either adjusting or removing the "version" argument in the provider blocks 533 throughout the configuration. 534 ` 535 536 const errProviderIncompatible = ` 537 [reset][bold][red]No available provider %[1]q plugins are compatible with this Terraform version.[reset][red] 538 539 From time to time, new Terraform major releases can change the requirements for 540 plugins such that older plugins become incompatible. 541 542 Terraform checked all of the plugin versions matching the given constraint: 543 %[2]s 544 545 Unfortunately, none of the suitable versions are compatible with this version 546 of Terraform. If you have recently upgraded Terraform, it may be necessary to 547 move to a newer major release of this provider. Alternatively, if you are 548 attempting to upgrade the provider to a new major version you may need to 549 also upgrade Terraform to support the new version. 550 551 Consult the documentation for this provider for more information on 552 compatibility between provider versions and Terraform versions. 553 ` 554 555 const errProviderInstallError = ` 556 [reset][bold][red]Error installing provider %[1]q: %[2]s.[reset][red] 557 558 Terraform analyses the configuration and state and automatically downloads 559 plugins for the providers used. However, when attempting to download this 560 plugin an unexpected error occured. 561 562 This may be caused if for some reason Terraform is unable to reach the 563 plugin repository. The repository may be unreachable if access is blocked 564 by a firewall. 565 566 If automatic installation is not possible or desirable in your environment, 567 you may alternatively manually install plugins by downloading a suitable 568 distribution package and placing the plugin's executable file in the 569 following directory: 570 %[3]s 571 ` 572 573 const errMissingProvidersNoInstall = ` 574 [reset][bold][red]Missing required providers.[reset][red] 575 576 The following provider constraints are not met by the currently-installed 577 provider plugins: 578 579 %[1]s 580 Terraform can automatically download and install plugins to meet the given 581 constraints, but this step was skipped due to the use of -get-plugins=false 582 and/or -plugin-dir on the command line. 583 584 If automatic installation is not possible or desirable in your environment, 585 you may manually install plugins by downloading a suitable distribution package 586 and placing the plugin's executable file in one of the directories given in 587 by -plugin-dir on the command line, or in the following directory if custom 588 plugin directories are not set: 589 %[2]s 590 `