opentofu/internal/configs/escaping_blocks_test.go
Martin Atkins 31349a9c3a Move configs/ to internal/configs/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

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