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:
Kristin Laemmert 2020-01-13 11:31:47 -05:00 committed by GitHub
parent 0ca6f743a4
commit 272cb44d3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 148 additions and 29 deletions

View File

@ -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 {

View File

@ -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}}
}
}

View File

@ -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)
})
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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))
}
})
}

View File

@ -0,0 +1,7 @@
terraform {
required_providers {
foo = {
version = ">=1.0.0"
}
}
}