github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/command/init.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 multierror "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/terraform/backend" 13 "github.com/hashicorp/terraform/config" 14 "github.com/hashicorp/terraform/config/module" 15 "github.com/hashicorp/terraform/helper/variables" 16 "github.com/hashicorp/terraform/plugin" 17 "github.com/hashicorp/terraform/plugin/discovery" 18 "github.com/hashicorp/terraform/terraform" 19 ) 20 21 // InitCommand is a Command implementation that takes a Terraform 22 // module and clones it to the working directory. 23 type InitCommand struct { 24 Meta 25 26 // providerInstaller is used to download and install providers that 27 // aren't found locally. This uses a discovery.ProviderInstaller instance 28 // by default, but it can be overridden here as a way to mock fetching 29 // providers for tests. 30 providerInstaller discovery.Installer 31 } 32 33 func (c *InitCommand) Run(args []string) int { 34 var flagBackend, flagGet, flagGetPlugins, flagUpgrade bool 35 var flagConfigExtra map[string]interface{} 36 37 args = c.Meta.process(args, false) 38 cmdFlags := c.flagSet("init") 39 cmdFlags.BoolVar(&flagBackend, "backend", true, "") 40 cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "") 41 cmdFlags.BoolVar(&flagGet, "get", true, "") 42 cmdFlags.BoolVar(&flagGetPlugins, "get-plugins", true, "") 43 cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") 44 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 45 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 46 cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure") 47 cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "") 48 49 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 50 if err := cmdFlags.Parse(args); err != nil { 51 return 1 52 } 53 54 // set getProvider if we don't have a test version already 55 if c.providerInstaller == nil { 56 c.providerInstaller = &discovery.ProviderInstaller{ 57 Dir: c.pluginDir(), 58 59 PluginProtocolVersion: plugin.Handshake.ProtocolVersion, 60 } 61 } 62 63 // Validate the arg count 64 args = cmdFlags.Args() 65 if len(args) > 1 { 66 c.Ui.Error("The init command expects at most one argument.\n") 67 cmdFlags.Usage() 68 return 1 69 } 70 71 // Get our pwd. We don't always need it but always getting it is easier 72 // than the logic to determine if it is or isn't needed. 73 pwd, err := os.Getwd() 74 if err != nil { 75 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 76 return 1 77 } 78 79 // Get the path and source module to copy 80 path := pwd 81 if len(args) == 1 { 82 path = args[0] 83 } 84 // Set the state out path to be the path requested for the module 85 // to be copied. This ensures any remote states gets setup in the 86 // proper directory. 87 c.Meta.dataDir = filepath.Join(path, DefaultDataDir) 88 89 // This will track whether we outputted anything so that we know whether 90 // to output a newline before the success message 91 var header bool 92 93 // If our directory is empty, then we're done. We can't get or setup 94 // the backend with an empty directory. 95 if empty, err := config.IsEmptyDir(path); err != nil { 96 c.Ui.Error(fmt.Sprintf( 97 "Error checking configuration: %s", err)) 98 return 1 99 } else if empty { 100 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty))) 101 return 0 102 } 103 104 var back backend.Backend 105 106 // If we're performing a get or loading the backend, then we perform 107 // some extra tasks. 108 if flagGet || flagBackend { 109 conf, err := c.Config(path) 110 if err != nil { 111 c.Ui.Error(fmt.Sprintf( 112 "Error loading configuration: %s", err)) 113 return 1 114 } 115 116 // If we requested downloading modules and have modules in the config 117 if flagGet && len(conf.Modules) > 0 { 118 header = true 119 120 getMode := module.GetModeGet 121 if flagUpgrade { 122 getMode = module.GetModeUpdate 123 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 124 "[reset][bold]Upgrading modules..."))) 125 } else { 126 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 127 "[reset][bold]Downloading modules..."))) 128 } 129 130 if err := getModules(&c.Meta, path, getMode); err != nil { 131 c.Ui.Error(fmt.Sprintf( 132 "Error downloading modules: %s", err)) 133 return 1 134 } 135 136 } 137 138 // If we're requesting backend configuration or looking for required 139 // plugins, load the backend 140 if flagBackend || flagGetPlugins { 141 header = true 142 143 // Only output that we're initializing a backend if we have 144 // something in the config. We can be UNSETTING a backend as well 145 // in which case we choose not to show this. 146 if conf.Terraform != nil && conf.Terraform.Backend != nil { 147 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 148 "[reset][bold]" + 149 "Initializing the backend..."))) 150 } 151 152 opts := &BackendOpts{ 153 Config: conf, 154 ConfigExtra: flagConfigExtra, 155 Init: true, 156 } 157 if back, err = c.Backend(opts); err != nil { 158 c.Ui.Error(err.Error()) 159 return 1 160 } 161 } 162 } 163 164 // Now that we have loaded all modules, check the module tree for missing providers 165 if flagGetPlugins { 166 sMgr, err := back.State(c.Workspace()) 167 if err != nil { 168 c.Ui.Error(fmt.Sprintf( 169 "Error loading state: %s", err)) 170 return 1 171 } 172 173 if err := sMgr.RefreshState(); err != nil { 174 c.Ui.Error(fmt.Sprintf( 175 "Error refreshing state: %s", err)) 176 return 1 177 } 178 179 c.Ui.Output(c.Colorize().Color( 180 "[reset][bold]Initializing provider plugins...", 181 )) 182 183 err = c.getProviders(path, sMgr.State(), flagUpgrade) 184 if err != nil { 185 // this function provides its own output 186 log.Printf("[ERROR] %s", err) 187 return 1 188 } 189 } 190 191 // If we outputted information, then we need to output a newline 192 // so that our success message is nicely spaced out from prior text. 193 if header { 194 c.Ui.Output("") 195 } 196 197 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess))) 198 199 return 0 200 } 201 202 // Load the complete module tree, and fetch any missing providers. 203 // This method outputs its own Ui. 204 func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) error { 205 mod, err := c.Module(path) 206 if err != nil { 207 c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err)) 208 return err 209 } 210 211 if err := mod.Validate(); err != nil { 212 c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err)) 213 return err 214 } 215 216 var available discovery.PluginMetaSet 217 if upgrade { 218 // If we're in upgrade mode, we ignore any auto-installed plugins 219 // in "available", causing us to reinstall and possibly upgrade them. 220 available = c.providerPluginManuallyInstalledSet() 221 } else { 222 available = c.providerPluginSet() 223 } 224 requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() 225 missing := c.missingPlugins(available, requirements) 226 227 var errs error 228 for provider, reqd := range missing { 229 c.Ui.Output(fmt.Sprintf("- downloading plugin for provider %q...", provider)) 230 _, err := c.providerInstaller.Get(provider, reqd.Versions) 231 232 if err != nil { 233 c.Ui.Error(fmt.Sprintf(errProviderNotFound, err, provider, reqd.Versions)) 234 errs = multierror.Append(errs, err) 235 } 236 } 237 238 if errs != nil { 239 return errs 240 } 241 242 // With all the providers downloaded, we'll generate our lock file 243 // that ensures the provider binaries remain unchanged until we init 244 // again. If anything changes, other commands that use providers will 245 // fail with an error instructing the user to re-run this command. 246 available = c.providerPluginSet() // re-discover to see newly-installed plugins 247 chosen := choosePlugins(available, requirements) 248 digests := map[string][]byte{} 249 for name, meta := range chosen { 250 digest, err := meta.SHA256() 251 if err != nil { 252 c.Ui.Error(fmt.Sprintf("failed to read provider plugin %s: %s", meta.Path, err)) 253 return err 254 } 255 digests[name] = digest 256 } 257 err = c.providerPluginsLock().Write(digests) 258 if err != nil { 259 c.Ui.Error(fmt.Sprintf("failed to save provider manifest: %s", err)) 260 return err 261 } 262 263 if upgrade { 264 // Purge any auto-installed plugins that aren't being used. 265 purged, err := c.providerInstaller.PurgeUnused(chosen) 266 if err != nil { 267 // Failure to purge old plugins is not a fatal error 268 c.Ui.Warn(fmt.Sprintf("failed to purge unused plugins: %s", err)) 269 } 270 if purged != nil { 271 for meta := range purged { 272 log.Printf("[DEBUG] Purged unused %s plugin %s", meta.Name, meta.Path) 273 } 274 } 275 } 276 277 // If any providers have "floating" versions (completely unconstrained) 278 // we'll suggest the user constrain with a pessimistic constraint to 279 // avoid implicitly adopting a later major release. 280 constraintSuggestions := make(map[string]discovery.ConstraintStr) 281 for name, meta := range chosen { 282 req := requirements[name] 283 if req == nil { 284 // should never happen, but we don't want to crash here, so we'll 285 // be cautious. 286 continue 287 } 288 289 if req.Versions.Unconstrained() { 290 // meta.Version.MustParse is safe here because our "chosen" metas 291 // were already filtered for validity of versions. 292 constraintSuggestions[name] = meta.Version.MustParse().MinorUpgradeConstraintStr() 293 } 294 } 295 if len(constraintSuggestions) != 0 { 296 names := make([]string, 0, len(constraintSuggestions)) 297 for name := range constraintSuggestions { 298 names = append(names, name) 299 } 300 sort.Strings(names) 301 302 c.Ui.Output(outputInitProvidersUnconstrained) 303 for _, name := range names { 304 c.Ui.Output(fmt.Sprintf("* provider.%s: version = %q", name, constraintSuggestions[name])) 305 } 306 } 307 308 return nil 309 } 310 311 func (c *InitCommand) Help() string { 312 helpText := ` 313 Usage: terraform init [options] [DIR] 314 315 Initialize a new or existing Terraform working directory by creating 316 initial files, loading any remote state, downloading modules, etc. 317 318 This is the first command that should be run for any new or existing 319 Terraform configuration per machine. This sets up all the local data 320 necessary to run Terraform that is typically not committed to version 321 control. 322 323 This command is always safe to run multiple times. Though subsequent runs 324 may give errors, this command will never delete your configuration or 325 state. Even so, if you have important information, please back it up prior 326 to running this command, just in case. 327 328 If no arguments are given, the configuration in this working directory 329 is initialized. 330 331 Options: 332 333 -backend=true Configure the backend for this configuration. 334 335 -backend-config=path This can be either a path to an HCL file with key/value 336 assignments (same format as terraform.tfvars) or a 337 'key=value' format. This is merged with what is in the 338 configuration file. This can be specified multiple 339 times. The backend type must be in the configuration 340 itself. 341 342 -force-copy Suppress prompts about copying state data. This is 343 equivalent to providing a "yes" to all confirmation 344 prompts. 345 346 -get=true Download any modules for this configuration. 347 348 -get-plugins=true Download any missing plugins for this configuration. 349 350 -input=true Ask for input if necessary. If false, will error if 351 input was required. 352 353 -lock=true Lock the state file when locking is supported. 354 355 -lock-timeout=0s Duration to retry a state lock. 356 357 -no-color If specified, output won't contain any color. 358 359 -reconfigure Reconfigure the backend, ignoring any saved configuration. 360 361 -upgrade=false If installing modules (-get) or plugins (-get-plugins), 362 ignore previously-downloaded objects and install the 363 latest version allowed within configured constraints. 364 ` 365 return strings.TrimSpace(helpText) 366 } 367 368 func (c *InitCommand) Synopsis() string { 369 return "Initialize a new or existing Terraform configuration" 370 } 371 372 const errInitCopyNotEmpty = ` 373 The destination path contains Terraform configuration files. The init command 374 with a SOURCE parameter can only be used on a directory without existing 375 Terraform files. 376 377 Please resolve this issue and try again. 378 ` 379 380 const outputInitEmpty = ` 381 [reset][bold]Terraform initialized in an empty directory![reset] 382 383 The directory has no Terraform configuration files. You may begin working 384 with Terraform immediately by creating Terraform configuration files. 385 ` 386 387 const outputInitSuccess = ` 388 [reset][bold][green]Terraform has been successfully initialized![reset][green] 389 390 You may now begin working with Terraform. Try running "terraform plan" to see 391 any changes that are required for your infrastructure. All Terraform commands 392 should now work. 393 394 If you ever set or change modules or backend configuration for Terraform, 395 rerun this command to reinitialize your working directory. If you forget, other 396 commands will detect it and remind you to do so if necessary. 397 ` 398 399 const outputInitProvidersUnconstrained = ` 400 The following providers do not have any version constraints in configuration, 401 so the latest version was installed. 402 403 To prevent automatic upgrades to new major versions that may contain breaking 404 changes, it is recommended to add version = "..." constraints to the 405 corresponding provider blocks in configuration, with the constraint strings 406 suggested below. 407 ` 408 409 const errProviderNotFound = ` 410 [reset][red]%[1]s 411 412 [reset][bold][red]Error: Satisfying %[2]q, provider not found 413 414 [reset][red]A version of the %[2]q provider that satisfies all version 415 constraints could not be found. The requested version 416 constraints are shown below. 417 418 %[2]s = %[3]q[reset] 419 `