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