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  `