initwd: cache registry responses for module versions and download URL (#23727)

* initwd: cache registry responses for module versions and download URL

Closes #23544
This commit is contained in:
Kristin Laemmert 2020-01-07 15:03:23 -05:00 committed by GitHub
parent 2a95d98383
commit 4045a6e5d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 27 deletions

View File

@ -14,18 +14,34 @@ import (
"github.com/hashicorp/terraform/internal/modsdir"
"github.com/hashicorp/terraform/registry"
"github.com/hashicorp/terraform/registry/regsrc"
"github.com/hashicorp/terraform/registry/response"
"github.com/hashicorp/terraform/tfdiags"
)
type ModuleInstaller struct {
modsDir string
reg *registry.Client
// The keys in moduleVersions are resolved and trimmed registry source
// addresses and the values are the registry response.
moduleVersions map[string]*response.ModuleVersions
// The keys in moduleVersionsUrl are the moduleVersion struct below and
// addresses and the values are the download URLs.
moduleVersionsUrl map[moduleVersion]string
}
type moduleVersion struct {
module string
version string
}
func NewModuleInstaller(modsDir string, reg *registry.Client) *ModuleInstaller {
return &ModuleInstaller{
modsDir: modsDir,
reg: reg,
modsDir: modsDir,
reg: reg,
moduleVersions: make(map[string]*response.ModuleVersions),
moduleVersionsUrl: make(map[moduleVersion]string),
}
}
@ -309,24 +325,32 @@ func (i *ModuleInstaller) installRegistryModule(req *earlyconfig.ModuleRequest,
}
reg := i.reg
var resp *response.ModuleVersions
var exists bool
log.Printf("[DEBUG] %s listing available versions of %s at %s", key, addr, hostname)
resp, err := reg.ModuleVersions(addr)
if err != nil {
if registry.IsModuleNotFound(err) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Module not found",
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),
))
} else {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Error accessing remote module registry",
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),
))
// check if we've already looked up this module from the registry
if resp, exists = i.moduleVersions[addr.String()]; exists {
log.Printf("[TRACE] %s using already found available versions of %s at %s", key, addr, hostname)
} else {
log.Printf("[DEBUG] %s listing available versions of %s at %s", key, addr, hostname)
resp, err = reg.ModuleVersions(addr)
if err != nil {
if registry.IsModuleNotFound(err) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Module not found",
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),
))
} else {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Error accessing remote module registry",
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),
))
}
return nil, nil, diags
}
return nil, nil, diags
i.moduleVersions[addr.String()] = resp
}
// The response might contain information about dependencies to allow us
@ -405,17 +429,25 @@ func (i *ModuleInstaller) installRegistryModule(req *earlyconfig.ModuleRequest,
// If we manage to get down here then we've found a suitable version to
// install, so we need to ask the registry where we should download it from.
// The response to this is a go-getter-style address string.
dlAddr, err := reg.ModuleLocation(addr, latestMatch.String())
if err != nil {
log.Printf("[ERROR] %s from %s %s: %s", key, addr, latestMatch, err)
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid response from remote module registry",
fmt.Sprintf("The remote registry at %s failed to return a download URL for %s %s.", hostname, addr, latestMatch),
))
return nil, nil, diags
// first check the cache for the download URL
moduleAddr := moduleVersion{module: addr.String(), version: latestMatch.String()}
if _, exists := i.moduleVersionsUrl[moduleAddr]; !exists {
url, err := reg.ModuleLocation(addr, latestMatch.String())
if err != nil {
log.Printf("[ERROR] %s from %s %s: %s", key, addr, latestMatch, err)
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid response from remote module registry",
fmt.Sprintf("The remote registry at %s failed to return a download URL for %s %s.", hostname, addr, latestMatch),
))
return nil, nil, diags
}
i.moduleVersionsUrl[moduleVersion{module: addr.String(), version: latestMatch.String()}] = url
}
dlAddr := i.moduleVersionsUrl[moduleAddr]
log.Printf("[TRACE] ModuleInstaller: %s %s %s is available at %q", key, addr, latestMatch, dlAddr)
modDir, err := getter.getWithGoGetter(instPath, dlAddr)

View File

@ -327,6 +327,14 @@ func TestLoaderInstallModules_registry(t *testing.T) {
return
}
//check that the registry reponses were cached
if _, ok := inst.moduleVersions["hashicorp/module-installer-acctest/aws"]; !ok {
t.Fatal("module versions cache was not populated")
}
if _, ok := inst.moduleVersionsUrl[moduleVersion{module: "hashicorp/module-installer-acctest/aws", version: "0.0.1"}]; !ok {
t.Fatal("module download url cache was not populated")
}
loader, err := configload.NewLoader(&configload.Config{
ModulesDir: modulesDir,
})