initial support for parsing configuration_aliases
Add support for parsing configuration_aliases in required_providers entries. The decoder needed to be re-written here in order to support the bare reference style usage of provider names so that they match the usage in other location within configuration. The only change to existing handling of the required_providers block is more precise error locations in a couple cases.
This commit is contained in:
parent
a033598224
commit
ac585be079
|
@ -1,6 +1,8 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
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"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
@ -17,6 +19,7 @@ type RequiredProvider struct {
|
||||||
Type addrs.Provider
|
Type addrs.Provider
|
||||||
Requirement VersionConstraint
|
Requirement VersionConstraint
|
||||||
DeclRange hcl.Range
|
DeclRange hcl.Range
|
||||||
|
Aliases []addrs.LocalProviderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequiredProviders struct {
|
type RequiredProviders struct {
|
||||||
|
@ -26,45 +29,97 @@ type RequiredProviders struct {
|
||||||
|
|
||||||
func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Diagnostics) {
|
func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Diagnostics) {
|
||||||
attrs, diags := block.Body.JustAttributes()
|
attrs, diags := block.Body.JustAttributes()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
ret := &RequiredProviders{
|
ret := &RequiredProviders{
|
||||||
RequiredProviders: make(map[string]*RequiredProvider),
|
RequiredProviders: make(map[string]*RequiredProvider),
|
||||||
DeclRange: block.DefRange,
|
DeclRange: block.DefRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, attr := range attrs {
|
for name, attr := range attrs {
|
||||||
expr, err := attr.Expr.Value(nil)
|
|
||||||
if err != nil {
|
|
||||||
diags = append(diags, err...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify that the local name is already localized or produce an error.
|
|
||||||
nameDiags := checkProviderNameNormalized(name, attr.Expr.Range())
|
|
||||||
diags = append(diags, nameDiags...)
|
|
||||||
|
|
||||||
rp := &RequiredProvider{
|
rp := &RequiredProvider{
|
||||||
Name: name,
|
Name: name,
|
||||||
DeclRange: attr.Expr.Range(),
|
DeclRange: attr.Expr.Range(),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
// Look for a single static string, in case we have the legacy version-only
|
||||||
case expr.Type().IsPrimitiveType():
|
// format in the configuration.
|
||||||
|
if expr, err := attr.Expr.Value(nil); err == nil && expr.Type().IsPrimitiveType() {
|
||||||
vc, reqDiags := decodeVersionConstraint(attr)
|
vc, reqDiags := decodeVersionConstraint(attr)
|
||||||
diags = append(diags, reqDiags...)
|
diags = append(diags, reqDiags...)
|
||||||
rp.Requirement = vc
|
|
||||||
|
|
||||||
case expr.Type().IsObjectType():
|
pType, err := addrs.ParseProviderPart(rp.Name)
|
||||||
if expr.Type().HasAttribute("version") {
|
if err != nil {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid provider name",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: attr.Expr.Range().Ptr(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rp.Requirement = vc
|
||||||
|
rp.Type = addrs.ImpliedProviderForUnqualifiedType(pType)
|
||||||
|
ret.RequiredProviders[name] = rp
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the local name is already localized or produce an error.
|
||||||
|
nameDiags := checkProviderNameNormalized(name, attr.Expr.Range())
|
||||||
|
if nameDiags.HasErrors() {
|
||||||
|
diags = append(diags, nameDiags...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs, mapDiags := hcl.ExprMap(attr.Expr)
|
||||||
|
if mapDiags.HasErrors() {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid required_providers object",
|
||||||
|
Detail: "required_providers entries must be strings or objects.",
|
||||||
|
Subject: attr.Expr.Range().Ptr(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range kvs {
|
||||||
|
key, keyDiags := kv.Key.Value(nil)
|
||||||
|
if keyDiags.HasErrors() {
|
||||||
|
diags = append(diags, keyDiags...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.Type() != cty.String {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid Attribute",
|
||||||
|
Detail: fmt.Sprintf("Invalid attribute value for provider requirement: %#v", key),
|
||||||
|
Subject: kv.Key.Range().Ptr(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key.AsString() {
|
||||||
|
case "version":
|
||||||
vc := VersionConstraint{
|
vc := VersionConstraint{
|
||||||
DeclRange: attr.Range,
|
DeclRange: attr.Range,
|
||||||
}
|
}
|
||||||
constraint := expr.GetAttr("version")
|
|
||||||
if !constraint.Type().Equals(cty.String) || constraint.IsNull() {
|
constraint, valDiags := kv.Value.Value(nil)
|
||||||
|
if valDiags.HasErrors() || !constraint.Type().Equals(cty.String) {
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid version constraint",
|
Summary: "Invalid version constraint",
|
||||||
Detail: "Version must be specified as a string.",
|
Detail: "Version must be specified as a string.",
|
||||||
Subject: attr.Expr.Range().Ptr(),
|
Subject: kv.Value.Range().Ptr(),
|
||||||
})
|
})
|
||||||
} else {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
constraintStr := constraint.AsString()
|
constraintStr := constraint.AsString()
|
||||||
constraints, err := version.NewConstraint(constraintStr)
|
constraints, err := version.NewConstraint(constraintStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,28 +129,27 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Dia
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid version constraint",
|
Summary: "Invalid version constraint",
|
||||||
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: kv.Value.Range().Ptr(),
|
||||||
})
|
})
|
||||||
} else {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
vc.Required = constraints
|
vc.Required = constraints
|
||||||
rp.Requirement = vc
|
rp.Requirement = vc
|
||||||
}
|
|
||||||
}
|
case "source":
|
||||||
}
|
source, err := kv.Value.Value(nil)
|
||||||
if expr.Type().HasAttribute("source") {
|
if err != nil || !source.Type().Equals(cty.String) {
|
||||||
source := expr.GetAttr("source")
|
|
||||||
if !source.Type().Equals(cty.String) || source.IsNull() {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid source",
|
Summary: "Invalid source",
|
||||||
Detail: "Source must be specified as a string.",
|
Detail: "Source must be specified as a string.",
|
||||||
Subject: attr.Expr.Range().Ptr(),
|
Subject: kv.Value.Range().Ptr(),
|
||||||
})
|
})
|
||||||
} else {
|
continue
|
||||||
rp.Source = source.AsString()
|
}
|
||||||
|
|
||||||
fqn, sourceDiags := addrs.ParseProviderSourceString(rp.Source)
|
|
||||||
|
|
||||||
|
fqn, sourceDiags := addrs.ParseProviderSourceString(source.AsString())
|
||||||
if sourceDiags.HasErrors() {
|
if sourceDiags.HasErrors() {
|
||||||
hclDiags := sourceDiags.ToHCL()
|
hclDiags := sourceDiags.ToHCL()
|
||||||
// The diagnostics from ParseProviderSourceString don't contain
|
// The diagnostics from ParseProviderSourceString don't contain
|
||||||
|
@ -104,40 +158,70 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Dia
|
||||||
// return.
|
// return.
|
||||||
for _, diag := range hclDiags {
|
for _, diag := range hclDiags {
|
||||||
if diag.Subject == nil {
|
if diag.Subject == nil {
|
||||||
diag.Subject = attr.Expr.Range().Ptr()
|
diag.Subject = kv.Value.Range().Ptr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
diags = append(diags, hclDiags...)
|
diags = append(diags, hclDiags...)
|
||||||
} else {
|
|
||||||
rp.Type = fqn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attrTypes := expr.Type().AttributeTypes()
|
|
||||||
for name := range attrTypes {
|
|
||||||
if name == "version" || name == "source" {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rp.Source = source.AsString()
|
||||||
|
rp.Type = fqn
|
||||||
|
|
||||||
|
case "configuration_aliases":
|
||||||
|
exprs, listDiags := hcl.ExprList(kv.Value)
|
||||||
|
if listDiags.HasErrors() {
|
||||||
|
diags = append(diags, listDiags...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expr := range exprs {
|
||||||
|
traversal, travDiags := hcl.AbsTraversalForExpr(expr)
|
||||||
|
if travDiags.HasErrors() {
|
||||||
|
diags = append(diags, travDiags...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, cfgDiags := ParseProviderConfigCompact(traversal)
|
||||||
|
if cfgDiags.HasErrors() {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid configuration_aliases value",
|
||||||
|
Detail: `Configuration aliases can only contain references to local provider configuration names in the format of provider.alias`,
|
||||||
|
Subject: kv.Value.Range().Ptr(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.LocalName != name {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid configuration_aliases value",
|
||||||
|
Detail: fmt.Sprintf(`Configuration aliases must be prefixed with the provider name. Expected %q, but found %q.`, name, addr.LocalName),
|
||||||
|
Subject: kv.Value.Range().Ptr(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rp.Aliases = append(rp.Aliases, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Invalid required_providers object",
|
Summary: "Invalid required_providers object",
|
||||||
Detail: `required_providers objects can only contain "version" and "source" attributes. To configure a provider, use a "provider" block.`,
|
Detail: `required_providers objects can only contain "version", "source" and "configuration_aliases" attributes. To configure a provider, use a "provider" block.`,
|
||||||
Subject: attr.Expr.Range().Ptr(),
|
Subject: kv.Key.Range().Ptr(),
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
// should not happen
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid required_providers syntax",
|
|
||||||
Detail: "required_providers entries must be strings or objects.",
|
|
||||||
Subject: attr.Expr.Range().Ptr(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rp.Type.IsZero() && !diags.HasErrors() { // Don't try to generate an FQN if we've encountered errors
|
// finally add the required provider as long as there were no errors
|
||||||
|
if !diags.HasErrors() {
|
||||||
|
// if a source was not given, create an implied type
|
||||||
|
if rp.Type.IsZero() {
|
||||||
pType, err := addrs.ParseProviderPart(rp.Name)
|
pType, err := addrs.ParseProviderPart(rp.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
@ -153,6 +237,7 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (*RequiredProviders, hcl.Dia
|
||||||
|
|
||||||
ret.RequiredProviders[rp.Name] = rp
|
ret.RequiredProviders[rp.Name] = rp
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ret, diags
|
return ret, diags
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,14 +185,7 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
DefRange: blockRange,
|
DefRange: blockRange,
|
||||||
},
|
},
|
||||||
Want: &RequiredProviders{
|
Want: &RequiredProviders{
|
||||||
RequiredProviders: map[string]*RequiredProvider{
|
RequiredProviders: map[string]*RequiredProvider{},
|
||||||
"my-test": {
|
|
||||||
Name: "my-test",
|
|
||||||
Source: "some/invalid/provider/source/test",
|
|
||||||
Requirement: testVC("~>2.0.0"),
|
|
||||||
DeclRange: mockRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DeclRange: blockRange,
|
DeclRange: blockRange,
|
||||||
},
|
},
|
||||||
Error: "Invalid provider source string",
|
Error: "Invalid provider source string",
|
||||||
|
@ -213,14 +206,7 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
DefRange: blockRange,
|
DefRange: blockRange,
|
||||||
},
|
},
|
||||||
Want: &RequiredProviders{
|
Want: &RequiredProviders{
|
||||||
RequiredProviders: map[string]*RequiredProvider{
|
RequiredProviders: map[string]*RequiredProvider{},
|
||||||
"my_test": {
|
|
||||||
Name: "my_test",
|
|
||||||
Type: addrs.Provider{},
|
|
||||||
Requirement: testVC("~>2.0.0"),
|
|
||||||
DeclRange: mockRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DeclRange: blockRange,
|
DeclRange: blockRange,
|
||||||
},
|
},
|
||||||
Error: "Invalid provider local name",
|
Error: "Invalid provider local name",
|
||||||
|
@ -241,14 +227,7 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
DefRange: blockRange,
|
DefRange: blockRange,
|
||||||
},
|
},
|
||||||
Want: &RequiredProviders{
|
Want: &RequiredProviders{
|
||||||
RequiredProviders: map[string]*RequiredProvider{
|
RequiredProviders: map[string]*RequiredProvider{},
|
||||||
"MYTEST": {
|
|
||||||
Name: "MYTEST",
|
|
||||||
Type: addrs.Provider{},
|
|
||||||
Requirement: testVC("~>2.0.0"),
|
|
||||||
DeclRange: mockRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DeclRange: blockRange,
|
DeclRange: blockRange,
|
||||||
},
|
},
|
||||||
Error: "Invalid provider local name",
|
Error: "Invalid provider local name",
|
||||||
|
@ -270,14 +249,7 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
DefRange: blockRange,
|
DefRange: blockRange,
|
||||||
},
|
},
|
||||||
Want: &RequiredProviders{
|
Want: &RequiredProviders{
|
||||||
RequiredProviders: map[string]*RequiredProvider{
|
RequiredProviders: map[string]*RequiredProvider{},
|
||||||
"my-test": {
|
|
||||||
Name: "my-test",
|
|
||||||
Source: "mycloud/test",
|
|
||||||
Type: addrs.NewProvider(addrs.DefaultRegistryHost, "mycloud", "test"),
|
|
||||||
DeclRange: mockRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DeclRange: blockRange,
|
DeclRange: blockRange,
|
||||||
},
|
},
|
||||||
Error: "Invalid version constraint",
|
Error: "Invalid version constraint",
|
||||||
|
@ -296,15 +268,10 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
DefRange: blockRange,
|
DefRange: blockRange,
|
||||||
},
|
},
|
||||||
Want: &RequiredProviders{
|
Want: &RequiredProviders{
|
||||||
RequiredProviders: map[string]*RequiredProvider{
|
RequiredProviders: map[string]*RequiredProvider{},
|
||||||
"test": {
|
|
||||||
Name: "test",
|
|
||||||
DeclRange: mockRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DeclRange: blockRange,
|
DeclRange: blockRange,
|
||||||
},
|
},
|
||||||
Error: "Invalid required_providers syntax",
|
Error: "Invalid required_providers object",
|
||||||
},
|
},
|
||||||
"invalid source attribute type": {
|
"invalid source attribute type": {
|
||||||
Block: &hcl.Block{
|
Block: &hcl.Block{
|
||||||
|
@ -322,12 +289,7 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
DefRange: blockRange,
|
DefRange: blockRange,
|
||||||
},
|
},
|
||||||
Want: &RequiredProviders{
|
Want: &RequiredProviders{
|
||||||
RequiredProviders: map[string]*RequiredProvider{
|
RequiredProviders: map[string]*RequiredProvider{},
|
||||||
"my-test": {
|
|
||||||
Name: "my-test",
|
|
||||||
DeclRange: mockRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DeclRange: blockRange,
|
DeclRange: blockRange,
|
||||||
},
|
},
|
||||||
Error: "Invalid source",
|
Error: "Invalid source",
|
||||||
|
@ -350,15 +312,7 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
DefRange: blockRange,
|
DefRange: blockRange,
|
||||||
},
|
},
|
||||||
Want: &RequiredProviders{
|
Want: &RequiredProviders{
|
||||||
RequiredProviders: map[string]*RequiredProvider{
|
RequiredProviders: map[string]*RequiredProvider{},
|
||||||
"my-test": {
|
|
||||||
Name: "my-test",
|
|
||||||
Source: "mycloud/test",
|
|
||||||
Type: addrs.NewProvider(addrs.DefaultRegistryHost, "mycloud", "test"),
|
|
||||||
Requirement: testVC("2.0.0"),
|
|
||||||
DeclRange: mockRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DeclRange: blockRange,
|
DeclRange: blockRange,
|
||||||
},
|
},
|
||||||
Error: "Invalid required_providers object",
|
Error: "Invalid required_providers object",
|
||||||
|
@ -370,7 +324,7 @@ func TestDecodeRequiredProvidersBlock(t *testing.T) {
|
||||||
got, diags := decodeRequiredProvidersBlock(test.Block)
|
got, diags := decodeRequiredProvidersBlock(test.Block)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
if test.Error == "" {
|
if test.Error == "" {
|
||||||
t.Fatalf("unexpected error")
|
t.Fatalf("unexpected error: %v", diags)
|
||||||
}
|
}
|
||||||
if gotErr := diags[0].Summary; gotErr != test.Error {
|
if gotErr := diags[0].Summary; gotErr != test.Error {
|
||||||
t.Errorf("wrong error, got %q, want %q", gotErr, test.Error)
|
t.Errorf("wrong error, got %q, want %q", gotErr, test.Error)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
terraform {
|
terraform {
|
||||||
required_providers {
|
required_providers {
|
||||||
usererror = { # ERROR: Invalid provider type
|
usererror = {
|
||||||
source = "foo/terraform-provider-foo"
|
source = "foo/terraform-provider-foo" # ERROR: Invalid provider type
|
||||||
}
|
}
|
||||||
badname = { # ERROR: Invalid provider type
|
badname = {
|
||||||
source = "foo/terraform-foo"
|
source = "foo/terraform-foo" # ERROR: Invalid provider type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
foo-test = {
|
||||||
|
source = "foo/test"
|
||||||
|
// TODO: these are strings until the parsing code is refactored to allow
|
||||||
|
// raw references
|
||||||
|
configuration_aliases = [foo-test.a, foo-test.b]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "explicit" {
|
||||||
|
provider = foo-test.a
|
||||||
|
}
|
||||||
|
|
||||||
|
data "test_resource" "explicit" {
|
||||||
|
provider = foo-test.b
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue