terraform/provider_source.go

133 lines
5.8 KiB
Go

package main
import (
"log"
"os"
"path/filepath"
"github.com/apparentlymart/go-userdirs/userdirs"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/cliconfig"
"github.com/hashicorp/terraform/internal/getproviders"
)
// providerSource constructs a provider source based on a combination of the
// CLI configuration and some default search locations. This will be the
// provider source used for provider installation in the "terraform init"
// command, unless overridden by the special -plugin-dir option.
func providerSource(services *disco.Disco) getproviders.Source {
// We're not yet using the CLI config here because we've not implemented
// yet the new configuration constructs to customize provider search
// locations. That'll come later. For now, we just always use the
// implicit default provider source.
return implicitProviderSource(services)
}
// implicitProviderSource builds a default provider source to use if there's
// no explicit provider installation configuration in the CLI config.
//
// This implicit source looks in a number of local filesystem directories and
// directly in a provider's upstream registry. Any providers that have at least
// one version available in a local directory are implicitly excluded from
// direct installation, as if the user had listed them explicitly in the
// "exclude" argument in the direct provider source in the CLI config.
func implicitProviderSource(services *disco.Disco) getproviders.Source {
// The local search directories we use for implicit configuration are:
// - The "terraform.d/plugins" directory in the current working directory,
// which we've historically documented as a place to put plugins as a
// way to include them in bundles uploaded to Terraform Cloud, where
// there has historically otherwise been no way to use custom providers.
// - The "plugins" subdirectory of the CLI config search directory.
// (thats ~/.terraform.d/plugins on Unix systems, equivalents elsewhere)
// - The "plugins" subdirectory of any platform-specific search paths,
// following e.g. the XDG base directory specification on Unix systems,
// Apple's guidelines on OS X, and "known folders" on Windows.
//
// Any provider we find in one of those implicit directories will be
// automatically excluded from direct installation from an upstream
// registry. Anything not available locally will query its primary
// upstream registry.
var searchRules []getproviders.MultiSourceSelector
// We'll track any providers we can find in the local search directories
// along the way, and then exclude them from the registry source we'll
// finally add at the end.
foundLocally := map[addrs.Provider]struct{}{}
addLocalDir := func(dir string) {
// We'll make sure the directory actually exists before we add it,
// because otherwise installation would always fail trying to look
// in non-existent directories. (This is done here rather than in
// the source itself because explicitly-selected directories via the
// CLI config, once we have them, _should_ produce an error if they
// don't exist to help users get their configurations right.)
if info, err := os.Stat(dir); err == nil && info.IsDir() {
log.Printf("[DEBUG] will search for provider plugins in %s", dir)
fsSource := getproviders.NewFilesystemMirrorSource(dir)
// We'll peep into the source to find out what providers it seems
// to be providing, so that we can exclude those from direct
// install. This might fail, in which case we'll just silently
// ignore it and assume it would fail during installation later too
// and therefore effectively doesn't provide _any_ packages.
if available, err := fsSource.AllAvailablePackages(); err == nil {
for found := range available {
foundLocally[found] = struct{}{}
}
}
searchRules = append(searchRules, getproviders.MultiSourceSelector{
Source: fsSource,
})
} else {
log.Printf("[DEBUG] ignoring non-existing provider search directory %s", dir)
}
}
addLocalDir("terraform.d/plugins") // our "vendor" directory
cliConfigDir, err := cliconfig.ConfigDir()
if err != nil {
addLocalDir(filepath.Join(cliConfigDir, "plugins"))
}
// This "userdirs" library implements an appropriate user-specific and
// app-specific directory layout for the current platform, such as XDG Base
// Directory on Unix, using the following name strings to construct a
// suitable application-specific subdirectory name following the
// conventions for each platform:
//
// XDG (Unix): lowercase of the first string, "terraform"
// Windows: two-level heirarchy of first two strings, "HashiCorp\Terraform"
// OS X: reverse-DNS unique identifier, "io.terraform".
sysSpecificDirs := userdirs.ForApp("Terraform", "HashiCorp", "io.terraform")
for _, dir := range sysSpecificDirs.DataSearchPaths("plugins") {
addLocalDir(dir)
}
// Anything we found in local directories above is excluded from being
// looked up via the registry source we're about to construct.
var directExcluded getproviders.MultiSourceMatchingPatterns
for addr := range foundLocally {
directExcluded = append(directExcluded, addr)
}
// Last but not least, the main registry source! We'll wrap a caching
// layer around this one to help optimize the several network requests
// we'll end up making to it while treating it as one of several sources
// in a MultiSource (as recommended in the MultiSource docs).
// This one is listed last so that if a particular version is available
// both in one of the above directories _and_ in a remote registry, the
// local copy will take precedence.
searchRules = append(searchRules, getproviders.MultiSourceSelector{
Source: getproviders.NewMemoizeSource(
getproviders.NewRegistrySource(services),
),
Exclude: directExcluded,
})
return getproviders.MultiSource(searchRules)
}