github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/addrs/parse_target_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package addrs
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/go-test/deep"
    10  	"github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/hcl/v2/hclsyntax"
    12  	"github.com/terramate-io/tf/tfdiags"
    13  )
    14  
    15  func TestParseTarget(t *testing.T) {
    16  	tests := []struct {
    17  		Input   string
    18  		Want    *Target
    19  		WantErr string
    20  	}{
    21  		{
    22  			`module.foo`,
    23  			&Target{
    24  				Subject: ModuleInstance{
    25  					{
    26  						Name: "foo",
    27  					},
    28  				},
    29  				SourceRange: tfdiags.SourceRange{
    30  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    31  					End:   tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
    32  				},
    33  			},
    34  			``,
    35  		},
    36  		{
    37  			`module.foo[2]`,
    38  			&Target{
    39  				Subject: ModuleInstance{
    40  					{
    41  						Name:        "foo",
    42  						InstanceKey: IntKey(2),
    43  					},
    44  				},
    45  				SourceRange: tfdiags.SourceRange{
    46  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    47  					End:   tfdiags.SourcePos{Line: 1, Column: 14, Byte: 13},
    48  				},
    49  			},
    50  			``,
    51  		},
    52  		{
    53  			`module.foo[2].module.bar`,
    54  			&Target{
    55  				Subject: ModuleInstance{
    56  					{
    57  						Name:        "foo",
    58  						InstanceKey: IntKey(2),
    59  					},
    60  					{
    61  						Name: "bar",
    62  					},
    63  				},
    64  				SourceRange: tfdiags.SourceRange{
    65  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    66  					End:   tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
    67  				},
    68  			},
    69  			``,
    70  		},
    71  		{
    72  			`aws_instance.foo`,
    73  			&Target{
    74  				Subject: AbsResource{
    75  					Resource: Resource{
    76  						Mode: ManagedResourceMode,
    77  						Type: "aws_instance",
    78  						Name: "foo",
    79  					},
    80  					Module: RootModuleInstance,
    81  				},
    82  				SourceRange: tfdiags.SourceRange{
    83  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
    84  					End:   tfdiags.SourcePos{Line: 1, Column: 17, Byte: 16},
    85  				},
    86  			},
    87  			``,
    88  		},
    89  		{
    90  			`aws_instance.foo[1]`,
    91  			&Target{
    92  				Subject: AbsResourceInstance{
    93  					Resource: ResourceInstance{
    94  						Resource: Resource{
    95  							Mode: ManagedResourceMode,
    96  							Type: "aws_instance",
    97  							Name: "foo",
    98  						},
    99  						Key: IntKey(1),
   100  					},
   101  					Module: RootModuleInstance,
   102  				},
   103  				SourceRange: tfdiags.SourceRange{
   104  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   105  					End:   tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
   106  				},
   107  			},
   108  			``,
   109  		},
   110  		{
   111  			`data.aws_instance.foo`,
   112  			&Target{
   113  				Subject: AbsResource{
   114  					Resource: Resource{
   115  						Mode: DataResourceMode,
   116  						Type: "aws_instance",
   117  						Name: "foo",
   118  					},
   119  					Module: RootModuleInstance,
   120  				},
   121  				SourceRange: tfdiags.SourceRange{
   122  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   123  					End:   tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
   124  				},
   125  			},
   126  			``,
   127  		},
   128  		{
   129  			`data.aws_instance.foo[1]`,
   130  			&Target{
   131  				Subject: AbsResourceInstance{
   132  					Resource: ResourceInstance{
   133  						Resource: Resource{
   134  							Mode: DataResourceMode,
   135  							Type: "aws_instance",
   136  							Name: "foo",
   137  						},
   138  						Key: IntKey(1),
   139  					},
   140  					Module: RootModuleInstance,
   141  				},
   142  				SourceRange: tfdiags.SourceRange{
   143  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   144  					End:   tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
   145  				},
   146  			},
   147  			``,
   148  		},
   149  		{
   150  			`module.foo.aws_instance.bar`,
   151  			&Target{
   152  				Subject: AbsResource{
   153  					Resource: Resource{
   154  						Mode: ManagedResourceMode,
   155  						Type: "aws_instance",
   156  						Name: "bar",
   157  					},
   158  					Module: ModuleInstance{
   159  						{Name: "foo"},
   160  					},
   161  				},
   162  				SourceRange: tfdiags.SourceRange{
   163  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   164  					End:   tfdiags.SourcePos{Line: 1, Column: 28, Byte: 27},
   165  				},
   166  			},
   167  			``,
   168  		},
   169  		{
   170  			`module.foo.module.bar.aws_instance.baz`,
   171  			&Target{
   172  				Subject: AbsResource{
   173  					Resource: Resource{
   174  						Mode: ManagedResourceMode,
   175  						Type: "aws_instance",
   176  						Name: "baz",
   177  					},
   178  					Module: ModuleInstance{
   179  						{Name: "foo"},
   180  						{Name: "bar"},
   181  					},
   182  				},
   183  				SourceRange: tfdiags.SourceRange{
   184  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   185  					End:   tfdiags.SourcePos{Line: 1, Column: 39, Byte: 38},
   186  				},
   187  			},
   188  			``,
   189  		},
   190  		{
   191  			`module.foo.module.bar.aws_instance.baz["hello"]`,
   192  			&Target{
   193  				Subject: AbsResourceInstance{
   194  					Resource: ResourceInstance{
   195  						Resource: Resource{
   196  							Mode: ManagedResourceMode,
   197  							Type: "aws_instance",
   198  							Name: "baz",
   199  						},
   200  						Key: StringKey("hello"),
   201  					},
   202  					Module: ModuleInstance{
   203  						{Name: "foo"},
   204  						{Name: "bar"},
   205  					},
   206  				},
   207  				SourceRange: tfdiags.SourceRange{
   208  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   209  					End:   tfdiags.SourcePos{Line: 1, Column: 48, Byte: 47},
   210  				},
   211  			},
   212  			``,
   213  		},
   214  		{
   215  			`module.foo.data.aws_instance.bar`,
   216  			&Target{
   217  				Subject: AbsResource{
   218  					Resource: Resource{
   219  						Mode: DataResourceMode,
   220  						Type: "aws_instance",
   221  						Name: "bar",
   222  					},
   223  					Module: ModuleInstance{
   224  						{Name: "foo"},
   225  					},
   226  				},
   227  				SourceRange: tfdiags.SourceRange{
   228  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   229  					End:   tfdiags.SourcePos{Line: 1, Column: 33, Byte: 32},
   230  				},
   231  			},
   232  			``,
   233  		},
   234  		{
   235  			`module.foo.module.bar.data.aws_instance.baz`,
   236  			&Target{
   237  				Subject: AbsResource{
   238  					Resource: Resource{
   239  						Mode: DataResourceMode,
   240  						Type: "aws_instance",
   241  						Name: "baz",
   242  					},
   243  					Module: ModuleInstance{
   244  						{Name: "foo"},
   245  						{Name: "bar"},
   246  					},
   247  				},
   248  				SourceRange: tfdiags.SourceRange{
   249  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   250  					End:   tfdiags.SourcePos{Line: 1, Column: 44, Byte: 43},
   251  				},
   252  			},
   253  			``,
   254  		},
   255  		{
   256  			`module.foo.module.bar[0].data.aws_instance.baz`,
   257  			&Target{
   258  				Subject: AbsResource{
   259  					Resource: Resource{
   260  						Mode: DataResourceMode,
   261  						Type: "aws_instance",
   262  						Name: "baz",
   263  					},
   264  					Module: ModuleInstance{
   265  						{Name: "foo", InstanceKey: NoKey},
   266  						{Name: "bar", InstanceKey: IntKey(0)},
   267  					},
   268  				},
   269  				SourceRange: tfdiags.SourceRange{
   270  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   271  					End:   tfdiags.SourcePos{Line: 1, Column: 47, Byte: 46},
   272  				},
   273  			},
   274  			``,
   275  		},
   276  		{
   277  			`module.foo.module.bar["a"].data.aws_instance.baz["hello"]`,
   278  			&Target{
   279  				Subject: AbsResourceInstance{
   280  					Resource: ResourceInstance{
   281  						Resource: Resource{
   282  							Mode: DataResourceMode,
   283  							Type: "aws_instance",
   284  							Name: "baz",
   285  						},
   286  						Key: StringKey("hello"),
   287  					},
   288  					Module: ModuleInstance{
   289  						{Name: "foo", InstanceKey: NoKey},
   290  						{Name: "bar", InstanceKey: StringKey("a")},
   291  					},
   292  				},
   293  				SourceRange: tfdiags.SourceRange{
   294  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   295  					End:   tfdiags.SourcePos{Line: 1, Column: 58, Byte: 57},
   296  				},
   297  			},
   298  			``,
   299  		},
   300  		{
   301  			`module.foo.module.bar.data.aws_instance.baz["hello"]`,
   302  			&Target{
   303  				Subject: AbsResourceInstance{
   304  					Resource: ResourceInstance{
   305  						Resource: Resource{
   306  							Mode: DataResourceMode,
   307  							Type: "aws_instance",
   308  							Name: "baz",
   309  						},
   310  						Key: StringKey("hello"),
   311  					},
   312  					Module: ModuleInstance{
   313  						{Name: "foo"},
   314  						{Name: "bar"},
   315  					},
   316  				},
   317  				SourceRange: tfdiags.SourceRange{
   318  					Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
   319  					End:   tfdiags.SourcePos{Line: 1, Column: 53, Byte: 52},
   320  				},
   321  			},
   322  			``,
   323  		},
   324  
   325  		{
   326  			`aws_instance`,
   327  			nil,
   328  			`Resource specification must include a resource type and name.`,
   329  		},
   330  		{
   331  			`module`,
   332  			nil,
   333  			`Prefix "module." must be followed by a module name.`,
   334  		},
   335  		{
   336  			`module["baz"]`,
   337  			nil,
   338  			`Prefix "module." must be followed by a module name.`,
   339  		},
   340  		{
   341  			`module.baz.bar`,
   342  			nil,
   343  			`Resource specification must include a resource type and name.`,
   344  		},
   345  		{
   346  			`aws_instance.foo.bar`,
   347  			nil,
   348  			`Resource instance key must be given in square brackets.`,
   349  		},
   350  		{
   351  			`aws_instance.foo[1].baz`,
   352  			nil,
   353  			`Unexpected extra operators after address.`,
   354  		},
   355  	}
   356  
   357  	for _, test := range tests {
   358  		t.Run(test.Input, func(t *testing.T) {
   359  			traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1})
   360  			if travDiags.HasErrors() {
   361  				t.Fatal(travDiags.Error())
   362  			}
   363  
   364  			got, diags := ParseTarget(traversal)
   365  
   366  			switch len(diags) {
   367  			case 0:
   368  				if test.WantErr != "" {
   369  					t.Fatalf("succeeded; want error: %s", test.WantErr)
   370  				}
   371  			case 1:
   372  				if test.WantErr == "" {
   373  					t.Fatalf("unexpected diagnostics: %s", diags.Err())
   374  				}
   375  				if got, want := diags[0].Description().Detail, test.WantErr; got != want {
   376  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   377  				}
   378  			default:
   379  				t.Fatalf("too many diagnostics: %s", diags.Err())
   380  			}
   381  
   382  			if diags.HasErrors() {
   383  				return
   384  			}
   385  
   386  			for _, problem := range deep.Equal(got, test.Want) {
   387  				t.Errorf(problem)
   388  			}
   389  		})
   390  	}
   391  }