mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add a new warning when a provider cannot be downloaded and it was requested by an implicit usage (#2479)
Signed-off-by: yottta <andrei.ciobanu@opentofu.org> Co-authored-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com>
This commit is contained in:
parent
5ff40ae505
commit
dfe2876931
@ -18,6 +18,7 @@ ENHANCEMENTS:
|
||||
* The `element` function now accepts negative indices, which extends the existing "wrapping" model into the negative direction. In particular, choosing element `-1` selects the final element in the sequence. ([#2371](https://github.com/opentofu/opentofu/pull/2371))
|
||||
* `moved` now supports moving between different types ([#2370](https://github.com/opentofu/opentofu/pull/2370))
|
||||
* `moved` block can now be used to migrate from the `null_resource` to the `terraform_data` resource. ([#2481](https://github.com/opentofu/opentofu/pull/2481))
|
||||
* Warn on implicit references of providers without a `required_providers` entry. ([#2084](https://github.com/opentofu/opentofu/issues/2084))
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
|
@ -502,6 +502,68 @@ func TestInitProviderNotFound(t *testing.T) {
|
||||
t.Errorf("wrong output:\n%s", cmp.Diff(stripAnsi(stderr), expectedErr))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("implicit provider resource and data not found", func(t *testing.T) {
|
||||
implicitFixturePath := filepath.Join("testdata", "provider-implicit-ref-not-found/implicit-by-resource-and-data")
|
||||
tf := e2e.NewBinary(t, tofuBin, implicitFixturePath)
|
||||
stdout, _, err := tf.Run("init")
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got success")
|
||||
}
|
||||
|
||||
// Testing that the warn wrote to the user is containing the resource address from where the provider
|
||||
// was registered to be downloaded
|
||||
expectedContentInOutput := []string{
|
||||
`(and one more similar warning elsewhere)`,
|
||||
`
|
||||
╷
|
||||
│ Warning: Automatically-inferred provider dependency
|
||||
│
|
||||
│ on main.tf line 2:
|
||||
│ 2: resource "nonexistingProv_res" "test1" {
|
||||
│
|
||||
│ Due to the prefix of the resource type name OpenTofu guessed that you
|
||||
│ intended to associate nonexistingProv_res.test1 with a provider whose local
|
||||
│ name is "nonexistingprov", but that name is not declared in this module's
|
||||
│ required_providers block. OpenTofu therefore guessed that you intended to
|
||||
│ use hashicorp/nonexistingprov, but that provider does not exist.
|
||||
│
|
||||
│ Make at least one of the following changes to tell OpenTofu which provider
|
||||
│ to use:
|
||||
│
|
||||
│ - Add a declaration for local name "nonexistingprov" to this module's
|
||||
│ required_providers block, specifying the full source address for the
|
||||
│ provider you intended to use.
|
||||
│ - Verify that "nonexistingProv_res" is the correct resource type name to
|
||||
│ use. Did you omit a prefix which would imply the correct provider?
|
||||
│ - Use a "provider" argument within this resource block to override
|
||||
│ OpenTofu's automatic selection of the local name "nonexistingprov".
|
||||
│`}
|
||||
for _, expectedOutput := range expectedContentInOutput {
|
||||
if cleanOut := strings.TrimSpace(stripAnsi(stdout)); !strings.Contains(cleanOut, expectedOutput) {
|
||||
t.Errorf("wrong output.\n\toutput:\n%s\n\n\tdoes not contain:\n%s", cleanOut, expectedOutput)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("resource pointing to a not configured provider does not warn on implicit reference", func(t *testing.T) {
|
||||
implicitFixturePath := filepath.Join("testdata", "provider-implicit-ref-not-found/resource-with-provider-attribute")
|
||||
tf := e2e.NewBinary(t, tofuBin, implicitFixturePath)
|
||||
stdout, _, err := tf.Run("init")
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got success")
|
||||
}
|
||||
|
||||
// Ensure that the output does not contain the warning since the resource is pointing already to a specific
|
||||
// provider (even though it is misspelled)
|
||||
expectedOutput := `Initializing the backend...
|
||||
|
||||
Initializing provider plugins...
|
||||
- Finding latest version of hashicorp/asw...`
|
||||
if cleanOut := strings.TrimSpace(stripAnsi(stdout)); cleanOut != expectedOutput {
|
||||
t.Errorf("wrong output:\n%s", cmp.Diff(cleanOut, expectedOutput))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// The following test is temporarily removed until the OpenTofu registry returns a deprecation warning
|
||||
|
@ -0,0 +1,10 @@
|
||||
# This is for testing that the implicitly defined providers cannot be fetched and the user is getting an info of the root cause
|
||||
resource "nonexistingProv_res" "test1" {
|
||||
}
|
||||
|
||||
data "nonexistingProv2_data" "test2" {
|
||||
}
|
||||
|
||||
module "testmod" {
|
||||
source = "./mod"
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
resource "nonexistingProv_res" "test2" {
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
// when a resource is pointing to a provider that is missing required_providers definition, tofu does not show the warn
|
||||
// about implicit reference of a provider
|
||||
resource "aws_iam_role" "test" {
|
||||
assume_role_policy = "test"
|
||||
provider = asw.test
|
||||
}
|
@ -566,7 +566,7 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
|
||||
|
||||
// First we'll collect all the provider dependencies we can see in the
|
||||
// configuration and the state.
|
||||
reqs, hclDiags := config.ProviderRequirements()
|
||||
reqs, qualifs, hclDiags := config.ProviderRequirements()
|
||||
diags = diags.Append(hclDiags)
|
||||
if hclDiags.HasErrors() {
|
||||
return false, true, diags
|
||||
@ -712,6 +712,9 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
|
||||
suggestion += "\n\nIf you believe this provider is missing from the registry, please submit a issue on the OpenTofu Registry https://github.com/opentofu/registry/issues/new/choose"
|
||||
}
|
||||
|
||||
warnDiags := warnOnFailedImplicitProvReference(provider, qualifs)
|
||||
diags = diags.Append(warnDiags)
|
||||
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to query available provider packages",
|
||||
@ -1039,6 +1042,43 @@ version control system if they represent changes you intended to make.`))
|
||||
return true, false, diags
|
||||
}
|
||||
|
||||
// warnOnFailedImplicitProvReference returns a warn diagnostic when the downloader fails to fetch a provider that is implicitly referenced.
|
||||
// In other words, if the failed to download provider is having no required_providers entry, this function is trying to give to the user
|
||||
// more information on the source of the issue and gives also instructions on how to fix it.
|
||||
func warnOnFailedImplicitProvReference(provider addrs.Provider, qualifs *getproviders.ProvidersQualification) tfdiags.Diagnostics {
|
||||
if _, ok := qualifs.Explicit[provider]; ok {
|
||||
return nil
|
||||
}
|
||||
refs, ok := qualifs.Implicit[provider]
|
||||
if !ok || len(refs) == 0 {
|
||||
// If there is no implicit reference for that provider, do not write the warn, let just the error to be returned.
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: if needed, in the future we can use the rest of the "refs" to print all the culprits or at least to give
|
||||
// a hint on how many resources are causing this
|
||||
ref := refs[0]
|
||||
if ref.ProviderAttribute {
|
||||
return nil
|
||||
}
|
||||
details := fmt.Sprintf(
|
||||
implicitProviderReferenceBody,
|
||||
ref.CfgRes.String(),
|
||||
provider.Type,
|
||||
provider.ForDisplay(),
|
||||
provider.Type,
|
||||
ref.CfgRes.Resource.Type,
|
||||
provider.Type,
|
||||
)
|
||||
return tfdiags.Diagnostics{}.Append(
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Subject: ref.Ref.ToHCL().Ptr(),
|
||||
Summary: implicitProviderReferenceHead,
|
||||
Detail: details,
|
||||
})
|
||||
}
|
||||
|
||||
// backendConfigOverrideBody interprets the raw values of -backend-config
|
||||
// arguments into a hcl Body that should override the backend settings given
|
||||
// in the configuration.
|
||||
@ -1379,3 +1419,14 @@ The current .terraform.lock.hcl file only includes checksums for %s, so OpenTofu
|
||||
To calculate additional checksums for another platform, run:
|
||||
tofu providers lock -platform=linux_amd64
|
||||
(where linux_amd64 is the platform to generate)`
|
||||
|
||||
const implicitProviderReferenceHead = `Automatically-inferred provider dependency`
|
||||
|
||||
const implicitProviderReferenceBody = `Due to the prefix of the resource type name OpenTofu guessed that you intended to associate %s with a provider whose local name is "%s", but that name is not declared in this module's required_providers block. OpenTofu therefore guessed that you intended to use %s, but that provider does not exist.
|
||||
|
||||
Make at least one of the following changes to tell OpenTofu which provider to use:
|
||||
|
||||
- Add a declaration for local name "%s" to this module's required_providers block, specifying the full source address for the provider you intended to use.
|
||||
- Verify that "%s" is the correct resource type name to use. Did you omit a prefix which would imply the correct provider?
|
||||
- Use a "provider" argument within this resource block to override OpenTofu's automatic selection of the local name "%s".
|
||||
`
|
||||
|
@ -124,7 +124,7 @@ func (c *ProvidersLockCommand) Run(args []string) int {
|
||||
|
||||
config, confDiags := c.loadConfig(".")
|
||||
diags = diags.Append(confDiags)
|
||||
reqs, hclDiags := config.ProviderRequirements()
|
||||
reqs, _, hclDiags := config.ProviderRequirements()
|
||||
diags = diags.Append(hclDiags)
|
||||
|
||||
// If we have explicit provider selections on the command line then
|
||||
|
@ -83,7 +83,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
|
||||
|
||||
config, confDiags := c.loadConfig(".")
|
||||
diags = diags.Append(confDiags)
|
||||
reqs, moreDiags := config.ProviderRequirements()
|
||||
reqs, _, moreDiags := config.ProviderRequirements()
|
||||
diags = diags.Append(moreDiags)
|
||||
|
||||
// Read lock file
|
||||
|
@ -12,10 +12,10 @@ import (
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/depsfile"
|
||||
"github.com/opentofu/opentofu/internal/getproviders"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
)
|
||||
|
||||
// A Config is a node in the tree of modules within a configuration.
|
||||
@ -230,7 +230,7 @@ func (c *Config) EntersNewPackage() bool {
|
||||
func (c *Config) VerifyDependencySelections(depLocks *depsfile.Locks) []error {
|
||||
var errs []error
|
||||
|
||||
reqs, diags := c.ProviderRequirements()
|
||||
reqs, _, diags := c.ProviderRequirements()
|
||||
if diags.HasErrors() {
|
||||
// It should be very unusual to get here, but unfortunately we can
|
||||
// end up here in some edge cases where the config loader doesn't
|
||||
@ -301,11 +301,12 @@ func (c *Config) VerifyDependencySelections(depLocks *depsfile.Locks) []error {
|
||||
//
|
||||
// If the returned diagnostics includes errors then the resulting Requirements
|
||||
// may be incomplete.
|
||||
func (c *Config) ProviderRequirements() (getproviders.Requirements, hcl.Diagnostics) {
|
||||
func (c *Config) ProviderRequirements() (getproviders.Requirements, *getproviders.ProvidersQualification, hcl.Diagnostics) {
|
||||
reqs := make(getproviders.Requirements)
|
||||
diags := c.addProviderRequirements(reqs, true, true)
|
||||
qualifs := new(getproviders.ProvidersQualification)
|
||||
diags := c.addProviderRequirements(reqs, qualifs, true, true)
|
||||
|
||||
return reqs, diags
|
||||
return reqs, qualifs, diags
|
||||
}
|
||||
|
||||
// ProviderRequirementsShallow searches only the direct receiver for explicit
|
||||
@ -315,7 +316,8 @@ func (c *Config) ProviderRequirements() (getproviders.Requirements, hcl.Diagnost
|
||||
// may be incomplete.
|
||||
func (c *Config) ProviderRequirementsShallow() (getproviders.Requirements, hcl.Diagnostics) {
|
||||
reqs := make(getproviders.Requirements)
|
||||
diags := c.addProviderRequirements(reqs, false, true)
|
||||
qualifs := new(getproviders.ProvidersQualification)
|
||||
diags := c.addProviderRequirements(reqs, qualifs, false, true)
|
||||
|
||||
return reqs, diags
|
||||
}
|
||||
@ -328,7 +330,8 @@ func (c *Config) ProviderRequirementsShallow() (getproviders.Requirements, hcl.D
|
||||
// may be incomplete.
|
||||
func (c *Config) ProviderRequirementsByModule() (*ModuleRequirements, hcl.Diagnostics) {
|
||||
reqs := make(getproviders.Requirements)
|
||||
diags := c.addProviderRequirements(reqs, false, false)
|
||||
qualifs := new(getproviders.ProvidersQualification)
|
||||
diags := c.addProviderRequirements(reqs, qualifs, false, false)
|
||||
|
||||
children := make(map[string]*ModuleRequirements)
|
||||
for name, child := range c.Children {
|
||||
@ -378,7 +381,7 @@ func (c *Config) ProviderRequirementsByModule() (*ModuleRequirements, hcl.Diagno
|
||||
// implementation, gradually mutating a shared requirements object to
|
||||
// eventually return. If the recurse argument is true, the requirements will
|
||||
// include all descendant modules; otherwise, only the specified module.
|
||||
func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse, tests bool) hcl.Diagnostics {
|
||||
func (c *Config) addProviderRequirements(reqs getproviders.Requirements, qualifs *getproviders.ProvidersQualification, recurse, tests bool) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
// First we'll deal with the requirements directly in _our_ module...
|
||||
@ -409,6 +412,7 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
|
||||
})
|
||||
}
|
||||
reqs[fqn] = append(reqs[fqn], constraints...)
|
||||
qualifs.AddExplicitProvider(providerReqs.Type)
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,17 +422,42 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
|
||||
for _, rc := range c.Module.ManagedResources {
|
||||
fqn := rc.Provider
|
||||
if _, exists := reqs[fqn]; exists {
|
||||
// If this is called for a child module, and the provider was added from another implicit reference and not
|
||||
// from a top level required_provider, we need to collect the reference of this resource as well as implicit provider.
|
||||
qualifs.AddImplicitProvider(fqn, getproviders.ResourceRef{
|
||||
CfgRes: rc.Addr().InModule(c.Path),
|
||||
Ref: tfdiags.SourceRangeFromHCL(rc.DeclRange),
|
||||
ProviderAttribute: rc.ProviderConfigRef != nil,
|
||||
})
|
||||
// Explicit dependency already present
|
||||
continue
|
||||
}
|
||||
qualifs.AddImplicitProvider(fqn, getproviders.ResourceRef{
|
||||
CfgRes: rc.Addr().InModule(c.Path),
|
||||
Ref: tfdiags.SourceRangeFromHCL(rc.DeclRange),
|
||||
ProviderAttribute: rc.ProviderConfigRef != nil,
|
||||
})
|
||||
reqs[fqn] = nil
|
||||
}
|
||||
for _, rc := range c.Module.DataResources {
|
||||
fqn := rc.Provider
|
||||
if _, exists := reqs[fqn]; exists {
|
||||
// If this is called for a child module, and the provider was added from another implicit reference and not
|
||||
// from a top level required_provider, we need to collect the reference of this resource as well as implicit provider.
|
||||
qualifs.AddImplicitProvider(fqn, getproviders.ResourceRef{
|
||||
CfgRes: rc.Addr().InModule(c.Path),
|
||||
Ref: tfdiags.SourceRangeFromHCL(rc.DeclRange),
|
||||
ProviderAttribute: rc.ProviderConfigRef != nil,
|
||||
})
|
||||
|
||||
// Explicit dependency already present
|
||||
continue
|
||||
}
|
||||
qualifs.AddImplicitProvider(fqn, getproviders.ResourceRef{
|
||||
CfgRes: rc.Addr().InModule(c.Path),
|
||||
Ref: tfdiags.SourceRangeFromHCL(rc.DeclRange),
|
||||
ProviderAttribute: rc.ProviderConfigRef != nil,
|
||||
})
|
||||
reqs[fqn] = nil
|
||||
}
|
||||
|
||||
@ -445,6 +474,10 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
|
||||
fqn := i.Provider
|
||||
if _, exists := reqs[fqn]; !exists {
|
||||
reqs[fqn] = nil
|
||||
qualifs.AddImplicitProvider(i.Provider, getproviders.ResourceRef{
|
||||
CfgRes: i.StaticTo,
|
||||
Ref: tfdiags.SourceRangeFromHCL(i.DeclRange),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: This should probably be moved to provider_validation.go so that
|
||||
@ -522,7 +555,7 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
|
||||
// Then we'll also look for requirements in testing modules.
|
||||
for _, run := range file.Runs {
|
||||
if run.ConfigUnderTest != nil {
|
||||
moreDiags := run.ConfigUnderTest.addProviderRequirements(reqs, true, false)
|
||||
moreDiags := run.ConfigUnderTest.addProviderRequirements(reqs, qualifs, true, false)
|
||||
diags = append(diags, moreDiags...)
|
||||
}
|
||||
}
|
||||
@ -532,7 +565,7 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
|
||||
|
||||
if recurse {
|
||||
for _, childConfig := range c.Children {
|
||||
moreDiags := childConfig.addProviderRequirements(reqs, true, false)
|
||||
moreDiags := childConfig.addProviderRequirements(reqs, qualifs, true, false)
|
||||
diags = append(diags, moreDiags...)
|
||||
}
|
||||
}
|
||||
@ -772,7 +805,7 @@ func (c *Config) resolveProviderTypesForTests(providers map[string]addrs.Provide
|
||||
// versions for each provider.
|
||||
func (c *Config) ProviderTypes() []addrs.Provider {
|
||||
// Ignore diagnostics here because they relate to version constraints
|
||||
reqs, _ := c.ProviderRequirements()
|
||||
reqs, _, _ := c.ProviderRequirements()
|
||||
|
||||
ret := make([]addrs.Provider, 0, len(reqs))
|
||||
for k := range reqs {
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
@ -155,7 +156,7 @@ func TestConfigProviderRequirements(t *testing.T) {
|
||||
configuredProvider := addrs.NewDefaultProvider("configured")
|
||||
grandchildProvider := addrs.NewDefaultProvider("grandchild")
|
||||
|
||||
got, diags := cfg.ProviderRequirements()
|
||||
got, qualifs, diags := cfg.ProviderRequirements()
|
||||
assertNoDiagnostics(t, diags)
|
||||
want := getproviders.Requirements{
|
||||
// the nullProvider constraints from the two modules are merged
|
||||
@ -170,9 +171,62 @@ func TestConfigProviderRequirements(t *testing.T) {
|
||||
terraformProvider: nil,
|
||||
grandchildProvider: nil,
|
||||
}
|
||||
wantQualifs := &getproviders.ProvidersQualification{
|
||||
Implicit: map[addrs.Provider][]getproviders.ResourceRef{
|
||||
grandchildProvider: {
|
||||
{
|
||||
CfgRes: addrs.ConfigResource{Module: []string{"kinder", "nested"}, Resource: addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "grandchild_foo", Name: "bar"}},
|
||||
Ref: tfdiags.SourceRange{Filename: "testdata/provider-reqs/child/grandchild/provider-reqs-grandchild.tf", Start: tfdiags.SourcePos{Line: 3, Column: 1, Byte: 136}, End: tfdiags.SourcePos{Line: 3, Column: 32, Byte: 167}},
|
||||
},
|
||||
},
|
||||
impliedProvider: {
|
||||
{
|
||||
CfgRes: addrs.ConfigResource{Resource: addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "implied_foo", Name: "bar"}},
|
||||
Ref: tfdiags.SourceRange{Filename: "testdata/provider-reqs/provider-reqs-root.tf", Start: tfdiags.SourcePos{Line: 16, Column: 1, Byte: 317}, End: tfdiags.SourcePos{Line: 16, Column: 29, Byte: 345}},
|
||||
},
|
||||
},
|
||||
importexplicitProvider: {
|
||||
{
|
||||
CfgRes: addrs.ConfigResource{Resource: addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "importimplied", Name: "targetB"}},
|
||||
Ref: tfdiags.SourceRange{Filename: "testdata/provider-reqs/provider-reqs-root.tf", Start: tfdiags.SourcePos{Line: 42, Column: 1, Byte: 939}, End: tfdiags.SourcePos{Line: 42, Column: 7, Byte: 945}},
|
||||
},
|
||||
},
|
||||
importimpliedProvider: {
|
||||
{
|
||||
CfgRes: addrs.ConfigResource{Resource: addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "importimplied", Name: "targetA"}},
|
||||
Ref: tfdiags.SourceRange{Filename: "testdata/provider-reqs/provider-reqs-root.tf", Start: tfdiags.SourcePos{Line: 37, Column: 1, Byte: 886}, End: tfdiags.SourcePos{Line: 37, Column: 7, Byte: 892}},
|
||||
},
|
||||
},
|
||||
terraformProvider: {
|
||||
{
|
||||
CfgRes: addrs.ConfigResource{Resource: addrs.Resource{Mode: addrs.DataResourceMode, Type: "terraform_remote_state", Name: "bar"}},
|
||||
Ref: tfdiags.SourceRange{Filename: "testdata/provider-reqs/provider-reqs-root.tf", Start: tfdiags.SourcePos{Line: 27, Column: 1, Byte: 628}, End: tfdiags.SourcePos{Line: 27, Column: 36, Byte: 663}},
|
||||
},
|
||||
},
|
||||
},
|
||||
Explicit: map[addrs.Provider]struct{}{
|
||||
happycloudProvider: {},
|
||||
nullProvider: {},
|
||||
randomProvider: {},
|
||||
tlsProvider: {},
|
||||
},
|
||||
}
|
||||
// These 2 assertions are strictly to ensure that later the "provider" blocks are not registered into the qualifications.
|
||||
// Technically speaking, provider blocks are indeed implicit references, but the current warning message
|
||||
// on implicitly referenced providers could be misleading for the "provider" blocks.
|
||||
if _, okExpl := qualifs.Explicit[configuredProvider]; okExpl {
|
||||
t.Errorf("provider blocks shouldn't be added into the explicit qualifications")
|
||||
}
|
||||
if _, okImpl := qualifs.Implicit[configuredProvider]; okImpl {
|
||||
t.Errorf("provider blocks shouldn't be added into the implicit qualifications")
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
t.Errorf("wrong reqs result\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantQualifs, qualifs); diff != "" {
|
||||
t.Errorf("wrong qualifs result\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +249,7 @@ func TestConfigProviderRequirementsInclTests(t *testing.T) {
|
||||
terraformProvider := addrs.NewBuiltInProvider("terraform")
|
||||
configuredProvider := addrs.NewDefaultProvider("configured")
|
||||
|
||||
got, diags := cfg.ProviderRequirements()
|
||||
got, qualifs, diags := cfg.ProviderRequirements()
|
||||
assertNoDiagnostics(t, diags)
|
||||
want := getproviders.Requirements{
|
||||
// the nullProvider constraints from the two modules are merged
|
||||
@ -207,9 +261,35 @@ func TestConfigProviderRequirementsInclTests(t *testing.T) {
|
||||
terraformProvider: nil,
|
||||
}
|
||||
|
||||
wantQualifs := &getproviders.ProvidersQualification{
|
||||
Implicit: map[addrs.Provider][]getproviders.ResourceRef{
|
||||
impliedProvider: {
|
||||
{
|
||||
CfgRes: addrs.ConfigResource{Resource: addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "implied_foo", Name: "bar"}},
|
||||
Ref: tfdiags.SourceRange{Filename: "testdata/provider-reqs-with-tests/provider-reqs-root.tf", Start: tfdiags.SourcePos{Line: 12, Column: 1, Byte: 247}, End: tfdiags.SourcePos{Line: 12, Column: 29, Byte: 275}},
|
||||
},
|
||||
},
|
||||
terraformProvider: {
|
||||
{
|
||||
CfgRes: addrs.ConfigResource{Resource: addrs.Resource{Mode: addrs.DataResourceMode, Type: "terraform_remote_state", Name: "bar"}},
|
||||
Ref: tfdiags.SourceRange{Filename: "testdata/provider-reqs-with-tests/provider-reqs-root.tf", Start: tfdiags.SourcePos{Line: 19, Column: 1, Byte: 516}, End: tfdiags.SourcePos{Line: 19, Column: 36, Byte: 551}},
|
||||
},
|
||||
},
|
||||
},
|
||||
Explicit: map[addrs.Provider]struct{}{
|
||||
nullProvider: {},
|
||||
randomProvider: {},
|
||||
tlsProvider: {},
|
||||
},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantQualifs, qualifs); diff != "" {
|
||||
t.Errorf("wrong qualifs result\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigProviderRequirementsDuplicate(t *testing.T) {
|
||||
@ -584,7 +664,8 @@ func TestConfigAddProviderRequirements(t *testing.T) {
|
||||
reqs := getproviders.Requirements{
|
||||
addrs.NewDefaultProvider("null"): nil,
|
||||
}
|
||||
diags = cfg.addProviderRequirements(reqs, true, false)
|
||||
qualifs := new(getproviders.ProvidersQualification)
|
||||
diags = cfg.addProviderRequirements(reqs, qualifs, true, false)
|
||||
assertNoDiagnostics(t, diags)
|
||||
}
|
||||
|
||||
@ -609,8 +690,9 @@ Use the providers argument within the module block to configure providers for al
|
||||
func TestConfigImportProviderClashesWithResources(t *testing.T) {
|
||||
cfg, diags := testModuleConfigFromFile("testdata/invalid-import-files/import-and-resource-clash.tf")
|
||||
assertNoDiagnostics(t, diags)
|
||||
qualifs := new(getproviders.ProvidersQualification)
|
||||
|
||||
diags = cfg.addProviderRequirements(getproviders.Requirements{}, true, false)
|
||||
diags = cfg.addProviderRequirements(getproviders.Requirements{}, qualifs, true, false)
|
||||
assertExactDiagnostics(t, diags, []string{
|
||||
`testdata/invalid-import-files/import-and-resource-clash.tf:9,3-19: Invalid import provider argument; The provider argument in the target resource block must match the import block.`,
|
||||
})
|
||||
@ -620,7 +702,8 @@ func TestConfigImportProviderWithNoResourceProvider(t *testing.T) {
|
||||
cfg, diags := testModuleConfigFromFile("testdata/invalid-import-files/import-and-no-resource.tf")
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
diags = cfg.addProviderRequirements(getproviders.Requirements{}, true, false)
|
||||
qualifs := new(getproviders.ProvidersQualification)
|
||||
diags = cfg.addProviderRequirements(getproviders.Requirements{}, qualifs, true, false)
|
||||
assertExactDiagnostics(t, diags, []string{
|
||||
`testdata/invalid-import-files/import-and-no-resource.tf:5,3-19: Invalid import provider argument; The provider argument in the target resource block must be specified and match the import block.`,
|
||||
})
|
||||
|
@ -13,8 +13,8 @@ import (
|
||||
|
||||
"github.com/apparentlymart/go-versions/versions"
|
||||
"github.com/apparentlymart/go-versions/versions/constraints"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
)
|
||||
|
||||
// Version represents a particular single version of a provider.
|
||||
@ -50,6 +50,48 @@ type Warnings = []string
|
||||
// altogether, which means that it is not required at all.
|
||||
type Requirements map[addrs.Provider]VersionConstraints
|
||||
|
||||
// ProvidersQualification is storing the implicit/explicit reference qualification of the providers.
|
||||
// This is necessary to be able to warn the user when the resources are referencing a provider that
|
||||
// is not specifically defined in a required_providers block. When the implicitly referenced
|
||||
// provider is tried to be downloaded without a specific provider requirement, it will be tried
|
||||
// from the default namespace (hashicorp), failing to download it when it does not exist in the default namespace.
|
||||
// Therefore, we want to let the user know what resources are generating this situation.
|
||||
type ProvidersQualification struct {
|
||||
Implicit map[addrs.Provider][]ResourceRef
|
||||
Explicit map[addrs.Provider]struct{}
|
||||
}
|
||||
|
||||
type ResourceRef struct {
|
||||
CfgRes addrs.ConfigResource
|
||||
Ref tfdiags.SourceRange
|
||||
ProviderAttribute bool
|
||||
}
|
||||
|
||||
// AddImplicitProvider saves an addrs.Provider with the place in the configuration where this is generated from.
|
||||
func (pq *ProvidersQualification) AddImplicitProvider(provider addrs.Provider, ref ResourceRef) {
|
||||
if pq.Implicit == nil {
|
||||
pq.Implicit = map[addrs.Provider][]ResourceRef{}
|
||||
}
|
||||
// This is avoiding adding the implicit reference of the provider if this is already explicitly configured.
|
||||
// Done this way, because when collecting these qualifications, if there are at least 2 resources (A from root module and B from an imported module),
|
||||
// root module could have no explicit definition but the module of B could have an explicit one. But in case none of the modules is having
|
||||
// an explicit definition, we want to gather all the resources that are implicitly referencing a provider.
|
||||
if _, ok := pq.Explicit[provider]; ok {
|
||||
return
|
||||
}
|
||||
refs := pq.Implicit[provider]
|
||||
refs = append(refs, ref)
|
||||
pq.Implicit[provider] = refs
|
||||
}
|
||||
|
||||
// AddExplicitProvider saves an addrs.Provider that is specifically configured in a required_providers block.
|
||||
func (pq *ProvidersQualification) AddExplicitProvider(provider addrs.Provider) {
|
||||
if pq.Explicit == nil {
|
||||
pq.Explicit = map[addrs.Provider]struct{}{}
|
||||
}
|
||||
pq.Explicit[provider] = struct{}{}
|
||||
}
|
||||
|
||||
// Merge takes the requirements in the receiver and the requirements in the
|
||||
// other given value and produces a new set of requirements that combines
|
||||
// all of the requirements of both.
|
||||
|
@ -358,7 +358,7 @@ func (c *Context) checkConfigDependencies(config *configs.Config) tfdiags.Diagno
|
||||
// We only check that we have a factory for each required provider, and
|
||||
// assume the caller already assured that any separately-installed
|
||||
// plugins are of a suitable version, match expected checksums, etc.
|
||||
providerReqs, hclDiags := config.ProviderRequirements()
|
||||
providerReqs, _, hclDiags := config.ProviderRequirements()
|
||||
diags = diags.Append(hclDiags)
|
||||
if hclDiags.HasErrors() {
|
||||
return diags
|
||||
|
@ -56,7 +56,7 @@ func MigrateStateProviderAddresses(config *configs.Config, state *states.State)
|
||||
// config could be nil when we're e.g. showing a statefile without the configuration present
|
||||
if config != nil {
|
||||
var hclDiags hcl.Diagnostics
|
||||
providers, hclDiags = config.ProviderRequirements()
|
||||
providers, _, hclDiags = config.ProviderRequirements()
|
||||
diags = diags.Append(hclDiags)
|
||||
if hclDiags.HasErrors() {
|
||||
return nil, diags
|
||||
|
Loading…
Reference in New Issue
Block a user