terraform/internal/configs/escaping_blocks_test.go

309 lines
10 KiB
Go

package configs
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
// "Escaping Blocks" are a special mechanism we have inside our block types
// that accept a mixture of meta-arguments and externally-defined arguments,
// which allow an author to force particular argument names to be interpreted
// as externally-defined even if they have the same name as a meta-argument.
//
// An escaping block is a block with the special type name "_" (just an
// underscore), and is allowed at the top-level of any resource, data, or
// module block. It intentionally has a rather "odd" look so that it stands
// out as something special and rare.
//
// This is not something we expect to see used a lot, but it's an important
// part of our strategy to evolve the Terraform language in future using
// editions, so that later editions can define new meta-arguments without
// blocking access to externally-defined arguments of the same name.
//
// We should still define new meta-arguments with care to avoid squatting on
// commonly-used names, but we can't see all modules and all providers in
// the world and so this is an escape hatch for edge cases. Module migration
// tools for future editions that define new meta-arguments should detect
// collisions and automatically migrate existing arguments into an escaping
// block.
func TestEscapingBlockResource(t *testing.T) {
// (this also tests escaping blocks in provisioner blocks, because
// they only appear nested inside resource blocks.)
parser := NewParser(nil)
mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/resource")
assertNoDiagnostics(t, diags)
if mod == nil {
t.Fatal("got nil root module; want non-nil")
}
rc := mod.ManagedResources["foo.bar"]
if rc == nil {
t.Fatal("no managed resource named foo.bar")
}
t.Run("resource body", func(t *testing.T) {
if got := rc.Count; got == nil {
t.Errorf("count not set; want count = 2")
} else {
got, diags := got.Value(nil)
assertNoDiagnostics(t, diags)
if want := cty.NumberIntVal(2); !want.RawEquals(got) {
t.Errorf("wrong count\ngot: %#v\nwant: %#v", got, want)
}
}
if got, want := rc.ForEach, hcl.Expression(nil); got != want {
// Shouldn't have any count because our test fixture only has
// for_each in the escaping block.
t.Errorf("wrong for_each\ngot: %#v\nwant: %#v", got, want)
}
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "normal", Required: true},
{Name: "count", Required: true},
{Name: "for_each", Required: true},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "normal_block"},
{Type: "lifecycle"},
{Type: "_"},
},
}
content, diags := rc.Config.Content(schema)
assertNoDiagnostics(t, diags)
normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want)
}
countVal, diags := content.Attributes["count"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) {
t.Errorf("wrong value for 'count'\ngot: %#v\nwant: %#v", got, want)
}
var gotBlockTypes []string
for _, block := range content.Blocks {
gotBlockTypes = append(gotBlockTypes, block.Type)
}
wantBlockTypes := []string{"normal_block", "lifecycle", "_"}
if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" {
t.Errorf("wrong block types\n%s", diff)
}
})
t.Run("provisioner body", func(t *testing.T) {
if got, want := len(rc.Managed.Provisioners), 1; got != want {
t.Fatalf("wrong number of provisioners %d; want %d", got, want)
}
pc := rc.Managed.Provisioners[0]
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "when", Required: true},
{Name: "normal", Required: true},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "normal_block"},
{Type: "lifecycle"},
{Type: "_"},
},
}
content, diags := pc.Config.Content(schema)
assertNoDiagnostics(t, diags)
normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := normalVal, cty.StringVal("yep"); !want.RawEquals(got) {
t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want)
}
whenVal, diags := content.Attributes["when"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := whenVal, cty.StringVal("hell freezes over"); !want.RawEquals(got) {
t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want)
}
})
}
func TestEscapingBlockData(t *testing.T) {
parser := NewParser(nil)
mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/data")
assertNoDiagnostics(t, diags)
if mod == nil {
t.Fatal("got nil root module; want non-nil")
}
rc := mod.DataResources["data.foo.bar"]
if rc == nil {
t.Fatal("no data resource named data.foo.bar")
}
if got := rc.Count; got == nil {
t.Errorf("count not set; want count = 2")
} else {
got, diags := got.Value(nil)
assertNoDiagnostics(t, diags)
if want := cty.NumberIntVal(2); !want.RawEquals(got) {
t.Errorf("wrong count\ngot: %#v\nwant: %#v", got, want)
}
}
if got, want := rc.ForEach, hcl.Expression(nil); got != want {
// Shouldn't have any count because our test fixture only has
// for_each in the escaping block.
t.Errorf("wrong for_each\ngot: %#v\nwant: %#v", got, want)
}
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "normal", Required: true},
{Name: "count", Required: true},
{Name: "for_each", Required: true},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "normal_block"},
{Type: "lifecycle"},
{Type: "_"},
},
}
content, diags := rc.Config.Content(schema)
assertNoDiagnostics(t, diags)
normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want)
}
countVal, diags := content.Attributes["count"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) {
t.Errorf("wrong value for 'count'\ngot: %#v\nwant: %#v", got, want)
}
var gotBlockTypes []string
for _, block := range content.Blocks {
gotBlockTypes = append(gotBlockTypes, block.Type)
}
wantBlockTypes := []string{"normal_block", "lifecycle", "_"}
if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" {
t.Errorf("wrong block types\n%s", diff)
}
}
func TestEscapingBlockModule(t *testing.T) {
parser := NewParser(nil)
mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/module")
assertNoDiagnostics(t, diags)
if mod == nil {
t.Fatal("got nil root module; want non-nil")
}
mc := mod.ModuleCalls["foo"]
if mc == nil {
t.Fatal("no module call named foo")
}
if got := mc.Count; got == nil {
t.Errorf("count not set; want count = 2")
} else {
got, diags := got.Value(nil)
assertNoDiagnostics(t, diags)
if want := cty.NumberIntVal(2); !want.RawEquals(got) {
t.Errorf("wrong count\ngot: %#v\nwant: %#v", got, want)
}
}
if got, want := mc.ForEach, hcl.Expression(nil); got != want {
// Shouldn't have any count because our test fixture only has
// for_each in the escaping block.
t.Errorf("wrong for_each\ngot: %#v\nwant: %#v", got, want)
}
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "normal", Required: true},
{Name: "count", Required: true},
{Name: "for_each", Required: true},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "normal_block"},
{Type: "lifecycle"},
{Type: "_"},
},
}
content, diags := mc.Config.Content(schema)
assertNoDiagnostics(t, diags)
normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want)
}
countVal, diags := content.Attributes["count"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) {
t.Errorf("wrong value for 'count'\ngot: %#v\nwant: %#v", got, want)
}
var gotBlockTypes []string
for _, block := range content.Blocks {
gotBlockTypes = append(gotBlockTypes, block.Type)
}
wantBlockTypes := []string{"normal_block", "lifecycle", "_"}
if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" {
t.Errorf("wrong block types\n%s", diff)
}
}
func TestEscapingBlockProvider(t *testing.T) {
parser := NewParser(nil)
mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/provider")
assertNoDiagnostics(t, diags)
if mod == nil {
t.Fatal("got nil root module; want non-nil")
}
pc := mod.ProviderConfigs["foo.bar"]
if pc == nil {
t.Fatal("no provider configuration named foo.bar")
}
if got, want := pc.Alias, "bar"; got != want {
t.Errorf("wrong alias\ngot: %#v\nwant: %#v", got, want)
}
schema := &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "normal", Required: true},
{Name: "alias", Required: true},
{Name: "version", Required: true},
},
}
content, diags := pc.Config.Content(schema)
assertNoDiagnostics(t, diags)
normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
t.Errorf("wrong value for 'normal'\ngot: %#v\nwant: %#v", got, want)
}
aliasVal, diags := content.Attributes["alias"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := aliasVal, cty.StringVal("not actually alias"); !want.RawEquals(got) {
t.Errorf("wrong value for 'alias'\ngot: %#v\nwant: %#v", got, want)
}
versionVal, diags := content.Attributes["version"].Expr.Value(nil)
assertNoDiagnostics(t, diags)
if got, want := versionVal, cty.StringVal("not actually version"); !want.RawEquals(got) {
t.Errorf("wrong value for 'version'\ngot: %#v\nwant: %#v", got, want)
}
}