github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/terraform/resource_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package terraform
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/terramate-io/tf/configs/configschema"
    12  	"github.com/zclconf/go-cty/cty"
    13  
    14  	"github.com/mitchellh/reflectwalk"
    15  	"github.com/terramate-io/tf/configs/hcl2shim"
    16  )
    17  
    18  func TestResourceConfigGet(t *testing.T) {
    19  	fooStringSchema := &configschema.Block{
    20  		Attributes: map[string]*configschema.Attribute{
    21  			"foo": {Type: cty.String, Optional: true},
    22  		},
    23  	}
    24  	fooListSchema := &configschema.Block{
    25  		Attributes: map[string]*configschema.Attribute{
    26  			"foo": {Type: cty.List(cty.Number), Optional: true},
    27  		},
    28  	}
    29  
    30  	cases := []struct {
    31  		Config cty.Value
    32  		Schema *configschema.Block
    33  		Key    string
    34  		Value  interface{}
    35  	}{
    36  		{
    37  			Config: cty.ObjectVal(map[string]cty.Value{
    38  				"foo": cty.StringVal("bar"),
    39  			}),
    40  			Schema: fooStringSchema,
    41  			Key:    "foo",
    42  			Value:  "bar",
    43  		},
    44  
    45  		{
    46  			Config: cty.ObjectVal(map[string]cty.Value{
    47  				"foo": cty.UnknownVal(cty.String),
    48  			}),
    49  			Schema: fooStringSchema,
    50  			Key:    "foo",
    51  			Value:  hcl2shim.UnknownVariableValue,
    52  		},
    53  
    54  		{
    55  			Config: cty.ObjectVal(map[string]cty.Value{
    56  				"foo": cty.ListVal([]cty.Value{
    57  					cty.NumberIntVal(1),
    58  					cty.NumberIntVal(2),
    59  					cty.NumberIntVal(5),
    60  				}),
    61  			}),
    62  			Schema: fooListSchema,
    63  			Key:    "foo.0",
    64  			Value:  1,
    65  		},
    66  
    67  		{
    68  			Config: cty.ObjectVal(map[string]cty.Value{
    69  				"foo": cty.ListVal([]cty.Value{
    70  					cty.NumberIntVal(1),
    71  					cty.NumberIntVal(2),
    72  					cty.NumberIntVal(5),
    73  				}),
    74  			}),
    75  			Schema: fooListSchema,
    76  			Key:    "foo.5",
    77  			Value:  nil,
    78  		},
    79  
    80  		{
    81  			Config: cty.ObjectVal(map[string]cty.Value{
    82  				"foo": cty.ListVal([]cty.Value{
    83  					cty.NumberIntVal(1),
    84  					cty.NumberIntVal(2),
    85  					cty.NumberIntVal(5),
    86  				}),
    87  			}),
    88  			Schema: fooListSchema,
    89  			Key:    "foo.-1",
    90  			Value:  nil,
    91  		},
    92  
    93  		// get from map
    94  		{
    95  			Config: cty.ObjectVal(map[string]cty.Value{
    96  				"mapname": cty.ListVal([]cty.Value{
    97  					cty.MapVal(map[string]cty.Value{
    98  						"key": cty.NumberIntVal(1),
    99  					}),
   100  				}),
   101  			}),
   102  			Schema: &configschema.Block{
   103  				Attributes: map[string]*configschema.Attribute{
   104  					"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
   105  				},
   106  			},
   107  			Key:   "mapname.0.key",
   108  			Value: 1,
   109  		},
   110  
   111  		// get from map with dot in key
   112  		{
   113  			Config: cty.ObjectVal(map[string]cty.Value{
   114  				"mapname": cty.ListVal([]cty.Value{
   115  					cty.MapVal(map[string]cty.Value{
   116  						"key.name": cty.NumberIntVal(1),
   117  					}),
   118  				}),
   119  			}),
   120  			Schema: &configschema.Block{
   121  				Attributes: map[string]*configschema.Attribute{
   122  					"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
   123  				},
   124  			},
   125  			Key:   "mapname.0.key.name",
   126  			Value: 1,
   127  		},
   128  
   129  		// get from map with overlapping key names
   130  		{
   131  			Config: cty.ObjectVal(map[string]cty.Value{
   132  				"mapname": cty.ListVal([]cty.Value{
   133  					cty.MapVal(map[string]cty.Value{
   134  						"key.name":   cty.NumberIntVal(1),
   135  						"key.name.2": cty.NumberIntVal(2),
   136  					}),
   137  				}),
   138  			}),
   139  			Schema: &configschema.Block{
   140  				Attributes: map[string]*configschema.Attribute{
   141  					"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
   142  				},
   143  			},
   144  			Key:   "mapname.0.key.name.2",
   145  			Value: 2,
   146  		},
   147  		{
   148  			Config: cty.ObjectVal(map[string]cty.Value{
   149  				"mapname": cty.ListVal([]cty.Value{
   150  					cty.MapVal(map[string]cty.Value{
   151  						"key.name":     cty.NumberIntVal(1),
   152  						"key.name.foo": cty.NumberIntVal(2),
   153  					}),
   154  				}),
   155  			}),
   156  			Schema: &configschema.Block{
   157  				Attributes: map[string]*configschema.Attribute{
   158  					"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
   159  				},
   160  			},
   161  			Key:   "mapname.0.key.name",
   162  			Value: 1,
   163  		},
   164  		{
   165  			Config: cty.ObjectVal(map[string]cty.Value{
   166  				"mapname": cty.ListVal([]cty.Value{
   167  					cty.MapVal(map[string]cty.Value{
   168  						"listkey": cty.ListVal([]cty.Value{
   169  							cty.MapVal(map[string]cty.Value{
   170  								"key": cty.NumberIntVal(3),
   171  							}),
   172  						}),
   173  					}),
   174  				}),
   175  			}),
   176  			Schema: &configschema.Block{
   177  				Attributes: map[string]*configschema.Attribute{
   178  					"mapname": {Type: cty.List(cty.Map(cty.List(cty.Map(cty.Number)))), Optional: true},
   179  				},
   180  			},
   181  			Key:   "mapname.0.listkey.0.key",
   182  			Value: 3,
   183  		},
   184  	}
   185  
   186  	for i, tc := range cases {
   187  		rc := NewResourceConfigShimmed(tc.Config, tc.Schema)
   188  
   189  		// Test getting a key
   190  		t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
   191  			v, ok := rc.Get(tc.Key)
   192  			if ok && v == nil {
   193  				t.Fatal("(nil, true) returned from Get")
   194  			}
   195  
   196  			if !reflect.DeepEqual(v, tc.Value) {
   197  				t.Fatalf("%d bad: %#v", i, v)
   198  			}
   199  		})
   200  
   201  		// Test copying and equality
   202  		t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) {
   203  			copy := rc.DeepCopy()
   204  			if !reflect.DeepEqual(copy, rc) {
   205  				t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc)
   206  			}
   207  
   208  			if !copy.Equal(rc) {
   209  				t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc)
   210  			}
   211  			if !rc.Equal(copy) {
   212  				t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc)
   213  			}
   214  		})
   215  	}
   216  }
   217  
   218  func TestResourceConfigDeepCopy_nil(t *testing.T) {
   219  	var nilRc *ResourceConfig
   220  	actual := nilRc.DeepCopy()
   221  	if actual != nil {
   222  		t.Fatalf("bad: %#v", actual)
   223  	}
   224  }
   225  
   226  func TestResourceConfigDeepCopy_nilComputed(t *testing.T) {
   227  	rc := &ResourceConfig{}
   228  	actual := rc.DeepCopy()
   229  	if actual.ComputedKeys != nil {
   230  		t.Fatalf("bad: %#v", actual)
   231  	}
   232  }
   233  
   234  func TestResourceConfigEqual_nil(t *testing.T) {
   235  	var nilRc *ResourceConfig
   236  	notNil := NewResourceConfigShimmed(cty.EmptyObjectVal, &configschema.Block{})
   237  
   238  	if nilRc.Equal(notNil) {
   239  		t.Fatal("should not be equal")
   240  	}
   241  
   242  	if notNil.Equal(nilRc) {
   243  		t.Fatal("should not be equal")
   244  	}
   245  }
   246  
   247  func TestResourceConfigEqual_computedKeyOrder(t *testing.T) {
   248  	v := cty.ObjectVal(map[string]cty.Value{
   249  		"foo": cty.UnknownVal(cty.String),
   250  	})
   251  	schema := &configschema.Block{
   252  		Attributes: map[string]*configschema.Attribute{
   253  			"foo": {Type: cty.String, Optional: true},
   254  		},
   255  	}
   256  	rc := NewResourceConfigShimmed(v, schema)
   257  	rc2 := NewResourceConfigShimmed(v, schema)
   258  
   259  	// Set the computed keys manually to force ordering to differ
   260  	rc.ComputedKeys = []string{"foo", "bar"}
   261  	rc2.ComputedKeys = []string{"bar", "foo"}
   262  
   263  	if !rc.Equal(rc2) {
   264  		t.Fatal("should be equal")
   265  	}
   266  }
   267  
   268  func TestUnknownCheckWalker(t *testing.T) {
   269  	cases := []struct {
   270  		Name   string
   271  		Input  interface{}
   272  		Result bool
   273  	}{
   274  		{
   275  			"primitive",
   276  			42,
   277  			false,
   278  		},
   279  
   280  		{
   281  			"primitive computed",
   282  			hcl2shim.UnknownVariableValue,
   283  			true,
   284  		},
   285  
   286  		{
   287  			"list",
   288  			[]interface{}{"foo", hcl2shim.UnknownVariableValue},
   289  			true,
   290  		},
   291  
   292  		{
   293  			"nested list",
   294  			[]interface{}{
   295  				"foo",
   296  				[]interface{}{hcl2shim.UnknownVariableValue},
   297  			},
   298  			true,
   299  		},
   300  	}
   301  
   302  	for i, tc := range cases {
   303  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
   304  			var w unknownCheckWalker
   305  			if err := reflectwalk.Walk(tc.Input, &w); err != nil {
   306  				t.Fatalf("err: %s", err)
   307  			}
   308  
   309  			if w.Unknown != tc.Result {
   310  				t.Fatalf("bad: %v", w.Unknown)
   311  			}
   312  		})
   313  	}
   314  }
   315  
   316  func TestNewResourceConfigShimmed(t *testing.T) {
   317  	for _, tc := range []struct {
   318  		Name     string
   319  		Val      cty.Value
   320  		Schema   *configschema.Block
   321  		Expected *ResourceConfig
   322  	}{
   323  		{
   324  			Name: "empty object",
   325  			Val:  cty.NullVal(cty.EmptyObject),
   326  			Schema: &configschema.Block{
   327  				Attributes: map[string]*configschema.Attribute{
   328  					"foo": {
   329  						Type:     cty.String,
   330  						Optional: true,
   331  					},
   332  				},
   333  			},
   334  			Expected: &ResourceConfig{
   335  				Raw:    map[string]interface{}{},
   336  				Config: map[string]interface{}{},
   337  			},
   338  		},
   339  		{
   340  			Name: "basic",
   341  			Val: cty.ObjectVal(map[string]cty.Value{
   342  				"foo": cty.StringVal("bar"),
   343  			}),
   344  			Schema: &configschema.Block{
   345  				Attributes: map[string]*configschema.Attribute{
   346  					"foo": {
   347  						Type:     cty.String,
   348  						Optional: true,
   349  					},
   350  				},
   351  			},
   352  			Expected: &ResourceConfig{
   353  				Raw: map[string]interface{}{
   354  					"foo": "bar",
   355  				},
   356  				Config: map[string]interface{}{
   357  					"foo": "bar",
   358  				},
   359  			},
   360  		},
   361  		{
   362  			Name: "null string",
   363  			Val: cty.ObjectVal(map[string]cty.Value{
   364  				"foo": cty.NullVal(cty.String),
   365  			}),
   366  			Schema: &configschema.Block{
   367  				Attributes: map[string]*configschema.Attribute{
   368  					"foo": {
   369  						Type:     cty.String,
   370  						Optional: true,
   371  					},
   372  				},
   373  			},
   374  			Expected: &ResourceConfig{
   375  				Raw:    map[string]interface{}{},
   376  				Config: map[string]interface{}{},
   377  			},
   378  		},
   379  		{
   380  			Name: "unknown string",
   381  			Val: cty.ObjectVal(map[string]cty.Value{
   382  				"foo": cty.UnknownVal(cty.String),
   383  			}),
   384  			Schema: &configschema.Block{
   385  				Attributes: map[string]*configschema.Attribute{
   386  					"foo": {
   387  						Type:     cty.String,
   388  						Optional: true,
   389  					},
   390  				},
   391  			},
   392  			Expected: &ResourceConfig{
   393  				ComputedKeys: []string{"foo"},
   394  				Raw: map[string]interface{}{
   395  					"foo": hcl2shim.UnknownVariableValue,
   396  				},
   397  				Config: map[string]interface{}{
   398  					"foo": hcl2shim.UnknownVariableValue,
   399  				},
   400  			},
   401  		},
   402  		{
   403  			Name: "unknown collections",
   404  			Val: cty.ObjectVal(map[string]cty.Value{
   405  				"bar": cty.UnknownVal(cty.Map(cty.String)),
   406  				"baz": cty.UnknownVal(cty.List(cty.String)),
   407  			}),
   408  			Schema: &configschema.Block{
   409  				Attributes: map[string]*configschema.Attribute{
   410  					"bar": {
   411  						Type:     cty.Map(cty.String),
   412  						Required: true,
   413  					},
   414  					"baz": {
   415  						Type:     cty.List(cty.String),
   416  						Optional: true,
   417  					},
   418  				},
   419  			},
   420  			Expected: &ResourceConfig{
   421  				ComputedKeys: []string{"bar", "baz"},
   422  				Raw: map[string]interface{}{
   423  					"bar": hcl2shim.UnknownVariableValue,
   424  					"baz": hcl2shim.UnknownVariableValue,
   425  				},
   426  				Config: map[string]interface{}{
   427  					"bar": hcl2shim.UnknownVariableValue,
   428  					"baz": hcl2shim.UnknownVariableValue,
   429  				},
   430  			},
   431  		},
   432  		{
   433  			Name: "null collections",
   434  			Val: cty.ObjectVal(map[string]cty.Value{
   435  				"bar": cty.NullVal(cty.Map(cty.String)),
   436  				"baz": cty.NullVal(cty.List(cty.String)),
   437  			}),
   438  			Schema: &configschema.Block{
   439  				Attributes: map[string]*configschema.Attribute{
   440  					"bar": {
   441  						Type:     cty.Map(cty.String),
   442  						Required: true,
   443  					},
   444  					"baz": {
   445  						Type:     cty.List(cty.String),
   446  						Optional: true,
   447  					},
   448  				},
   449  			},
   450  			Expected: &ResourceConfig{
   451  				Raw:    map[string]interface{}{},
   452  				Config: map[string]interface{}{},
   453  			},
   454  		},
   455  		{
   456  			Name: "unknown blocks",
   457  			Val: cty.ObjectVal(map[string]cty.Value{
   458  				"bar": cty.UnknownVal(cty.Map(cty.String)),
   459  				"baz": cty.UnknownVal(cty.List(cty.String)),
   460  			}),
   461  			Schema: &configschema.Block{
   462  				BlockTypes: map[string]*configschema.NestedBlock{
   463  					"bar": {
   464  						Block:   configschema.Block{},
   465  						Nesting: configschema.NestingList,
   466  					},
   467  					"baz": {
   468  						Block:   configschema.Block{},
   469  						Nesting: configschema.NestingSet,
   470  					},
   471  				},
   472  			},
   473  			Expected: &ResourceConfig{
   474  				ComputedKeys: []string{"bar", "baz"},
   475  				Raw: map[string]interface{}{
   476  					"bar": hcl2shim.UnknownVariableValue,
   477  					"baz": hcl2shim.UnknownVariableValue,
   478  				},
   479  				Config: map[string]interface{}{
   480  					"bar": hcl2shim.UnknownVariableValue,
   481  					"baz": hcl2shim.UnknownVariableValue,
   482  				},
   483  			},
   484  		},
   485  		{
   486  			Name: "unknown in nested blocks",
   487  			Val: cty.ObjectVal(map[string]cty.Value{
   488  				"bar": cty.ListVal([]cty.Value{
   489  					cty.ObjectVal(map[string]cty.Value{
   490  						"baz": cty.ListVal([]cty.Value{
   491  							cty.ObjectVal(map[string]cty.Value{
   492  								"list": cty.UnknownVal(cty.List(cty.String)),
   493  							}),
   494  						}),
   495  					}),
   496  				}),
   497  			}),
   498  			Schema: &configschema.Block{
   499  				BlockTypes: map[string]*configschema.NestedBlock{
   500  					"bar": {
   501  						Block: configschema.Block{
   502  							BlockTypes: map[string]*configschema.NestedBlock{
   503  								"baz": {
   504  									Block: configschema.Block{
   505  										Attributes: map[string]*configschema.Attribute{
   506  											"list": {Type: cty.List(cty.String),
   507  												Optional: true,
   508  											},
   509  										},
   510  									},
   511  									Nesting: configschema.NestingList,
   512  								},
   513  							},
   514  						},
   515  						Nesting: configschema.NestingList,
   516  					},
   517  				},
   518  			},
   519  			Expected: &ResourceConfig{
   520  				ComputedKeys: []string{"bar.0.baz.0.list"},
   521  				Raw: map[string]interface{}{
   522  					"bar": []interface{}{map[string]interface{}{
   523  						"baz": []interface{}{map[string]interface{}{
   524  							"list": "74D93920-ED26-11E3-AC10-0800200C9A66",
   525  						}},
   526  					}},
   527  				},
   528  				Config: map[string]interface{}{
   529  					"bar": []interface{}{map[string]interface{}{
   530  						"baz": []interface{}{map[string]interface{}{
   531  							"list": "74D93920-ED26-11E3-AC10-0800200C9A66",
   532  						}},
   533  					}},
   534  				},
   535  			},
   536  		},
   537  		{
   538  			Name: "unknown in set",
   539  			Val: cty.ObjectVal(map[string]cty.Value{
   540  				"bar": cty.SetVal([]cty.Value{
   541  					cty.ObjectVal(map[string]cty.Value{
   542  						"val": cty.UnknownVal(cty.String),
   543  					}),
   544  				}),
   545  			}),
   546  			Schema: &configschema.Block{
   547  				BlockTypes: map[string]*configschema.NestedBlock{
   548  					"bar": {
   549  						Block: configschema.Block{
   550  							Attributes: map[string]*configschema.Attribute{
   551  								"val": {
   552  									Type:     cty.String,
   553  									Optional: true,
   554  								},
   555  							},
   556  						},
   557  						Nesting: configschema.NestingSet,
   558  					},
   559  				},
   560  			},
   561  			Expected: &ResourceConfig{
   562  				ComputedKeys: []string{"bar.0.val"},
   563  				Raw: map[string]interface{}{
   564  					"bar": []interface{}{map[string]interface{}{
   565  						"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
   566  					}},
   567  				},
   568  				Config: map[string]interface{}{
   569  					"bar": []interface{}{map[string]interface{}{
   570  						"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
   571  					}},
   572  				},
   573  			},
   574  		},
   575  		{
   576  			Name: "unknown in attribute sets",
   577  			Val: cty.ObjectVal(map[string]cty.Value{
   578  				"bar": cty.SetVal([]cty.Value{
   579  					cty.ObjectVal(map[string]cty.Value{
   580  						"val": cty.UnknownVal(cty.String),
   581  					}),
   582  				}),
   583  				"baz": cty.SetVal([]cty.Value{
   584  					cty.ObjectVal(map[string]cty.Value{
   585  						"obj": cty.UnknownVal(cty.Object(map[string]cty.Type{
   586  							"attr": cty.List(cty.String),
   587  						})),
   588  					}),
   589  					cty.ObjectVal(map[string]cty.Value{
   590  						"obj": cty.ObjectVal(map[string]cty.Value{
   591  							"attr": cty.UnknownVal(cty.List(cty.String)),
   592  						}),
   593  					}),
   594  				}),
   595  			}),
   596  			Schema: &configschema.Block{
   597  				Attributes: map[string]*configschema.Attribute{
   598  					"bar": &configschema.Attribute{
   599  						Type: cty.Set(cty.Object(map[string]cty.Type{
   600  							"val": cty.String,
   601  						})),
   602  					},
   603  					"baz": &configschema.Attribute{
   604  						Type: cty.Set(cty.Object(map[string]cty.Type{
   605  							"obj": cty.Object(map[string]cty.Type{
   606  								"attr": cty.List(cty.String),
   607  							}),
   608  						})),
   609  					},
   610  				},
   611  			},
   612  			Expected: &ResourceConfig{
   613  				ComputedKeys: []string{"bar.0.val", "baz.0.obj.attr", "baz.1.obj"},
   614  				Raw: map[string]interface{}{
   615  					"bar": []interface{}{map[string]interface{}{
   616  						"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
   617  					}},
   618  					"baz": []interface{}{
   619  						map[string]interface{}{
   620  							"obj": map[string]interface{}{
   621  								"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
   622  							},
   623  						},
   624  						map[string]interface{}{
   625  							"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
   626  						},
   627  					},
   628  				},
   629  				Config: map[string]interface{}{
   630  					"bar": []interface{}{map[string]interface{}{
   631  						"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
   632  					}},
   633  					"baz": []interface{}{
   634  						map[string]interface{}{
   635  							"obj": map[string]interface{}{
   636  								"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
   637  							},
   638  						},
   639  						map[string]interface{}{
   640  							"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
   641  						},
   642  					},
   643  				},
   644  			},
   645  		},
   646  		{
   647  			Name: "null blocks",
   648  			Val: cty.ObjectVal(map[string]cty.Value{
   649  				"bar": cty.NullVal(cty.Map(cty.String)),
   650  				"baz": cty.NullVal(cty.List(cty.String)),
   651  			}),
   652  			Schema: &configschema.Block{
   653  				BlockTypes: map[string]*configschema.NestedBlock{
   654  					"bar": {
   655  						Block:   configschema.Block{},
   656  						Nesting: configschema.NestingMap,
   657  					},
   658  					"baz": {
   659  						Block:   configschema.Block{},
   660  						Nesting: configschema.NestingSingle,
   661  					},
   662  				},
   663  			},
   664  			Expected: &ResourceConfig{
   665  				Raw:    map[string]interface{}{},
   666  				Config: map[string]interface{}{},
   667  			},
   668  		},
   669  	} {
   670  		t.Run(tc.Name, func(*testing.T) {
   671  			cfg := NewResourceConfigShimmed(tc.Val, tc.Schema)
   672  			if !tc.Expected.Equal(cfg) {
   673  				t.Fatalf("expected:\n%#v\ngot:\n%#v", tc.Expected, cfg)
   674  			}
   675  		})
   676  	}
   677  }