github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/instances/expander_test.go (about)

     1  package instances
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/hashicorp/terraform/internal/addrs"
    12  )
    13  
    14  func TestExpander(t *testing.T) {
    15  	// Some module and resource addresses and values we'll use repeatedly below.
    16  	singleModuleAddr := addrs.ModuleCall{Name: "single"}
    17  	count2ModuleAddr := addrs.ModuleCall{Name: "count2"}
    18  	count0ModuleAddr := addrs.ModuleCall{Name: "count0"}
    19  	forEachModuleAddr := addrs.ModuleCall{Name: "for_each"}
    20  	singleResourceAddr := addrs.Resource{
    21  		Mode: addrs.ManagedResourceMode,
    22  		Type: "test",
    23  		Name: "single",
    24  	}
    25  	count2ResourceAddr := addrs.Resource{
    26  		Mode: addrs.ManagedResourceMode,
    27  		Type: "test",
    28  		Name: "count2",
    29  	}
    30  	count0ResourceAddr := addrs.Resource{
    31  		Mode: addrs.ManagedResourceMode,
    32  		Type: "test",
    33  		Name: "count0",
    34  	}
    35  	forEachResourceAddr := addrs.Resource{
    36  		Mode: addrs.ManagedResourceMode,
    37  		Type: "test",
    38  		Name: "for_each",
    39  	}
    40  	eachMap := map[string]cty.Value{
    41  		"a": cty.NumberIntVal(1),
    42  		"b": cty.NumberIntVal(2),
    43  	}
    44  
    45  	// In normal use, Expander would be called in the context of a graph
    46  	// traversal to ensure that information is registered/requested in the
    47  	// correct sequence, but to keep this test self-contained we'll just
    48  	// manually write out the steps here.
    49  	//
    50  	// The steps below are assuming a configuration tree like the following:
    51  	// - root module
    52  	//   - resource test.single with no count or for_each
    53  	//   - resource test.count2 with count = 2
    54  	//   - resource test.count0 with count = 0
    55  	//   - resource test.for_each with for_each = { a = 1, b = 2 }
    56  	//   - child module "single" with no count or for_each
    57  	//     - resource test.single with no count or for_each
    58  	//     - resource test.count2 with count = 2
    59  	//   - child module "count2" with count = 2
    60  	//     - resource test.single with no count or for_each
    61  	//     - resource test.count2 with count = 2
    62  	//     - child module "count2" with count = 2
    63  	//       - resource test.count2 with count = 2
    64  	//   - child module "count0" with count = 0
    65  	//     - resource test.single with no count or for_each
    66  	//   - child module for_each with for_each = { a = 1, b = 2 }
    67  	//     - resource test.single with no count or for_each
    68  	//     - resource test.count2 with count = 2
    69  
    70  	ex := NewExpander()
    71  
    72  	// We don't register the root module, because it's always implied to exist.
    73  	//
    74  	// Below we're going to use braces and indentation just to help visually
    75  	// reflect the tree structure from the tree in the above comment, in the
    76  	// hope that the following is easier to follow.
    77  	//
    78  	// The Expander API requires that we register containing modules before
    79  	// registering anything inside them, so we'll work through the above
    80  	// in a depth-first order in the registration steps that follow.
    81  	{
    82  		ex.SetResourceSingle(addrs.RootModuleInstance, singleResourceAddr)
    83  		ex.SetResourceCount(addrs.RootModuleInstance, count2ResourceAddr, 2)
    84  		ex.SetResourceCount(addrs.RootModuleInstance, count0ResourceAddr, 0)
    85  		ex.SetResourceForEach(addrs.RootModuleInstance, forEachResourceAddr, eachMap)
    86  
    87  		ex.SetModuleSingle(addrs.RootModuleInstance, singleModuleAddr)
    88  		{
    89  			// The single instance of the module
    90  			moduleInstanceAddr := addrs.RootModuleInstance.Child("single", addrs.NoKey)
    91  			ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
    92  			ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
    93  		}
    94  
    95  		ex.SetModuleCount(addrs.RootModuleInstance, count2ModuleAddr, 2)
    96  		for i1 := 0; i1 < 2; i1++ {
    97  			moduleInstanceAddr := addrs.RootModuleInstance.Child("count2", addrs.IntKey(i1))
    98  			ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
    99  			ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
   100  			ex.SetModuleCount(moduleInstanceAddr, count2ModuleAddr, 2)
   101  			for i2 := 0; i2 < 2; i2++ {
   102  				moduleInstanceAddr := moduleInstanceAddr.Child("count2", addrs.IntKey(i2))
   103  				ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
   104  			}
   105  		}
   106  
   107  		ex.SetModuleCount(addrs.RootModuleInstance, count0ModuleAddr, 0)
   108  		{
   109  			// There are no instances of module "count0", so our nested module
   110  			// would never actually get registered here: the expansion node
   111  			// for the resource would see that its containing module has no
   112  			// instances and so do nothing.
   113  		}
   114  
   115  		ex.SetModuleForEach(addrs.RootModuleInstance, forEachModuleAddr, eachMap)
   116  		for k := range eachMap {
   117  			moduleInstanceAddr := addrs.RootModuleInstance.Child("for_each", addrs.StringKey(k))
   118  			ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
   119  			ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
   120  		}
   121  	}
   122  
   123  	t.Run("root module", func(t *testing.T) {
   124  		// Requesting expansion of the root module doesn't really mean anything
   125  		// since it's always a singleton, but for consistency it should work.
   126  		got := ex.ExpandModule(addrs.RootModule)
   127  		want := []addrs.ModuleInstance{addrs.RootModuleInstance}
   128  		if diff := cmp.Diff(want, got); diff != "" {
   129  			t.Errorf("wrong result\n%s", diff)
   130  		}
   131  	})
   132  	t.Run("resource single", func(t *testing.T) {
   133  		got := ex.ExpandModuleResource(
   134  			addrs.RootModule,
   135  			singleResourceAddr,
   136  		)
   137  		want := []addrs.AbsResourceInstance{
   138  			mustAbsResourceInstanceAddr(`test.single`),
   139  		}
   140  		if diff := cmp.Diff(want, got); diff != "" {
   141  			t.Errorf("wrong result\n%s", diff)
   142  		}
   143  	})
   144  	t.Run("resource count2", func(t *testing.T) {
   145  		got := ex.ExpandModuleResource(
   146  			addrs.RootModule,
   147  			count2ResourceAddr,
   148  		)
   149  		want := []addrs.AbsResourceInstance{
   150  			mustAbsResourceInstanceAddr(`test.count2[0]`),
   151  			mustAbsResourceInstanceAddr(`test.count2[1]`),
   152  		}
   153  		if diff := cmp.Diff(want, got); diff != "" {
   154  			t.Errorf("wrong result\n%s", diff)
   155  		}
   156  	})
   157  	t.Run("resource count0", func(t *testing.T) {
   158  		got := ex.ExpandModuleResource(
   159  			addrs.RootModule,
   160  			count0ResourceAddr,
   161  		)
   162  		want := []addrs.AbsResourceInstance(nil)
   163  		if diff := cmp.Diff(want, got); diff != "" {
   164  			t.Errorf("wrong result\n%s", diff)
   165  		}
   166  	})
   167  	t.Run("resource for_each", func(t *testing.T) {
   168  		got := ex.ExpandModuleResource(
   169  			addrs.RootModule,
   170  			forEachResourceAddr,
   171  		)
   172  		want := []addrs.AbsResourceInstance{
   173  			mustAbsResourceInstanceAddr(`test.for_each["a"]`),
   174  			mustAbsResourceInstanceAddr(`test.for_each["b"]`),
   175  		}
   176  		if diff := cmp.Diff(want, got); diff != "" {
   177  			t.Errorf("wrong result\n%s", diff)
   178  		}
   179  	})
   180  	t.Run("module single", func(t *testing.T) {
   181  		got := ex.ExpandModule(addrs.RootModule.Child("single"))
   182  		want := []addrs.ModuleInstance{
   183  			mustModuleInstanceAddr(`module.single`),
   184  		}
   185  		if diff := cmp.Diff(want, got); diff != "" {
   186  			t.Errorf("wrong result\n%s", diff)
   187  		}
   188  	})
   189  	t.Run("module single resource single", func(t *testing.T) {
   190  		got := ex.ExpandModuleResource(
   191  			mustModuleAddr("single"),
   192  			singleResourceAddr,
   193  		)
   194  		want := []addrs.AbsResourceInstance{
   195  			mustAbsResourceInstanceAddr("module.single.test.single"),
   196  		}
   197  		if diff := cmp.Diff(want, got); diff != "" {
   198  			t.Errorf("wrong result\n%s", diff)
   199  		}
   200  	})
   201  	t.Run("module single resource count2", func(t *testing.T) {
   202  		got := ex.ExpandModuleResource(
   203  			mustModuleAddr(`single`),
   204  			count2ResourceAddr,
   205  		)
   206  		want := []addrs.AbsResourceInstance{
   207  			mustAbsResourceInstanceAddr(`module.single.test.count2[0]`),
   208  			mustAbsResourceInstanceAddr(`module.single.test.count2[1]`),
   209  		}
   210  		if diff := cmp.Diff(want, got); diff != "" {
   211  			t.Errorf("wrong result\n%s", diff)
   212  		}
   213  	})
   214  	t.Run("module count2", func(t *testing.T) {
   215  		got := ex.ExpandModule(mustModuleAddr(`count2`))
   216  		want := []addrs.ModuleInstance{
   217  			mustModuleInstanceAddr(`module.count2[0]`),
   218  			mustModuleInstanceAddr(`module.count2[1]`),
   219  		}
   220  		if diff := cmp.Diff(want, got); diff != "" {
   221  			t.Errorf("wrong result\n%s", diff)
   222  		}
   223  	})
   224  	t.Run("module count2 resource single", func(t *testing.T) {
   225  		got := ex.ExpandModuleResource(
   226  			mustModuleAddr(`count2`),
   227  			singleResourceAddr,
   228  		)
   229  		want := []addrs.AbsResourceInstance{
   230  			mustAbsResourceInstanceAddr(`module.count2[0].test.single`),
   231  			mustAbsResourceInstanceAddr(`module.count2[1].test.single`),
   232  		}
   233  		if diff := cmp.Diff(want, got); diff != "" {
   234  			t.Errorf("wrong result\n%s", diff)
   235  		}
   236  	})
   237  	t.Run("module count2 resource count2", func(t *testing.T) {
   238  		got := ex.ExpandModuleResource(
   239  			mustModuleAddr(`count2`),
   240  			count2ResourceAddr,
   241  		)
   242  		want := []addrs.AbsResourceInstance{
   243  			mustAbsResourceInstanceAddr(`module.count2[0].test.count2[0]`),
   244  			mustAbsResourceInstanceAddr(`module.count2[0].test.count2[1]`),
   245  			mustAbsResourceInstanceAddr(`module.count2[1].test.count2[0]`),
   246  			mustAbsResourceInstanceAddr(`module.count2[1].test.count2[1]`),
   247  		}
   248  		if diff := cmp.Diff(want, got); diff != "" {
   249  			t.Errorf("wrong result\n%s", diff)
   250  		}
   251  	})
   252  	t.Run("module count2 module count2", func(t *testing.T) {
   253  		got := ex.ExpandModule(mustModuleAddr(`count2.count2`))
   254  		want := []addrs.ModuleInstance{
   255  			mustModuleInstanceAddr(`module.count2[0].module.count2[0]`),
   256  			mustModuleInstanceAddr(`module.count2[0].module.count2[1]`),
   257  			mustModuleInstanceAddr(`module.count2[1].module.count2[0]`),
   258  			mustModuleInstanceAddr(`module.count2[1].module.count2[1]`),
   259  		}
   260  		if diff := cmp.Diff(want, got); diff != "" {
   261  			t.Errorf("wrong result\n%s", diff)
   262  		}
   263  	})
   264  	t.Run("module count2 resource count2 resource count2", func(t *testing.T) {
   265  		got := ex.ExpandModuleResource(
   266  			mustModuleAddr(`count2.count2`),
   267  			count2ResourceAddr,
   268  		)
   269  		want := []addrs.AbsResourceInstance{
   270  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[0]`),
   271  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[1]`),
   272  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`),
   273  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`),
   274  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[0]`),
   275  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[1]`),
   276  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[0]`),
   277  			mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[1]`),
   278  		}
   279  		if diff := cmp.Diff(want, got); diff != "" {
   280  			t.Errorf("wrong result\n%s", diff)
   281  		}
   282  	})
   283  	t.Run("module count2 resource count2 resource count2", func(t *testing.T) {
   284  		got := ex.ExpandResource(
   285  			count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.count2[0].module.count2[1]`)),
   286  		)
   287  		want := []addrs.AbsResourceInstance{
   288  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`),
   289  			mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`),
   290  		}
   291  		if diff := cmp.Diff(want, got); diff != "" {
   292  			t.Errorf("wrong result\n%s", diff)
   293  		}
   294  	})
   295  	t.Run("module count0", func(t *testing.T) {
   296  		got := ex.ExpandModule(mustModuleAddr(`count0`))
   297  		want := []addrs.ModuleInstance(nil)
   298  		if diff := cmp.Diff(want, got); diff != "" {
   299  			t.Errorf("wrong result\n%s", diff)
   300  		}
   301  	})
   302  	t.Run("module count0 resource single", func(t *testing.T) {
   303  		got := ex.ExpandModuleResource(
   304  			mustModuleAddr(`count0`),
   305  			singleResourceAddr,
   306  		)
   307  		// The containing module has zero instances, so therefore there
   308  		// are zero instances of this resource even though it doesn't have
   309  		// count = 0 set itself.
   310  		want := []addrs.AbsResourceInstance(nil)
   311  		if diff := cmp.Diff(want, got); diff != "" {
   312  			t.Errorf("wrong result\n%s", diff)
   313  		}
   314  	})
   315  	t.Run("module for_each", func(t *testing.T) {
   316  		got := ex.ExpandModule(mustModuleAddr(`for_each`))
   317  		want := []addrs.ModuleInstance{
   318  			mustModuleInstanceAddr(`module.for_each["a"]`),
   319  			mustModuleInstanceAddr(`module.for_each["b"]`),
   320  		}
   321  		if diff := cmp.Diff(want, got); diff != "" {
   322  			t.Errorf("wrong result\n%s", diff)
   323  		}
   324  	})
   325  	t.Run("module for_each resource single", func(t *testing.T) {
   326  		got := ex.ExpandModuleResource(
   327  			mustModuleAddr(`for_each`),
   328  			singleResourceAddr,
   329  		)
   330  		want := []addrs.AbsResourceInstance{
   331  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`),
   332  			mustAbsResourceInstanceAddr(`module.for_each["b"].test.single`),
   333  		}
   334  		if diff := cmp.Diff(want, got); diff != "" {
   335  			t.Errorf("wrong result\n%s", diff)
   336  		}
   337  	})
   338  	t.Run("module for_each resource count2", func(t *testing.T) {
   339  		got := ex.ExpandModuleResource(
   340  			mustModuleAddr(`for_each`),
   341  			count2ResourceAddr,
   342  		)
   343  		want := []addrs.AbsResourceInstance{
   344  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`),
   345  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
   346  			mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[0]`),
   347  			mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[1]`),
   348  		}
   349  		if diff := cmp.Diff(want, got); diff != "" {
   350  			t.Errorf("wrong result\n%s", diff)
   351  		}
   352  	})
   353  	t.Run("module for_each resource count2", func(t *testing.T) {
   354  		got := ex.ExpandResource(
   355  			count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.for_each["a"]`)),
   356  		)
   357  		want := []addrs.AbsResourceInstance{
   358  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`),
   359  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
   360  		}
   361  		if diff := cmp.Diff(want, got); diff != "" {
   362  			t.Errorf("wrong result\n%s", diff)
   363  		}
   364  	})
   365  
   366  	t.Run(`module.for_each["b"] repetitiondata`, func(t *testing.T) {
   367  		got := ex.GetModuleInstanceRepetitionData(
   368  			mustModuleInstanceAddr(`module.for_each["b"]`),
   369  		)
   370  		want := RepetitionData{
   371  			EachKey:   cty.StringVal("b"),
   372  			EachValue: cty.NumberIntVal(2),
   373  		}
   374  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   375  			t.Errorf("wrong result\n%s", diff)
   376  		}
   377  	})
   378  	t.Run(`module.count2[0].module.count2[1] repetitiondata`, func(t *testing.T) {
   379  		got := ex.GetModuleInstanceRepetitionData(
   380  			mustModuleInstanceAddr(`module.count2[0].module.count2[1]`),
   381  		)
   382  		want := RepetitionData{
   383  			CountIndex: cty.NumberIntVal(1),
   384  		}
   385  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   386  			t.Errorf("wrong result\n%s", diff)
   387  		}
   388  	})
   389  	t.Run(`module.for_each["a"] repetitiondata`, func(t *testing.T) {
   390  		got := ex.GetModuleInstanceRepetitionData(
   391  			mustModuleInstanceAddr(`module.for_each["a"]`),
   392  		)
   393  		want := RepetitionData{
   394  			EachKey:   cty.StringVal("a"),
   395  			EachValue: cty.NumberIntVal(1),
   396  		}
   397  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   398  			t.Errorf("wrong result\n%s", diff)
   399  		}
   400  	})
   401  
   402  	t.Run(`test.for_each["a"] repetitiondata`, func(t *testing.T) {
   403  		got := ex.GetResourceInstanceRepetitionData(
   404  			mustAbsResourceInstanceAddr(`test.for_each["a"]`),
   405  		)
   406  		want := RepetitionData{
   407  			EachKey:   cty.StringVal("a"),
   408  			EachValue: cty.NumberIntVal(1),
   409  		}
   410  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   411  			t.Errorf("wrong result\n%s", diff)
   412  		}
   413  	})
   414  	t.Run(`module.for_each["a"].test.single repetitiondata`, func(t *testing.T) {
   415  		got := ex.GetResourceInstanceRepetitionData(
   416  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`),
   417  		)
   418  		want := RepetitionData{}
   419  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   420  			t.Errorf("wrong result\n%s", diff)
   421  		}
   422  	})
   423  	t.Run(`module.for_each["a"].test.count2[1] repetitiondata`, func(t *testing.T) {
   424  		got := ex.GetResourceInstanceRepetitionData(
   425  			mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
   426  		)
   427  		want := RepetitionData{
   428  			CountIndex: cty.NumberIntVal(1),
   429  		}
   430  		if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
   431  			t.Errorf("wrong result\n%s", diff)
   432  		}
   433  	})
   434  }
   435  
   436  func mustAbsResourceInstanceAddr(str string) addrs.AbsResourceInstance {
   437  	addr, diags := addrs.ParseAbsResourceInstanceStr(str)
   438  	if diags.HasErrors() {
   439  		panic(fmt.Sprintf("invalid absolute resource instance address: %s", diags.Err()))
   440  	}
   441  	return addr
   442  }
   443  
   444  func mustModuleAddr(str string) addrs.Module {
   445  	if len(str) == 0 {
   446  		return addrs.RootModule
   447  	}
   448  	// We don't have a real parser for these because they don't appear in the
   449  	// language anywhere, but this interpretation mimics the format we
   450  	// produce from the String method on addrs.Module.
   451  	parts := strings.Split(str, ".")
   452  	return addrs.Module(parts)
   453  }
   454  
   455  func mustModuleInstanceAddr(str string) addrs.ModuleInstance {
   456  	if len(str) == 0 {
   457  		return addrs.RootModuleInstance
   458  	}
   459  	addr, diags := addrs.ParseModuleInstanceStr(str)
   460  	if diags.HasErrors() {
   461  		panic(fmt.Sprintf("invalid module instance address: %s", diags.Err()))
   462  	}
   463  	return addr
   464  }
   465  
   466  func valueEquals(a, b cty.Value) bool {
   467  	if a == cty.NilVal || b == cty.NilVal {
   468  		return a == b
   469  	}
   470  	return a.RawEquals(b)
   471  }