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