github.com/spirius/terraform@v0.10.0-beta2.0.20170714185654-87b2c0cf8fea/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  `