github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_refresh_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 tofu 7 8 import ( 9 "reflect" 10 "sort" 11 "strings" 12 "sync" 13 "testing" 14 15 "github.com/google/go-cmp/cmp" 16 "github.com/zclconf/go-cty/cty" 17 18 "github.com/opentofu/opentofu/internal/addrs" 19 "github.com/opentofu/opentofu/internal/configs/configschema" 20 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 21 "github.com/opentofu/opentofu/internal/plans" 22 "github.com/opentofu/opentofu/internal/providers" 23 "github.com/opentofu/opentofu/internal/states" 24 ) 25 26 func TestContext2Refresh(t *testing.T) { 27 p := testProvider("aws") 28 m := testModule(t, "refresh-basic") 29 30 state := states.NewState() 31 root := state.EnsureModule(addrs.RootModuleInstance) 32 root.SetResourceInstanceCurrent( 33 mustResourceInstanceAddr("aws_instance.web").Resource, 34 &states.ResourceInstanceObjectSrc{ 35 Status: states.ObjectReady, 36 AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), 37 }, 38 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 39 ) 40 41 ctx := testContext2(t, &ContextOpts{ 42 Providers: map[addrs.Provider]providers.Factory{ 43 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 44 }, 45 }) 46 47 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 48 ty := schema.ImpliedType() 49 readState, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"id": "foo", "foo": "baz"}, ty) 50 if err != nil { 51 t.Fatal(err) 52 } 53 54 p.ReadResourceResponse = &providers.ReadResourceResponse{ 55 NewState: readState, 56 } 57 58 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 59 if diags.HasErrors() { 60 t.Fatal(diags.Err()) 61 } 62 63 if !p.ReadResourceCalled { 64 t.Fatal("ReadResource should be called") 65 } 66 67 mod := s.RootModule() 68 fromState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(ty) 69 if err != nil { 70 t.Fatal(err) 71 } 72 73 newState, err := schema.CoerceValue(fromState.Value) 74 if err != nil { 75 t.Fatal(err) 76 } 77 78 if !cmp.Equal(readState, newState, valueComparer) { 79 t.Fatal(cmp.Diff(readState, newState, valueComparer, equateEmpty)) 80 } 81 } 82 83 func TestContext2Refresh_dynamicAttr(t *testing.T) { 84 m := testModule(t, "refresh-dynamic") 85 86 startingState := states.BuildState(func(ss *states.SyncState) { 87 ss.SetResourceInstanceCurrent( 88 addrs.Resource{ 89 Mode: addrs.ManagedResourceMode, 90 Type: "test_instance", 91 Name: "foo", 92 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 93 &states.ResourceInstanceObjectSrc{ 94 Status: states.ObjectReady, 95 AttrsJSON: []byte(`{"dynamic":{"type":"string","value":"hello"}}`), 96 }, 97 addrs.AbsProviderConfig{ 98 Provider: addrs.NewDefaultProvider("test"), 99 Module: addrs.RootModule, 100 }, 101 ) 102 }) 103 104 readStateVal := cty.ObjectVal(map[string]cty.Value{ 105 "dynamic": cty.EmptyTupleVal, 106 }) 107 108 p := testProvider("test") 109 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 110 ResourceTypes: map[string]*configschema.Block{ 111 "test_instance": { 112 Attributes: map[string]*configschema.Attribute{ 113 "dynamic": {Type: cty.DynamicPseudoType, Optional: true}, 114 }, 115 }, 116 }, 117 }) 118 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 119 return providers.ReadResourceResponse{ 120 NewState: readStateVal, 121 } 122 } 123 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 124 resp.PlannedState = req.ProposedNewState 125 return resp 126 } 127 128 ctx := testContext2(t, &ContextOpts{ 129 Providers: map[addrs.Provider]providers.Factory{ 130 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 131 }, 132 }) 133 134 schema := p.GetProviderSchemaResponse.ResourceTypes["test_instance"].Block 135 ty := schema.ImpliedType() 136 137 s, diags := ctx.Refresh(m, startingState, &PlanOpts{Mode: plans.NormalMode}) 138 if diags.HasErrors() { 139 t.Fatal(diags.Err()) 140 } 141 142 if !p.ReadResourceCalled { 143 t.Fatal("ReadResource should be called") 144 } 145 146 mod := s.RootModule() 147 newState, err := mod.Resources["test_instance.foo"].Instances[addrs.NoKey].Current.Decode(ty) 148 if err != nil { 149 t.Fatal(err) 150 } 151 152 if !cmp.Equal(readStateVal, newState.Value, valueComparer) { 153 t.Error(cmp.Diff(newState.Value, readStateVal, valueComparer, equateEmpty)) 154 } 155 } 156 157 func TestContext2Refresh_dataComputedModuleVar(t *testing.T) { 158 p := testProvider("aws") 159 m := testModule(t, "refresh-data-module-var") 160 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 161 obj := req.ProposedNewState.AsValueMap() 162 obj["id"] = cty.UnknownVal(cty.String) 163 resp.PlannedState = cty.ObjectVal(obj) 164 return resp 165 } 166 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { 167 resp.State = req.Config 168 return resp 169 } 170 171 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 172 Provider: &configschema.Block{}, 173 ResourceTypes: map[string]*configschema.Block{ 174 "aws_instance": { 175 Attributes: map[string]*configschema.Attribute{ 176 "foo": { 177 Type: cty.String, 178 Optional: true, 179 }, 180 "id": { 181 Type: cty.String, 182 Computed: true, 183 }, 184 }, 185 }, 186 }, 187 DataSources: map[string]*configschema.Block{ 188 "aws_data_source": { 189 Attributes: map[string]*configschema.Attribute{ 190 "id": { 191 Type: cty.String, 192 Optional: true, 193 }, 194 "output": { 195 Type: cty.String, 196 Computed: true, 197 }, 198 }, 199 }, 200 }, 201 }) 202 203 ctx := testContext2(t, &ContextOpts{ 204 Providers: map[addrs.Provider]providers.Factory{ 205 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 206 }, 207 }) 208 209 plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{Mode: plans.RefreshOnlyMode}) 210 if diags.HasErrors() { 211 t.Fatalf("refresh errors: %s", diags.Err()) 212 } 213 214 checkStateString(t, plan.PriorState, ` 215 <no state> 216 `) 217 } 218 219 func TestContext2Refresh_targeted(t *testing.T) { 220 p := testProvider("aws") 221 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 222 Provider: &configschema.Block{}, 223 ResourceTypes: map[string]*configschema.Block{ 224 "aws_elb": { 225 Attributes: map[string]*configschema.Attribute{ 226 "id": { 227 Type: cty.String, 228 Computed: true, 229 }, 230 "instances": { 231 Type: cty.Set(cty.String), 232 Optional: true, 233 }, 234 }, 235 }, 236 "aws_instance": { 237 Attributes: map[string]*configschema.Attribute{ 238 "id": { 239 Type: cty.String, 240 Computed: true, 241 }, 242 "vpc_id": { 243 Type: cty.String, 244 Optional: true, 245 }, 246 }, 247 }, 248 "aws_vpc": { 249 Attributes: map[string]*configschema.Attribute{ 250 "id": { 251 Type: cty.String, 252 Computed: true, 253 }, 254 }, 255 }, 256 }, 257 }) 258 259 state := states.NewState() 260 root := state.EnsureModule(addrs.RootModuleInstance) 261 testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 262 testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 263 testSetResourceInstanceCurrent(root, "aws_instance.me", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 264 testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 265 266 m := testModule(t, "refresh-targeted") 267 ctx := testContext2(t, &ContextOpts{ 268 Providers: map[addrs.Provider]providers.Factory{ 269 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 270 }, 271 }) 272 273 refreshedResources := make([]string, 0, 2) 274 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 275 refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString()) 276 return providers.ReadResourceResponse{ 277 NewState: req.PriorState, 278 } 279 } 280 281 _, diags := ctx.Refresh(m, state, &PlanOpts{ 282 Mode: plans.NormalMode, 283 Targets: []addrs.Targetable{ 284 addrs.RootModuleInstance.Resource( 285 addrs.ManagedResourceMode, "aws_instance", "me", 286 ), 287 }, 288 }) 289 if diags.HasErrors() { 290 t.Fatalf("refresh errors: %s", diags.Err()) 291 } 292 293 expected := []string{"vpc-abc123", "i-abc123"} 294 if !reflect.DeepEqual(refreshedResources, expected) { 295 t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources) 296 } 297 } 298 299 func TestContext2Refresh_targetedCount(t *testing.T) { 300 p := testProvider("aws") 301 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 302 Provider: &configschema.Block{}, 303 ResourceTypes: map[string]*configschema.Block{ 304 "aws_elb": { 305 Attributes: map[string]*configschema.Attribute{ 306 "id": { 307 Type: cty.String, 308 Computed: true, 309 }, 310 "instances": { 311 Type: cty.Set(cty.String), 312 Optional: true, 313 }, 314 }, 315 }, 316 "aws_instance": { 317 Attributes: map[string]*configschema.Attribute{ 318 "id": { 319 Type: cty.String, 320 Computed: true, 321 }, 322 "vpc_id": { 323 Type: cty.String, 324 Optional: true, 325 }, 326 }, 327 }, 328 "aws_vpc": { 329 Attributes: map[string]*configschema.Attribute{ 330 "id": { 331 Type: cty.String, 332 Computed: true, 333 }, 334 }, 335 }, 336 }, 337 }) 338 339 state := states.NewState() 340 root := state.EnsureModule(addrs.RootModuleInstance) 341 testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 342 testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 343 testSetResourceInstanceCurrent(root, "aws_instance.me[0]", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 344 testSetResourceInstanceCurrent(root, "aws_instance.me[1]", `{"id":"i-cde567"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 345 testSetResourceInstanceCurrent(root, "aws_instance.me[2]", `{"id":"i-cde789"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 346 testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 347 348 m := testModule(t, "refresh-targeted-count") 349 ctx := testContext2(t, &ContextOpts{ 350 Providers: map[addrs.Provider]providers.Factory{ 351 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 352 }, 353 }) 354 355 refreshedResources := make([]string, 0, 2) 356 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 357 refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString()) 358 return providers.ReadResourceResponse{ 359 NewState: req.PriorState, 360 } 361 } 362 363 _, diags := ctx.Refresh(m, state, &PlanOpts{ 364 Mode: plans.NormalMode, 365 Targets: []addrs.Targetable{ 366 addrs.RootModuleInstance.Resource( 367 addrs.ManagedResourceMode, "aws_instance", "me", 368 ), 369 }, 370 }) 371 if diags.HasErrors() { 372 t.Fatalf("refresh errors: %s", diags.Err()) 373 } 374 375 // Target didn't specify index, so we should get all our instances 376 expected := []string{ 377 "vpc-abc123", 378 "i-abc123", 379 "i-cde567", 380 "i-cde789", 381 } 382 sort.Strings(expected) 383 sort.Strings(refreshedResources) 384 if !reflect.DeepEqual(refreshedResources, expected) { 385 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", refreshedResources, expected) 386 } 387 } 388 389 func TestContext2Refresh_targetedCountIndex(t *testing.T) { 390 p := testProvider("aws") 391 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 392 Provider: &configschema.Block{}, 393 ResourceTypes: map[string]*configschema.Block{ 394 "aws_elb": { 395 Attributes: map[string]*configschema.Attribute{ 396 "id": { 397 Type: cty.String, 398 Computed: true, 399 }, 400 "instances": { 401 Type: cty.Set(cty.String), 402 Optional: true, 403 }, 404 }, 405 }, 406 "aws_instance": { 407 Attributes: map[string]*configschema.Attribute{ 408 "id": { 409 Type: cty.String, 410 Computed: true, 411 }, 412 "vpc_id": { 413 Type: cty.String, 414 Optional: true, 415 }, 416 }, 417 }, 418 "aws_vpc": { 419 Attributes: map[string]*configschema.Attribute{ 420 "id": { 421 Type: cty.String, 422 Computed: true, 423 }, 424 }, 425 }, 426 }, 427 }) 428 429 state := states.NewState() 430 root := state.EnsureModule(addrs.RootModuleInstance) 431 testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 432 testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 433 testSetResourceInstanceCurrent(root, "aws_instance.me[0]", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 434 testSetResourceInstanceCurrent(root, "aws_instance.me[1]", `{"id":"i-cde567"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 435 testSetResourceInstanceCurrent(root, "aws_instance.me[2]", `{"id":"i-cde789"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 436 testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 437 438 m := testModule(t, "refresh-targeted-count") 439 ctx := testContext2(t, &ContextOpts{ 440 Providers: map[addrs.Provider]providers.Factory{ 441 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 442 }, 443 }) 444 445 refreshedResources := make([]string, 0, 2) 446 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 447 refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString()) 448 return providers.ReadResourceResponse{ 449 NewState: req.PriorState, 450 } 451 } 452 453 _, diags := ctx.Refresh(m, state, &PlanOpts{ 454 Mode: plans.NormalMode, 455 Targets: []addrs.Targetable{ 456 addrs.RootModuleInstance.ResourceInstance( 457 addrs.ManagedResourceMode, "aws_instance", "me", addrs.IntKey(0), 458 ), 459 }, 460 }) 461 if diags.HasErrors() { 462 t.Fatalf("refresh errors: %s", diags.Err()) 463 } 464 465 expected := []string{"vpc-abc123", "i-abc123"} 466 if !reflect.DeepEqual(refreshedResources, expected) { 467 t.Fatalf("wrong result\ngot: %#v\nwant: %#v", refreshedResources, expected) 468 } 469 } 470 471 func TestContext2Refresh_moduleComputedVar(t *testing.T) { 472 p := testProvider("aws") 473 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 474 Provider: &configschema.Block{}, 475 ResourceTypes: map[string]*configschema.Block{ 476 "aws_instance": { 477 Attributes: map[string]*configschema.Attribute{ 478 "id": { 479 Type: cty.String, 480 Computed: true, 481 }, 482 "value": { 483 Type: cty.String, 484 Optional: true, 485 }, 486 }, 487 }, 488 }, 489 }) 490 491 m := testModule(t, "refresh-module-computed-var") 492 ctx := testContext2(t, &ContextOpts{ 493 Providers: map[addrs.Provider]providers.Factory{ 494 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 495 }, 496 }) 497 498 // This was failing (see GH-2188) at some point, so this test just 499 // verifies that the failure goes away. 500 if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() { 501 t.Fatalf("refresh errs: %s", diags.Err()) 502 } 503 } 504 505 func TestContext2Refresh_delete(t *testing.T) { 506 p := testProvider("aws") 507 m := testModule(t, "refresh-basic") 508 509 state := states.NewState() 510 root := state.EnsureModule(addrs.RootModuleInstance) 511 testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 512 513 ctx := testContext2(t, &ContextOpts{ 514 Providers: map[addrs.Provider]providers.Factory{ 515 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 516 }, 517 }) 518 519 p.ReadResourceResponse = &providers.ReadResourceResponse{ 520 NewState: cty.NullVal(p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block.ImpliedType()), 521 } 522 523 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 524 if diags.HasErrors() { 525 t.Fatalf("refresh errors: %s", diags.Err()) 526 } 527 528 mod := s.RootModule() 529 if len(mod.Resources) > 0 { 530 t.Fatal("resources should be empty") 531 } 532 } 533 534 func TestContext2Refresh_ignoreUncreated(t *testing.T) { 535 p := testProvider("aws") 536 m := testModule(t, "refresh-basic") 537 ctx := testContext2(t, &ContextOpts{ 538 Providers: map[addrs.Provider]providers.Factory{ 539 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 540 }, 541 }) 542 543 p.ReadResourceResponse = &providers.ReadResourceResponse{ 544 NewState: cty.ObjectVal(map[string]cty.Value{ 545 "id": cty.StringVal("foo"), 546 }), 547 } 548 549 _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}) 550 if diags.HasErrors() { 551 t.Fatalf("refresh errors: %s", diags.Err()) 552 } 553 if p.ReadResourceCalled { 554 t.Fatal("refresh should not be called") 555 } 556 } 557 558 func TestContext2Refresh_hook(t *testing.T) { 559 h := new(MockHook) 560 p := testProvider("aws") 561 m := testModule(t, "refresh-basic") 562 563 state := states.NewState() 564 root := state.EnsureModule(addrs.RootModuleInstance) 565 testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 566 567 ctx := testContext2(t, &ContextOpts{ 568 Hooks: []Hook{h}, 569 Providers: map[addrs.Provider]providers.Factory{ 570 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 571 }, 572 }) 573 574 if _, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() { 575 t.Fatalf("refresh errs: %s", diags.Err()) 576 } 577 if !h.PreRefreshCalled { 578 t.Fatal("should be called") 579 } 580 if !h.PostRefreshCalled { 581 t.Fatal("should be called") 582 } 583 } 584 585 func TestContext2Refresh_modules(t *testing.T) { 586 p := testProvider("aws") 587 m := testModule(t, "refresh-modules") 588 589 state := states.NewState() 590 root := state.EnsureModule(addrs.RootModuleInstance) 591 testSetResourceInstanceTainted(root, "aws_instance.web", `{"id":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 592 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 593 testSetResourceInstanceCurrent(child, "aws_instance.web", `{"id":"baz"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 594 595 ctx := testContext2(t, &ContextOpts{ 596 Providers: map[addrs.Provider]providers.Factory{ 597 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 598 }, 599 }) 600 601 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 602 if !req.PriorState.GetAttr("id").RawEquals(cty.StringVal("baz")) { 603 return providers.ReadResourceResponse{ 604 NewState: req.PriorState, 605 } 606 } 607 608 new, _ := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) { 609 if len(path) == 1 && path[0].(cty.GetAttrStep).Name == "id" { 610 return cty.StringVal("new"), nil 611 } 612 return v, nil 613 }) 614 return providers.ReadResourceResponse{ 615 NewState: new, 616 } 617 } 618 619 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 620 if diags.HasErrors() { 621 t.Fatalf("refresh errors: %s", diags.Err()) 622 } 623 624 actual := strings.TrimSpace(s.String()) 625 expected := strings.TrimSpace(testContextRefreshModuleStr) 626 if actual != expected { 627 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 628 } 629 } 630 631 func TestContext2Refresh_moduleInputComputedOutput(t *testing.T) { 632 m := testModule(t, "refresh-module-input-computed-output") 633 p := testProvider("aws") 634 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 635 Provider: &configschema.Block{}, 636 ResourceTypes: map[string]*configschema.Block{ 637 "aws_instance": { 638 Attributes: map[string]*configschema.Attribute{ 639 "foo": { 640 Type: cty.String, 641 Optional: true, 642 Computed: true, 643 }, 644 "compute": { 645 Type: cty.String, 646 Optional: true, 647 }, 648 }, 649 }, 650 }, 651 }) 652 653 ctx := testContext2(t, &ContextOpts{ 654 Providers: map[addrs.Provider]providers.Factory{ 655 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 656 }, 657 }) 658 659 if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() { 660 t.Fatalf("refresh errs: %s", diags.Err()) 661 } 662 } 663 664 func TestContext2Refresh_moduleVarModule(t *testing.T) { 665 m := testModule(t, "refresh-module-var-module") 666 p := testProvider("aws") 667 ctx := testContext2(t, &ContextOpts{ 668 Providers: map[addrs.Provider]providers.Factory{ 669 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 670 }, 671 }) 672 673 if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() { 674 t.Fatalf("refresh errs: %s", diags.Err()) 675 } 676 } 677 678 // GH-70 679 func TestContext2Refresh_noState(t *testing.T) { 680 p := testProvider("aws") 681 m := testModule(t, "refresh-no-state") 682 ctx := testContext2(t, &ContextOpts{ 683 Providers: map[addrs.Provider]providers.Factory{ 684 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 685 }, 686 }) 687 688 p.ReadResourceResponse = &providers.ReadResourceResponse{ 689 NewState: cty.ObjectVal(map[string]cty.Value{ 690 "id": cty.StringVal("foo"), 691 }), 692 } 693 694 if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() { 695 t.Fatalf("refresh errs: %s", diags.Err()) 696 } 697 } 698 699 func TestContext2Refresh_output(t *testing.T) { 700 p := testProvider("aws") 701 p.PlanResourceChangeFn = testDiffFn 702 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 703 Provider: &configschema.Block{}, 704 ResourceTypes: map[string]*configschema.Block{ 705 "aws_instance": { 706 Attributes: map[string]*configschema.Attribute{ 707 "id": { 708 Type: cty.String, 709 Computed: true, 710 }, 711 "foo": { 712 Type: cty.String, 713 Optional: true, 714 Computed: true, 715 }, 716 }, 717 }, 718 }, 719 }) 720 721 m := testModule(t, "refresh-output") 722 723 state := states.NewState() 724 root := state.EnsureModule(addrs.RootModuleInstance) 725 testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo","foo":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 726 root.SetOutputValue("foo", cty.StringVal("foo"), false) 727 728 ctx := testContext2(t, &ContextOpts{ 729 Providers: map[addrs.Provider]providers.Factory{ 730 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 731 }, 732 }) 733 734 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 735 if diags.HasErrors() { 736 t.Fatalf("refresh errors: %s", diags.Err()) 737 } 738 739 actual := strings.TrimSpace(s.String()) 740 expected := strings.TrimSpace(testContextRefreshOutputStr) 741 if actual != expected { 742 t.Fatalf("wrong result\n\ngot:\n%q\n\nwant:\n%q", actual, expected) 743 } 744 } 745 746 func TestContext2Refresh_outputPartial(t *testing.T) { 747 p := testProvider("aws") 748 m := testModule(t, "refresh-output-partial") 749 750 // Refresh creates a partial plan for any instances that don't have 751 // remote objects yet, to get stub values for interpolation. Therefore 752 // we need to make DiffFn available to let that complete. 753 754 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 755 Provider: &configschema.Block{}, 756 ResourceTypes: map[string]*configschema.Block{ 757 "aws_instance": { 758 Attributes: map[string]*configschema.Attribute{ 759 "foo": { 760 Type: cty.String, 761 Computed: true, 762 }, 763 }, 764 }, 765 }, 766 }) 767 768 p.ReadResourceResponse = &providers.ReadResourceResponse{ 769 NewState: cty.NullVal(p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block.ImpliedType()), 770 } 771 772 state := states.NewState() 773 root := state.EnsureModule(addrs.RootModuleInstance) 774 testSetResourceInstanceCurrent(root, "aws_instance.foo", `{}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 775 776 ctx := testContext2(t, &ContextOpts{ 777 Providers: map[addrs.Provider]providers.Factory{ 778 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 779 }, 780 }) 781 782 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 783 if diags.HasErrors() { 784 t.Fatalf("refresh errors: %s", diags.Err()) 785 } 786 787 actual := strings.TrimSpace(s.String()) 788 expected := strings.TrimSpace(testContextRefreshOutputPartialStr) 789 if actual != expected { 790 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 791 } 792 } 793 794 func TestContext2Refresh_stateBasic(t *testing.T) { 795 p := testProvider("aws") 796 m := testModule(t, "refresh-basic") 797 798 state := states.NewState() 799 root := state.EnsureModule(addrs.RootModuleInstance) 800 testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 801 802 ctx := testContext2(t, &ContextOpts{ 803 Providers: map[addrs.Provider]providers.Factory{ 804 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 805 }, 806 }) 807 808 schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block 809 ty := schema.ImpliedType() 810 811 readStateVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ 812 "id": cty.StringVal("foo"), 813 })) 814 if err != nil { 815 t.Fatal(err) 816 } 817 818 p.ReadResourceResponse = &providers.ReadResourceResponse{ 819 NewState: readStateVal, 820 } 821 822 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 823 if diags.HasErrors() { 824 t.Fatalf("refresh errors: %s", diags.Err()) 825 } 826 827 if !p.ReadResourceCalled { 828 t.Fatal("read resource should be called") 829 } 830 831 mod := s.RootModule() 832 newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(ty) 833 if err != nil { 834 t.Fatal(err) 835 } 836 837 if !cmp.Equal(readStateVal, newState.Value, valueComparer, equateEmpty) { 838 t.Fatal(cmp.Diff(readStateVal, newState.Value, valueComparer, equateEmpty)) 839 } 840 } 841 842 func TestContext2Refresh_dataCount(t *testing.T) { 843 p := testProvider("test") 844 m := testModule(t, "refresh-data-count") 845 846 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 847 m := req.ProposedNewState.AsValueMap() 848 m["things"] = cty.ListVal([]cty.Value{cty.StringVal("foo")}) 849 resp.PlannedState = cty.ObjectVal(m) 850 return resp 851 } 852 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 853 ResourceTypes: map[string]*configschema.Block{ 854 "test": { 855 Attributes: map[string]*configschema.Attribute{ 856 "id": {Type: cty.String, Computed: true}, 857 "things": {Type: cty.List(cty.String), Computed: true}, 858 }, 859 }, 860 }, 861 DataSources: map[string]*configschema.Block{ 862 "test": {}, 863 }, 864 }) 865 866 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 867 return providers.ReadDataSourceResponse{ 868 State: req.Config, 869 } 870 } 871 872 ctx := testContext2(t, &ContextOpts{ 873 Providers: map[addrs.Provider]providers.Factory{ 874 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 875 }, 876 }) 877 878 s, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}) 879 880 if diags.HasErrors() { 881 t.Fatalf("refresh errors: %s", diags.Err()) 882 } 883 884 checkStateString(t, s, `<no state>`) 885 } 886 887 func TestContext2Refresh_dataState(t *testing.T) { 888 m := testModule(t, "refresh-data-resource-basic") 889 state := states.NewState() 890 schema := &configschema.Block{ 891 Attributes: map[string]*configschema.Attribute{ 892 "inputs": { 893 Type: cty.Map(cty.String), 894 Optional: true, 895 }, 896 }, 897 } 898 899 p := testProvider("null") 900 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 901 Provider: &configschema.Block{}, 902 DataSources: map[string]*configschema.Block{ 903 "null_data_source": schema, 904 }, 905 }) 906 907 ctx := testContext2(t, &ContextOpts{ 908 Providers: map[addrs.Provider]providers.Factory{ 909 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 910 }, 911 }) 912 913 var readStateVal cty.Value 914 915 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 916 m := req.Config.AsValueMap() 917 readStateVal = cty.ObjectVal(m) 918 919 return providers.ReadDataSourceResponse{ 920 State: readStateVal, 921 } 922 } 923 924 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 925 if diags.HasErrors() { 926 t.Fatalf("refresh errors: %s", diags.Err()) 927 } 928 929 if !p.ReadDataSourceCalled { 930 t.Fatal("ReadDataSource should have been called") 931 } 932 933 mod := s.RootModule() 934 935 newState, err := mod.Resources["data.null_data_source.testing"].Instances[addrs.NoKey].Current.Decode(schema.ImpliedType()) 936 if err != nil { 937 t.Fatal(err) 938 } 939 940 if !cmp.Equal(readStateVal, newState.Value, valueComparer, equateEmpty) { 941 t.Fatal(cmp.Diff(readStateVal, newState.Value, valueComparer, equateEmpty)) 942 } 943 } 944 945 func TestContext2Refresh_dataStateRefData(t *testing.T) { 946 p := testProvider("null") 947 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 948 Provider: &configschema.Block{}, 949 DataSources: map[string]*configschema.Block{ 950 "null_data_source": { 951 Attributes: map[string]*configschema.Attribute{ 952 "id": { 953 Type: cty.String, 954 Computed: true, 955 }, 956 "foo": { 957 Type: cty.String, 958 Optional: true, 959 }, 960 "bar": { 961 Type: cty.String, 962 Optional: true, 963 }, 964 }, 965 }, 966 }, 967 }) 968 969 m := testModule(t, "refresh-data-ref-data") 970 state := states.NewState() 971 ctx := testContext2(t, &ContextOpts{ 972 Providers: map[addrs.Provider]providers.Factory{ 973 addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), 974 }, 975 }) 976 977 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 978 // add the required id 979 m := req.Config.AsValueMap() 980 m["id"] = cty.StringVal("foo") 981 982 return providers.ReadDataSourceResponse{ 983 State: cty.ObjectVal(m), 984 } 985 } 986 987 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 988 if diags.HasErrors() { 989 t.Fatalf("refresh errors: %s", diags.Err()) 990 } 991 992 actual := strings.TrimSpace(s.String()) 993 expected := strings.TrimSpace(testTofuRefreshDataRefDataStr) 994 if actual != expected { 995 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 996 } 997 } 998 999 func TestContext2Refresh_tainted(t *testing.T) { 1000 p := testProvider("aws") 1001 m := testModule(t, "refresh-basic") 1002 1003 state := states.NewState() 1004 root := state.EnsureModule(addrs.RootModuleInstance) 1005 testSetResourceInstanceTainted(root, "aws_instance.web", `{"id":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 1006 1007 ctx := testContext2(t, &ContextOpts{ 1008 Providers: map[addrs.Provider]providers.Factory{ 1009 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1010 }, 1011 }) 1012 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 1013 // add the required id 1014 m := req.PriorState.AsValueMap() 1015 m["id"] = cty.StringVal("foo") 1016 1017 return providers.ReadResourceResponse{ 1018 NewState: cty.ObjectVal(m), 1019 } 1020 } 1021 1022 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 1023 if diags.HasErrors() { 1024 t.Fatalf("refresh errors: %s", diags.Err()) 1025 } 1026 if !p.ReadResourceCalled { 1027 t.Fatal("ReadResource was not called; should have been") 1028 } 1029 1030 actual := strings.TrimSpace(s.String()) 1031 expected := strings.TrimSpace(testContextRefreshTaintedStr) 1032 if actual != expected { 1033 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 1034 } 1035 } 1036 1037 // Doing a Refresh (or any operation really, but Refresh usually 1038 // happens first) with a config with an unknown provider should result in 1039 // an error. The key bug this found was that this wasn't happening if 1040 // Providers was _empty_. 1041 func TestContext2Refresh_unknownProvider(t *testing.T) { 1042 m := testModule(t, "refresh-unknown-provider") 1043 1044 state := states.NewState() 1045 root := state.EnsureModule(addrs.RootModuleInstance) 1046 testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 1047 1048 c, diags := NewContext(&ContextOpts{ 1049 Providers: map[addrs.Provider]providers.Factory{}, 1050 }) 1051 assertNoDiagnostics(t, diags) 1052 1053 _, diags = c.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}) 1054 if !diags.HasErrors() { 1055 t.Fatal("successfully refreshed; want error") 1056 } 1057 1058 if got, want := diags.Err().Error(), "Missing required provider"; !strings.Contains(got, want) { 1059 t.Errorf("missing expected error\nwant substring: %s\ngot:\n%s", want, got) 1060 } 1061 } 1062 1063 func TestContext2Refresh_vars(t *testing.T) { 1064 p := testProvider("aws") 1065 1066 schema := &configschema.Block{ 1067 Attributes: map[string]*configschema.Attribute{ 1068 "ami": { 1069 Type: cty.String, 1070 Optional: true, 1071 }, 1072 "id": { 1073 Type: cty.String, 1074 Computed: true, 1075 }, 1076 }, 1077 } 1078 1079 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1080 Provider: &configschema.Block{}, 1081 ResourceTypes: map[string]*configschema.Block{"aws_instance": schema}, 1082 }) 1083 1084 m := testModule(t, "refresh-vars") 1085 state := states.NewState() 1086 root := state.EnsureModule(addrs.RootModuleInstance) 1087 testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 1088 1089 ctx := testContext2(t, &ContextOpts{ 1090 Providers: map[addrs.Provider]providers.Factory{ 1091 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1092 }, 1093 }) 1094 1095 readStateVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ 1096 "id": cty.StringVal("foo"), 1097 })) 1098 if err != nil { 1099 t.Fatal(err) 1100 } 1101 1102 p.ReadResourceResponse = &providers.ReadResourceResponse{ 1103 NewState: readStateVal, 1104 } 1105 1106 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1107 return providers.PlanResourceChangeResponse{ 1108 PlannedState: req.ProposedNewState, 1109 } 1110 } 1111 1112 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 1113 if diags.HasErrors() { 1114 t.Fatalf("refresh errors: %s", diags.Err()) 1115 } 1116 1117 if !p.ReadResourceCalled { 1118 t.Fatal("read resource should be called") 1119 } 1120 1121 mod := s.RootModule() 1122 1123 newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema.ImpliedType()) 1124 if err != nil { 1125 t.Fatal(err) 1126 } 1127 1128 if !cmp.Equal(readStateVal, newState.Value, valueComparer, equateEmpty) { 1129 t.Fatal(cmp.Diff(readStateVal, newState.Value, valueComparer, equateEmpty)) 1130 } 1131 1132 for _, r := range mod.Resources { 1133 if r.Addr.Resource.Type == "" { 1134 t.Fatalf("no type: %#v", r) 1135 } 1136 } 1137 } 1138 1139 func TestContext2Refresh_orphanModule(t *testing.T) { 1140 p := testProvider("aws") 1141 m := testModule(t, "refresh-module-orphan") 1142 1143 // Create a custom refresh function to track the order they were visited 1144 var order []string 1145 var orderLock sync.Mutex 1146 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 1147 orderLock.Lock() 1148 defer orderLock.Unlock() 1149 1150 order = append(order, req.PriorState.GetAttr("id").AsString()) 1151 return providers.ReadResourceResponse{ 1152 NewState: req.PriorState, 1153 } 1154 } 1155 1156 state := states.NewState() 1157 root := state.EnsureModule(addrs.RootModuleInstance) 1158 root.SetResourceInstanceCurrent( 1159 mustResourceInstanceAddr("aws_instance.foo").Resource, 1160 &states.ResourceInstanceObjectSrc{ 1161 Status: states.ObjectReady, 1162 AttrsJSON: []byte(`{"id":"i-abc123"}`), 1163 Dependencies: []addrs.ConfigResource{ 1164 {Module: addrs.Module{"module.child"}}, 1165 {Module: addrs.Module{"module.child"}}, 1166 }, 1167 }, 1168 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1169 ) 1170 child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) 1171 child.SetResourceInstanceCurrent( 1172 mustResourceInstanceAddr("aws_instance.bar").Resource, 1173 &states.ResourceInstanceObjectSrc{ 1174 Status: states.ObjectReady, 1175 AttrsJSON: []byte(`{"id":"i-bcd23"}`), 1176 Dependencies: []addrs.ConfigResource{{Module: addrs.Module{"module.grandchild"}}}, 1177 }, 1178 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`), 1179 ) 1180 grandchild := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey).Child("grandchild", addrs.NoKey)) 1181 testSetResourceInstanceCurrent(grandchild, "aws_instance.baz", `{"id":"i-cde345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`) 1182 1183 ctx := testContext2(t, &ContextOpts{ 1184 Providers: map[addrs.Provider]providers.Factory{ 1185 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1186 }, 1187 }) 1188 1189 testCheckDeadlock(t, func() { 1190 _, err := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 1191 if err != nil { 1192 t.Fatalf("err: %s", err.Err()) 1193 } 1194 1195 // TODO: handle order properly for orphaned modules / resources 1196 // expected := []string{"i-abc123", "i-bcd234", "i-cde345"} 1197 // if !reflect.DeepEqual(order, expected) { 1198 // t.Fatalf("expected: %#v, got: %#v", expected, order) 1199 // } 1200 }) 1201 } 1202 1203 func TestContext2Validate(t *testing.T) { 1204 p := testProvider("aws") 1205 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1206 Provider: &configschema.Block{}, 1207 ResourceTypes: map[string]*configschema.Block{ 1208 "aws_instance": { 1209 Attributes: map[string]*configschema.Attribute{ 1210 "foo": { 1211 Type: cty.String, 1212 Optional: true, 1213 }, 1214 "num": { 1215 Type: cty.String, 1216 Optional: true, 1217 }, 1218 }, 1219 }, 1220 }, 1221 }) 1222 1223 m := testModule(t, "validate-good") 1224 c := testContext2(t, &ContextOpts{ 1225 Providers: map[addrs.Provider]providers.Factory{ 1226 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1227 }, 1228 }) 1229 1230 diags := c.Validate(m) 1231 if len(diags) != 0 { 1232 t.Fatalf("unexpected error: %#v", diags.ErrWithWarnings()) 1233 } 1234 } 1235 1236 func TestContext2Refresh_updateProviderInState(t *testing.T) { 1237 m := testModule(t, "update-resource-provider") 1238 p := testProvider("aws") 1239 1240 state := states.NewState() 1241 root := state.EnsureModule(addrs.RootModuleInstance) 1242 testSetResourceInstanceCurrent(root, "aws_instance.bar", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"].baz`) 1243 1244 ctx := testContext2(t, &ContextOpts{ 1245 Providers: map[addrs.Provider]providers.Factory{ 1246 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1247 }, 1248 }) 1249 1250 expected := strings.TrimSpace(` 1251 aws_instance.bar: 1252 ID = foo 1253 provider = provider["registry.opentofu.org/hashicorp/aws"].foo`) 1254 1255 s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 1256 if diags.HasErrors() { 1257 t.Fatal(diags.Err()) 1258 } 1259 1260 actual := s.String() 1261 if actual != expected { 1262 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) 1263 } 1264 } 1265 1266 func TestContext2Refresh_schemaUpgradeFlatmap(t *testing.T) { 1267 m := testModule(t, "refresh-schema-upgrade") 1268 p := testProvider("test") 1269 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1270 ResourceTypes: map[string]*configschema.Block{ 1271 "test_thing": { 1272 Attributes: map[string]*configschema.Attribute{ 1273 "name": { // imagining we renamed this from "id" 1274 Type: cty.String, 1275 Optional: true, 1276 }, 1277 }, 1278 }, 1279 }, 1280 ResourceTypeSchemaVersions: map[string]uint64{ 1281 "test_thing": 5, 1282 }, 1283 }) 1284 p.UpgradeResourceStateResponse = &providers.UpgradeResourceStateResponse{ 1285 UpgradedState: cty.ObjectVal(map[string]cty.Value{ 1286 "name": cty.StringVal("foo"), 1287 }), 1288 } 1289 1290 s := states.BuildState(func(s *states.SyncState) { 1291 s.SetResourceInstanceCurrent( 1292 addrs.Resource{ 1293 Mode: addrs.ManagedResourceMode, 1294 Type: "test_thing", 1295 Name: "bar", 1296 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1297 &states.ResourceInstanceObjectSrc{ 1298 Status: states.ObjectReady, 1299 SchemaVersion: 3, 1300 AttrsFlat: map[string]string{ 1301 "id": "foo", 1302 }, 1303 }, 1304 addrs.AbsProviderConfig{ 1305 Provider: addrs.NewDefaultProvider("test"), 1306 Module: addrs.RootModule, 1307 }, 1308 ) 1309 }) 1310 1311 ctx := testContext2(t, &ContextOpts{ 1312 Providers: map[addrs.Provider]providers.Factory{ 1313 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1314 }, 1315 }) 1316 1317 state, diags := ctx.Refresh(m, s, &PlanOpts{Mode: plans.NormalMode}) 1318 if diags.HasErrors() { 1319 t.Fatal(diags.Err()) 1320 } 1321 1322 { 1323 got := p.UpgradeResourceStateRequest 1324 want := providers.UpgradeResourceStateRequest{ 1325 TypeName: "test_thing", 1326 Version: 3, 1327 RawStateFlatmap: map[string]string{ 1328 "id": "foo", 1329 }, 1330 } 1331 if !cmp.Equal(got, want) { 1332 t.Errorf("wrong upgrade request\n%s", cmp.Diff(want, got)) 1333 } 1334 } 1335 1336 { 1337 got := state.String() 1338 want := strings.TrimSpace(` 1339 test_thing.bar: 1340 ID = 1341 provider = provider["registry.opentofu.org/hashicorp/test"] 1342 name = foo 1343 `) 1344 if got != want { 1345 t.Fatalf("wrong result state\ngot:\n%s\n\nwant:\n%s", got, want) 1346 } 1347 } 1348 } 1349 1350 func TestContext2Refresh_schemaUpgradeJSON(t *testing.T) { 1351 m := testModule(t, "refresh-schema-upgrade") 1352 p := testProvider("test") 1353 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1354 ResourceTypes: map[string]*configschema.Block{ 1355 "test_thing": { 1356 Attributes: map[string]*configschema.Attribute{ 1357 "name": { // imagining we renamed this from "id" 1358 Type: cty.String, 1359 Optional: true, 1360 }, 1361 }, 1362 }, 1363 }, 1364 ResourceTypeSchemaVersions: map[string]uint64{ 1365 "test_thing": 5, 1366 }, 1367 }) 1368 p.UpgradeResourceStateResponse = &providers.UpgradeResourceStateResponse{ 1369 UpgradedState: cty.ObjectVal(map[string]cty.Value{ 1370 "name": cty.StringVal("foo"), 1371 }), 1372 } 1373 1374 s := states.BuildState(func(s *states.SyncState) { 1375 s.SetResourceInstanceCurrent( 1376 addrs.Resource{ 1377 Mode: addrs.ManagedResourceMode, 1378 Type: "test_thing", 1379 Name: "bar", 1380 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1381 &states.ResourceInstanceObjectSrc{ 1382 Status: states.ObjectReady, 1383 SchemaVersion: 3, 1384 AttrsJSON: []byte(`{"id":"foo"}`), 1385 }, 1386 addrs.AbsProviderConfig{ 1387 Provider: addrs.NewDefaultProvider("test"), 1388 Module: addrs.RootModule, 1389 }, 1390 ) 1391 }) 1392 1393 ctx := testContext2(t, &ContextOpts{ 1394 Providers: map[addrs.Provider]providers.Factory{ 1395 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1396 }, 1397 }) 1398 1399 state, diags := ctx.Refresh(m, s, &PlanOpts{Mode: plans.NormalMode}) 1400 if diags.HasErrors() { 1401 t.Fatal(diags.Err()) 1402 } 1403 1404 { 1405 got := p.UpgradeResourceStateRequest 1406 want := providers.UpgradeResourceStateRequest{ 1407 TypeName: "test_thing", 1408 Version: 3, 1409 RawStateJSON: []byte(`{"id":"foo"}`), 1410 } 1411 if !cmp.Equal(got, want) { 1412 t.Errorf("wrong upgrade request\n%s", cmp.Diff(want, got)) 1413 } 1414 } 1415 1416 { 1417 got := state.String() 1418 want := strings.TrimSpace(` 1419 test_thing.bar: 1420 ID = 1421 provider = provider["registry.opentofu.org/hashicorp/test"] 1422 name = foo 1423 `) 1424 if got != want { 1425 t.Fatalf("wrong result state\ngot:\n%s\n\nwant:\n%s", got, want) 1426 } 1427 } 1428 } 1429 1430 func TestContext2Refresh_dataValidation(t *testing.T) { 1431 m := testModuleInline(t, map[string]string{ 1432 "main.tf": ` 1433 data "aws_data_source" "foo" { 1434 foo = "bar" 1435 } 1436 `, 1437 }) 1438 1439 p := testProvider("aws") 1440 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 1441 resp.PlannedState = req.ProposedNewState 1442 return 1443 } 1444 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { 1445 resp.State = req.Config 1446 return 1447 } 1448 1449 ctx := testContext2(t, &ContextOpts{ 1450 Providers: map[addrs.Provider]providers.Factory{ 1451 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1452 }, 1453 }) 1454 1455 _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}) 1456 if diags.HasErrors() { 1457 // Should get this error: 1458 // Unsupported attribute: This object does not have an attribute named "missing" 1459 t.Fatal(diags.Err()) 1460 } 1461 1462 if !p.ValidateDataResourceConfigCalled { 1463 t.Fatal("ValidateDataSourceConfig not called during plan") 1464 } 1465 } 1466 1467 func TestContext2Refresh_dataResourceDependsOn(t *testing.T) { 1468 m := testModule(t, "plan-data-depends-on") 1469 p := testProvider("test") 1470 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1471 ResourceTypes: map[string]*configschema.Block{ 1472 "test_resource": { 1473 Attributes: map[string]*configschema.Attribute{ 1474 "id": {Type: cty.String, Computed: true}, 1475 "foo": {Type: cty.String, Optional: true}, 1476 }, 1477 }, 1478 }, 1479 DataSources: map[string]*configschema.Block{ 1480 "test_data": { 1481 Attributes: map[string]*configschema.Attribute{ 1482 "compute": {Type: cty.String, Computed: true}, 1483 }, 1484 }, 1485 }, 1486 }) 1487 p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ 1488 State: cty.ObjectVal(map[string]cty.Value{ 1489 "compute": cty.StringVal("value"), 1490 }), 1491 } 1492 1493 state := states.NewState() 1494 root := state.EnsureModule(addrs.RootModuleInstance) 1495 testSetResourceInstanceCurrent(root, "test_resource.a", `{"id":"a"}`, `provider["registry.opentofu.org/hashicorp/test"]`) 1496 1497 ctx := testContext2(t, &ContextOpts{ 1498 Providers: map[addrs.Provider]providers.Factory{ 1499 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1500 }, 1501 }) 1502 1503 _, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 1504 if diags.HasErrors() { 1505 t.Fatalf("unexpected errors: %s", diags.Err()) 1506 } 1507 } 1508 1509 // verify that create_before_destroy is updated in the state during refresh 1510 func TestRefresh_updateLifecycle(t *testing.T) { 1511 state := states.NewState() 1512 root := state.EnsureModule(addrs.RootModuleInstance) 1513 root.SetResourceInstanceCurrent( 1514 addrs.Resource{ 1515 Mode: addrs.ManagedResourceMode, 1516 Type: "aws_instance", 1517 Name: "bar", 1518 }.Instance(addrs.NoKey), 1519 &states.ResourceInstanceObjectSrc{ 1520 Status: states.ObjectReady, 1521 AttrsJSON: []byte(`{"id":"bar"}`), 1522 }, 1523 addrs.AbsProviderConfig{ 1524 Provider: addrs.NewDefaultProvider("aws"), 1525 Module: addrs.RootModule, 1526 }, 1527 ) 1528 1529 m := testModuleInline(t, map[string]string{ 1530 "main.tf": ` 1531 resource "aws_instance" "bar" { 1532 lifecycle { 1533 create_before_destroy = true 1534 } 1535 } 1536 `, 1537 }) 1538 1539 p := testProvider("aws") 1540 1541 ctx := testContext2(t, &ContextOpts{ 1542 Providers: map[addrs.Provider]providers.Factory{ 1543 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 1544 }, 1545 }) 1546 1547 state, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 1548 if diags.HasErrors() { 1549 t.Fatalf("plan errors: %s", diags.Err()) 1550 } 1551 1552 r := state.ResourceInstance(mustResourceInstanceAddr("aws_instance.bar")) 1553 if !r.Current.CreateBeforeDestroy { 1554 t.Fatal("create_before_destroy not updated in instance state") 1555 } 1556 } 1557 1558 func TestContext2Refresh_dataSourceOrphan(t *testing.T) { 1559 m := testModuleInline(t, map[string]string{ 1560 "main.tf": ``, 1561 }) 1562 1563 state := states.NewState() 1564 root := state.EnsureModule(addrs.RootModuleInstance) 1565 root.SetResourceInstanceCurrent( 1566 addrs.Resource{ 1567 Mode: addrs.DataResourceMode, 1568 Type: "test_data_source", 1569 Name: "foo", 1570 }.Instance(addrs.NoKey), 1571 &states.ResourceInstanceObjectSrc{ 1572 Status: states.ObjectReady, 1573 AttrsJSON: []byte(`{"id":"foo"}`), 1574 Dependencies: []addrs.ConfigResource{}, 1575 }, 1576 addrs.AbsProviderConfig{ 1577 Provider: addrs.NewDefaultProvider("test"), 1578 Module: addrs.RootModule, 1579 }, 1580 ) 1581 p := testProvider("test") 1582 p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { 1583 resp.State = cty.NullVal(req.Config.Type()) 1584 return 1585 } 1586 1587 ctx := testContext2(t, &ContextOpts{ 1588 Providers: map[addrs.Provider]providers.Factory{ 1589 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1590 }, 1591 }) 1592 1593 _, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}) 1594 if diags.HasErrors() { 1595 t.Fatal(diags.Err()) 1596 } 1597 1598 if p.ReadResourceCalled { 1599 t.Fatal("there are no managed resources to read") 1600 } 1601 1602 if p.ReadDataSourceCalled { 1603 t.Fatal("orphaned data source instance should not be read") 1604 } 1605 } 1606 1607 // Legacy providers may return invalid null values for blocks, causing noise in 1608 // the diff output and unexpected behavior with ignore_changes. Make sure 1609 // refresh fixes these up before storing the state. 1610 func TestContext2Refresh_reifyNullBlock(t *testing.T) { 1611 m := testModuleInline(t, map[string]string{ 1612 "main.tf": ` 1613 resource "test_resource" "foo" { 1614 } 1615 `, 1616 }) 1617 1618 p := new(MockProvider) 1619 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 1620 // incorrectly return a null _set_block value 1621 v := req.PriorState.AsValueMap() 1622 v["set_block"] = cty.NullVal(v["set_block"].Type()) 1623 return providers.ReadResourceResponse{NewState: cty.ObjectVal(v)} 1624 } 1625 1626 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 1627 Provider: &configschema.Block{}, 1628 ResourceTypes: map[string]*configschema.Block{ 1629 "test_resource": { 1630 Attributes: map[string]*configschema.Attribute{ 1631 "id": { 1632 Type: cty.String, 1633 Computed: true, 1634 }, 1635 }, 1636 BlockTypes: map[string]*configschema.NestedBlock{ 1637 "set_block": { 1638 Block: configschema.Block{ 1639 Attributes: map[string]*configschema.Attribute{ 1640 "a": {Type: cty.String, Optional: true}, 1641 }, 1642 }, 1643 Nesting: configschema.NestingSet, 1644 }, 1645 }, 1646 }, 1647 }, 1648 }) 1649 p.PlanResourceChangeFn = testDiffFn 1650 1651 fooAddr := addrs.Resource{ 1652 Mode: addrs.ManagedResourceMode, 1653 Type: "test_resource", 1654 Name: "foo", 1655 }.Instance(addrs.NoKey) 1656 1657 state := states.NewState() 1658 root := state.EnsureModule(addrs.RootModuleInstance) 1659 root.SetResourceInstanceCurrent( 1660 fooAddr, 1661 &states.ResourceInstanceObjectSrc{ 1662 Status: states.ObjectReady, 1663 AttrsJSON: []byte(`{"id":"foo", "network_interface":[]}`), 1664 Dependencies: []addrs.ConfigResource{}, 1665 }, 1666 addrs.AbsProviderConfig{ 1667 Provider: addrs.NewDefaultProvider("test"), 1668 Module: addrs.RootModule, 1669 }, 1670 ) 1671 1672 ctx := testContext2(t, &ContextOpts{ 1673 Providers: map[addrs.Provider]providers.Factory{ 1674 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 1675 }, 1676 }) 1677 1678 plan, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.RefreshOnlyMode}) 1679 if diags.HasErrors() { 1680 t.Fatalf("refresh errors: %s", diags.Err()) 1681 } 1682 1683 jsonState := plan.PriorState.ResourceInstance(fooAddr.Absolute(addrs.RootModuleInstance)).Current.AttrsJSON 1684 1685 // the set_block should still be an empty container, and not null 1686 expected := `{"id":"foo","set_block":[]}` 1687 if string(jsonState) != expected { 1688 t.Fatalf("invalid state\nexpected: %s\ngot: %s\n", expected, jsonState) 1689 } 1690 }