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
|
||||
ProviderConfigs map[string]*Provider
|
||||
ProviderRequirements map[string][]VersionConstraint
|
||||
ProviderRequirements map[string]ProviderRequirements
|
||||
|
||||
Variables map[string]*Variable
|
||||
Locals map[string]*Local
|
||||
|
@ -58,9 +58,9 @@ type File struct {
|
|||
|
||||
ActiveExperiments experiments.Set
|
||||
|
||||
Backends []*Backend
|
||||
ProviderConfigs []*Provider
|
||||
ProviderRequirements []*ProviderRequirement
|
||||
Backends []*Backend
|
||||
ProviderConfigs []*Provider
|
||||
RequiredProviders []*RequiredProvider
|
||||
|
||||
Variables []*Variable
|
||||
Locals []*Local
|
||||
|
@ -84,7 +84,7 @@ func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
|
|||
var diags hcl.Diagnostics
|
||||
mod := &Module{
|
||||
ProviderConfigs: map[string]*Provider{},
|
||||
ProviderRequirements: map[string][]VersionConstraint{},
|
||||
ProviderRequirements: map[string]ProviderRequirements{},
|
||||
Variables: map[string]*Variable{},
|
||||
Locals: map[string]*Local{},
|
||||
Outputs: map[string]*Output{},
|
||||
|
@ -103,8 +103,7 @@ func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
|
|||
diags = append(diags, fileDiags...)
|
||||
}
|
||||
|
||||
moreDiags := checkModuleExperiments(mod)
|
||||
diags = append(diags, moreDiags...)
|
||||
diags = append(diags, checkModuleExperiments(mod)...)
|
||||
|
||||
return mod, diags
|
||||
}
|
||||
|
@ -170,8 +169,22 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
|
|||
m.ProviderConfigs[key] = pc
|
||||
}
|
||||
|
||||
for _, reqd := range file.ProviderRequirements {
|
||||
m.ProviderRequirements[reqd.Name] = append(m.ProviderRequirements[reqd.Name], reqd.Requirement)
|
||||
for _, reqd := range file.RequiredProviders {
|
||||
// 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 {
|
||||
|
@ -314,8 +327,8 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
|
|||
}
|
||||
}
|
||||
|
||||
if len(file.ProviderRequirements) != 0 {
|
||||
mergeProviderVersionConstraints(m.ProviderRequirements, file.ProviderRequirements)
|
||||
if len(file.RequiredProviders) != 0 {
|
||||
mergeProviderVersionConstraints(m.ProviderRequirements, file.RequiredProviders)
|
||||
}
|
||||
|
||||
for _, v := range file.Variables {
|
||||
|
|
|
@ -35,7 +35,7 @@ func (p *Provider) merge(op *Provider) hcl.Diagnostics {
|
|||
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
|
||||
// our map so that we'll rebuild it below. Any provider not mentioned is
|
||||
// left unchanged.
|
||||
|
@ -43,7 +43,8 @@ func mergeProviderVersionConstraints(recv map[string][]VersionConstraint, ovrd [
|
|||
delete(recv, reqd.Name)
|
||||
}
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"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":
|
||||
reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock)
|
||||
diags = append(diags, reqsDiags...)
|
||||
file.ProviderRequirements = append(file.ProviderRequirements, reqs...)
|
||||
file.RequiredProviders = append(file.RequiredProviders, reqs...)
|
||||
|
||||
default:
|
||||
// Should never happen because the above cases should be exhaustive
|
||||
|
|
|
@ -3,22 +3,31 @@ package configs
|
|||
import (
|
||||
version "github.com/hashicorp/go-version"
|
||||
"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
|
||||
// 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.
|
||||
type ProviderRequirement struct {
|
||||
type RequiredProvider struct {
|
||||
Name string
|
||||
Source string // TODO
|
||||
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()
|
||||
var reqs []*ProviderRequirement
|
||||
var reqs []*RequiredProvider
|
||||
for name, attr := range attrs {
|
||||
expr, err := attr.Expr.Value(nil)
|
||||
if err != nil {
|
||||
|
@ -29,7 +38,7 @@ func decodeRequiredProvidersBlock(block *hcl.Block) ([]*ProviderRequirement, hcl
|
|||
case expr.Type().IsPrimitiveType():
|
||||
vc, reqDiags := decodeVersionConstraint(attr)
|
||||
diags = append(diags, reqDiags...)
|
||||
reqs = append(reqs, &ProviderRequirement{
|
||||
reqs = append(reqs, &RequiredProvider{
|
||||
Name: name,
|
||||
Requirement: vc,
|
||||
})
|
||||
|
@ -49,14 +58,14 @@ func decodeRequiredProvidersBlock(block *hcl.Block) ([]*ProviderRequirement, hcl
|
|||
Detail: "This string does not use correct version constraint syntax.",
|
||||
Subject: attr.Expr.Range().Ptr(),
|
||||
})
|
||||
reqs = append(reqs, &ProviderRequirement{Name: name})
|
||||
reqs = append(reqs, &RequiredProvider{Name: name})
|
||||
return reqs, diags
|
||||
}
|
||||
vc.Required = constraints
|
||||
reqs = append(reqs, &ProviderRequirement{Name: name, Requirement: vc})
|
||||
reqs = append(reqs, &RequiredProvider{Name: name, Requirement: vc})
|
||||
}
|
||||
// No version
|
||||
reqs = append(reqs, &ProviderRequirement{Name: name})
|
||||
reqs = append(reqs, &RequiredProvider{Name: name})
|
||||
default:
|
||||
// should not happen
|
||||
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.",
|
||||
Subject: attr.Expr.Range().Ptr(),
|
||||
})
|
||||
reqs = append(reqs, &ProviderRequirement{Name: name})
|
||||
reqs = append(reqs, &RequiredProvider{Name: name})
|
||||
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 "terraform" block, which allows declaring a requirement without
|
||||
// also creating a configuration.
|
||||
for fullName, constraints := range module.ProviderRequirements {
|
||||
for fullName, req := range module.ProviderRequirements {
|
||||
inst := moduledeps.ProviderInstance(fullName)
|
||||
|
||||
// The handling here is a bit fiddly because the moduledeps package
|
||||
// was designed around the legacy (pre-0.12) configuration model
|
||||
// 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
|
||||
// more informative output from the "terraform providers" command.
|
||||
var rawConstraints version.Constraints
|
||||
for _, constraint := range constraints {
|
||||
for _, constraint := range req.VersionConstraints {
|
||||
rawConstraints = append(rawConstraints, constraint.Required...)
|
||||
}
|
||||
discoConstraints := discovery.NewConstraints(rawConstraints)
|
||||
|
|
|
@ -3,7 +3,7 @@ package terraform
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/moduledeps"
|
||||
|
@ -52,6 +52,20 @@ func TestModuleTreeDependencies(t *testing.T) {
|
|||
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": {
|
||||
"module-deps-explicit-provider-unconstrained",
|
||||
nil,
|
||||
|
@ -251,8 +265,8 @@ func TestModuleTreeDependencies(t *testing.T) {
|
|||
}
|
||||
|
||||
got := ConfigTreeDependencies(root, MustShimLegacyState(test.State))
|
||||
for _, problem := range deep.Equal(got, test.Want) {
|
||||
t.Error(problem)
|
||||
if !cmp.Equal(got, test.Want) {
|
||||
t.Error(cmp.Diff(got, test.Want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
foo = {
|
||||
version = ">=1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue