github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/init.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/iaas-resource-provision/iaas-rpc-config-inspect/tfconfig"
    11  	svchost "github.com/iaas-resource-provision/iaas-rpc-svchost"
    12  	"github.com/posener/complete"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/iaas-resource-provision/iaas-rpc/internal/addrs"
    16  	"github.com/iaas-resource-provision/iaas-rpc/internal/backend"
    17  	backendInit "github.com/iaas-resource-provision/iaas-rpc/internal/backend/init"
    18  	"github.com/iaas-resource-provision/iaas-rpc/internal/configs"
    19  	"github.com/iaas-resource-provision/iaas-rpc/internal/configs/configschema"
    20  	"github.com/iaas-resource-provision/iaas-rpc/internal/getproviders"
    21  	"github.com/iaas-resource-provision/iaas-rpc/internal/providercache"
    22  	"github.com/iaas-resource-provision/iaas-rpc/internal/states"
    23  	"github.com/iaas-resource-provision/iaas-rpc/internal/terraform"
    24  	"github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags"
    25  	tfversion "github.com/iaas-resource-provision/iaas-rpc/version"
    26  )
    27  
    28  // InitCommand is a Command implementation that takes a IaaS-RPC
    29  // module and clones it to the working directory.
    30  type InitCommand struct {
    31  	Meta
    32  }
    33  
    34  func (c *InitCommand) Run(args []string) int {
    35  	var flagFromModule, flagLockfile string
    36  	var flagBackend, flagGet, flagUpgrade bool
    37  	var flagPluginPath FlagStringSlice
    38  	flagConfigExtra := newRawFlags("-backend-config")
    39  
    40  	args = c.Meta.process(args)
    41  	cmdFlags := c.Meta.extendedFlagSet("init")
    42  	cmdFlags.BoolVar(&flagBackend, "backend", true, "")
    43  	cmdFlags.Var(flagConfigExtra, "backend-config", "")
    44  	cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init")
    45  	cmdFlags.BoolVar(&flagGet, "get", true, "")
    46  	cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
    47  	cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
    48  	cmdFlags.BoolVar(&c.migrateState, "migrate-state", false, "migrate state")
    49  	cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "")
    50  	cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory")
    51  	cmdFlags.StringVar(&flagLockfile, "lockfile", "", "Set a dependency lockfile mode")
    52  	cmdFlags.BoolVar(&c.Meta.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local IaaS-RPC versions are incompatible")
    53  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    54  	if err := cmdFlags.Parse(args); err != nil {
    55  		return 1
    56  	}
    57  
    58  	if c.migrateState && c.reconfigure {
    59  		c.Ui.Error("The -migrate-state and -reconfigure options are mutually-exclusive")
    60  		return 1
    61  	}
    62  
    63  	// Copying the state only happens during backend migration, so setting
    64  	// -force-copy implies -migrate-state
    65  	if c.forceInitCopy {
    66  		c.migrateState = true
    67  	}
    68  
    69  	var diags tfdiags.Diagnostics
    70  
    71  	if len(flagPluginPath) > 0 {
    72  		c.pluginPath = flagPluginPath
    73  	}
    74  
    75  	// Validate the arg count and get the working directory
    76  	args = cmdFlags.Args()
    77  	path, err := ModulePath(args)
    78  	if err != nil {
    79  		c.Ui.Error(err.Error())
    80  		return 1
    81  	}
    82  
    83  	if err := c.storePluginPath(c.pluginPath); err != nil {
    84  		c.Ui.Error(fmt.Sprintf("Error saving -plugin-path values: %s", err))
    85  		return 1
    86  	}
    87  
    88  	// This will track whether we outputted anything so that we know whether
    89  	// to output a newline before the success message
    90  	var header bool
    91  
    92  	if flagFromModule != "" {
    93  		src := flagFromModule
    94  
    95  		empty, err := configs.IsEmptyDir(path)
    96  		if err != nil {
    97  			c.Ui.Error(fmt.Sprintf("Error validating destination directory: %s", err))
    98  			return 1
    99  		}
   100  		if !empty {
   101  			c.Ui.Error(strings.TrimSpace(errInitCopyNotEmpty))
   102  			return 1
   103  		}
   104  
   105  		c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   106  			"[reset][bold]Copying configuration[reset] from %q...", src,
   107  		)))
   108  		header = true
   109  
   110  		hooks := uiModuleInstallHooks{
   111  			Ui:             c.Ui,
   112  			ShowLocalPaths: false, // since they are in a weird location for init
   113  		}
   114  
   115  		initDiags := c.initDirFromModule(path, src, hooks)
   116  		diags = diags.Append(initDiags)
   117  		if initDiags.HasErrors() {
   118  			c.showDiagnostics(diags)
   119  			return 1
   120  		}
   121  
   122  		c.Ui.Output("")
   123  	}
   124  
   125  	// If our directory is empty, then we're done. We can't get or set up
   126  	// the backend with an empty directory.
   127  	empty, err := configs.IsEmptyDir(path)
   128  	if err != nil {
   129  		diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err))
   130  		c.showDiagnostics(diags)
   131  		return 1
   132  	}
   133  	if empty {
   134  		c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty)))
   135  		return 0
   136  	}
   137  
   138  	// For IaaS-RPC v0.12 we introduced a special loading mode where we would
   139  	// use the 0.11-syntax-compatible "earlyconfig" package as a heuristic to
   140  	// identify situations where it was likely that the user was trying to use
   141  	// 0.11-only syntax that the upgrade tool might help with.
   142  	//
   143  	// However, as the language has moved on that is no longer a suitable
   144  	// heuristic in IaaS-RPC 0.13 and later: other new additions to the
   145  	// language can cause the main loader to disagree with earlyconfig, which
   146  	// would lead us to give poor advice about how to respond.
   147  	//
   148  	// For that reason, we no longer use a different error message in that
   149  	// situation, but for now we still use both codepaths because some of our
   150  	// initialization functionality remains built around "earlyconfig" and
   151  	// so we need to still load the module via that mechanism anyway until we
   152  	// can do some more invasive refactoring here.
   153  	rootMod, confDiags := c.loadSingleModule(path)
   154  	rootModEarly, earlyConfDiags := c.loadSingleModuleEarly(path)
   155  	if confDiags.HasErrors() {
   156  		c.Ui.Error(c.Colorize().Color(strings.TrimSpace(errInitConfigError)))
   157  		// TODO: It would be nice to check the version constraints in
   158  		// rootModEarly.RequiredCore and print out a hint if the module is
   159  		// declaring that it's not compatible with this version of IaaS-RPC,
   160  		// though we're deferring that for now because we're intending to
   161  		// refactor our use of "earlyconfig" here anyway and so whatever we
   162  		// might do here right now would likely be invalidated by that.
   163  		c.showDiagnostics(confDiags)
   164  		return 1
   165  	}
   166  	// If _only_ the early loader encountered errors then that's unusual
   167  	// (it should generally be a superset of the normal loader) but we'll
   168  	// return those errors anyway since otherwise we'll probably get
   169  	// some weird behavior downstream. Errors from the early loader are
   170  	// generally not as high-quality since it has less context to work with.
   171  	if earlyConfDiags.HasErrors() {
   172  		c.Ui.Error(c.Colorize().Color(strings.TrimSpace(errInitConfigError)))
   173  		// Errors from the early loader are generally not as high-quality since
   174  		// it has less context to work with.
   175  		diags = diags.Append(confDiags)
   176  		c.showDiagnostics(diags)
   177  		return 1
   178  	}
   179  
   180  	if flagGet {
   181  		modsOutput, modsDiags := c.getModules(path, rootModEarly, flagUpgrade)
   182  		diags = diags.Append(modsDiags)
   183  		if modsDiags.HasErrors() {
   184  			c.showDiagnostics(diags)
   185  			return 1
   186  		}
   187  		if modsOutput {
   188  			header = true
   189  		}
   190  	}
   191  
   192  	// With all of the modules (hopefully) installed, we can now try to load the
   193  	// whole configuration tree.
   194  	config, confDiags := c.loadConfig(path)
   195  	diags = diags.Append(confDiags)
   196  	if confDiags.HasErrors() {
   197  		c.Ui.Error(strings.TrimSpace(errInitConfigError))
   198  		c.showDiagnostics(diags)
   199  		return 1
   200  	}
   201  
   202  	// Before we go further, we'll check to make sure none of the modules in the
   203  	// configuration declare that they don't support this IaaS-RPC version, so
   204  	// we can produce a version-related error message rather than
   205  	// potentially-confusing downstream errors.
   206  	versionDiags := terraform.CheckCoreVersionRequirements(config)
   207  	diags = diags.Append(versionDiags)
   208  	if versionDiags.HasErrors() {
   209  		c.showDiagnostics(diags)
   210  		return 1
   211  	}
   212  
   213  	var back backend.Backend
   214  	if flagBackend {
   215  
   216  		be, backendOutput, backendDiags := c.initBackend(rootMod, flagConfigExtra)
   217  		diags = diags.Append(backendDiags)
   218  		if backendDiags.HasErrors() {
   219  			c.showDiagnostics(diags)
   220  			return 1
   221  		}
   222  		if backendOutput {
   223  			header = true
   224  		}
   225  		back = be
   226  	} else {
   227  		// load the previously-stored backend config
   228  		be, backendDiags := c.Meta.backendFromState()
   229  		diags = diags.Append(backendDiags)
   230  		if backendDiags.HasErrors() {
   231  			c.showDiagnostics(diags)
   232  			return 1
   233  		}
   234  		back = be
   235  	}
   236  
   237  	if back == nil {
   238  		// If we didn't initialize a backend then we'll try to at least
   239  		// instantiate one. This might fail if it wasn't already initialized
   240  		// by a previous run, so we must still expect that "back" may be nil
   241  		// in code that follows.
   242  		var backDiags tfdiags.Diagnostics
   243  		back, backDiags = c.Backend(nil)
   244  		if backDiags.HasErrors() {
   245  			// This is fine. We'll proceed with no backend, then.
   246  			back = nil
   247  		}
   248  	}
   249  
   250  	var state *states.State
   251  
   252  	// If we have a functional backend (either just initialized or initialized
   253  	// on a previous run) we'll use the current state as a potential source
   254  	// of provider dependencies.
   255  	if back != nil {
   256  		c.ignoreRemoteBackendVersionConflict(back)
   257  		workspace, err := c.Workspace()
   258  		if err != nil {
   259  			c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
   260  			return 1
   261  		}
   262  		sMgr, err := back.StateMgr(workspace)
   263  		if err != nil {
   264  			c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
   265  			return 1
   266  		}
   267  
   268  		if err := sMgr.RefreshState(); err != nil {
   269  			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
   270  			return 1
   271  		}
   272  
   273  		state = sMgr.State()
   274  	}
   275  
   276  	// Now that we have loaded all modules, check the module tree for missing providers.
   277  	providersOutput, providersAbort, providerDiags := c.getProviders(config, state, flagUpgrade, flagPluginPath, flagLockfile)
   278  	diags = diags.Append(providerDiags)
   279  	if providersAbort || providerDiags.HasErrors() {
   280  		c.showDiagnostics(diags)
   281  		return 1
   282  	}
   283  	if providersOutput {
   284  		header = true
   285  	}
   286  
   287  	// If we outputted information, then we need to output a newline
   288  	// so that our success message is nicely spaced out from prior text.
   289  	if header {
   290  		c.Ui.Output("")
   291  	}
   292  
   293  	// If we accumulated any warnings along the way that weren't accompanied
   294  	// by errors then we'll output them here so that the success message is
   295  	// still the final thing shown.
   296  	c.showDiagnostics(diags)
   297  	c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
   298  	if !c.RunningInAutomation {
   299  		// If we're not running in an automation wrapper, give the user
   300  		// some more detailed next steps that are appropriate for interactive
   301  		// shell usage.
   302  		c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessCLI)))
   303  	}
   304  	return 0
   305  }
   306  
   307  func (c *InitCommand) getModules(path string, earlyRoot *tfconfig.Module, upgrade bool) (output bool, diags tfdiags.Diagnostics) {
   308  	if len(earlyRoot.ModuleCalls) == 0 {
   309  		// Nothing to do
   310  		return false, nil
   311  	}
   312  
   313  	if upgrade {
   314  		c.Ui.Output(c.Colorize().Color("[reset][bold]Upgrading modules..."))
   315  	} else {
   316  		c.Ui.Output(c.Colorize().Color("[reset][bold]Initializing modules..."))
   317  	}
   318  
   319  	hooks := uiModuleInstallHooks{
   320  		Ui:             c.Ui,
   321  		ShowLocalPaths: true,
   322  	}
   323  	instDiags := c.installModules(path, upgrade, hooks)
   324  	diags = diags.Append(instDiags)
   325  
   326  	// Since module installer has modified the module manifest on disk, we need
   327  	// to refresh the cache of it in the loader.
   328  	if c.configLoader != nil {
   329  		if err := c.configLoader.RefreshModules(); err != nil {
   330  			// Should never happen
   331  			diags = diags.Append(tfdiags.Sourceless(
   332  				tfdiags.Error,
   333  				"Failed to read module manifest",
   334  				fmt.Sprintf("After installing modules, IaaS-RPC could not re-read the manifest of installed modules. This is a bug in IaaS-RPC. %s.", err),
   335  			))
   336  		}
   337  	}
   338  
   339  	return true, diags
   340  }
   341  
   342  func (c *InitCommand) initBackend(root *configs.Module, extraConfig rawFlags) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
   343  	c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing the backend..."))
   344  
   345  	var backendConfig *configs.Backend
   346  	var backendConfigOverride hcl.Body
   347  	if root.Backend != nil {
   348  		backendType := root.Backend.Type
   349  		bf := backendInit.Backend(backendType)
   350  		if bf == nil {
   351  			diags = diags.Append(&hcl.Diagnostic{
   352  				Severity: hcl.DiagError,
   353  				Summary:  "Unsupported backend type",
   354  				Detail:   fmt.Sprintf("There is no backend type named %q.", backendType),
   355  				Subject:  &root.Backend.TypeRange,
   356  			})
   357  			return nil, true, diags
   358  		}
   359  
   360  		b := bf()
   361  		backendSchema := b.ConfigSchema()
   362  		backendConfig = root.Backend
   363  
   364  		var overrideDiags tfdiags.Diagnostics
   365  		backendConfigOverride, overrideDiags = c.backendConfigOverrideBody(extraConfig, backendSchema)
   366  		diags = diags.Append(overrideDiags)
   367  		if overrideDiags.HasErrors() {
   368  			return nil, true, diags
   369  		}
   370  	} else {
   371  		// If the user supplied a -backend-config on the CLI but no backend
   372  		// block was found in the configuration, it's likely - but not
   373  		// necessarily - a mistake. Return a warning.
   374  		if !extraConfig.Empty() {
   375  			diags = diags.Append(tfdiags.Sourceless(
   376  				tfdiags.Warning,
   377  				"Missing backend configuration",
   378  				`-backend-config was used without a "backend" block in the configuration.
   379  
   380  If you intended to override the default local backend configuration,
   381  no action is required, but you may add an explicit backend block to your
   382  configuration to clear this warning:
   383  
   384  iaas-rpc {
   385    backend "local" {}
   386  }
   387  
   388  However, if you intended to override a defined backend, please verify that
   389  the backend configuration is present and valid.
   390  `,
   391  			))
   392  		}
   393  	}
   394  
   395  	opts := &BackendOpts{
   396  		Config:         backendConfig,
   397  		ConfigOverride: backendConfigOverride,
   398  		Init:           true,
   399  	}
   400  
   401  	back, backDiags := c.Backend(opts)
   402  	diags = diags.Append(backDiags)
   403  	return back, true, diags
   404  }
   405  
   406  // Load the complete module tree, and fetch any missing providers.
   407  // This method outputs its own Ui.
   408  func (c *InitCommand) getProviders(config *configs.Config, state *states.State, upgrade bool, pluginDirs []string, flagLockfile string) (output, abort bool, diags tfdiags.Diagnostics) {
   409  	// Dev overrides cause the result of "iaas-rpc init" to be irrelevant for
   410  	// any overridden providers, so we'll warn about it to avoid later
   411  	// confusion when IaaS-RPC ends up using a different provider than the
   412  	// lock file called for.
   413  	diags = diags.Append(c.providerDevOverrideInitWarnings())
   414  
   415  	// First we'll collect all the provider dependencies we can see in the
   416  	// configuration and the state.
   417  	reqs, hclDiags := config.ProviderRequirements()
   418  	diags = diags.Append(hclDiags)
   419  	if hclDiags.HasErrors() {
   420  		return false, true, diags
   421  	}
   422  	if state != nil {
   423  		stateReqs := state.ProviderRequirements()
   424  		reqs = reqs.Merge(stateReqs)
   425  	}
   426  
   427  	for providerAddr := range reqs {
   428  		if providerAddr.IsLegacy() {
   429  			diags = diags.Append(tfdiags.Sourceless(
   430  				tfdiags.Error,
   431  				"Invalid legacy provider address",
   432  				fmt.Sprintf(
   433  					"This configuration or its associated state refers to the unqualified provider %q.\n\nYou must complete the IaaS-RPC 0.13 upgrade process before upgrading to later versions.",
   434  					providerAddr.Type,
   435  				),
   436  			))
   437  		}
   438  	}
   439  
   440  	previousLocks, moreDiags := c.lockedDependencies()
   441  	diags = diags.Append(moreDiags)
   442  
   443  	if diags.HasErrors() {
   444  		return false, true, diags
   445  	}
   446  
   447  	var inst *providercache.Installer
   448  	if len(pluginDirs) == 0 {
   449  		// By default we use a source that looks for providers in all of the
   450  		// standard locations, possibly customized by the user in CLI config.
   451  		inst = c.providerInstaller()
   452  	} else {
   453  		// If the user passes at least one -plugin-dir then that circumvents
   454  		// the usual sources and forces IaaS-RPC to consult only the given
   455  		// directories. Anything not available in one of those directories
   456  		// is not available for installation.
   457  		source := c.providerCustomLocalDirectorySource(pluginDirs)
   458  		inst = c.providerInstallerCustomSource(source)
   459  
   460  		// The default (or configured) search paths are logged earlier, in provider_source.go
   461  		// Log that those are being overridden by the `-plugin-dir` command line options
   462  		log.Println("[DEBUG] init: overriding provider plugin search paths")
   463  		log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs)
   464  	}
   465  
   466  	// Installation can be aborted by interruption signals
   467  	ctx, done := c.InterruptibleContext()
   468  	defer done()
   469  
   470  	// Because we're currently just streaming a series of events sequentially
   471  	// into the terminal, we're showing only a subset of the events to keep
   472  	// things relatively concise. Later it'd be nice to have a progress UI
   473  	// where statuses update in-place, but we can't do that as long as we
   474  	// are shimming our vt100 output to the legacy console API on Windows.
   475  	evts := &providercache.InstallerEvents{
   476  		PendingProviders: func(reqs map[addrs.Provider]getproviders.VersionConstraints) {
   477  			c.Ui.Output(c.Colorize().Color(
   478  				"\n[reset][bold]Initializing provider plugins...",
   479  			))
   480  		},
   481  		ProviderAlreadyInstalled: func(provider addrs.Provider, selectedVersion getproviders.Version) {
   482  			c.Ui.Info(fmt.Sprintf("- Using previously-installed %s v%s", provider.ForDisplay(), selectedVersion))
   483  		},
   484  		BuiltInProviderAvailable: func(provider addrs.Provider) {
   485  			c.Ui.Info(fmt.Sprintf("- %s is built in to IaaS-RPC", provider.ForDisplay()))
   486  		},
   487  		BuiltInProviderFailure: func(provider addrs.Provider, err error) {
   488  			diags = diags.Append(tfdiags.Sourceless(
   489  				tfdiags.Error,
   490  				"Invalid dependency on built-in provider",
   491  				fmt.Sprintf("Cannot use %s: %s.", provider.ForDisplay(), err),
   492  			))
   493  		},
   494  		QueryPackagesBegin: func(provider addrs.Provider, versionConstraints getproviders.VersionConstraints, locked bool) {
   495  			if locked {
   496  				c.Ui.Info(fmt.Sprintf("- Reusing previous version of %s from the dependency lock file", provider.ForDisplay()))
   497  			} else {
   498  				if len(versionConstraints) > 0 {
   499  					c.Ui.Info(fmt.Sprintf("- Finding %s versions matching %q...", provider.ForDisplay(), getproviders.VersionConstraintsString(versionConstraints)))
   500  				} else {
   501  					c.Ui.Info(fmt.Sprintf("- Finding latest version of %s...", provider.ForDisplay()))
   502  				}
   503  			}
   504  		},
   505  		LinkFromCacheBegin: func(provider addrs.Provider, version getproviders.Version, cacheRoot string) {
   506  			c.Ui.Info(fmt.Sprintf("- Using %s v%s from the shared cache directory", provider.ForDisplay(), version))
   507  		},
   508  		FetchPackageBegin: func(provider addrs.Provider, version getproviders.Version, location getproviders.PackageLocation) {
   509  			c.Ui.Info(fmt.Sprintf("- Installing %s v%s...", provider.ForDisplay(), version))
   510  		},
   511  		QueryPackagesFailure: func(provider addrs.Provider, err error) {
   512  			switch errorTy := err.(type) {
   513  			case getproviders.ErrProviderNotFound:
   514  				sources := errorTy.Sources
   515  				displaySources := make([]string, len(sources))
   516  				for i, source := range sources {
   517  					displaySources[i] = fmt.Sprintf("  - %s", source)
   518  				}
   519  				diags = diags.Append(tfdiags.Sourceless(
   520  					tfdiags.Error,
   521  					"Failed to query available provider packages",
   522  					fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s\n\n%s",
   523  						provider.ForDisplay(), err, strings.Join(displaySources, "\n"),
   524  					),
   525  				))
   526  			case getproviders.ErrRegistryProviderNotKnown:
   527  				// We might be able to suggest an alternative provider to use
   528  				// instead of this one.
   529  				suggestion := fmt.Sprintf("\n\nAll modules should specify their required_providers so that external consumers will get the correct providers when using a module. To see which modules are currently depending on %s, run the following command:\n    iaas-rpc providers", provider.ForDisplay())
   530  				alternative := getproviders.MissingProviderSuggestion(ctx, provider, inst.ProviderSource(), reqs)
   531  				if alternative != provider {
   532  					suggestion = fmt.Sprintf(
   533  						"\n\nDid you intend to use %s? If so, you must specify that source address in each module which requires that provider. To see which modules are currently depending on %s, run the following command:\n    iaas-rpc providers",
   534  						alternative.ForDisplay(), provider.ForDisplay(),
   535  					)
   536  				}
   537  
   538  				diags = diags.Append(tfdiags.Sourceless(
   539  					tfdiags.Error,
   540  					"Failed to query available provider packages",
   541  					fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s",
   542  						provider.ForDisplay(), err, suggestion,
   543  					),
   544  				))
   545  			case getproviders.ErrHostNoProviders:
   546  				switch {
   547  				case errorTy.Hostname == svchost.Hostname("github.com") && !errorTy.HasOtherVersion:
   548  					// If a user copies the URL of a GitHub repository into
   549  					// the source argument and removes the schema to make it
   550  					// provider-address-shaped then that's one way we can end up
   551  					// here. We'll use a specialized error message in anticipation
   552  					// of that mistake. We only do this if github.com isn't a
   553  					// provider registry, to allow for the (admittedly currently
   554  					// rather unlikely) possibility that github.com starts being
   555  					// a real IaaS-RPC provider registry in the future.
   556  					diags = diags.Append(tfdiags.Sourceless(
   557  						tfdiags.Error,
   558  						"Invalid provider registry host",
   559  						fmt.Sprintf("The given source address %q specifies a GitHub repository rather than a IaaS-RPC provider. Refer to the documentation of the provider to find the correct source address to use.",
   560  							provider.String(),
   561  						),
   562  					))
   563  
   564  				case errorTy.HasOtherVersion:
   565  					diags = diags.Append(tfdiags.Sourceless(
   566  						tfdiags.Error,
   567  						"Invalid provider registry host",
   568  						fmt.Sprintf("The host %q given in in provider source address %q does not offer a IaaS-RPC provider registry that is compatible with this IaaS-RPC version, but it may be compatible with a different IaaS-RPC version.",
   569  							errorTy.Hostname, provider.String(),
   570  						),
   571  					))
   572  
   573  				default:
   574  					diags = diags.Append(tfdiags.Sourceless(
   575  						tfdiags.Error,
   576  						"Invalid provider registry host",
   577  						fmt.Sprintf("The host %q given in in provider source address %q does not offer a IaaS-RPC provider registry.",
   578  							errorTy.Hostname, provider.String(),
   579  						),
   580  					))
   581  				}
   582  
   583  			case getproviders.ErrRequestCanceled:
   584  				// We don't attribute cancellation to any particular operation,
   585  				// but rather just emit a single general message about it at
   586  				// the end, by checking ctx.Err().
   587  
   588  			default:
   589  				diags = diags.Append(tfdiags.Sourceless(
   590  					tfdiags.Error,
   591  					"Failed to query available provider packages",
   592  					fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s",
   593  						provider.ForDisplay(), err,
   594  					),
   595  				))
   596  			}
   597  
   598  		},
   599  		QueryPackagesWarning: func(provider addrs.Provider, warnings []string) {
   600  			displayWarnings := make([]string, len(warnings))
   601  			for i, warning := range warnings {
   602  				displayWarnings[i] = fmt.Sprintf("- %s", warning)
   603  			}
   604  
   605  			diags = diags.Append(tfdiags.Sourceless(
   606  				tfdiags.Warning,
   607  				"Additional provider information from registry",
   608  				fmt.Sprintf("The remote registry returned warnings for %s:\n%s",
   609  					provider.String(),
   610  					strings.Join(displayWarnings, "\n"),
   611  				),
   612  			))
   613  		},
   614  		LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
   615  			diags = diags.Append(tfdiags.Sourceless(
   616  				tfdiags.Error,
   617  				"Failed to install provider from shared cache",
   618  				fmt.Sprintf("Error while importing %s v%s from the shared cache directory: %s.", provider.ForDisplay(), version, err),
   619  			))
   620  		},
   621  		FetchPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
   622  			const summaryIncompatible = "Incompatible provider version"
   623  			switch err := err.(type) {
   624  			case getproviders.ErrProtocolNotSupported:
   625  				closestAvailable := err.Suggestion
   626  				switch {
   627  				case closestAvailable == getproviders.UnspecifiedVersion:
   628  					diags = diags.Append(tfdiags.Sourceless(
   629  						tfdiags.Error,
   630  						summaryIncompatible,
   631  						fmt.Sprintf(errProviderVersionIncompatible, provider.String()),
   632  					))
   633  				case version.GreaterThan(closestAvailable):
   634  					diags = diags.Append(tfdiags.Sourceless(
   635  						tfdiags.Error,
   636  						summaryIncompatible,
   637  						fmt.Sprintf(providerProtocolTooNew, provider.ForDisplay(),
   638  							version, tfversion.String(), closestAvailable, closestAvailable,
   639  							getproviders.VersionConstraintsString(reqs[provider]),
   640  						),
   641  					))
   642  				default: // version is less than closestAvailable
   643  					diags = diags.Append(tfdiags.Sourceless(
   644  						tfdiags.Error,
   645  						summaryIncompatible,
   646  						fmt.Sprintf(providerProtocolTooOld, provider.ForDisplay(),
   647  							version, tfversion.String(), closestAvailable, closestAvailable,
   648  							getproviders.VersionConstraintsString(reqs[provider]),
   649  						),
   650  					))
   651  				}
   652  			case getproviders.ErrPlatformNotSupported:
   653  				switch {
   654  				case err.MirrorURL != nil:
   655  					// If we're installing from a mirror then it may just be
   656  					// the mirror lacking the package, rather than it being
   657  					// unavailable from upstream.
   658  					diags = diags.Append(tfdiags.Sourceless(
   659  						tfdiags.Error,
   660  						summaryIncompatible,
   661  						fmt.Sprintf(
   662  							"Your chosen provider mirror at %s does not have a %s v%s package available for your current platform, %s.\n\nProvider releases are separate from IaaS-RPC CLI releases, so this provider might not support your current platform. Alternatively, the mirror itself might have only a subset of the plugin packages available in the origin registry, at %s.",
   663  							err.MirrorURL, err.Provider, err.Version, err.Platform,
   664  							err.Provider.Hostname,
   665  						),
   666  					))
   667  				default:
   668  					diags = diags.Append(tfdiags.Sourceless(
   669  						tfdiags.Error,
   670  						summaryIncompatible,
   671  						fmt.Sprintf(
   672  							"Provider %s v%s does not have a package available for your current platform, %s.\n\nProvider releases are separate from IaaS-RPC CLI releases, so not all providers are available for all platforms. Other versions of this provider may have different platforms supported.",
   673  							err.Provider, err.Version, err.Platform,
   674  						),
   675  					))
   676  				}
   677  
   678  			case getproviders.ErrRequestCanceled:
   679  				// We don't attribute cancellation to any particular operation,
   680  				// but rather just emit a single general message about it at
   681  				// the end, by checking ctx.Err().
   682  
   683  			default:
   684  				// We can potentially end up in here under cancellation too,
   685  				// in spite of our getproviders.ErrRequestCanceled case above,
   686  				// because not all of the outgoing requests we do under the
   687  				// "fetch package" banner are source metadata requests.
   688  				// In that case we will emit a redundant error here about
   689  				// the request being cancelled, but we'll still detect it
   690  				// as a cancellation after the installer returns and do the
   691  				// normal cancellation handling.
   692  
   693  				diags = diags.Append(tfdiags.Sourceless(
   694  					tfdiags.Error,
   695  					"Failed to install provider",
   696  					fmt.Sprintf("Error while installing %s v%s: %s", provider.ForDisplay(), version, err),
   697  				))
   698  			}
   699  		},
   700  		FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) {
   701  			var keyID string
   702  			if authResult != nil && authResult.ThirdPartySigned() {
   703  				keyID = authResult.KeyID
   704  			}
   705  			if keyID != "" {
   706  				keyID = c.Colorize().Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID))
   707  			}
   708  
   709  			c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s%s)", provider.ForDisplay(), version, authResult, keyID))
   710  		},
   711  		ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) {
   712  			thirdPartySigned := false
   713  			for _, authResult := range authResults {
   714  				if authResult.ThirdPartySigned() {
   715  					thirdPartySigned = true
   716  					break
   717  				}
   718  			}
   719  			if thirdPartySigned {
   720  				c.Ui.Info(fmt.Sprintf("\nPartner and community providers are signed by their developers.\n" +
   721  					"If you'd like to know more about provider signing, you can read about it here:\n" +
   722  					"https://www.terraform.io/docs/cli/plugins/signing.html"))
   723  			}
   724  		},
   725  		HashPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
   726  			diags = diags.Append(tfdiags.Sourceless(
   727  				tfdiags.Error,
   728  				"Failed to validate installed provider",
   729  				fmt.Sprintf(
   730  					"Validating provider %s v%s failed: %s",
   731  					provider.ForDisplay(),
   732  					version,
   733  					err,
   734  				),
   735  			))
   736  		},
   737  	}
   738  	ctx = evts.OnContext(ctx)
   739  
   740  	mode := providercache.InstallNewProvidersOnly
   741  	if upgrade {
   742  		if flagLockfile == "readonly" {
   743  			c.Ui.Error("The -upgrade flag conflicts with -lockfile=readonly.")
   744  			return true, true, diags
   745  		}
   746  
   747  		mode = providercache.InstallUpgrades
   748  	}
   749  	newLocks, err := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode)
   750  	if ctx.Err() == context.Canceled {
   751  		c.showDiagnostics(diags)
   752  		c.Ui.Error("Provider installation was canceled by an interrupt signal.")
   753  		return true, true, diags
   754  	}
   755  	if err != nil {
   756  		// The errors captured in "err" should be redundant with what we
   757  		// received via the InstallerEvents callbacks above, so we'll
   758  		// just return those as long as we have some.
   759  		if !diags.HasErrors() {
   760  			diags = diags.Append(err)
   761  		}
   762  
   763  		return true, true, diags
   764  	}
   765  
   766  	// If the provider dependencies have changed since the last run then we'll
   767  	// say a little about that in case the reader wasn't expecting a change.
   768  	// (When we later integrate module dependencies into the lock file we'll
   769  	// probably want to refactor this so that we produce one lock-file related
   770  	// message for all changes together, but this is here for now just because
   771  	// it's the smallest change relative to what came before it, which was
   772  	// a hidden JSON file specifically for tracking providers.)
   773  	if !newLocks.Equal(previousLocks) {
   774  		// if readonly mode
   775  		if flagLockfile == "readonly" {
   776  			// check if required provider dependences change
   777  			if !newLocks.EqualProviderAddress(previousLocks) {
   778  				diags = diags.Append(tfdiags.Sourceless(
   779  					tfdiags.Error,
   780  					`Provider dependency changes detected`,
   781  					`Changes to the required provider dependencies were detected, but the lock file is read-only. To use and record these requirements, run "iaas-rpc init" without the "-lockfile=readonly" flag.`,
   782  				))
   783  				return true, true, diags
   784  			}
   785  
   786  			// suppress updating the file to record any new information it learned,
   787  			// such as a hash using a new scheme.
   788  			diags = diags.Append(tfdiags.Sourceless(
   789  				tfdiags.Warning,
   790  				`Provider lock file not updated`,
   791  				`Changes to the provider selections were detected, but not saved in the .iaas-rpc.lock file. To record these selections, run "iaas-rpc init" without the "-lockfile=readonly" flag.`,
   792  			))
   793  			return true, false, diags
   794  		}
   795  
   796  		if previousLocks.Empty() {
   797  			// A change from empty to non-empty is special because it suggests
   798  			// we're running "iaas-rpc init" for the first time against a
   799  			// new configuration. In that case we'll take the opportunity to
   800  			// say a little about what the dependency lock file is, for new
   801  			// users or those who are upgrading from a previous IaaS-RPC
   802  			// version that didn't have dependency lock files.
   803  			c.Ui.Output(c.Colorize().Color(`
   804  IaaS-RPC has created a lock file [bold].iaas-rpc.lock[reset] to record the provider
   805  selections it made above. Include this file in your version control repository
   806  so that IaaS-RPC can guarantee to make the same selections by default when
   807  you run "iaas-rpc init" in the future.`))
   808  		} else {
   809  			c.Ui.Output(c.Colorize().Color(`
   810  IaaS-RPC has made some changes to the provider dependency selections recorded
   811  in the .iaas-rpc.lock file. Review those changes and commit them to your
   812  version control system if they represent changes you intended to make.`))
   813  		}
   814  
   815  		moreDiags = c.replaceLockedDependencies(newLocks)
   816  		diags = diags.Append(moreDiags)
   817  	}
   818  
   819  	return true, false, diags
   820  }
   821  
   822  // backendConfigOverrideBody interprets the raw values of -backend-config
   823  // arguments into a hcl Body that should override the backend settings given
   824  // in the configuration.
   825  //
   826  // If the result is nil then no override needs to be provided.
   827  //
   828  // If the returned diagnostics contains errors then the returned body may be
   829  // incomplete or invalid.
   830  func (c *InitCommand) backendConfigOverrideBody(flags rawFlags, schema *configschema.Block) (hcl.Body, tfdiags.Diagnostics) {
   831  	items := flags.AllItems()
   832  	if len(items) == 0 {
   833  		return nil, nil
   834  	}
   835  
   836  	var ret hcl.Body
   837  	var diags tfdiags.Diagnostics
   838  	synthVals := make(map[string]cty.Value)
   839  
   840  	mergeBody := func(newBody hcl.Body) {
   841  		if ret == nil {
   842  			ret = newBody
   843  		} else {
   844  			ret = configs.MergeBodies(ret, newBody)
   845  		}
   846  	}
   847  	flushVals := func() {
   848  		if len(synthVals) == 0 {
   849  			return
   850  		}
   851  		newBody := configs.SynthBody("-backend-config=...", synthVals)
   852  		mergeBody(newBody)
   853  		synthVals = make(map[string]cty.Value)
   854  	}
   855  
   856  	if len(items) == 1 && items[0].Value == "" {
   857  		// Explicitly remove all -backend-config options.
   858  		// We do this by setting an empty but non-nil ConfigOverrides.
   859  		return configs.SynthBody("-backend-config=''", synthVals), diags
   860  	}
   861  
   862  	for _, item := range items {
   863  		eq := strings.Index(item.Value, "=")
   864  
   865  		if eq == -1 {
   866  			// The value is interpreted as a filename.
   867  			newBody, fileDiags := c.loadHCLFile(item.Value)
   868  			diags = diags.Append(fileDiags)
   869  			if fileDiags.HasErrors() {
   870  				continue
   871  			}
   872  			// Generate an HCL body schema for the backend block.
   873  			var bodySchema hcl.BodySchema
   874  			for name := range schema.Attributes {
   875  				// We intentionally ignore the `Required` attribute here
   876  				// because backend config override files can be partial. The
   877  				// goal is to make sure we're not loading a file with
   878  				// extraneous attributes or blocks.
   879  				bodySchema.Attributes = append(bodySchema.Attributes, hcl.AttributeSchema{
   880  					Name: name,
   881  				})
   882  			}
   883  			for name, block := range schema.BlockTypes {
   884  				var labelNames []string
   885  				if block.Nesting == configschema.NestingMap {
   886  					labelNames = append(labelNames, "key")
   887  				}
   888  				bodySchema.Blocks = append(bodySchema.Blocks, hcl.BlockHeaderSchema{
   889  					Type:       name,
   890  					LabelNames: labelNames,
   891  				})
   892  			}
   893  			// Verify that the file body matches the expected backend schema.
   894  			_, schemaDiags := newBody.Content(&bodySchema)
   895  			diags = diags.Append(schemaDiags)
   896  			if schemaDiags.HasErrors() {
   897  				continue
   898  			}
   899  			flushVals() // deal with any accumulated individual values first
   900  			mergeBody(newBody)
   901  		} else {
   902  			name := item.Value[:eq]
   903  			rawValue := item.Value[eq+1:]
   904  			attrS := schema.Attributes[name]
   905  			if attrS == nil {
   906  				diags = diags.Append(tfdiags.Sourceless(
   907  					tfdiags.Error,
   908  					"Invalid backend configuration argument",
   909  					fmt.Sprintf("The backend configuration argument %q given on the command line is not expected for the selected backend type.", name),
   910  				))
   911  				continue
   912  			}
   913  			value, valueDiags := configValueFromCLI(item.String(), rawValue, attrS.Type)
   914  			diags = diags.Append(valueDiags)
   915  			if valueDiags.HasErrors() {
   916  				continue
   917  			}
   918  			synthVals[name] = value
   919  		}
   920  	}
   921  
   922  	flushVals()
   923  
   924  	return ret, diags
   925  }
   926  
   927  func (c *InitCommand) AutocompleteArgs() complete.Predictor {
   928  	return complete.PredictDirs("")
   929  }
   930  
   931  func (c *InitCommand) AutocompleteFlags() complete.Flags {
   932  	return complete.Flags{
   933  		"-backend":        completePredictBoolean,
   934  		"-backend-config": complete.PredictFiles("*.tfvars"), // can also be key=value, but we can't "predict" that
   935  		"-force-copy":     complete.PredictNothing,
   936  		"-from-module":    completePredictModuleSource,
   937  		"-get":            completePredictBoolean,
   938  		"-input":          completePredictBoolean,
   939  		"-no-color":       complete.PredictNothing,
   940  		"-plugin-dir":     complete.PredictDirs(""),
   941  		"-reconfigure":    complete.PredictNothing,
   942  		"-migrate-state":  complete.PredictNothing,
   943  		"-upgrade":        completePredictBoolean,
   944  	}
   945  }
   946  
   947  func (c *InitCommand) Help() string {
   948  	helpText := `
   949  Usage: iaas-rpc [global options] init [options]
   950  
   951    Initialize a new or existing IaaS-RPC working directory by creating
   952    initial files, loading any remote state, downloading modules, etc.
   953  
   954    This is the first command that should be run for any new or existing
   955    IaaS-RPC configuration per machine. This sets up all the local data
   956    necessary to run IaaS-RPC that is typically not committed to version
   957    control.
   958  
   959    This command is always safe to run multiple times. Though subsequent runs
   960    may give errors, this command will never delete your configuration or
   961    state. Even so, if you have important information, please back it up prior
   962    to running this command, just in case.
   963  
   964  Options:
   965  
   966    -backend=true           Configure the backend for this configuration.
   967  
   968    -backend-config=path    This can be either a path to an HCL file with key/value
   969                            assignments (same format as terraform.tfvars) or a
   970                            'key=value' format. This is merged with what is in the
   971                            configuration file. This can be specified multiple
   972                            times. The backend type must be in the configuration
   973                            itself.
   974  
   975    -force-copy             Suppress prompts about copying state data. This is
   976                            equivalent to providing a "yes" to all confirmation
   977                            prompts.
   978  
   979    -from-module=SOURCE     Copy the contents of the given module into the target
   980                            directory before initialization.
   981  
   982    -get=true               Download any modules for this configuration.
   983  
   984    -input=true             Ask for input if necessary. If false, will error if
   985                            input was required.
   986  
   987    -no-color               If specified, output won't contain any color.
   988  
   989    -plugin-dir             Directory containing plugin binaries. This overrides all
   990                            default search paths for plugins, and prevents the
   991                            automatic installation of plugins. This flag can be used
   992                            multiple times.
   993  
   994    -reconfigure            Reconfigure the backend, ignoring any saved
   995                            configuration.
   996  
   997    -migrate-state          Reconfigure the backend, and attempt to migrate any
   998                            existing state.
   999  
  1000    -upgrade=false          If installing modules (-get) or plugins, ignore
  1001                            previously-downloaded objects and install the
  1002                            latest version allowed within configured constraints.
  1003  
  1004    -lockfile=MODE          Set a dependency lockfile mode.
  1005                            Currently only "readonly" is valid.
  1006  
  1007    -ignore-remote-version  A rare option used for the remote backend only. See
  1008                            the remote backend documentation for more information.
  1009  
  1010  `
  1011  	return strings.TrimSpace(helpText)
  1012  }
  1013  
  1014  func (c *InitCommand) Synopsis() string {
  1015  	return "Prepare your working directory for other commands"
  1016  }
  1017  
  1018  const errInitConfigError = `
  1019  [reset]There are some problems with the configuration, described below.
  1020  
  1021  The IaaS-RPC configuration must be valid before initialization so that
  1022  IaaS-RPC can determine which modules and providers need to be installed.
  1023  `
  1024  
  1025  const errInitCopyNotEmpty = `
  1026  The working directory already contains files. The -from-module option requires
  1027  an empty directory into which a copy of the referenced module will be placed.
  1028  
  1029  To initialize the configuration already in this working directory, omit the
  1030  -from-module option.
  1031  `
  1032  
  1033  const outputInitEmpty = `
  1034  [reset][bold]IaaS-RPC initialized in an empty directory![reset]
  1035  
  1036  The directory has no IaaS-RPC configuration files. You may begin working
  1037  with IaaS-RPC immediately by creating IaaS-RPC configuration files.
  1038  `
  1039  
  1040  const outputInitSuccess = `
  1041  [reset][bold][green]IaaS RPC (Resource Provision Center) has been successfully initialized![reset][green]
  1042  `
  1043  
  1044  const outputInitSuccessCLI = `[reset][green]
  1045  You may now begin working with IaaS-RPC. Try running "iaas-rpc plan" to see
  1046  any changes that are required for your infrastructure. All IaaS-RPC commands
  1047  should now work.
  1048  
  1049  If you ever set or change modules or backend configuration for IaaS-RPC,
  1050  rerun this command to reinitialize your working directory. If you forget, other
  1051  commands will detect it and remind you to do so if necessary.
  1052  `
  1053  
  1054  // providerProtocolTooOld is a message sent to the CLI UI if the provider's
  1055  // supported protocol versions are too old for the user's version of iaas-rpc,
  1056  // but a newer version of the provider is compatible.
  1057  const providerProtocolTooOld = `Provider %q v%s is not compatible with IaaS-RPC %s.
  1058  Provider version %s is the latest compatible version. Select it with the following version constraint:
  1059  	version = %q
  1060  
  1061  IaaS-RPC checked all of the plugin versions matching the given constraint:
  1062  	%s
  1063  
  1064  Consult the documentation for this provider for more information on compatibility between provider and IaaS-RPC versions.
  1065  `
  1066  
  1067  // providerProtocolTooNew is a message sent to the CLI UI if the provider's
  1068  // supported protocol versions are too new for the user's version of iaas-rpc,
  1069  // and the user could either upgrade iaas-rpc or choose an older version of the
  1070  // provider.
  1071  const providerProtocolTooNew = `Provider %q v%s is not compatible with IaaS-RPC %s.
  1072  You need to downgrade to v%s or earlier. Select it with the following constraint:
  1073  	version = %q
  1074  
  1075  IaaS-RPC checked all of the plugin versions matching the given constraint:
  1076  	%s
  1077  
  1078  Consult the documentation for this provider for more information on compatibility between provider and IaaS-RPC versions.
  1079  Alternatively, upgrade to the latest version of IaaS-RPC for compatibility with newer provider releases.
  1080  `
  1081  
  1082  // No version of the provider is compatible.
  1083  const errProviderVersionIncompatible = `No compatible versions of provider %s were found.`