github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/node_resource_validate_test.go (about) 1 package durgaform 2 3 import ( 4 "errors" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/hcl/v2/hcltest" 10 "github.com/eliastor/durgaform/internal/addrs" 11 "github.com/eliastor/durgaform/internal/configs" 12 "github.com/eliastor/durgaform/internal/configs/configschema" 13 "github.com/eliastor/durgaform/internal/lang/marks" 14 "github.com/eliastor/durgaform/internal/providers" 15 "github.com/eliastor/durgaform/internal/provisioners" 16 "github.com/eliastor/durgaform/internal/tfdiags" 17 "github.com/zclconf/go-cty/cty" 18 ) 19 20 func TestNodeValidatableResource_ValidateProvisioner_valid(t *testing.T) { 21 ctx := &MockEvalContext{} 22 ctx.installSimpleEval() 23 mp := &MockProvisioner{} 24 ps := &configschema.Block{} 25 ctx.ProvisionerSchemaSchema = ps 26 ctx.ProvisionerProvisioner = mp 27 28 pc := &configs.Provisioner{ 29 Type: "baz", 30 Config: hcl.EmptyBody(), 31 Connection: &configs.Connection{ 32 Config: configs.SynthBody("", map[string]cty.Value{ 33 "host": cty.StringVal("localhost"), 34 "type": cty.StringVal("ssh"), 35 "port": cty.NumberIntVal(10022), 36 }), 37 }, 38 } 39 40 rc := &configs.Resource{ 41 Mode: addrs.ManagedResourceMode, 42 Type: "test_foo", 43 Name: "bar", 44 Config: configs.SynthBody("", map[string]cty.Value{}), 45 } 46 47 node := NodeValidatableResource{ 48 NodeAbstractResource: &NodeAbstractResource{ 49 Addr: mustConfigResourceAddr("test_foo.bar"), 50 Config: rc, 51 }, 52 } 53 54 diags := node.validateProvisioner(ctx, pc) 55 if diags.HasErrors() { 56 t.Fatalf("node.Eval failed: %s", diags.Err()) 57 } 58 if !mp.ValidateProvisionerConfigCalled { 59 t.Fatalf("p.ValidateProvisionerConfig not called") 60 } 61 } 62 63 func TestNodeValidatableResource_ValidateProvisioner__warning(t *testing.T) { 64 ctx := &MockEvalContext{} 65 ctx.installSimpleEval() 66 mp := &MockProvisioner{} 67 ps := &configschema.Block{} 68 ctx.ProvisionerSchemaSchema = ps 69 ctx.ProvisionerProvisioner = mp 70 71 pc := &configs.Provisioner{ 72 Type: "baz", 73 Config: hcl.EmptyBody(), 74 } 75 76 rc := &configs.Resource{ 77 Mode: addrs.ManagedResourceMode, 78 Type: "test_foo", 79 Name: "bar", 80 Config: configs.SynthBody("", map[string]cty.Value{}), 81 Managed: &configs.ManagedResource{}, 82 } 83 84 node := NodeValidatableResource{ 85 NodeAbstractResource: &NodeAbstractResource{ 86 Addr: mustConfigResourceAddr("test_foo.bar"), 87 Config: rc, 88 }, 89 } 90 91 { 92 var diags tfdiags.Diagnostics 93 diags = diags.Append(tfdiags.SimpleWarning("foo is deprecated")) 94 mp.ValidateProvisionerConfigResponse = provisioners.ValidateProvisionerConfigResponse{ 95 Diagnostics: diags, 96 } 97 } 98 99 diags := node.validateProvisioner(ctx, pc) 100 if len(diags) != 1 { 101 t.Fatalf("wrong number of diagnostics in %s; want one warning", diags.ErrWithWarnings()) 102 } 103 104 if got, want := diags[0].Description().Summary, mp.ValidateProvisionerConfigResponse.Diagnostics[0].Description().Summary; got != want { 105 t.Fatalf("wrong warning %q; want %q", got, want) 106 } 107 } 108 109 func TestNodeValidatableResource_ValidateProvisioner__connectionInvalid(t *testing.T) { 110 ctx := &MockEvalContext{} 111 ctx.installSimpleEval() 112 mp := &MockProvisioner{} 113 ps := &configschema.Block{} 114 ctx.ProvisionerSchemaSchema = ps 115 ctx.ProvisionerProvisioner = mp 116 117 pc := &configs.Provisioner{ 118 Type: "baz", 119 Config: hcl.EmptyBody(), 120 Connection: &configs.Connection{ 121 Config: configs.SynthBody("", map[string]cty.Value{ 122 "type": cty.StringVal("ssh"), 123 "bananananananana": cty.StringVal("foo"), 124 "bazaz": cty.StringVal("bar"), 125 }), 126 }, 127 } 128 129 rc := &configs.Resource{ 130 Mode: addrs.ManagedResourceMode, 131 Type: "test_foo", 132 Name: "bar", 133 Config: configs.SynthBody("", map[string]cty.Value{}), 134 Managed: &configs.ManagedResource{}, 135 } 136 137 node := NodeValidatableResource{ 138 NodeAbstractResource: &NodeAbstractResource{ 139 Addr: mustConfigResourceAddr("test_foo.bar"), 140 Config: rc, 141 }, 142 } 143 144 diags := node.validateProvisioner(ctx, pc) 145 if !diags.HasErrors() { 146 t.Fatalf("node.Eval succeeded; want error") 147 } 148 if len(diags) != 3 { 149 t.Fatalf("wrong number of diagnostics; want two errors\n\n%s", diags.Err()) 150 } 151 152 errStr := diags.Err().Error() 153 if !(strings.Contains(errStr, "bananananananana") && strings.Contains(errStr, "bazaz")) { 154 t.Fatalf("wrong errors %q; want something about each of our invalid connInfo keys", errStr) 155 } 156 } 157 158 func TestNodeValidatableResource_ValidateResource_managedResource(t *testing.T) { 159 mp := simpleMockProvider() 160 mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 161 if got, want := req.TypeName, "test_object"; got != want { 162 t.Fatalf("wrong resource type\ngot: %#v\nwant: %#v", got, want) 163 } 164 if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { 165 t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) 166 } 167 if got, want := req.Config.GetAttr("test_number"), cty.NumberIntVal(2); !got.RawEquals(want) { 168 t.Fatalf("wrong value for test_number\ngot: %#v\nwant: %#v", got, want) 169 } 170 return providers.ValidateResourceConfigResponse{} 171 } 172 173 p := providers.Interface(mp) 174 rc := &configs.Resource{ 175 Mode: addrs.ManagedResourceMode, 176 Type: "test_object", 177 Name: "foo", 178 Config: configs.SynthBody("", map[string]cty.Value{ 179 "test_string": cty.StringVal("bar"), 180 "test_number": cty.NumberIntVal(2).Mark(marks.Sensitive), 181 }), 182 } 183 node := NodeValidatableResource{ 184 NodeAbstractResource: &NodeAbstractResource{ 185 Addr: mustConfigResourceAddr("test_foo.bar"), 186 Config: rc, 187 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 188 }, 189 } 190 191 ctx := &MockEvalContext{} 192 ctx.installSimpleEval() 193 ctx.ProviderSchemaSchema = mp.ProviderSchema() 194 ctx.ProviderProvider = p 195 196 err := node.validateResource(ctx) 197 if err != nil { 198 t.Fatalf("err: %s", err) 199 } 200 201 if !mp.ValidateResourceConfigCalled { 202 t.Fatal("Expected ValidateResourceConfig to be called, but it was not!") 203 } 204 } 205 206 func TestNodeValidatableResource_ValidateResource_managedResourceCount(t *testing.T) { 207 // Setup 208 mp := simpleMockProvider() 209 mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 210 if got, want := req.TypeName, "test_object"; got != want { 211 t.Fatalf("wrong resource type\ngot: %#v\nwant: %#v", got, want) 212 } 213 if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { 214 t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) 215 } 216 return providers.ValidateResourceConfigResponse{} 217 } 218 219 p := providers.Interface(mp) 220 221 ctx := &MockEvalContext{} 222 ctx.installSimpleEval() 223 ctx.ProviderSchemaSchema = mp.ProviderSchema() 224 ctx.ProviderProvider = p 225 226 tests := []struct { 227 name string 228 count hcl.Expression 229 }{ 230 { 231 "simple count", 232 hcltest.MockExprLiteral(cty.NumberIntVal(2)), 233 }, 234 { 235 "marked count value", 236 hcltest.MockExprLiteral(cty.NumberIntVal(3).Mark("marked")), 237 }, 238 } 239 240 for _, test := range tests { 241 t.Run(test.name, func(t *testing.T) { 242 rc := &configs.Resource{ 243 Mode: addrs.ManagedResourceMode, 244 Type: "test_object", 245 Name: "foo", 246 Count: test.count, 247 Config: configs.SynthBody("", map[string]cty.Value{ 248 "test_string": cty.StringVal("bar"), 249 }), 250 } 251 node := NodeValidatableResource{ 252 NodeAbstractResource: &NodeAbstractResource{ 253 Addr: mustConfigResourceAddr("test_foo.bar"), 254 Config: rc, 255 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 256 }, 257 } 258 259 diags := node.validateResource(ctx) 260 if diags.HasErrors() { 261 t.Fatalf("err: %s", diags.Err()) 262 } 263 264 if !mp.ValidateResourceConfigCalled { 265 t.Fatal("Expected ValidateResourceConfig to be called, but it was not!") 266 } 267 }) 268 } 269 } 270 271 func TestNodeValidatableResource_ValidateResource_dataSource(t *testing.T) { 272 mp := simpleMockProvider() 273 mp.ValidateDataResourceConfigFn = func(req providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse { 274 if got, want := req.TypeName, "test_object"; got != want { 275 t.Fatalf("wrong resource type\ngot: %#v\nwant: %#v", got, want) 276 } 277 if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { 278 t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) 279 } 280 if got, want := req.Config.GetAttr("test_number"), cty.NumberIntVal(2); !got.RawEquals(want) { 281 t.Fatalf("wrong value for test_number\ngot: %#v\nwant: %#v", got, want) 282 } 283 return providers.ValidateDataResourceConfigResponse{} 284 } 285 286 p := providers.Interface(mp) 287 rc := &configs.Resource{ 288 Mode: addrs.DataResourceMode, 289 Type: "test_object", 290 Name: "foo", 291 Config: configs.SynthBody("", map[string]cty.Value{ 292 "test_string": cty.StringVal("bar"), 293 "test_number": cty.NumberIntVal(2).Mark(marks.Sensitive), 294 }), 295 } 296 297 node := NodeValidatableResource{ 298 NodeAbstractResource: &NodeAbstractResource{ 299 Addr: mustConfigResourceAddr("test_foo.bar"), 300 Config: rc, 301 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 302 }, 303 } 304 305 ctx := &MockEvalContext{} 306 ctx.installSimpleEval() 307 ctx.ProviderSchemaSchema = mp.ProviderSchema() 308 ctx.ProviderProvider = p 309 310 diags := node.validateResource(ctx) 311 if diags.HasErrors() { 312 t.Fatalf("err: %s", diags.Err()) 313 } 314 315 if !mp.ValidateDataResourceConfigCalled { 316 t.Fatal("Expected ValidateDataSourceConfig to be called, but it was not!") 317 } 318 } 319 320 func TestNodeValidatableResource_ValidateResource_valid(t *testing.T) { 321 mp := simpleMockProvider() 322 mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 323 return providers.ValidateResourceConfigResponse{} 324 } 325 326 p := providers.Interface(mp) 327 rc := &configs.Resource{ 328 Mode: addrs.ManagedResourceMode, 329 Type: "test_object", 330 Name: "foo", 331 Config: configs.SynthBody("", map[string]cty.Value{}), 332 } 333 node := NodeValidatableResource{ 334 NodeAbstractResource: &NodeAbstractResource{ 335 Addr: mustConfigResourceAddr("test_object.foo"), 336 Config: rc, 337 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 338 }, 339 } 340 341 ctx := &MockEvalContext{} 342 ctx.installSimpleEval() 343 ctx.ProviderSchemaSchema = mp.ProviderSchema() 344 ctx.ProviderProvider = p 345 346 diags := node.validateResource(ctx) 347 if diags.HasErrors() { 348 t.Fatalf("err: %s", diags.Err()) 349 } 350 } 351 352 func TestNodeValidatableResource_ValidateResource_warningsAndErrorsPassedThrough(t *testing.T) { 353 mp := simpleMockProvider() 354 mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 355 var diags tfdiags.Diagnostics 356 diags = diags.Append(tfdiags.SimpleWarning("warn")) 357 diags = diags.Append(errors.New("err")) 358 return providers.ValidateResourceConfigResponse{ 359 Diagnostics: diags, 360 } 361 } 362 363 p := providers.Interface(mp) 364 rc := &configs.Resource{ 365 Mode: addrs.ManagedResourceMode, 366 Type: "test_object", 367 Name: "foo", 368 Config: configs.SynthBody("", map[string]cty.Value{}), 369 } 370 node := NodeValidatableResource{ 371 NodeAbstractResource: &NodeAbstractResource{ 372 Addr: mustConfigResourceAddr("test_foo.bar"), 373 Config: rc, 374 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 375 }, 376 } 377 378 ctx := &MockEvalContext{} 379 ctx.installSimpleEval() 380 ctx.ProviderSchemaSchema = mp.ProviderSchema() 381 ctx.ProviderProvider = p 382 383 diags := node.validateResource(ctx) 384 if !diags.HasErrors() { 385 t.Fatal("unexpected success; want error") 386 } 387 388 bySeverity := map[tfdiags.Severity]tfdiags.Diagnostics{} 389 for _, diag := range diags { 390 bySeverity[diag.Severity()] = append(bySeverity[diag.Severity()], diag) 391 } 392 if len(bySeverity[tfdiags.Warning]) != 1 || bySeverity[tfdiags.Warning][0].Description().Summary != "warn" { 393 t.Errorf("Expected 1 warning 'warn', got: %s", bySeverity[tfdiags.Warning].ErrWithWarnings()) 394 } 395 if len(bySeverity[tfdiags.Error]) != 1 || bySeverity[tfdiags.Error][0].Description().Summary != "err" { 396 t.Errorf("Expected 1 error 'err', got: %s", bySeverity[tfdiags.Error].Err()) 397 } 398 } 399 400 func TestNodeValidatableResource_ValidateResource_invalidDependsOn(t *testing.T) { 401 mp := simpleMockProvider() 402 mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 403 return providers.ValidateResourceConfigResponse{} 404 } 405 406 // We'll check a _valid_ config first, to make sure we're not failing 407 // for some other reason, and then make it invalid. 408 p := providers.Interface(mp) 409 rc := &configs.Resource{ 410 Mode: addrs.ManagedResourceMode, 411 Type: "test_object", 412 Name: "foo", 413 Config: configs.SynthBody("", map[string]cty.Value{}), 414 DependsOn: []hcl.Traversal{ 415 // Depending on path.module is pointless, since it is immediately 416 // available, but we allow all of the referencable addrs here 417 // for consistency: referencing them is harmless, and avoids the 418 // need for us to document a different subset of addresses that 419 // are valid in depends_on. 420 // For the sake of this test, it's a valid address we can use that 421 // doesn't require something else to exist in the configuration. 422 { 423 hcl.TraverseRoot{ 424 Name: "path", 425 }, 426 hcl.TraverseAttr{ 427 Name: "module", 428 }, 429 }, 430 }, 431 } 432 node := NodeValidatableResource{ 433 NodeAbstractResource: &NodeAbstractResource{ 434 Addr: mustConfigResourceAddr("test_foo.bar"), 435 Config: rc, 436 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 437 }, 438 } 439 440 ctx := &MockEvalContext{} 441 ctx.installSimpleEval() 442 443 ctx.ProviderSchemaSchema = mp.ProviderSchema() 444 ctx.ProviderProvider = p 445 446 diags := node.validateResource(ctx) 447 if diags.HasErrors() { 448 t.Fatalf("error for supposedly-valid config: %s", diags.ErrWithWarnings()) 449 } 450 451 // Now we'll make it invalid by adding additional traversal steps at 452 // the end of what we're referencing. This is intended to catch the 453 // situation where the user tries to depend on e.g. a specific resource 454 // attribute, rather than the whole resource, like aws_instance.foo.id. 455 rc.DependsOn = append(rc.DependsOn, hcl.Traversal{ 456 hcl.TraverseRoot{ 457 Name: "path", 458 }, 459 hcl.TraverseAttr{ 460 Name: "module", 461 }, 462 hcl.TraverseAttr{ 463 Name: "extra", 464 }, 465 }) 466 467 diags = node.validateResource(ctx) 468 if !diags.HasErrors() { 469 t.Fatal("no error for invalid depends_on") 470 } 471 if got, want := diags.Err().Error(), "Invalid depends_on reference"; !strings.Contains(got, want) { 472 t.Fatalf("wrong error\ngot: %s\nwant: Message containing %q", got, want) 473 } 474 475 // Test for handling an unknown root without attribute, like a 476 // typo that omits the dot inbetween "path.module". 477 rc.DependsOn = append(rc.DependsOn, hcl.Traversal{ 478 hcl.TraverseRoot{ 479 Name: "pathmodule", 480 }, 481 }) 482 483 diags = node.validateResource(ctx) 484 if !diags.HasErrors() { 485 t.Fatal("no error for invalid depends_on") 486 } 487 if got, want := diags.Err().Error(), "Invalid depends_on reference"; !strings.Contains(got, want) { 488 t.Fatalf("wrong error\ngot: %s\nwant: Message containing %q", got, want) 489 } 490 } 491 492 func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesNonexistent(t *testing.T) { 493 mp := simpleMockProvider() 494 mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 495 return providers.ValidateResourceConfigResponse{} 496 } 497 498 // We'll check a _valid_ config first, to make sure we're not failing 499 // for some other reason, and then make it invalid. 500 p := providers.Interface(mp) 501 rc := &configs.Resource{ 502 Mode: addrs.ManagedResourceMode, 503 Type: "test_object", 504 Name: "foo", 505 Config: configs.SynthBody("", map[string]cty.Value{}), 506 Managed: &configs.ManagedResource{ 507 IgnoreChanges: []hcl.Traversal{ 508 { 509 hcl.TraverseAttr{ 510 Name: "test_string", 511 }, 512 }, 513 }, 514 }, 515 } 516 node := NodeValidatableResource{ 517 NodeAbstractResource: &NodeAbstractResource{ 518 Addr: mustConfigResourceAddr("test_foo.bar"), 519 Config: rc, 520 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 521 }, 522 } 523 524 ctx := &MockEvalContext{} 525 ctx.installSimpleEval() 526 527 ctx.ProviderSchemaSchema = mp.ProviderSchema() 528 ctx.ProviderProvider = p 529 530 diags := node.validateResource(ctx) 531 if diags.HasErrors() { 532 t.Fatalf("error for supposedly-valid config: %s", diags.ErrWithWarnings()) 533 } 534 535 // Now we'll make it invalid by attempting to ignore a nonexistent 536 // attribute. 537 rc.Managed.IgnoreChanges = append(rc.Managed.IgnoreChanges, hcl.Traversal{ 538 hcl.TraverseAttr{ 539 Name: "nonexistent", 540 }, 541 }) 542 543 diags = node.validateResource(ctx) 544 if !diags.HasErrors() { 545 t.Fatal("no error for invalid ignore_changes") 546 } 547 if got, want := diags.Err().Error(), "Unsupported attribute: This object has no argument, nested block, or exported attribute named \"nonexistent\""; !strings.Contains(got, want) { 548 t.Fatalf("wrong error\ngot: %s\nwant: Message containing %q", got, want) 549 } 550 } 551 552 func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesComputed(t *testing.T) { 553 // construct a schema with a computed attribute 554 ms := &configschema.Block{ 555 Attributes: map[string]*configschema.Attribute{ 556 "test_string": { 557 Type: cty.String, 558 Optional: true, 559 }, 560 "computed_string": { 561 Type: cty.String, 562 Computed: true, 563 Optional: false, 564 }, 565 }, 566 } 567 568 mp := &MockProvider{ 569 GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ 570 Provider: providers.Schema{Block: ms}, 571 ResourceTypes: map[string]providers.Schema{ 572 "test_object": providers.Schema{Block: ms}, 573 }, 574 }, 575 } 576 577 mp.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { 578 return providers.ValidateResourceConfigResponse{} 579 } 580 581 // We'll check a _valid_ config first, to make sure we're not failing 582 // for some other reason, and then make it invalid. 583 p := providers.Interface(mp) 584 rc := &configs.Resource{ 585 Mode: addrs.ManagedResourceMode, 586 Type: "test_object", 587 Name: "foo", 588 Config: configs.SynthBody("", map[string]cty.Value{}), 589 Managed: &configs.ManagedResource{ 590 IgnoreChanges: []hcl.Traversal{ 591 { 592 hcl.TraverseAttr{ 593 Name: "test_string", 594 }, 595 }, 596 }, 597 }, 598 } 599 node := NodeValidatableResource{ 600 NodeAbstractResource: &NodeAbstractResource{ 601 Addr: mustConfigResourceAddr("test_foo.bar"), 602 Config: rc, 603 ResolvedProvider: mustProviderConfig(`provider["registry.durgaform.io/hashicorp/aws"]`), 604 }, 605 } 606 607 ctx := &MockEvalContext{} 608 ctx.installSimpleEval() 609 610 ctx.ProviderSchemaSchema = mp.ProviderSchema() 611 ctx.ProviderProvider = p 612 613 diags := node.validateResource(ctx) 614 if diags.HasErrors() { 615 t.Fatalf("error for supposedly-valid config: %s", diags.ErrWithWarnings()) 616 } 617 618 // Now we'll make it invalid by attempting to ignore a computed 619 // attribute. 620 rc.Managed.IgnoreChanges = append(rc.Managed.IgnoreChanges, hcl.Traversal{ 621 hcl.TraverseAttr{ 622 Name: "computed_string", 623 }, 624 }) 625 626 diags = node.validateResource(ctx) 627 if diags.HasErrors() { 628 t.Fatalf("got unexpected error: %s", diags.ErrWithWarnings()) 629 } 630 if got, want := diags.ErrWithWarnings().Error(), `Redundant ignore_changes element: Adding an attribute name to ignore_changes tells Durgaform to ignore future changes to the argument in configuration after the object has been created, retaining the value originally configured. 631 632 The attribute computed_string is decided by the provider alone and therefore there can be no configured value to compare with. Including this attribute in ignore_changes has no effect. Remove the attribute from ignore_changes to quiet this warning.`; !strings.Contains(got, want) { 633 t.Fatalf("wrong error\ngot: %s\nwant: Message containing %q", got, want) 634 } 635 }