diff --git a/main.go b/main.go index f9a3b9cf80..09da719c40 100644 --- a/main.go +++ b/main.go @@ -127,6 +127,7 @@ func wrappedMain() int { log.Printf("[INFO] CLI args: %#v", os.Args) config, diags := cliconfig.LoadConfig() + if len(diags) > 0 { // Since we haven't instantiated a command.Meta yet, we need to do // some things manually here and use some "safe" defaults for things @@ -168,7 +169,22 @@ func wrappedMain() int { // direct from a registry. In future there should be a mechanism to // configure providers sources from the CLI config, which will then // change how we construct this object. - providerSrc := providerSource(services) + providerSrc, diags := providerSource(config.ProviderInstallation, services) + if len(diags) > 0 { + Ui.Error("There are some problems with the provider_installation configuration:") + for _, diag := range diags { + earlyColor := &colorstring.Colorize{ + Colors: colorstring.DefaultColors, + Disable: true, // Disable color to be conservative until we know better + Reset: true, + } + Ui.Error(format.Diagnostic(diag, nil, earlyColor, 78)) + } + if diags.HasErrors() { + Ui.Error("As a result of the above problems, Terraform's provider installer may not behave as intended.\n\n") + // We continue to run anyway, because most commands don't do provider installation. + } + } // Initialize the backends. backendInit.Init(services) diff --git a/provider_source.go b/provider_source.go index 8e91658a85..bfbdf0f3fd 100644 --- a/provider_source.go +++ b/provider_source.go @@ -1,28 +1,76 @@ package main import ( + "fmt" "log" "os" "path/filepath" "github.com/apparentlymart/go-userdirs/userdirs" + svchost "github.com/hashicorp/terraform-svchost" "github.com/hashicorp/terraform-svchost/disco" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/command/cliconfig" "github.com/hashicorp/terraform/internal/getproviders" + "github.com/hashicorp/terraform/tfdiags" ) // 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) +func providerSource(configs []*cliconfig.ProviderInstallation, services *disco.Disco) (getproviders.Source, tfdiags.Diagnostics) { + if len(configs) == 0 { + // If there's no explicit installation configuration then we'll build + // up an implicit one with direct registry installation along with + // some automatically-selected local filesystem mirrors. + return implicitProviderSource(services), nil + } + + // There should only be zero or one configurations, which is checked by + // the validation logic in the cliconfig package. Therefore we'll just + // ignore any additional configurations in here. + config := configs[0] + return explicitProviderSource(config, services) +} + +func explicitProviderSource(config *cliconfig.ProviderInstallation, services *disco.Disco) (getproviders.Source, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + var searchRules []getproviders.MultiSourceSelector + + for _, sourceConfig := range config.Sources { + source, moreDiags := providerSourceForCLIConfigLocation(sourceConfig.Location, services) + diags = diags.Append(moreDiags) + if moreDiags.HasErrors() { + continue + } + + include, err := getproviders.ParseMultiSourceMatchingPatterns(sourceConfig.Include) + if err != nil { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid provider source inclusion patterns", + fmt.Sprintf("CLI config specifies invalid provider inclusion patterns: %s.", err), + )) + } + exclude, err := getproviders.ParseMultiSourceMatchingPatterns(sourceConfig.Include) + if err != nil { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid provider source exclusion patterns", + fmt.Sprintf("CLI config specifies invalid provider exclusion patterns: %s.", err), + )) + } + + searchRules = append(searchRules, getproviders.MultiSourceSelector{ + Source: source, + Include: include, + Exclude: exclude, + }) + } + + return getproviders.MultiSource(searchRules), diags } // implicitProviderSource builds a default provider source to use if there's @@ -130,3 +178,36 @@ func implicitProviderSource(services *disco.Disco) getproviders.Source { return getproviders.MultiSource(searchRules) } + +func providerSourceForCLIConfigLocation(loc cliconfig.ProviderInstallationSourceLocation, services *disco.Disco) (getproviders.Source, tfdiags.Diagnostics) { + if loc == cliconfig.ProviderInstallationDirect { + return getproviders.NewMemoizeSource( + getproviders.NewRegistrySource(services), + ), nil + } + + switch loc := loc.(type) { + + case cliconfig.ProviderInstallationFilesystemMirror: + return getproviders.NewFilesystemMirrorSource(string(loc)), nil + + case cliconfig.ProviderInstallationNetworkMirror: + host, err := svchost.ForComparison(string(loc)) + if err != nil { + var diags tfdiags.Diagnostics + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid hostname for provider installation source", + fmt.Sprintf("Cannot parse %q as a hostname for a network provider mirror: %s.", string(loc), err), + )) + return nil, diags + } + return getproviders.NewNetworkMirrorSource(host), nil + + default: + // We should not get here because the set of cases above should + // be comprehensive for all of the + // cliconfig.ProviderInstallationLocation implementations. + panic(fmt.Sprintf("unexpected provider source location type %T", loc)) + } +}