mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
configs: extend module.ProviderRequirements to include the addrs.Provider instead of just version constraints. (#23843)
Renamed file.ProviderRequirements to file.RequiredProviders to match the name of the block in the configuration. file.RequiredProviders contains the contents of the file(s); module.ProviderRequirements contains the parsed and merged provider requirements. Extended decodeRequiredProvidersBlock to parse the new provider source syntax (version only, it will ignore any other attributes). Added some tests; swapped deep.Equal with cmp.Equal in the terraform/module_dependencies_test.go because deep was not catching incorrect constraints.
This commit is contained in:
parent
0ca6f743a4
commit
272cb44d3d
@ -30,7 +30,7 @@ type Module struct {
|
|||||||
|
|
||||||
Backend *Backend
|
Backend *Backend
|
||||||
ProviderConfigs map[string]*Provider
|
ProviderConfigs map[string]*Provider
|
||||||
ProviderRequirements map[string][]VersionConstraint
|
ProviderRequirements map[string]ProviderRequirements
|
||||||
|
|
||||||
Variables map[string]*Variable
|
Variables map[string]*Variable
|
||||||
Locals map[string]*Local
|
Locals map[string]*Local
|
||||||
@ -58,9 +58,9 @@ type File struct {
|
|||||||
|
|
||||||
ActiveExperiments experiments.Set
|
ActiveExperiments experiments.Set
|
||||||
|
|
||||||
Backends []*Backend
|
Backends []*Backend
|
||||||
ProviderConfigs []*Provider
|
ProviderConfigs []*Provider
|
||||||
ProviderRequirements []*ProviderRequirement
|
RequiredProviders []*RequiredProvider
|
||||||
|
|
||||||
Variables []*Variable
|
Variables []*Variable
|
||||||
Locals []*Local
|
Locals []*Local
|
||||||
@ -84,7 +84,7 @@ func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
|
|||||||
var diags hcl.Diagnostics
|
var diags hcl.Diagnostics
|
||||||
mod := &Module{
|
mod := &Module{
|
||||||
ProviderConfigs: map[string]*Provider{},
|
ProviderConfigs: map[string]*Provider{},
|
||||||
ProviderRequirements: map[string][]VersionConstraint{},
|
ProviderRequirements: map[string]ProviderRequirements{},
|
||||||
Variables: map[string]*Variable{},
|
Variables: map[string]*Variable{},
|
||||||
Locals: map[string]*Local{},
|
Locals: map[string]*Local{},
|
||||||
Outputs: map[string]*Output{},
|
Outputs: map[string]*Output{},
|
||||||
@ -103,8 +103,7 @@ func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
|
|||||||
diags = append(diags, fileDiags...)
|
diags = append(diags, fileDiags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
moreDiags := checkModuleExperiments(mod)
|
diags = append(diags, checkModuleExperiments(mod)...)
|
||||||
diags = append(diags, moreDiags...)
|
|
||||||
|
|
||||||
return mod, diags
|
return mod, diags
|
||||||
}
|
}
|
||||||
@ -170,8 +169,22 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
|
|||||||
m.ProviderConfigs[key] = pc
|
m.ProviderConfigs[key] = pc
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, reqd := range file.ProviderRequirements {
|
for _, reqd := range file.RequiredProviders {
|
||||||
m.ProviderRequirements[reqd.Name] = append(m.ProviderRequirements[reqd.Name], reqd.Requirement)
|
// TODO: once the remaining provider source functionality is
|
||||||
|
// implemented, get addrs.Provider from source if set, or
|
||||||
|
// addrs.NewDefaultProvider(name) if not
|
||||||
|
if reqd.Source != "" {
|
||||||
|
panic("source is not yet supported")
|
||||||
|
}
|
||||||
|
fqn := addrs.NewLegacyProvider(reqd.Name)
|
||||||
|
if existing, exists := m.ProviderRequirements[reqd.Name]; exists {
|
||||||
|
if existing.Type != fqn {
|
||||||
|
panic("provider fqn mismatch")
|
||||||
|
}
|
||||||
|
existing.VersionConstraints = append(existing.VersionConstraints, reqd.Requirement)
|
||||||
|
} else {
|
||||||
|
m.ProviderRequirements[reqd.Name] = ProviderRequirements{Type: fqn, VersionConstraints: []VersionConstraint{reqd.Requirement}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range file.Variables {
|
for _, v := range file.Variables {
|
||||||
@ -314,8 +327,8 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(file.ProviderRequirements) != 0 {
|
if len(file.RequiredProviders) != 0 {
|
||||||
mergeProviderVersionConstraints(m.ProviderRequirements, file.ProviderRequirements)
|
mergeProviderVersionConstraints(m.ProviderRequirements, file.RequiredProviders)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range file.Variables {
|
for _, v := range file.Variables {
|
||||||
|
@ -35,7 +35,7 @@ func (p *Provider) merge(op *Provider) hcl.Diagnostics {
|
|||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeProviderVersionConstraints(recv map[string][]VersionConstraint, ovrd []*ProviderRequirement) {
|
func mergeProviderVersionConstraints(recv map[string]ProviderRequirements, ovrd []*RequiredProvider) {
|
||||||
// Any provider name that's mentioned in the override gets nilled out in
|
// Any provider name that's mentioned in the override gets nilled out in
|
||||||
// our map so that we'll rebuild it below. Any provider not mentioned is
|
// our map so that we'll rebuild it below. Any provider not mentioned is
|
||||||
// left unchanged.
|
// left unchanged.
|
||||||
@ -43,7 +43,8 @@ func mergeProviderVersionConstraints(recv map[string][]VersionConstraint, ovrd [
|
|||||||
delete(recv, reqd.Name)
|
delete(recv, reqd.Name)
|
||||||
}
|
}
|
||||||
for _, reqd := range ovrd {
|
for _, reqd := range ovrd {
|
||||||
recv[reqd.Name] = append(recv[reqd.Name], reqd.Requirement)
|
fqn := addrs.NewLegacyProvider(reqd.Name)
|
||||||
|
recv[reqd.Name] = ProviderRequirements{Type: fqn, VersionConstraints: []VersionConstraint{reqd.Requirement}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,10 @@ package configs
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/gohcl"
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -199,3 +201,77 @@ func TestModuleOverrideDynamic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMergeProviderVersionConstraints(t *testing.T) {
|
||||||
|
v1, _ := version.NewConstraint("1.0.0")
|
||||||
|
vc1 := VersionConstraint{
|
||||||
|
Required: v1,
|
||||||
|
}
|
||||||
|
v2, _ := version.NewConstraint("2.0.0")
|
||||||
|
vc2 := VersionConstraint{
|
||||||
|
Required: v2,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
Input map[string]ProviderRequirements
|
||||||
|
Override []*RequiredProvider
|
||||||
|
Want map[string]ProviderRequirements
|
||||||
|
}{
|
||||||
|
"basic merge": {
|
||||||
|
map[string]ProviderRequirements{
|
||||||
|
"random": ProviderRequirements{
|
||||||
|
Type: addrs.Provider{Type: "random"},
|
||||||
|
VersionConstraints: []VersionConstraint{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*RequiredProvider{
|
||||||
|
&RequiredProvider{
|
||||||
|
Name: "null",
|
||||||
|
Requirement: VersionConstraint{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]ProviderRequirements{
|
||||||
|
"random": ProviderRequirements{
|
||||||
|
Type: addrs.Provider{Type: "random"},
|
||||||
|
VersionConstraints: []VersionConstraint{},
|
||||||
|
},
|
||||||
|
"null": ProviderRequirements{
|
||||||
|
Type: addrs.NewLegacyProvider("null"),
|
||||||
|
VersionConstraints: []VersionConstraint{
|
||||||
|
VersionConstraint{
|
||||||
|
Required: version.Constraints(nil),
|
||||||
|
DeclRange: hcl.Range{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"override version constraint": {
|
||||||
|
map[string]ProviderRequirements{
|
||||||
|
"random": ProviderRequirements{
|
||||||
|
Type: addrs.Provider{Type: "random"},
|
||||||
|
VersionConstraints: []VersionConstraint{vc1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*RequiredProvider{
|
||||||
|
&RequiredProvider{
|
||||||
|
Name: "random",
|
||||||
|
Requirement: vc2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]ProviderRequirements{
|
||||||
|
"random": ProviderRequirements{
|
||||||
|
Type: addrs.NewLegacyProvider("random"),
|
||||||
|
VersionConstraints: []VersionConstraint{vc2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
mergeProviderVersionConstraints(test.Input, test.Override)
|
||||||
|
assertResultDeepEqual(t, test.Input, test.Want)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -75,7 +75,7 @@ func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnost
|
|||||||
case "required_providers":
|
case "required_providers":
|
||||||
reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock)
|
reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock)
|
||||||
diags = append(diags, reqsDiags...)
|
diags = append(diags, reqsDiags...)
|
||||||
file.ProviderRequirements = append(file.ProviderRequirements, reqs...)
|
file.RequiredProviders = append(file.RequiredProviders, reqs...)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Should never happen because the above cases should be exhaustive
|
// Should never happen because the above cases should be exhaustive
|
||||||
|
@ -3,22 +3,31 @@ package configs
|
|||||||
import (
|
import (
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProviderRequirement represents a declaration of a dependency on a particular
|
// RequiredProvider represents a declaration of a dependency on a particular
|
||||||
// provider version without actually configuring that provider. This is used in
|
// provider version without actually configuring that provider. This is used in
|
||||||
// child modules that expect a provider to be passed in from their parent.
|
// child modules that expect a provider to be passed in from their parent.
|
||||||
//
|
//
|
||||||
// TODO: "Source" is a placeholder for an attribute that is not yet supported.
|
// TODO: "Source" is a placeholder for an attribute that is not yet supported.
|
||||||
type ProviderRequirement struct {
|
type RequiredProvider struct {
|
||||||
Name string
|
Name string
|
||||||
Source string // TODO
|
Source string // TODO
|
||||||
Requirement VersionConstraint
|
Requirement VersionConstraint
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeRequiredProvidersBlock(block *hcl.Block) ([]*ProviderRequirement, hcl.Diagnostics) {
|
// ProviderRequirements represents merged provider version constraints.
|
||||||
|
// VersionConstraints come from terraform.require_providers blocks and provider
|
||||||
|
// blocks.
|
||||||
|
type ProviderRequirements struct {
|
||||||
|
Type addrs.Provider
|
||||||
|
VersionConstraints []VersionConstraint
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeRequiredProvidersBlock(block *hcl.Block) ([]*RequiredProvider, hcl.Diagnostics) {
|
||||||
attrs, diags := block.Body.JustAttributes()
|
attrs, diags := block.Body.JustAttributes()
|
||||||
var reqs []*ProviderRequirement
|
var reqs []*RequiredProvider
|
||||||
for name, attr := range attrs {
|
for name, attr := range attrs {
|
||||||
expr, err := attr.Expr.Value(nil)
|
expr, err := attr.Expr.Value(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -29,7 +38,7 @@ func decodeRequiredProvidersBlock(block *hcl.Block) ([]*ProviderRequirement, hcl
|
|||||||
case expr.Type().IsPrimitiveType():
|
case expr.Type().IsPrimitiveType():
|
||||||
vc, reqDiags := decodeVersionConstraint(attr)
|
vc, reqDiags := decodeVersionConstraint(attr)
|
||||||
diags = append(diags, reqDiags...)
|
diags = append(diags, reqDiags...)
|
||||||
reqs = append(reqs, &ProviderRequirement{
|
reqs = append(reqs, &RequiredProvider{
|
||||||
Name: name,
|
Name: name,
|
||||||
Requirement: vc,
|
Requirement: vc,
|
||||||
})
|
})
|
||||||
@ -49,14 +58,14 @@ func decodeRequiredProvidersBlock(block *hcl.Block) ([]*ProviderRequirement, hcl
|
|||||||
Detail: "This string does not use correct version constraint syntax.",
|
Detail: "This string does not use correct version constraint syntax.",
|
||||||
Subject: attr.Expr.Range().Ptr(),
|
Subject: attr.Expr.Range().Ptr(),
|
||||||
})
|
})
|
||||||
reqs = append(reqs, &ProviderRequirement{Name: name})
|
reqs = append(reqs, &RequiredProvider{Name: name})
|
||||||
return reqs, diags
|
return reqs, diags
|
||||||
}
|
}
|
||||||
vc.Required = constraints
|
vc.Required = constraints
|
||||||
reqs = append(reqs, &ProviderRequirement{Name: name, Requirement: vc})
|
reqs = append(reqs, &RequiredProvider{Name: name, Requirement: vc})
|
||||||
}
|
}
|
||||||
// No version
|
// No version
|
||||||
reqs = append(reqs, &ProviderRequirement{Name: name})
|
reqs = append(reqs, &RequiredProvider{Name: name})
|
||||||
default:
|
default:
|
||||||
// should not happen
|
// should not happen
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
@ -65,7 +74,7 @@ func decodeRequiredProvidersBlock(block *hcl.Block) ([]*ProviderRequirement, hcl
|
|||||||
Detail: "provider_requirements entries must be strings or objects.",
|
Detail: "provider_requirements entries must be strings or objects.",
|
||||||
Subject: attr.Expr.Range().Ptr(),
|
Subject: attr.Expr.Range().Ptr(),
|
||||||
})
|
})
|
||||||
reqs = append(reqs, &ProviderRequirement{Name: name})
|
reqs = append(reqs, &RequiredProvider{Name: name})
|
||||||
return reqs, diags
|
return reqs, diags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,9 +58,8 @@ func configTreeConfigDependencies(root *configs.Config, inheritProviders map[str
|
|||||||
// The main way to declare a provider dependency is explicitly inside
|
// The main way to declare a provider dependency is explicitly inside
|
||||||
// the "terraform" block, which allows declaring a requirement without
|
// the "terraform" block, which allows declaring a requirement without
|
||||||
// also creating a configuration.
|
// also creating a configuration.
|
||||||
for fullName, constraints := range module.ProviderRequirements {
|
for fullName, req := range module.ProviderRequirements {
|
||||||
inst := moduledeps.ProviderInstance(fullName)
|
inst := moduledeps.ProviderInstance(fullName)
|
||||||
|
|
||||||
// The handling here is a bit fiddly because the moduledeps package
|
// The handling here is a bit fiddly because the moduledeps package
|
||||||
// was designed around the legacy (pre-0.12) configuration model
|
// was designed around the legacy (pre-0.12) configuration model
|
||||||
// and hasn't yet been revised to handle the new model. As a result,
|
// and hasn't yet been revised to handle the new model. As a result,
|
||||||
@ -69,7 +68,7 @@ func configTreeConfigDependencies(root *configs.Config, inheritProviders map[str
|
|||||||
// can also retain the source location of each constraint, for
|
// can also retain the source location of each constraint, for
|
||||||
// more informative output from the "terraform providers" command.
|
// more informative output from the "terraform providers" command.
|
||||||
var rawConstraints version.Constraints
|
var rawConstraints version.Constraints
|
||||||
for _, constraint := range constraints {
|
for _, constraint := range req.VersionConstraints {
|
||||||
rawConstraints = append(rawConstraints, constraint.Required...)
|
rawConstraints = append(rawConstraints, constraint.Required...)
|
||||||
}
|
}
|
||||||
discoConstraints := discovery.NewConstraints(rawConstraints)
|
discoConstraints := discovery.NewConstraints(rawConstraints)
|
||||||
|
@ -3,7 +3,7 @@ package terraform
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/moduledeps"
|
"github.com/hashicorp/terraform/moduledeps"
|
||||||
@ -52,6 +52,20 @@ func TestModuleTreeDependencies(t *testing.T) {
|
|||||||
Children: nil,
|
Children: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"required_providers block": {
|
||||||
|
"module-deps-required-providers",
|
||||||
|
nil,
|
||||||
|
&moduledeps.Module{
|
||||||
|
Name: "root",
|
||||||
|
Providers: moduledeps.Providers{
|
||||||
|
"foo": moduledeps.ProviderDependency{
|
||||||
|
Constraints: discovery.ConstraintStr(">=1.0.0").MustParse(),
|
||||||
|
Reason: moduledeps.ProviderDependencyExplicit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Children: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
"explicit provider unconstrained": {
|
"explicit provider unconstrained": {
|
||||||
"module-deps-explicit-provider-unconstrained",
|
"module-deps-explicit-provider-unconstrained",
|
||||||
nil,
|
nil,
|
||||||
@ -251,8 +265,8 @@ func TestModuleTreeDependencies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
got := ConfigTreeDependencies(root, MustShimLegacyState(test.State))
|
got := ConfigTreeDependencies(root, MustShimLegacyState(test.State))
|
||||||
for _, problem := range deep.Equal(got, test.Want) {
|
if !cmp.Equal(got, test.Want) {
|
||||||
t.Error(problem)
|
t.Error(cmp.Diff(got, test.Want))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
7
terraform/testdata/module-deps-required-providers/main.tf
vendored
Normal file
7
terraform/testdata/module-deps-required-providers/main.tf
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
foo = {
|
||||||
|
version = ">=1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user