kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/initwd/module_install.go (about)

     1  package initwd
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	version "github.com/hashicorp/go-version"
    12  	"github.com/hashicorp/terraform-config-inspect/tfconfig"
    13  	"kubeform.dev/terraform-backend-sdk/addrs"
    14  	"kubeform.dev/terraform-backend-sdk/earlyconfig"
    15  	"kubeform.dev/terraform-backend-sdk/getmodules"
    16  	"kubeform.dev/terraform-backend-sdk/modsdir"
    17  	"kubeform.dev/terraform-backend-sdk/registry"
    18  	"kubeform.dev/terraform-backend-sdk/registry/regsrc"
    19  	"kubeform.dev/terraform-backend-sdk/registry/response"
    20  	"kubeform.dev/terraform-backend-sdk/tfdiags"
    21  )
    22  
    23  type ModuleInstaller struct {
    24  	modsDir string
    25  	reg     *registry.Client
    26  
    27  	// The keys in moduleVersions are resolved and trimmed registry source
    28  	// addresses and the values are the registry response.
    29  	registryPackageVersions map[addrs.ModuleRegistryPackage]*response.ModuleVersions
    30  
    31  	// The keys in moduleVersionsUrl are the moduleVersion struct below and
    32  	// addresses and the values are underlying remote source addresses.
    33  	registryPackageSources map[moduleVersion]addrs.ModuleSourceRemote
    34  }
    35  
    36  type moduleVersion struct {
    37  	module  addrs.ModuleRegistryPackage
    38  	version string
    39  }
    40  
    41  func NewModuleInstaller(modsDir string, reg *registry.Client) *ModuleInstaller {
    42  	return &ModuleInstaller{
    43  		modsDir:                 modsDir,
    44  		reg:                     reg,
    45  		registryPackageVersions: make(map[addrs.ModuleRegistryPackage]*response.ModuleVersions),
    46  		registryPackageSources:  make(map[moduleVersion]addrs.ModuleSourceRemote),
    47  	}
    48  }
    49  
    50  // InstallModules analyses the root module in the given directory and installs
    51  // all of its direct and transitive dependencies into the given modules
    52  // directory, which must already exist.
    53  //
    54  // Since InstallModules makes possibly-time-consuming calls to remote services,
    55  // a hook interface is supported to allow the caller to be notified when
    56  // each module is installed and, for remote modules, when downloading begins.
    57  // LoadConfig guarantees that two hook calls will not happen concurrently but
    58  // it does not guarantee any particular ordering of hook calls. This mechanism
    59  // is for UI feedback only and does not give the caller any control over the
    60  // process.
    61  //
    62  // If modules are already installed in the target directory, they will be
    63  // skipped unless their source address or version have changed or unless
    64  // the upgrade flag is set.
    65  //
    66  // InstallModules never deletes any directory, except in the case where it
    67  // needs to replace a directory that is already present with a newly-extracted
    68  // package.
    69  //
    70  // If the returned diagnostics contains errors then the module installation
    71  // may have wholly or partially completed. Modules must be loaded in order
    72  // to find their dependencies, so this function does many of the same checks
    73  // as LoadConfig as a side-effect.
    74  //
    75  // If successful (the returned diagnostics contains no errors) then the
    76  // first return value is the early configuration tree that was constructed by
    77  // the installation process.
    78  func (i *ModuleInstaller) InstallModules(rootDir string, upgrade bool, hooks ModuleInstallHooks) (*earlyconfig.Config, tfdiags.Diagnostics) {
    79  	log.Printf("[TRACE] ModuleInstaller: installing child modules for %s into %s", rootDir, i.modsDir)
    80  
    81  	rootMod, diags := earlyconfig.LoadModule(rootDir)
    82  	if rootMod == nil {
    83  		return nil, diags
    84  	}
    85  
    86  	manifest, err := modsdir.ReadManifestSnapshotForDir(i.modsDir)
    87  	if err != nil {
    88  		diags = diags.Append(tfdiags.Sourceless(
    89  			tfdiags.Error,
    90  			"Failed to read modules manifest file",
    91  			fmt.Sprintf("Error reading manifest for %s: %s.", i.modsDir, err),
    92  		))
    93  		return nil, diags
    94  	}
    95  
    96  	fetcher := getmodules.NewPackageFetcher()
    97  	cfg, instDiags := i.installDescendentModules(rootMod, rootDir, manifest, upgrade, hooks, fetcher)
    98  	diags = append(diags, instDiags...)
    99  
   100  	return cfg, diags
   101  }
   102  
   103  func (i *ModuleInstaller) installDescendentModules(rootMod *tfconfig.Module, rootDir string, manifest modsdir.Manifest, upgrade bool, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*earlyconfig.Config, tfdiags.Diagnostics) {
   104  	var diags tfdiags.Diagnostics
   105  
   106  	if hooks == nil {
   107  		// Use our no-op implementation as a placeholder
   108  		hooks = ModuleInstallHooksImpl{}
   109  	}
   110  
   111  	// Create a manifest record for the root module. This will be used if
   112  	// there are any relative-pathed modules in the root.
   113  	manifest[""] = modsdir.Record{
   114  		Key: "",
   115  		Dir: rootDir,
   116  	}
   117  
   118  	cfg, cDiags := earlyconfig.BuildConfig(rootMod, earlyconfig.ModuleWalkerFunc(
   119  		func(req *earlyconfig.ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
   120  
   121  			key := manifest.ModuleKey(req.Path)
   122  			instPath := i.packageInstallPath(req.Path)
   123  
   124  			log.Printf("[DEBUG] Module installer: begin %s", key)
   125  
   126  			// First we'll check if we need to upgrade/replace an existing
   127  			// installed module, and delete it out of the way if so.
   128  			replace := upgrade
   129  			if !replace {
   130  				record, recorded := manifest[key]
   131  				switch {
   132  				case !recorded:
   133  					log.Printf("[TRACE] ModuleInstaller: %s is not yet installed", key)
   134  					replace = true
   135  				case record.SourceAddr != req.SourceAddr.String():
   136  					log.Printf("[TRACE] ModuleInstaller: %s source address has changed from %q to %q", key, record.SourceAddr, req.SourceAddr)
   137  					replace = true
   138  				case record.Version != nil && !req.VersionConstraints.Check(record.Version):
   139  					log.Printf("[TRACE] ModuleInstaller: %s version %s no longer compatible with constraints %s", key, record.Version, req.VersionConstraints)
   140  					replace = true
   141  				}
   142  			}
   143  
   144  			// If we _are_ planning to replace this module, then we'll remove
   145  			// it now so our installation code below won't conflict with any
   146  			// existing remnants.
   147  			if replace {
   148  				if _, recorded := manifest[key]; recorded {
   149  					log.Printf("[TRACE] ModuleInstaller: discarding previous record of %s prior to reinstall", key)
   150  				}
   151  				delete(manifest, key)
   152  				// Deleting a module invalidates all of its descendent modules too.
   153  				keyPrefix := key + "."
   154  				for subKey := range manifest {
   155  					if strings.HasPrefix(subKey, keyPrefix) {
   156  						if _, recorded := manifest[subKey]; recorded {
   157  							log.Printf("[TRACE] ModuleInstaller: also discarding downstream %s", subKey)
   158  						}
   159  						delete(manifest, subKey)
   160  					}
   161  				}
   162  			}
   163  
   164  			record, recorded := manifest[key]
   165  			if !recorded {
   166  				// Clean up any stale cache directory that might be present.
   167  				// If this is a local (relative) source then the dir will
   168  				// not exist, but we'll ignore that.
   169  				log.Printf("[TRACE] ModuleInstaller: cleaning directory %s prior to install of %s", instPath, key)
   170  				err := os.RemoveAll(instPath)
   171  				if err != nil && !os.IsNotExist(err) {
   172  					log.Printf("[TRACE] ModuleInstaller: failed to remove %s: %s", key, err)
   173  					diags = diags.Append(tfdiags.Sourceless(
   174  						tfdiags.Error,
   175  						"Failed to remove local module cache",
   176  						fmt.Sprintf(
   177  							"Terraform tried to remove %s in order to reinstall this module, but encountered an error: %s",
   178  							instPath, err,
   179  						),
   180  					))
   181  					return nil, nil, diags
   182  				}
   183  			} else {
   184  				// If this module is already recorded and its root directory
   185  				// exists then we will just load what's already there and
   186  				// keep our existing record.
   187  				info, err := os.Stat(record.Dir)
   188  				if err == nil && info.IsDir() {
   189  					mod, mDiags := earlyconfig.LoadModule(record.Dir)
   190  					diags = diags.Append(mDiags)
   191  
   192  					log.Printf("[TRACE] ModuleInstaller: Module installer: %s %s already installed in %s", key, record.Version, record.Dir)
   193  					return mod, record.Version, diags
   194  				}
   195  			}
   196  
   197  			// If we get down here then it's finally time to actually install
   198  			// the module. There are some variants to this process depending
   199  			// on what type of module source address we have.
   200  
   201  			switch addr := req.SourceAddr.(type) {
   202  
   203  			case addrs.ModuleSourceLocal:
   204  				log.Printf("[TRACE] ModuleInstaller: %s has local path %q", key, addr.String())
   205  				mod, mDiags := i.installLocalModule(req, key, manifest, hooks)
   206  				mDiags = maybeImproveLocalInstallError(req, mDiags)
   207  				diags = append(diags, mDiags...)
   208  				return mod, nil, diags
   209  
   210  			case addrs.ModuleSourceRegistry:
   211  				log.Printf("[TRACE] ModuleInstaller: %s is a registry module at %s", key, addr.String())
   212  				mod, v, mDiags := i.installRegistryModule(req, key, instPath, addr, manifest, hooks, fetcher)
   213  				diags = append(diags, mDiags...)
   214  				return mod, v, diags
   215  
   216  			case addrs.ModuleSourceRemote:
   217  				log.Printf("[TRACE] ModuleInstaller: %s address %q will be handled by go-getter", key, addr.String())
   218  				mod, mDiags := i.installGoGetterModule(req, key, instPath, manifest, hooks, fetcher)
   219  				diags = append(diags, mDiags...)
   220  				return mod, nil, diags
   221  
   222  			default:
   223  				// Shouldn't get here, because there are no other implementations
   224  				// of addrs.ModuleSource.
   225  				panic(fmt.Sprintf("unsupported module source address %#v", addr))
   226  			}
   227  
   228  		},
   229  	))
   230  	diags = append(diags, cDiags...)
   231  
   232  	err := manifest.WriteSnapshotToDir(i.modsDir)
   233  	if err != nil {
   234  		diags = diags.Append(tfdiags.Sourceless(
   235  			tfdiags.Error,
   236  			"Failed to update module manifest",
   237  			fmt.Sprintf("Unable to write the module manifest file: %s", err),
   238  		))
   239  	}
   240  
   241  	return cfg, diags
   242  }
   243  
   244  func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key string, manifest modsdir.Manifest, hooks ModuleInstallHooks) (*tfconfig.Module, tfdiags.Diagnostics) {
   245  	var diags tfdiags.Diagnostics
   246  
   247  	parentKey := manifest.ModuleKey(req.Parent.Path)
   248  	parentRecord, recorded := manifest[parentKey]
   249  	if !recorded {
   250  		// This is indicative of a bug rather than a user-actionable error
   251  		panic(fmt.Errorf("missing manifest record for parent module %s", parentKey))
   252  	}
   253  
   254  	if len(req.VersionConstraints) != 0 {
   255  		diags = diags.Append(tfdiags.Sourceless(
   256  			tfdiags.Error,
   257  			"Invalid version constraint",
   258  			fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a relative local path.", req.Name, req.CallPos.Filename, req.CallPos.Line),
   259  		))
   260  	}
   261  
   262  	// For local sources we don't actually need to modify the
   263  	// filesystem at all because the parent already wrote
   264  	// the files we need, and so we just load up what's already here.
   265  	newDir := filepath.Join(parentRecord.Dir, req.SourceAddr.String())
   266  
   267  	log.Printf("[TRACE] ModuleInstaller: %s uses directory from parent: %s", key, newDir)
   268  	// it is possible that the local directory is a symlink
   269  	newDir, err := filepath.EvalSymlinks(newDir)
   270  	if err != nil {
   271  		diags = diags.Append(tfdiags.Sourceless(
   272  			tfdiags.Error,
   273  			"Unreadable module directory",
   274  			fmt.Sprintf("Unable to evaluate directory symlink: %s", err.Error()),
   275  		))
   276  	}
   277  
   278  	mod, mDiags := earlyconfig.LoadModule(newDir)
   279  	if mod == nil {
   280  		// nil indicates missing or unreadable directory, so we'll
   281  		// discard the returned diags and return a more specific
   282  		// error message here.
   283  		diags = diags.Append(tfdiags.Sourceless(
   284  			tfdiags.Error,
   285  			"Unreadable module directory",
   286  			fmt.Sprintf("The directory %s could not be read for module %q at %s:%d.", newDir, req.Name, req.CallPos.Filename, req.CallPos.Line),
   287  		))
   288  	} else {
   289  		diags = diags.Append(mDiags)
   290  	}
   291  
   292  	// Note the local location in our manifest.
   293  	manifest[key] = modsdir.Record{
   294  		Key:        key,
   295  		Dir:        newDir,
   296  		SourceAddr: req.SourceAddr.String(),
   297  	}
   298  	log.Printf("[DEBUG] Module installer: %s installed at %s", key, newDir)
   299  	hooks.Install(key, nil, newDir)
   300  
   301  	return mod, diags
   302  }
   303  
   304  func (i *ModuleInstaller) installRegistryModule(req *earlyconfig.ModuleRequest, key string, instPath string, addr addrs.ModuleSourceRegistry, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
   305  	var diags tfdiags.Diagnostics
   306  
   307  	hostname := addr.PackageAddr.Host
   308  	reg := i.reg
   309  	var resp *response.ModuleVersions
   310  	var exists bool
   311  
   312  	// A registry entry isn't _really_ a module package, but we'll pretend it's
   313  	// one for the sake of this reporting by just trimming off any source
   314  	// directory.
   315  	packageAddr := addr.PackageAddr
   316  
   317  	// Our registry client is still using the legacy model of addresses, so
   318  	// we'll shim it here for now.
   319  	regsrcAddr := regsrc.ModuleFromRegistryPackageAddr(packageAddr)
   320  
   321  	// check if we've already looked up this module from the registry
   322  	if resp, exists = i.registryPackageVersions[packageAddr]; exists {
   323  		log.Printf("[TRACE] %s using already found available versions of %s at %s", key, addr, hostname)
   324  	} else {
   325  		var err error
   326  		log.Printf("[DEBUG] %s listing available versions of %s at %s", key, addr, hostname)
   327  		resp, err = reg.ModuleVersions(regsrcAddr)
   328  		if err != nil {
   329  			if registry.IsModuleNotFound(err) {
   330  				diags = diags.Append(tfdiags.Sourceless(
   331  					tfdiags.Error,
   332  					"Module not found",
   333  					fmt.Sprintf("Module %q (from %s:%d) cannot be found in the module registry at %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname),
   334  				))
   335  			} else {
   336  				diags = diags.Append(tfdiags.Sourceless(
   337  					tfdiags.Error,
   338  					"Error accessing remote module registry",
   339  					fmt.Sprintf("Failed to retrieve available versions for module %q (%s:%d) from %s: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname, err),
   340  				))
   341  			}
   342  			return nil, nil, diags
   343  		}
   344  		i.registryPackageVersions[packageAddr] = resp
   345  	}
   346  
   347  	// The response might contain information about dependencies to allow us
   348  	// to potentially optimize future requests, but we don't currently do that
   349  	// and so for now we'll just take the first item which is guaranteed to
   350  	// be the address we requested.
   351  	if len(resp.Modules) < 1 {
   352  		// Should never happen, but since this is a remote service that may
   353  		// be implemented by third-parties we will handle it gracefully.
   354  		diags = diags.Append(tfdiags.Sourceless(
   355  			tfdiags.Error,
   356  			"Invalid response from remote module registry",
   357  			fmt.Sprintf("The registry at %s returned an invalid response when Terraform requested available versions for module %q (%s:%d).", hostname, req.Name, req.CallPos.Filename, req.CallPos.Line),
   358  		))
   359  		return nil, nil, diags
   360  	}
   361  
   362  	modMeta := resp.Modules[0]
   363  
   364  	var latestMatch *version.Version
   365  	var latestVersion *version.Version
   366  	for _, mv := range modMeta.Versions {
   367  		v, err := version.NewVersion(mv.Version)
   368  		if err != nil {
   369  			// Should never happen if the registry server is compliant with
   370  			// the protocol, but we'll warn if not to assist someone who
   371  			// might be developing a module registry server.
   372  			diags = diags.Append(tfdiags.Sourceless(
   373  				tfdiags.Warning,
   374  				"Invalid response from remote module registry",
   375  				fmt.Sprintf("The registry at %s returned an invalid version string %q for module %q (%s:%d), which Terraform ignored.", hostname, mv.Version, req.Name, req.CallPos.Filename, req.CallPos.Line),
   376  			))
   377  			continue
   378  		}
   379  
   380  		// If we've found a pre-release version then we'll ignore it unless
   381  		// it was exactly requested.
   382  		if v.Prerelease() != "" && req.VersionConstraints.String() != v.String() {
   383  			log.Printf("[TRACE] ModuleInstaller: %s ignoring %s because it is a pre-release and was not requested exactly", key, v)
   384  			continue
   385  		}
   386  
   387  		if latestVersion == nil || v.GreaterThan(latestVersion) {
   388  			latestVersion = v
   389  		}
   390  
   391  		if req.VersionConstraints.Check(v) {
   392  			if latestMatch == nil || v.GreaterThan(latestMatch) {
   393  				latestMatch = v
   394  			}
   395  		}
   396  	}
   397  
   398  	if latestVersion == nil {
   399  		diags = diags.Append(tfdiags.Sourceless(
   400  			tfdiags.Error,
   401  			"Module has no versions",
   402  			fmt.Sprintf("Module %q (%s:%d) has no versions available on %s.", addr, req.CallPos.Filename, req.CallPos.Line, hostname),
   403  		))
   404  		return nil, nil, diags
   405  	}
   406  
   407  	if latestMatch == nil {
   408  		diags = diags.Append(tfdiags.Sourceless(
   409  			tfdiags.Error,
   410  			"Unresolvable module version constraint",
   411  			fmt.Sprintf("There is no available version of module %q (%s:%d) which matches the given version constraint. The newest available version is %s.", addr, req.CallPos.Filename, req.CallPos.Line, latestVersion),
   412  		))
   413  		return nil, nil, diags
   414  	}
   415  
   416  	// Report up to the caller that we're about to start downloading.
   417  	hooks.Download(key, packageAddr.String(), latestMatch)
   418  
   419  	// If we manage to get down here then we've found a suitable version to
   420  	// install, so we need to ask the registry where we should download it from.
   421  	// The response to this is a go-getter-style address string.
   422  
   423  	// first check the cache for the download URL
   424  	moduleAddr := moduleVersion{module: packageAddr, version: latestMatch.String()}
   425  	if _, exists := i.registryPackageSources[moduleAddr]; !exists {
   426  		realAddrRaw, err := reg.ModuleLocation(regsrcAddr, latestMatch.String())
   427  		if err != nil {
   428  			log.Printf("[ERROR] %s from %s %s: %s", key, addr, latestMatch, err)
   429  			diags = diags.Append(tfdiags.Sourceless(
   430  				tfdiags.Error,
   431  				"Error accessing remote module registry",
   432  				fmt.Sprintf("Failed to retrieve a download URL for %s %s from %s: %s", addr, latestMatch, hostname, err),
   433  			))
   434  			return nil, nil, diags
   435  		}
   436  		realAddr, err := addrs.ParseModuleSource(realAddrRaw)
   437  		if err != nil {
   438  			diags = diags.Append(tfdiags.Sourceless(
   439  				tfdiags.Error,
   440  				"Invalid package location from module registry",
   441  				fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: %s.", hostname, realAddrRaw, addr, latestMatch, err),
   442  			))
   443  			return nil, nil, diags
   444  		}
   445  		switch realAddr := realAddr.(type) {
   446  		// Only a remote source address is allowed here: a registry isn't
   447  		// allowed to return a local path (because it doesn't know what
   448  		// its being called from) and we also don't allow recursively pointing
   449  		// at another registry source for simplicity's sake.
   450  		case addrs.ModuleSourceRemote:
   451  			i.registryPackageSources[moduleAddr] = realAddr
   452  		default:
   453  			diags = diags.Append(tfdiags.Sourceless(
   454  				tfdiags.Error,
   455  				"Invalid package location from module registry",
   456  				fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: must be a direct remote package address.", hostname, realAddrRaw, addr, latestMatch),
   457  			))
   458  			return nil, nil, diags
   459  		}
   460  	}
   461  
   462  	dlAddr := i.registryPackageSources[moduleAddr]
   463  
   464  	log.Printf("[TRACE] ModuleInstaller: %s %s %s is available at %q", key, packageAddr, latestMatch, dlAddr.PackageAddr)
   465  
   466  	err := fetcher.FetchPackage(instPath, dlAddr.PackageAddr.String())
   467  	if err != nil {
   468  		// Errors returned by go-getter have very inconsistent quality as
   469  		// end-user error messages, but for now we're accepting that because
   470  		// we have no way to recognize any specific errors to improve them
   471  		// and masking the error entirely would hide valuable diagnostic
   472  		// information from the user.
   473  		diags = diags.Append(tfdiags.Sourceless(
   474  			tfdiags.Error,
   475  			"Failed to download module",
   476  			fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, dlAddr, err),
   477  		))
   478  		return nil, nil, diags
   479  	}
   480  
   481  	log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, dlAddr.PackageAddr, instPath)
   482  
   483  	// Incorporate any subdir information from the original path into the
   484  	// address returned by the registry in order to find the final directory
   485  	// of the target module.
   486  	finalAddr := dlAddr.FromRegistry(addr)
   487  	subDir := filepath.FromSlash(finalAddr.Subdir)
   488  	modDir := filepath.Join(instPath, subDir)
   489  
   490  	log.Printf("[TRACE] ModuleInstaller: %s should now be at %s", key, modDir)
   491  
   492  	// Finally we are ready to try actually loading the module.
   493  	mod, mDiags := earlyconfig.LoadModule(modDir)
   494  	if mod == nil {
   495  		// nil indicates missing or unreadable directory, so we'll
   496  		// discard the returned diags and return a more specific
   497  		// error message here. For registry modules this actually
   498  		// indicates a bug in the code above, since it's not the
   499  		// user's responsibility to create the directory in this case.
   500  		diags = diags.Append(tfdiags.Sourceless(
   501  			tfdiags.Error,
   502  			"Unreadable module directory",
   503  			fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
   504  		))
   505  	} else {
   506  		diags = append(diags, mDiags...)
   507  	}
   508  
   509  	// Note the local location in our manifest.
   510  	manifest[key] = modsdir.Record{
   511  		Key:        key,
   512  		Version:    latestMatch,
   513  		Dir:        modDir,
   514  		SourceAddr: req.SourceAddr.String(),
   515  	}
   516  	log.Printf("[DEBUG] Module installer: %s installed at %s", key, modDir)
   517  	hooks.Install(key, latestMatch, modDir)
   518  
   519  	return mod, latestMatch, diags
   520  }
   521  
   522  func (i *ModuleInstaller) installGoGetterModule(req *earlyconfig.ModuleRequest, key string, instPath string, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*tfconfig.Module, tfdiags.Diagnostics) {
   523  	var diags tfdiags.Diagnostics
   524  
   525  	// Report up to the caller that we're about to start downloading.
   526  	addr := req.SourceAddr.(addrs.ModuleSourceRemote)
   527  	packageAddr := addr.PackageAddr
   528  	hooks.Download(key, packageAddr.String(), nil)
   529  
   530  	if len(req.VersionConstraints) != 0 {
   531  		diags = diags.Append(tfdiags.Sourceless(
   532  			tfdiags.Error,
   533  			"Invalid version constraint",
   534  			fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a non Registry URL.", req.Name, req.CallPos.Filename, req.CallPos.Line),
   535  		))
   536  		return nil, diags
   537  	}
   538  
   539  	err := fetcher.FetchPackage(instPath, packageAddr.String())
   540  	if err != nil {
   541  		// go-getter generates a poor error for an invalid relative path, so
   542  		// we'll detect that case and generate a better one.
   543  		if _, ok := err.(*getmodules.MaybeRelativePathErr); ok {
   544  			log.Printf(
   545  				"[TRACE] ModuleInstaller: %s looks like a local path but is missing ./ or ../",
   546  				req.SourceAddr,
   547  			)
   548  			diags = diags.Append(tfdiags.Sourceless(
   549  				tfdiags.Error,
   550  				"Module not found",
   551  				fmt.Sprintf(
   552  					"The module address %q could not be resolved.\n\n"+
   553  						"If you intended this as a path relative to the current "+
   554  						"module, use \"./%s\" instead. The \"./\" prefix "+
   555  						"indicates that the address is a relative filesystem path.",
   556  					req.SourceAddr, req.SourceAddr,
   557  				),
   558  			))
   559  		} else {
   560  			// Errors returned by go-getter have very inconsistent quality as
   561  			// end-user error messages, but for now we're accepting that because
   562  			// we have no way to recognize any specific errors to improve them
   563  			// and masking the error entirely would hide valuable diagnostic
   564  			// information from the user.
   565  			diags = diags.Append(tfdiags.Sourceless(
   566  				tfdiags.Error,
   567  				"Failed to download module",
   568  				fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s", req.Name, req.CallPos.Filename, req.CallPos.Line, packageAddr, err),
   569  			))
   570  		}
   571  		return nil, diags
   572  	}
   573  
   574  	subDir := filepath.FromSlash(addr.Subdir)
   575  	modDir := filepath.Join(instPath, subDir)
   576  
   577  	log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, addr, modDir)
   578  
   579  	mod, mDiags := earlyconfig.LoadModule(modDir)
   580  	if mod == nil {
   581  		// nil indicates missing or unreadable directory, so we'll
   582  		// discard the returned diags and return a more specific
   583  		// error message here. For go-getter modules this actually
   584  		// indicates a bug in the code above, since it's not the
   585  		// user's responsibility to create the directory in this case.
   586  		diags = diags.Append(tfdiags.Sourceless(
   587  			tfdiags.Error,
   588  			"Unreadable module directory",
   589  			fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
   590  		))
   591  	} else {
   592  		diags = append(diags, mDiags...)
   593  	}
   594  
   595  	// Note the local location in our manifest.
   596  	manifest[key] = modsdir.Record{
   597  		Key:        key,
   598  		Dir:        modDir,
   599  		SourceAddr: req.SourceAddr.String(),
   600  	}
   601  	log.Printf("[DEBUG] Module installer: %s installed at %s", key, modDir)
   602  	hooks.Install(key, nil, modDir)
   603  
   604  	return mod, diags
   605  }
   606  
   607  func (i *ModuleInstaller) packageInstallPath(modulePath addrs.Module) string {
   608  	return filepath.Join(i.modsDir, strings.Join(modulePath, "."))
   609  }
   610  
   611  // maybeImproveLocalInstallError is a helper function which can recognize
   612  // some specific situations where it can return a more helpful error message
   613  // and thus replace the given errors with those if so.
   614  //
   615  // If this function can't do anything about a particular situation then it
   616  // will just return the given diags verbatim.
   617  //
   618  // This function's behavior is only reasonable for errors returned from the
   619  // ModuleInstaller.installLocalModule function.
   620  func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags.Diagnostics) tfdiags.Diagnostics {
   621  	if !diags.HasErrors() {
   622  		return diags
   623  	}
   624  
   625  	// The main situation we're interested in detecting here is whether the
   626  	// current module or any of its ancestors use relative paths that reach
   627  	// outside of the "package" established by the nearest non-local ancestor.
   628  	// That's never really valid, but unfortunately we historically didn't
   629  	// have any explicit checking for it and so now for compatibility in
   630  	// situations where things just happened to "work" we treat this as an
   631  	// error only in situations where installation would've failed anyway,
   632  	// so we can give a better error about it than just a generic
   633  	// "directory not found" or whatever.
   634  	//
   635  	// Since it's never actually valid to relative out of the containing
   636  	// package, we just assume that any failed local package install which
   637  	// does so was caused by that, because to stop doing it should always
   638  	// improve the situation, even if it leads to another error describing
   639  	// a different problem.
   640  
   641  	// To decide this we need to find the subset of our ancestors that
   642  	// belong to the same "package" as our request, along with the closest
   643  	// ancestor that defined that package, and then we can work forwards
   644  	// to see if any of the local paths "escaped" the package.
   645  	type Step struct {
   646  		Path       addrs.Module
   647  		SourceAddr addrs.ModuleSource
   648  	}
   649  	var packageDefiner Step
   650  	var localRefs []Step
   651  	localRefs = append(localRefs, Step{
   652  		Path:       req.Path,
   653  		SourceAddr: req.SourceAddr,
   654  	})
   655  	current := req.Parent // an earlyconfig.Config where Children isn't populated yet
   656  	for {
   657  		if current == nil || current.SourceAddr == nil {
   658  			// We've reached the root module, in which case we aren't
   659  			// in an external "package" at all and so our special case
   660  			// can't apply.
   661  			return diags
   662  		}
   663  		if _, ok := current.SourceAddr.(addrs.ModuleSourceLocal); !ok {
   664  			// We've found the package definer, then!
   665  			packageDefiner = Step{
   666  				Path:       current.Path,
   667  				SourceAddr: current.SourceAddr,
   668  			}
   669  			break
   670  		}
   671  
   672  		localRefs = append(localRefs, Step{
   673  			Path:       current.Path,
   674  			SourceAddr: current.SourceAddr,
   675  		})
   676  		current = current.Parent
   677  	}
   678  	// Our localRefs list is reversed because we were traversing up the tree,
   679  	// so we'll flip it the other way and thus walk "downwards" through it.
   680  	for i, j := 0, len(localRefs)-1; i < j; i, j = i+1, j-1 {
   681  		localRefs[i], localRefs[j] = localRefs[j], localRefs[i]
   682  	}
   683  
   684  	// Our method here is to start with a known base path prefix and
   685  	// then apply each of the local refs to it in sequence until one of
   686  	// them causes us to "lose" the prefix. If that happens, we've found
   687  	// an escape to report. This is not an exact science but good enough
   688  	// heuristic for choosing a better error message.
   689  	const prefix = "*/" // NOTE: this can find a false negative if the user chooses "*" as a directory name, but we consider that unlikely
   690  	packageAddr, startPath := splitAddrSubdir(packageDefiner.SourceAddr)
   691  	currentPath := path.Join(prefix, startPath)
   692  	for _, step := range localRefs {
   693  		rel := step.SourceAddr.String()
   694  
   695  		nextPath := path.Join(currentPath, rel)
   696  		if !strings.HasPrefix(nextPath, prefix) { // ESCAPED!
   697  			escapeeAddr := step.Path.String()
   698  
   699  			var newDiags tfdiags.Diagnostics
   700  
   701  			// First we'll copy over any non-error diagnostics from the source diags
   702  			for _, diag := range diags {
   703  				if diag.Severity() != tfdiags.Error {
   704  					newDiags = newDiags.Append(diag)
   705  				}
   706  			}
   707  
   708  			// ...but we'll replace any errors with this more precise error.
   709  			var suggestion string
   710  			if strings.HasPrefix(packageAddr, "/") || filepath.VolumeName(packageAddr) != "" {
   711  				// It might be somewhat surprising that Terraform treats
   712  				// absolute filesystem paths as "external" even though it
   713  				// treats relative paths as local, so if it seems like that's
   714  				// what the user was doing then we'll add an additional note
   715  				// about it.
   716  				suggestion = "\n\nTerraform treats absolute filesystem paths as external modules which establish a new module package. To treat this directory as part of the same package as its caller, use a local path starting with either \"./\" or \"../\"."
   717  			}
   718  			newDiags = newDiags.Append(tfdiags.Sourceless(
   719  				tfdiags.Error,
   720  				"Local module path escapes module package",
   721  				fmt.Sprintf(
   722  					"The given source directory for %s would be outside of its containing package %q. Local source addresses starting with \"../\" must stay within the same package that the calling module belongs to.%s",
   723  					escapeeAddr, packageAddr, suggestion,
   724  				),
   725  			))
   726  
   727  			return newDiags
   728  		}
   729  
   730  		currentPath = nextPath
   731  	}
   732  
   733  	// If we get down here then we have nothing useful to do, so we'll just
   734  	// echo back what we were given.
   735  	return diags
   736  }
   737  
   738  func splitAddrSubdir(addr addrs.ModuleSource) (string, string) {
   739  	switch addr := addr.(type) {
   740  	case addrs.ModuleSourceRegistry:
   741  		subDir := addr.Subdir
   742  		addr.Subdir = ""
   743  		return addr.String(), subDir
   744  	case addrs.ModuleSourceRemote:
   745  		return addr.PackageAddr.String(), addr.Subdir
   746  	case nil:
   747  		panic("splitAddrSubdir on nil addrs.ModuleSource")
   748  	default:
   749  		return addr.String(), ""
   750  	}
   751  }