kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/escaping_blocks_test.go (about)

     1  package configs
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/google/go-cmp/cmp"
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/zclconf/go-cty/cty"
     9  )
    10  
    11  // "Escaping Blocks" are a special mechanism we have inside our block types
    12  // that accept a mixture of meta-arguments and externally-defined arguments,
    13  // which allow an author to force particular argument names to be interpreted
    14  // as externally-defined even if they have the same name as a meta-argument.
    15  //
    16  // An escaping block is a block with the special type name "_" (just an
    17  // underscore), and is allowed at the top-level of any resource, data, or
    18  // module block. It intentionally has a rather "odd" look so that it stands
    19  // out as something special and rare.
    20  //
    21  // This is not something we expect to see used a lot, but it's an important
    22  // part of our strategy to evolve the Terraform language in future using
    23  // editions, so that later editions can define new meta-arguments without
    24  // blocking access to externally-defined arguments of the same name.
    25  //
    26  // We should still define new meta-arguments with care to avoid squatting on
    27  // commonly-used names, but we can't see all modules and all providers in
    28  // the world and so this is an escape hatch for edge cases. Module migration
    29  // tools for future editions that define new meta-arguments should detect
    30  // collisions and automatically migrate existing arguments into an escaping
    31  // block.
    32  
    33  func TestEscapingBlockResource(t *testing.T) {
    34  	// (this also tests escaping blocks in provisioner blocks, because
    35  	// they only appear nested inside resource blocks.)
    36  
    37  	parser := NewParser(nil)
    38  	mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/resource")
    39  	assertNoDiagnostics(t, diags)
    40  	if mod == nil {
    41  		t.Fatal("got nil root module; want non-nil")
    42  	}
    43  
    44  	rc := mod.ManagedResources["foo.bar"]
    45  	if rc == nil {
    46  		t.Fatal("no managed resource named foo.bar")
    47  	}
    48  
    49  	t.Run("resource body", func(t *testing.T) {
    50  		if got := rc.Count; got == nil {
    51  			t.Errorf("count not set; want count = 2")
    52  		} else {
    53  			got, diags := got.Value(nil)
    54  			assertNoDiagnostics(t, diags)
    55  			if want := cty.NumberIntVal(2); !want.RawEquals(got) {
    56  				t.Errorf("wrong count\ngot:  %#v\nwant: %#v", got, want)
    57  			}
    58  		}
    59  		if got, want := rc.ForEach, hcl.Expression(nil); got != want {
    60  			// Shouldn't have any count because our test fixture only has
    61  			// for_each in the escaping block.
    62  			t.Errorf("wrong for_each\ngot:  %#v\nwant: %#v", got, want)
    63  		}
    64  
    65  		schema := &hcl.BodySchema{
    66  			Attributes: []hcl.AttributeSchema{
    67  				{Name: "normal", Required: true},
    68  				{Name: "count", Required: true},
    69  				{Name: "for_each", Required: true},
    70  			},
    71  			Blocks: []hcl.BlockHeaderSchema{
    72  				{Type: "normal_block"},
    73  				{Type: "lifecycle"},
    74  				{Type: "_"},
    75  			},
    76  		}
    77  		content, diags := rc.Config.Content(schema)
    78  		assertNoDiagnostics(t, diags)
    79  
    80  		normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
    81  		assertNoDiagnostics(t, diags)
    82  		if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
    83  			t.Errorf("wrong value for 'normal'\ngot:  %#v\nwant: %#v", got, want)
    84  		}
    85  
    86  		countVal, diags := content.Attributes["count"].Expr.Value(nil)
    87  		assertNoDiagnostics(t, diags)
    88  		if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) {
    89  			t.Errorf("wrong value for 'count'\ngot:  %#v\nwant: %#v", got, want)
    90  		}
    91  
    92  		var gotBlockTypes []string
    93  		for _, block := range content.Blocks {
    94  			gotBlockTypes = append(gotBlockTypes, block.Type)
    95  		}
    96  		wantBlockTypes := []string{"normal_block", "lifecycle", "_"}
    97  		if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" {
    98  			t.Errorf("wrong block types\n%s", diff)
    99  		}
   100  	})
   101  	t.Run("provisioner body", func(t *testing.T) {
   102  		if got, want := len(rc.Managed.Provisioners), 1; got != want {
   103  			t.Fatalf("wrong number of provisioners %d; want %d", got, want)
   104  		}
   105  		pc := rc.Managed.Provisioners[0]
   106  
   107  		schema := &hcl.BodySchema{
   108  			Attributes: []hcl.AttributeSchema{
   109  				{Name: "when", Required: true},
   110  				{Name: "normal", Required: true},
   111  			},
   112  			Blocks: []hcl.BlockHeaderSchema{
   113  				{Type: "normal_block"},
   114  				{Type: "lifecycle"},
   115  				{Type: "_"},
   116  			},
   117  		}
   118  		content, diags := pc.Config.Content(schema)
   119  		assertNoDiagnostics(t, diags)
   120  
   121  		normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
   122  		assertNoDiagnostics(t, diags)
   123  		if got, want := normalVal, cty.StringVal("yep"); !want.RawEquals(got) {
   124  			t.Errorf("wrong value for 'normal'\ngot:  %#v\nwant: %#v", got, want)
   125  		}
   126  		whenVal, diags := content.Attributes["when"].Expr.Value(nil)
   127  		assertNoDiagnostics(t, diags)
   128  		if got, want := whenVal, cty.StringVal("hell freezes over"); !want.RawEquals(got) {
   129  			t.Errorf("wrong value for 'normal'\ngot:  %#v\nwant: %#v", got, want)
   130  		}
   131  	})
   132  }
   133  
   134  func TestEscapingBlockData(t *testing.T) {
   135  	parser := NewParser(nil)
   136  	mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/data")
   137  	assertNoDiagnostics(t, diags)
   138  	if mod == nil {
   139  		t.Fatal("got nil root module; want non-nil")
   140  	}
   141  
   142  	rc := mod.DataResources["data.foo.bar"]
   143  	if rc == nil {
   144  		t.Fatal("no data resource named data.foo.bar")
   145  	}
   146  
   147  	if got := rc.Count; got == nil {
   148  		t.Errorf("count not set; want count = 2")
   149  	} else {
   150  		got, diags := got.Value(nil)
   151  		assertNoDiagnostics(t, diags)
   152  		if want := cty.NumberIntVal(2); !want.RawEquals(got) {
   153  			t.Errorf("wrong count\ngot:  %#v\nwant: %#v", got, want)
   154  		}
   155  	}
   156  	if got, want := rc.ForEach, hcl.Expression(nil); got != want {
   157  		// Shouldn't have any count because our test fixture only has
   158  		// for_each in the escaping block.
   159  		t.Errorf("wrong for_each\ngot:  %#v\nwant: %#v", got, want)
   160  	}
   161  
   162  	schema := &hcl.BodySchema{
   163  		Attributes: []hcl.AttributeSchema{
   164  			{Name: "normal", Required: true},
   165  			{Name: "count", Required: true},
   166  			{Name: "for_each", Required: true},
   167  		},
   168  		Blocks: []hcl.BlockHeaderSchema{
   169  			{Type: "normal_block"},
   170  			{Type: "lifecycle"},
   171  			{Type: "_"},
   172  		},
   173  	}
   174  	content, diags := rc.Config.Content(schema)
   175  	assertNoDiagnostics(t, diags)
   176  
   177  	normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
   178  	assertNoDiagnostics(t, diags)
   179  	if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
   180  		t.Errorf("wrong value for 'normal'\ngot:  %#v\nwant: %#v", got, want)
   181  	}
   182  
   183  	countVal, diags := content.Attributes["count"].Expr.Value(nil)
   184  	assertNoDiagnostics(t, diags)
   185  	if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) {
   186  		t.Errorf("wrong value for 'count'\ngot:  %#v\nwant: %#v", got, want)
   187  	}
   188  
   189  	var gotBlockTypes []string
   190  	for _, block := range content.Blocks {
   191  		gotBlockTypes = append(gotBlockTypes, block.Type)
   192  	}
   193  	wantBlockTypes := []string{"normal_block", "lifecycle", "_"}
   194  	if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" {
   195  		t.Errorf("wrong block types\n%s", diff)
   196  	}
   197  
   198  }
   199  
   200  func TestEscapingBlockModule(t *testing.T) {
   201  	parser := NewParser(nil)
   202  	mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/module")
   203  	assertNoDiagnostics(t, diags)
   204  	if mod == nil {
   205  		t.Fatal("got nil root module; want non-nil")
   206  	}
   207  
   208  	mc := mod.ModuleCalls["foo"]
   209  	if mc == nil {
   210  		t.Fatal("no module call named foo")
   211  	}
   212  
   213  	if got := mc.Count; got == nil {
   214  		t.Errorf("count not set; want count = 2")
   215  	} else {
   216  		got, diags := got.Value(nil)
   217  		assertNoDiagnostics(t, diags)
   218  		if want := cty.NumberIntVal(2); !want.RawEquals(got) {
   219  			t.Errorf("wrong count\ngot:  %#v\nwant: %#v", got, want)
   220  		}
   221  	}
   222  	if got, want := mc.ForEach, hcl.Expression(nil); got != want {
   223  		// Shouldn't have any count because our test fixture only has
   224  		// for_each in the escaping block.
   225  		t.Errorf("wrong for_each\ngot:  %#v\nwant: %#v", got, want)
   226  	}
   227  
   228  	schema := &hcl.BodySchema{
   229  		Attributes: []hcl.AttributeSchema{
   230  			{Name: "normal", Required: true},
   231  			{Name: "count", Required: true},
   232  			{Name: "for_each", Required: true},
   233  		},
   234  		Blocks: []hcl.BlockHeaderSchema{
   235  			{Type: "normal_block"},
   236  			{Type: "lifecycle"},
   237  			{Type: "_"},
   238  		},
   239  	}
   240  	content, diags := mc.Config.Content(schema)
   241  	assertNoDiagnostics(t, diags)
   242  
   243  	normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
   244  	assertNoDiagnostics(t, diags)
   245  	if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
   246  		t.Errorf("wrong value for 'normal'\ngot:  %#v\nwant: %#v", got, want)
   247  	}
   248  
   249  	countVal, diags := content.Attributes["count"].Expr.Value(nil)
   250  	assertNoDiagnostics(t, diags)
   251  	if got, want := countVal, cty.StringVal("not actually count"); !want.RawEquals(got) {
   252  		t.Errorf("wrong value for 'count'\ngot:  %#v\nwant: %#v", got, want)
   253  	}
   254  
   255  	var gotBlockTypes []string
   256  	for _, block := range content.Blocks {
   257  		gotBlockTypes = append(gotBlockTypes, block.Type)
   258  	}
   259  	wantBlockTypes := []string{"normal_block", "lifecycle", "_"}
   260  	if diff := cmp.Diff(gotBlockTypes, wantBlockTypes); diff != "" {
   261  		t.Errorf("wrong block types\n%s", diff)
   262  	}
   263  
   264  }
   265  
   266  func TestEscapingBlockProvider(t *testing.T) {
   267  	parser := NewParser(nil)
   268  	mod, diags := parser.LoadConfigDir("testdata/escaping-blocks/provider")
   269  	assertNoDiagnostics(t, diags)
   270  	if mod == nil {
   271  		t.Fatal("got nil root module; want non-nil")
   272  	}
   273  
   274  	pc := mod.ProviderConfigs["foo.bar"]
   275  	if pc == nil {
   276  		t.Fatal("no provider configuration named foo.bar")
   277  	}
   278  
   279  	if got, want := pc.Alias, "bar"; got != want {
   280  		t.Errorf("wrong alias\ngot:  %#v\nwant: %#v", got, want)
   281  	}
   282  
   283  	schema := &hcl.BodySchema{
   284  		Attributes: []hcl.AttributeSchema{
   285  			{Name: "normal", Required: true},
   286  			{Name: "alias", Required: true},
   287  			{Name: "version", Required: true},
   288  		},
   289  	}
   290  	content, diags := pc.Config.Content(schema)
   291  	assertNoDiagnostics(t, diags)
   292  
   293  	normalVal, diags := content.Attributes["normal"].Expr.Value(nil)
   294  	assertNoDiagnostics(t, diags)
   295  	if got, want := normalVal, cty.StringVal("yes"); !want.RawEquals(got) {
   296  		t.Errorf("wrong value for 'normal'\ngot:  %#v\nwant: %#v", got, want)
   297  	}
   298  	aliasVal, diags := content.Attributes["alias"].Expr.Value(nil)
   299  	assertNoDiagnostics(t, diags)
   300  	if got, want := aliasVal, cty.StringVal("not actually alias"); !want.RawEquals(got) {
   301  		t.Errorf("wrong value for 'alias'\ngot:  %#v\nwant: %#v", got, want)
   302  	}
   303  	versionVal, diags := content.Attributes["version"].Expr.Value(nil)
   304  	assertNoDiagnostics(t, diags)
   305  	if got, want := versionVal, cty.StringVal("not actually version"); !want.RawEquals(got) {
   306  		t.Errorf("wrong value for 'version'\ngot:  %#v\nwant: %#v", got, want)
   307  	}
   308  }