configs: Reserve various names for future use
We want the forthcoming v0.12.0 release to be the last significant breaking change to our main configuration constructs for a long time, but not everything could be implemented in that release. As a compromise then, we reserve various names we have some intent of using in a future release so that such future uses will not be a further breaking change later. Some of these names are associated with specific short-term plans, while others are reserved conservatively for possible later work and may be "un-reserved" in a later release if we don't end up using them. The ones that we expect to use in the near future were already being handled, so we'll continue to decode them at the config layer but also produce an error so that we don't get weird behavior downstream where the corresponding features don't work yet.
This commit is contained in:
parent
3259e969cb
commit
0681935df5
|
@ -68,16 +68,40 @@ func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagno
|
||||||
|
|
||||||
if attr, exists := content.Attributes["count"]; exists {
|
if attr, exists := content.Attributes["count"]; exists {
|
||||||
mc.Count = attr.Expr
|
mc.Count = attr.Expr
|
||||||
|
|
||||||
|
// We currently parse this, but don't yet do anything with it.
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved argument name in module block",
|
||||||
|
Detail: fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
|
||||||
|
Subject: &attr.NameRange,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["for_each"]; exists {
|
if attr, exists := content.Attributes["for_each"]; exists {
|
||||||
mc.ForEach = attr.Expr
|
mc.ForEach = attr.Expr
|
||||||
|
|
||||||
|
// We currently parse this, but don't yet do anything with it.
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved argument name in module block",
|
||||||
|
Detail: fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
|
||||||
|
Subject: &attr.NameRange,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["depends_on"]; exists {
|
if attr, exists := content.Attributes["depends_on"]; exists {
|
||||||
deps, depsDiags := decodeDependsOn(attr)
|
deps, depsDiags := decodeDependsOn(attr)
|
||||||
diags = append(diags, depsDiags...)
|
diags = append(diags, depsDiags...)
|
||||||
mc.DependsOn = append(mc.DependsOn, deps...)
|
mc.DependsOn = append(mc.DependsOn, deps...)
|
||||||
|
|
||||||
|
// We currently parse this, but don't yet do anything with it.
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved argument name in module block",
|
||||||
|
Detail: fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
|
||||||
|
Subject: &attr.NameRange,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["providers"]; exists {
|
if attr, exists := content.Attributes["providers"]; exists {
|
||||||
|
@ -113,6 +137,16 @@ func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagno
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reserved block types (all of them)
|
||||||
|
for _, block := range content.Blocks {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved block type name in module block",
|
||||||
|
Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
|
||||||
|
Subject: &block.TypeRange,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return mc, diags
|
return mc, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,4 +179,10 @@ var moduleBlockSchema = &hcl.BodySchema{
|
||||||
Name: "providers",
|
Name: "providers",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Blocks: []hcl.BlockHeaderSchema{
|
||||||
|
// These are all reserved for future use.
|
||||||
|
{Type: "lifecycle"},
|
||||||
|
{Type: "locals"},
|
||||||
|
{Type: "provider", LabelNames: []string{"type"}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadModuleCall(t *testing.T) {
|
func TestLoadModuleCall(t *testing.T) {
|
||||||
src, err := ioutil.ReadFile("test-fixtures/valid-files/module-calls.tf")
|
src, err := ioutil.ReadFile("test-fixtures/invalid-files/module-calls.tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,11 @@ func TestLoadModuleCall(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
file, diags := parser.LoadConfigFile("module-calls.tf")
|
file, diags := parser.LoadConfigFile("module-calls.tf")
|
||||||
if len(diags) != 0 {
|
assertExactDiagnostics(t, diags, []string{
|
||||||
t.Errorf("Wrong number of diagnostics %d; want 0", len(diags))
|
`module-calls.tf:19,3-8: Reserved argument name in module block; The name "count" is reserved for use in a future version of Terraform.`,
|
||||||
for _, diag := range diags {
|
`module-calls.tf:20,3-11: Reserved argument name in module block; The name "for_each" is reserved for use in a future version of Terraform.`,
|
||||||
t.Logf("- %s", diag)
|
`module-calls.tf:22,3-13: Reserved argument name in module block; The name "depends_on" is reserved for use in a future version of Terraform.`,
|
||||||
}
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gotModules := file.ModuleCalls
|
gotModules := file.ModuleCalls
|
||||||
wantModules := []*ModuleCall{
|
wantModules := []*ModuleCall{
|
||||||
|
|
|
@ -68,6 +68,16 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, blockS := range moduleBlockSchema.Blocks {
|
||||||
|
if blockS.Type == v.Name {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid variable name",
|
||||||
|
Detail: fmt.Sprintf("The variable name %q is reserved due to its special meaning inside module blocks.", blockS.Type),
|
||||||
|
Subject: &block.LabelRanges[0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["description"]; exists {
|
if attr, exists := content.Attributes["description"]; exists {
|
||||||
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
|
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
|
||||||
|
|
|
@ -85,6 +85,36 @@ func assertDiagnosticSummary(t *testing.T, diags hcl.Diagnostics, want string) b
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertExactDiagnostics(t *testing.T, diags hcl.Diagnostics, want []string) bool {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
gotDiags := map[string]bool{}
|
||||||
|
wantDiags := map[string]bool{}
|
||||||
|
|
||||||
|
for _, diag := range diags {
|
||||||
|
gotDiags[diag.Error()] = true
|
||||||
|
}
|
||||||
|
for _, msg := range want {
|
||||||
|
wantDiags[msg] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
bad := false
|
||||||
|
for got := range gotDiags {
|
||||||
|
if _, exists := wantDiags[got]; !exists {
|
||||||
|
t.Errorf("unexpected diagnostic: %s", got)
|
||||||
|
bad = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for want := range wantDiags {
|
||||||
|
if _, exists := gotDiags[want]; !exists {
|
||||||
|
t.Errorf("missing expected diagnostic: %s", want)
|
||||||
|
bad = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bad
|
||||||
|
}
|
||||||
|
|
||||||
func assertResultDeepEqual(t *testing.T, got, want interface{}) bool {
|
func assertResultDeepEqual(t *testing.T, got, want interface{}) bool {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
|
|
@ -56,6 +56,28 @@ func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) {
|
||||||
diags = append(diags, versionDiags...)
|
diags = append(diags, versionDiags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reserved attribute names
|
||||||
|
for _, name := range []string{"count", "depends_on", "for_each", "source"} {
|
||||||
|
if attr, exists := content.Attributes[name]; exists {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved argument name in provider block",
|
||||||
|
Detail: fmt.Sprintf("The provider argument name %q is reserved for use by Terraform in a future version.", name),
|
||||||
|
Subject: &attr.NameRange,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserved block types (all of them)
|
||||||
|
for _, block := range content.Blocks {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved block type name in provider block",
|
||||||
|
Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
|
||||||
|
Subject: &block.TypeRange,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return provider, diags
|
return provider, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,5 +129,16 @@ var providerBlockSchema = &hcl.BodySchema{
|
||||||
{
|
{
|
||||||
Name: "version",
|
Name: "version",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Attribute names reserved for future expansion.
|
||||||
|
{Name: "count"},
|
||||||
|
{Name: "depends_on"},
|
||||||
|
{Name: "for_each"},
|
||||||
|
{Name: "source"},
|
||||||
|
},
|
||||||
|
Blocks: []hcl.BlockHeaderSchema{
|
||||||
|
// _All_ of these are reserved for future expansion.
|
||||||
|
{Type: "lifecycle"},
|
||||||
|
{Type: "locals"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProviderReservedNames(t *testing.T) {
|
||||||
|
src, err := ioutil.ReadFile("test-fixtures/invalid-files/provider-reserved.tf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
parser := testParser(map[string]string{
|
||||||
|
"config.tf": string(src),
|
||||||
|
})
|
||||||
|
_, diags := parser.LoadConfigFile("config.tf")
|
||||||
|
|
||||||
|
assertExactDiagnostics(t, diags, []string{
|
||||||
|
`config.tf:10,3-8: Reserved argument name in provider block; The provider argument name "count" is reserved for use by Terraform in a future version.`,
|
||||||
|
`config.tf:11,3-13: Reserved argument name in provider block; The provider argument name "depends_on" is reserved for use by Terraform in a future version.`,
|
||||||
|
`config.tf:12,3-11: Reserved argument name in provider block; The provider argument name "for_each" is reserved for use by Terraform in a future version.`,
|
||||||
|
`config.tf:14,3-12: Reserved block type name in provider block; The block type name "lifecycle" is reserved for use by Terraform in a future version.`,
|
||||||
|
`config.tf:15,3-9: Reserved block type name in provider block; The block type name "locals" is reserved for use by Terraform in a future version.`,
|
||||||
|
`config.tf:13,3-9: Reserved argument name in provider block; The provider argument name "source" is reserved for use by Terraform in a future version.`,
|
||||||
|
})
|
||||||
|
}
|
|
@ -93,8 +93,14 @@ func decodeProvisionerBlock(block *hcl.Block) (*Provisioner, hcl.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Should never happen because there are no other block types
|
// Any other block types are ones we've reserved for future use,
|
||||||
// declared in our schema.
|
// so they get a generic message.
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved block type name in provisioner block",
|
||||||
|
Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
|
||||||
|
Subject: &block.TypeRange,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,16 +140,11 @@ const (
|
||||||
|
|
||||||
var provisionerBlockSchema = &hcl.BodySchema{
|
var provisionerBlockSchema = &hcl.BodySchema{
|
||||||
Attributes: []hcl.AttributeSchema{
|
Attributes: []hcl.AttributeSchema{
|
||||||
{
|
{Name: "when"},
|
||||||
Name: "when",
|
{Name: "on_failure"},
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "on_failure",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
Blocks: []hcl.BlockHeaderSchema{
|
||||||
{
|
{Type: "connection"},
|
||||||
Type: "connection",
|
{Type: "lifecycle"}, // reserved for future use
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,14 @@ func decodeResourceBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["for_each"]; exists {
|
if attr, exists := content.Attributes["for_each"]; exists {
|
||||||
r.Count = attr.Expr
|
r.ForEach = attr.Expr
|
||||||
|
// We currently parse this, but don't yet do anything with it.
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved argument name in resource block",
|
||||||
|
Detail: fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
|
||||||
|
Subject: &attr.NameRange,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["provider"]; exists {
|
if attr, exists := content.Attributes["provider"]; exists {
|
||||||
|
@ -244,9 +251,14 @@ func decodeResourceBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Should never happen, because the above cases should always be
|
// Any other block types are ones we've reserved for future use,
|
||||||
// exhaustive for all the types specified in our schema.
|
// so they get a generic message.
|
||||||
continue
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved block type name in resource block",
|
||||||
|
Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
|
||||||
|
Subject: &block.TypeRange,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +299,14 @@ func decodeDataBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["for_each"]; exists {
|
if attr, exists := content.Attributes["for_each"]; exists {
|
||||||
r.Count = attr.Expr
|
r.ForEach = attr.Expr
|
||||||
|
// We currently parse this, but don't yet do anything with it.
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved argument name in module block",
|
||||||
|
Detail: fmt.Sprintf("The name %q is reserved for use in a future version of Terraform.", attr.Name),
|
||||||
|
Subject: &attr.NameRange,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, exists := content.Attributes["provider"]; exists {
|
if attr, exists := content.Attributes["provider"]; exists {
|
||||||
|
@ -303,17 +322,23 @@ func decodeDataBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, block := range content.Blocks {
|
for _, block := range content.Blocks {
|
||||||
// Our schema only allows for "lifecycle" blocks, so we can assume
|
// All of the block types we accept are just reserved for future use, but some get a specialized error message.
|
||||||
// that this is all we will see here. We don't have any lifecycle
|
switch block.Type {
|
||||||
// attributes for data resources currently, so we'll just produce
|
case "lifecycle":
|
||||||
// an error.
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
Severity: hcl.DiagError,
|
||||||
Severity: hcl.DiagError,
|
Summary: "Unsupported lifecycle block",
|
||||||
Summary: "Unsupported lifecycle block",
|
Detail: "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
|
||||||
Detail: "Data resources do not have lifecycle settings, so a lifecycle block is not allowed.",
|
Subject: &block.DefRange,
|
||||||
Subject: &block.DefRange,
|
})
|
||||||
})
|
default:
|
||||||
break
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Reserved block type name in data block",
|
||||||
|
Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
|
||||||
|
Subject: &block.TypeRange,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, diags
|
return r, diags
|
||||||
|
@ -431,25 +456,18 @@ var commonResourceAttributes = []hcl.AttributeSchema{
|
||||||
var resourceBlockSchema = &hcl.BodySchema{
|
var resourceBlockSchema = &hcl.BodySchema{
|
||||||
Attributes: commonResourceAttributes,
|
Attributes: commonResourceAttributes,
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
Blocks: []hcl.BlockHeaderSchema{
|
||||||
{
|
{Type: "locals"}, // reserved for future use
|
||||||
Type: "lifecycle",
|
{Type: "lifecycle"},
|
||||||
},
|
{Type: "connection"},
|
||||||
{
|
{Type: "provisioner", LabelNames: []string{"type"}},
|
||||||
Type: "connection",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: "provisioner",
|
|
||||||
LabelNames: []string{"type"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataBlockSchema = &hcl.BodySchema{
|
var dataBlockSchema = &hcl.BodySchema{
|
||||||
Attributes: commonResourceAttributes,
|
Attributes: commonResourceAttributes,
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
Blocks: []hcl.BlockHeaderSchema{
|
||||||
{
|
{Type: "lifecycle"}, // reserved for future use
|
||||||
Type: "lifecycle",
|
{Type: "locals"}, // reserved for future use
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
data "test" "foo" {
|
||||||
|
for_each = ["a"]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
data "test" "foo" {
|
||||||
|
lifecycle {}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
data "test" "foo" {
|
||||||
|
locals {}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
provider "test" {
|
||||||
|
# These are okay
|
||||||
|
alias = "foo"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
# Provider-specific arguments are also okay
|
||||||
|
arbitrary = true
|
||||||
|
|
||||||
|
# These are all reserved and should generate errors.
|
||||||
|
count = 3
|
||||||
|
depends_on = ["foo.bar"]
|
||||||
|
for_each = ["a", "b"]
|
||||||
|
source = "foo.example.com/baz/bar"
|
||||||
|
lifecycle {}
|
||||||
|
locals {}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "test" "foo" {
|
||||||
|
for_each = ["a"]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "test" "foo" {
|
||||||
|
locals {}
|
||||||
|
}
|
Loading…
Reference in New Issue