configs: added map to configs.Module for provider local name lookup (#24039)
* configs: added map of ProviderLocalNames to configs.Module We will need to lookup any user-supplied local names for a given FQN. This PR adds a map of ProviderLocalNames to the Module, along with adding tests for this and for decodeRequiredProvidersBlock. This also introduces the appearance of support for a required_provider "source" attribute, but ignores any user-supplied source and instead continues to assume that addrs.NewLegacyProvider is the way to go.
This commit is contained in:
parent
7e76ce33a9
commit
851e6dcdbb
|
@ -31,6 +31,7 @@ type Module struct {
|
||||||
Backend *Backend
|
Backend *Backend
|
||||||
ProviderConfigs map[string]*Provider
|
ProviderConfigs map[string]*Provider
|
||||||
ProviderRequirements map[string]ProviderRequirements
|
ProviderRequirements map[string]ProviderRequirements
|
||||||
|
ProviderLocalNames map[addrs.Provider]string
|
||||||
|
|
||||||
Variables map[string]*Variable
|
Variables map[string]*Variable
|
||||||
Locals map[string]*Local
|
Locals map[string]*Local
|
||||||
|
@ -85,6 +86,7 @@ func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
|
||||||
mod := &Module{
|
mod := &Module{
|
||||||
ProviderConfigs: map[string]*Provider{},
|
ProviderConfigs: map[string]*Provider{},
|
||||||
ProviderRequirements: map[string]ProviderRequirements{},
|
ProviderRequirements: map[string]ProviderRequirements{},
|
||||||
|
ProviderLocalNames: map[addrs.Provider]string{},
|
||||||
Variables: map[string]*Variable{},
|
Variables: map[string]*Variable{},
|
||||||
Locals: map[string]*Local{},
|
Locals: map[string]*Local{},
|
||||||
Outputs: map[string]*Output{},
|
Outputs: map[string]*Output{},
|
||||||
|
@ -105,6 +107,9 @@ func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
|
||||||
|
|
||||||
diags = append(diags, checkModuleExperiments(mod)...)
|
diags = append(diags, checkModuleExperiments(mod)...)
|
||||||
|
|
||||||
|
// Generate the FQN -> LocalProviderName map
|
||||||
|
mod.gatherProviderLocalNames()
|
||||||
|
|
||||||
return mod, diags
|
return mod, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,11 +175,14 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, reqd := range file.RequiredProviders {
|
for _, reqd := range file.RequiredProviders {
|
||||||
// TODO: once the remaining provider source functionality is
|
// As an interim *testing* step, we will accept a source argument
|
||||||
// implemented, get addrs.Provider from source if set, or
|
// but assume that the source is a legacy provider. This allows us to
|
||||||
// addrs.NewDefaultProvider(name) if not
|
// exercise the provider local names -> fqn logic without changing
|
||||||
|
// terraform's behavior.
|
||||||
if reqd.Source != "" {
|
if reqd.Source != "" {
|
||||||
panic("source is not yet supported")
|
// Fixme: once the rest of the provider source logic is implemented,
|
||||||
|
// update this to get the addrs.Provider by using
|
||||||
|
// addrs.ParseProviderSourceString()
|
||||||
}
|
}
|
||||||
fqn := addrs.NewLegacyProvider(reqd.Name)
|
fqn := addrs.NewLegacyProvider(reqd.Name)
|
||||||
if existing, exists := m.ProviderRequirements[reqd.Name]; exists {
|
if existing, exists := m.ProviderRequirements[reqd.Name]; exists {
|
||||||
|
@ -425,3 +433,27 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
|
||||||
|
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gatherProviderLocalNames is a helper function that populatesA a map of
|
||||||
|
// provider FQNs -> provider local names. This information is useful for
|
||||||
|
// user-facing output, which should include both the FQN and LocalName. It must
|
||||||
|
// only be populated after the module has been parsed.
|
||||||
|
func (m *Module) gatherProviderLocalNames() {
|
||||||
|
providers := make(map[addrs.Provider]string)
|
||||||
|
for k, v := range m.ProviderRequirements {
|
||||||
|
providers[v.Type] = k
|
||||||
|
}
|
||||||
|
m.ProviderLocalNames = providers
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalNameForProvider returns the module-specific user-supplied local name for
|
||||||
|
// a given provider FQN, or the default local name if none was supplied.
|
||||||
|
func (m *Module) LocalNameForProvider(p addrs.Provider) string {
|
||||||
|
if existing, exists := m.ProviderLocalNames[p]; exists {
|
||||||
|
return existing
|
||||||
|
} else {
|
||||||
|
// If there isn't a map entry, fall back to the default:
|
||||||
|
// Type = LocalName
|
||||||
|
return p.Type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNewModule_provider_fqns exercises module.gatherProviderLocalNames()
|
||||||
|
func TestNewModule_provider_local_name(t *testing.T) {
|
||||||
|
mod, diags := testModuleFromDir("testdata/providers-explicit-fqn")
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: while the provider source is set to "foo/test", terraform
|
||||||
|
// currently assumes everything is a legacy provider and the localname and
|
||||||
|
// type match. This test will be updated when provider source is fully
|
||||||
|
// implemented.
|
||||||
|
p := addrs.NewLegacyProvider("foo_test")
|
||||||
|
if name, exists := mod.ProviderLocalNames[p]; !exists {
|
||||||
|
t.Fatal("provider FQN foo/test not found")
|
||||||
|
} else {
|
||||||
|
if name != "foo_test" {
|
||||||
|
t.Fatalf("provider localname mismatch: got %s, want foo_test", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure the reverse lookup (fqn to local name) works as well
|
||||||
|
localName := mod.LocalNameForProvider(p)
|
||||||
|
if localName != "foo_test" {
|
||||||
|
t.Fatal("provider local name not found")
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,6 @@ import (
|
||||||
// RequiredProvider 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.
|
|
||||||
type RequiredProvider struct {
|
type RequiredProvider struct {
|
||||||
Name string
|
Name string
|
||||||
Source string // TODO
|
Source string // TODO
|
||||||
|
@ -43,6 +41,7 @@ func decodeRequiredProvidersBlock(block *hcl.Block) ([]*RequiredProvider, hcl.Di
|
||||||
Requirement: vc,
|
Requirement: vc,
|
||||||
})
|
})
|
||||||
case expr.Type().IsObjectType():
|
case expr.Type().IsObjectType():
|
||||||
|
ret := &RequiredProvider{Name: name}
|
||||||
if expr.Type().HasAttribute("version") {
|
if expr.Type().HasAttribute("version") {
|
||||||
vc := VersionConstraint{
|
vc := VersionConstraint{
|
||||||
DeclRange: attr.Range,
|
DeclRange: attr.Range,
|
||||||
|
@ -58,14 +57,15 @@ func decodeRequiredProvidersBlock(block *hcl.Block) ([]*RequiredProvider, hcl.Di
|
||||||
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, &RequiredProvider{Name: name})
|
} else {
|
||||||
return reqs, diags
|
vc.Required = constraints
|
||||||
|
ret.Requirement = vc
|
||||||
}
|
}
|
||||||
vc.Required = constraints
|
|
||||||
reqs = append(reqs, &RequiredProvider{Name: name, Requirement: vc})
|
|
||||||
}
|
}
|
||||||
// No version
|
if expr.Type().HasAttribute("source") {
|
||||||
reqs = append(reqs, &RequiredProvider{Name: name})
|
ret.Source = expr.GetAttr("source").AsString()
|
||||||
|
}
|
||||||
|
reqs = append(reqs, ret)
|
||||||
default:
|
default:
|
||||||
// should not happen
|
// should not happen
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
version "github.com/hashicorp/go-version"
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/hcltest"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ignoreUnexported = cmpopts.IgnoreUnexported(version.Constraint{})
|
||||||
|
comparer = cmp.Comparer(func(x, y RequiredProvider) bool {
|
||||||
|
if x.Name != y.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if x.Source != y.Source {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if x.Requirement.Required.String() != y.Requirement.Required.String() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeRequiredProvidersBlock_legacy(t *testing.T) {
|
||||||
|
block := &hcl.Block{
|
||||||
|
Type: "required_providers",
|
||||||
|
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||||
|
Attributes: hcl.Attributes{
|
||||||
|
"default": {
|
||||||
|
Name: "default",
|
||||||
|
Expr: hcltest.MockExprLiteral(cty.StringVal("1.0.0")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &RequiredProvider{
|
||||||
|
Name: "default",
|
||||||
|
Requirement: testVC("1.0.0"),
|
||||||
|
}
|
||||||
|
|
||||||
|
got, diags := decodeRequiredProvidersBlock(block)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatalf("unexpected error")
|
||||||
|
}
|
||||||
|
if len(got) != 1 {
|
||||||
|
t.Fatalf("wrong number of results, got %d, wanted 1", len(got))
|
||||||
|
}
|
||||||
|
if !cmp.Equal(got[0], want, ignoreUnexported, comparer) {
|
||||||
|
t.Fatalf("wrong result:\n %s", cmp.Diff(got[0], want, ignoreUnexported, comparer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeRequiredProvidersBlock_provider_source(t *testing.T) {
|
||||||
|
block := &hcl.Block{
|
||||||
|
Type: "required_providers",
|
||||||
|
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||||
|
Attributes: hcl.Attributes{
|
||||||
|
"my_test": {
|
||||||
|
Name: "my_test",
|
||||||
|
Expr: hcltest.MockExprLiteral(cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"source": cty.StringVal("mycloud/test"),
|
||||||
|
"version": cty.StringVal("2.0.0"),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &RequiredProvider{
|
||||||
|
Name: "my_test",
|
||||||
|
Source: "mycloud/test",
|
||||||
|
Requirement: testVC("2.0.0"),
|
||||||
|
}
|
||||||
|
got, diags := decodeRequiredProvidersBlock(block)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatalf("unexpected error")
|
||||||
|
}
|
||||||
|
if len(got) != 1 {
|
||||||
|
t.Fatalf("wrong number of results, got %d, wanted 1", len(got))
|
||||||
|
}
|
||||||
|
if !cmp.Equal(got[0], want, ignoreUnexported, comparer) {
|
||||||
|
t.Fatalf("wrong result:\n %s", cmp.Diff(got[0], want, ignoreUnexported, comparer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeRequiredProvidersBlock_mixed(t *testing.T) {
|
||||||
|
block := &hcl.Block{
|
||||||
|
Type: "required_providers",
|
||||||
|
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||||
|
Attributes: hcl.Attributes{
|
||||||
|
"legacy": {
|
||||||
|
Name: "legacy",
|
||||||
|
Expr: hcltest.MockExprLiteral(cty.StringVal("1.0.0")),
|
||||||
|
},
|
||||||
|
"my_test": {
|
||||||
|
Name: "my_test",
|
||||||
|
Expr: hcltest.MockExprLiteral(cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"source": cty.StringVal("mycloud/test"),
|
||||||
|
"version": cty.StringVal("2.0.0"),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []*RequiredProvider{
|
||||||
|
{
|
||||||
|
Name: "legacy",
|
||||||
|
Requirement: testVC("1.0.0"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "my_test",
|
||||||
|
Source: "mycloud/test",
|
||||||
|
Requirement: testVC("2.0.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, diags := decodeRequiredProvidersBlock(block)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatalf("unexpected error")
|
||||||
|
}
|
||||||
|
if len(got) != 2 {
|
||||||
|
t.Fatalf("wrong number of results, got %d, wanted 2", len(got))
|
||||||
|
}
|
||||||
|
for i, rp := range want {
|
||||||
|
if !cmp.Equal(got[i], rp, ignoreUnexported, comparer) {
|
||||||
|
t.Fatalf("wrong result:\n %s", cmp.Diff(got[0], rp, ignoreUnexported, comparer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeRequiredProvidersBlock_version_error(t *testing.T) {
|
||||||
|
block := &hcl.Block{
|
||||||
|
Type: "required_providers",
|
||||||
|
Body: hcltest.MockBody(&hcl.BodyContent{
|
||||||
|
Attributes: hcl.Attributes{
|
||||||
|
"my_test": {
|
||||||
|
Name: "my_test",
|
||||||
|
Expr: hcltest.MockExprLiteral(cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"source": cty.StringVal("mycloud/test"),
|
||||||
|
"version": cty.StringVal("invalid"),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []*RequiredProvider{
|
||||||
|
{
|
||||||
|
Name: "my_test",
|
||||||
|
Source: "mycloud/test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, diags := decodeRequiredProvidersBlock(block)
|
||||||
|
if !diags.HasErrors() {
|
||||||
|
t.Fatalf("expected error, got success")
|
||||||
|
} else {
|
||||||
|
fmt.Printf(diags[0].Summary)
|
||||||
|
}
|
||||||
|
if len(got) != 1 {
|
||||||
|
t.Fatalf("wrong number of results, got %d, wanted 1", len(got))
|
||||||
|
}
|
||||||
|
for i, rp := range want {
|
||||||
|
if !cmp.Equal(got[i], rp, ignoreUnexported, comparer) {
|
||||||
|
t.Fatalf("wrong result:\n %s", cmp.Diff(got[0], rp, ignoreUnexported, comparer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testVC(ver string) VersionConstraint {
|
||||||
|
constraint, _ := version.NewConstraint(ver)
|
||||||
|
return VersionConstraint{
|
||||||
|
Required: constraint,
|
||||||
|
DeclRange: hcl.Range{},
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue