github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/terraform/context_apply2_test.go (about) 1 package terraform 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/muratcelep/terraform/not-internal/addrs" 11 "github.com/muratcelep/terraform/not-internal/configs/configschema" 12 "github.com/muratcelep/terraform/not-internal/lang/marks" 13 "github.com/muratcelep/terraform/not-internal/plans" 14 "github.com/muratcelep/terraform/not-internal/providers" 15 "github.com/muratcelep/terraform/not-internal/states" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 // Test that the PreApply hook is called with the correct deposed key 20 func TestContext2Apply_createBeforeDestroy_deposedKeyPreApply(t *testing.T) { 21 m := testModule(t, "apply-cbd-deposed-only") 22 p := testProvider("aws") 23 p.PlanResourceChangeFn = testDiffFn 24 p.ApplyResourceChangeFn = testApplyFn 25 26 deposedKey := states.NewDeposedKey() 27 28 state := states.NewState() 29 root := state.EnsureModule(addrs.RootModuleInstance) 30 root.SetResourceInstanceCurrent( 31 mustResourceInstanceAddr("aws_instance.bar").Resource, 32 &states.ResourceInstanceObjectSrc{ 33 Status: states.ObjectReady, 34 AttrsJSON: []byte(`{"id":"bar"}`), 35 }, 36 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 37 ) 38 root.SetResourceInstanceDeposed( 39 mustResourceInstanceAddr("aws_instance.bar").Resource, 40 deposedKey, 41 &states.ResourceInstanceObjectSrc{ 42 Status: states.ObjectTainted, 43 AttrsJSON: []byte(`{"id":"foo"}`), 44 }, 45 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 46 ) 47 48 hook := new(MockHook) 49 ctx := testContext2(t, &ContextOpts{ 50 Hooks: []Hook{hook}, 51 Providers: map[addrs.Provider]providers.Factory{ 52 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 53 }, 54 }) 55 56 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 57 if diags.HasErrors() { 58 t.Fatalf("diags: %s", diags.Err()) 59 } else { 60 t.Logf(legacyDiffComparisonString(plan.Changes)) 61 } 62 63 _, diags = ctx.Apply(plan, m) 64 if diags.HasErrors() { 65 t.Fatalf("diags: %s", diags.Err()) 66 } 67 68 // Verify PreApply was called correctly 69 if !hook.PreApplyCalled { 70 t.Fatalf("PreApply hook not called") 71 } 72 if addr, wantAddr := hook.PreApplyAddr, mustResourceInstanceAddr("aws_instance.bar"); !addr.Equal(wantAddr) { 73 t.Errorf("expected addr to be %s, but was %s", wantAddr, addr) 74 } 75 if gen := hook.PreApplyGen; gen != deposedKey { 76 t.Errorf("expected gen to be %q, but was %q", deposedKey, gen) 77 } 78 } 79 80 func TestContext2Apply_destroyWithDataSourceExpansion(t *testing.T) { 81 // While managed resources store their destroy-time dependencies, data 82 // sources do not. This means that if a provider were only included in a 83 // destroy graph because of data sources, it could have dependencies which 84 // are not correctly ordered. Here we verify that the provider is not 85 // included in the destroy operation, and all dependency evaluations 86 // succeed. 87 88 m := testModuleInline(t, map[string]string{ 89 "main.tf": ` 90 module "mod" { 91 source = "./mod" 92 } 93 94 provider "other" { 95 foo = module.mod.data 96 } 97 98 # this should not require the provider be present during destroy 99 data "other_data_source" "a" { 100 } 101 `, 102 103 "mod/main.tf": ` 104 data "test_data_source" "a" { 105 count = 1 106 } 107 108 data "test_data_source" "b" { 109 count = data.test_data_source.a[0].foo == "ok" ? 1 : 0 110 } 111 112 output "data" { 113 value = data.test_data_source.a[0].foo == "ok" ? data.test_data_source.b[0].foo : "nope" 114 } 115 `, 116 }) 117 118 testP := testProvider("test") 119 otherP := testProvider("other") 120 121 readData := func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { 122 return providers.ReadDataSourceResponse{ 123 State: cty.ObjectVal(map[string]cty.Value{ 124 "id": cty.StringVal("data_source"), 125 "foo": cty.StringVal("ok"), 126 }), 127 } 128 } 129 130 testP.ReadDataSourceFn = readData 131 otherP.ReadDataSourceFn = readData 132 133 ps := map[addrs.Provider]providers.Factory{ 134 addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP), 135 addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherP), 136 } 137 138 otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 139 foo := req.Config.GetAttr("foo") 140 if foo.IsNull() || foo.AsString() != "ok" { 141 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect config val: %#v\n", foo)) 142 } 143 return resp 144 } 145 146 ctx := testContext2(t, &ContextOpts{ 147 Providers: ps, 148 }) 149 150 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 151 if diags.HasErrors() { 152 t.Fatal(diags.Err()) 153 } 154 155 _, diags = ctx.Apply(plan, m) 156 if diags.HasErrors() { 157 t.Fatal(diags.Err()) 158 } 159 160 // now destroy the whole thing 161 ctx = testContext2(t, &ContextOpts{ 162 Providers: ps, 163 }) 164 165 plan, diags = ctx.Plan(m, states.NewState(), &PlanOpts{ 166 Mode: plans.DestroyMode, 167 }) 168 if diags.HasErrors() { 169 t.Fatal(diags.Err()) 170 } 171 172 otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 173 // should not be used to destroy data sources 174 resp.Diagnostics = resp.Diagnostics.Append(errors.New("provider should not be used")) 175 return resp 176 } 177 178 _, diags = ctx.Apply(plan, m) 179 if diags.HasErrors() { 180 t.Fatal(diags.Err()) 181 } 182 } 183 184 func TestContext2Apply_destroyThenUpdate(t *testing.T) { 185 m := testModuleInline(t, map[string]string{ 186 "main.tf": ` 187 resource "test_instance" "a" { 188 value = "udpated" 189 } 190 `, 191 }) 192 193 p := testProvider("test") 194 p.PlanResourceChangeFn = testDiffFn 195 196 var orderMu sync.Mutex 197 var order []string 198 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 199 id := req.PriorState.GetAttr("id").AsString() 200 if id == "b" { 201 // slow down the b destroy, since a should wait for it 202 time.Sleep(100 * time.Millisecond) 203 } 204 205 orderMu.Lock() 206 order = append(order, id) 207 orderMu.Unlock() 208 209 resp.NewState = req.PlannedState 210 return resp 211 } 212 213 addrA := mustResourceInstanceAddr(`test_instance.a`) 214 addrB := mustResourceInstanceAddr(`test_instance.b`) 215 216 state := states.BuildState(func(s *states.SyncState) { 217 s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ 218 AttrsJSON: []byte(`{"id":"a","value":"old","type":"test"}`), 219 Status: states.ObjectReady, 220 }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) 221 222 // test_instance.b depended on test_instance.a, and therefor should be 223 // destroyed before any changes to test_instance.a 224 s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{ 225 AttrsJSON: []byte(`{"id":"b"}`), 226 Status: states.ObjectReady, 227 Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()}, 228 }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) 229 }) 230 231 ctx := testContext2(t, &ContextOpts{ 232 Providers: map[addrs.Provider]providers.Factory{ 233 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 234 }, 235 }) 236 237 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 238 assertNoErrors(t, diags) 239 240 _, diags = ctx.Apply(plan, m) 241 if diags.HasErrors() { 242 t.Fatal(diags.Err()) 243 } 244 245 if order[0] != "b" { 246 t.Fatalf("expected apply order [b, a], got: %v\n", order) 247 } 248 } 249 250 // verify that dependencies are updated in the state during refresh and apply 251 func TestApply_updateDependencies(t *testing.T) { 252 state := states.NewState() 253 root := state.EnsureModule(addrs.RootModuleInstance) 254 255 fooAddr := mustResourceInstanceAddr("aws_instance.foo") 256 barAddr := mustResourceInstanceAddr("aws_instance.bar") 257 bazAddr := mustResourceInstanceAddr("aws_instance.baz") 258 bamAddr := mustResourceInstanceAddr("aws_instance.bam") 259 binAddr := mustResourceInstanceAddr("aws_instance.bin") 260 root.SetResourceInstanceCurrent( 261 fooAddr.Resource, 262 &states.ResourceInstanceObjectSrc{ 263 Status: states.ObjectReady, 264 AttrsJSON: []byte(`{"id":"foo"}`), 265 Dependencies: []addrs.ConfigResource{ 266 bazAddr.ContainingResource().Config(), 267 }, 268 }, 269 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 270 ) 271 root.SetResourceInstanceCurrent( 272 binAddr.Resource, 273 &states.ResourceInstanceObjectSrc{ 274 Status: states.ObjectReady, 275 AttrsJSON: []byte(`{"id":"bin","type":"aws_instance","unknown":"ok"}`), 276 Dependencies: []addrs.ConfigResource{ 277 bazAddr.ContainingResource().Config(), 278 }, 279 }, 280 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 281 ) 282 root.SetResourceInstanceCurrent( 283 bazAddr.Resource, 284 &states.ResourceInstanceObjectSrc{ 285 Status: states.ObjectReady, 286 AttrsJSON: []byte(`{"id":"baz"}`), 287 Dependencies: []addrs.ConfigResource{ 288 // Existing dependencies should not be removed from orphaned instances 289 bamAddr.ContainingResource().Config(), 290 }, 291 }, 292 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 293 ) 294 root.SetResourceInstanceCurrent( 295 barAddr.Resource, 296 &states.ResourceInstanceObjectSrc{ 297 Status: states.ObjectReady, 298 AttrsJSON: []byte(`{"id":"bar","foo":"foo"}`), 299 }, 300 mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 301 ) 302 303 m := testModuleInline(t, map[string]string{ 304 "main.tf": ` 305 resource "aws_instance" "bar" { 306 foo = aws_instance.foo.id 307 } 308 309 resource "aws_instance" "foo" { 310 } 311 312 resource "aws_instance" "bin" { 313 } 314 `, 315 }) 316 317 p := testProvider("aws") 318 319 ctx := testContext2(t, &ContextOpts{ 320 Providers: map[addrs.Provider]providers.Factory{ 321 addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), 322 }, 323 }) 324 325 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 326 assertNoErrors(t, diags) 327 328 bar := plan.PriorState.ResourceInstance(barAddr) 329 if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) { 330 t.Fatalf("bar should depend on foo after refresh, but got %s", bar.Current.Dependencies) 331 } 332 333 foo := plan.PriorState.ResourceInstance(fooAddr) 334 if len(foo.Current.Dependencies) == 0 || !foo.Current.Dependencies[0].Equal(bazAddr.ContainingResource().Config()) { 335 t.Fatalf("foo should depend on baz after refresh because of the update, but got %s", foo.Current.Dependencies) 336 } 337 338 bin := plan.PriorState.ResourceInstance(binAddr) 339 if len(bin.Current.Dependencies) != 0 { 340 t.Fatalf("bin should depend on nothing after refresh because there is no change, but got %s", bin.Current.Dependencies) 341 } 342 343 baz := plan.PriorState.ResourceInstance(bazAddr) 344 if len(baz.Current.Dependencies) == 0 || !baz.Current.Dependencies[0].Equal(bamAddr.ContainingResource().Config()) { 345 t.Fatalf("baz should depend on bam after refresh, but got %s", baz.Current.Dependencies) 346 } 347 348 state, diags = ctx.Apply(plan, m) 349 if diags.HasErrors() { 350 t.Fatal(diags.Err()) 351 } 352 353 bar = state.ResourceInstance(barAddr) 354 if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) { 355 t.Fatalf("bar should still depend on foo after apply, but got %s", bar.Current.Dependencies) 356 } 357 358 foo = state.ResourceInstance(fooAddr) 359 if len(foo.Current.Dependencies) != 0 { 360 t.Fatalf("foo should have no deps after apply, but got %s", foo.Current.Dependencies) 361 } 362 363 } 364 365 func TestContext2Apply_additionalSensitiveFromState(t *testing.T) { 366 // Ensure we're not trying to double-mark values decoded from state 367 m := testModuleInline(t, map[string]string{ 368 "main.tf": ` 369 variable "secret" { 370 sensitive = true 371 default = ["secret"] 372 } 373 374 resource "test_resource" "a" { 375 sensitive_attr = var.secret 376 } 377 378 resource "test_resource" "b" { 379 value = test_resource.a.id 380 } 381 `, 382 }) 383 384 p := new(MockProvider) 385 p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ 386 ResourceTypes: map[string]*configschema.Block{ 387 "test_resource": { 388 Attributes: map[string]*configschema.Attribute{ 389 "id": { 390 Type: cty.String, 391 Computed: true, 392 }, 393 "value": { 394 Type: cty.String, 395 Optional: true, 396 }, 397 "sensitive_attr": { 398 Type: cty.List(cty.String), 399 Optional: true, 400 Sensitive: true, 401 }, 402 }, 403 }, 404 }, 405 }) 406 407 state := states.BuildState(func(s *states.SyncState) { 408 s.SetResourceInstanceCurrent( 409 mustResourceInstanceAddr(`test_resource.a`), 410 &states.ResourceInstanceObjectSrc{ 411 AttrsJSON: []byte(`{"id":"a","sensitive_attr":["secret"]}`), 412 AttrSensitivePaths: []cty.PathValueMarks{ 413 { 414 Path: cty.GetAttrPath("sensitive_attr"), 415 Marks: cty.NewValueMarks(marks.Sensitive), 416 }, 417 }, 418 Status: states.ObjectReady, 419 }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 420 ) 421 }) 422 423 ctx := testContext2(t, &ContextOpts{ 424 Providers: map[addrs.Provider]providers.Factory{ 425 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 426 }, 427 }) 428 429 plan, diags := ctx.Plan(m, state, DefaultPlanOpts) 430 assertNoErrors(t, diags) 431 432 _, diags = ctx.Apply(plan, m) 433 if diags.HasErrors() { 434 t.Fatal(diags.ErrWithWarnings()) 435 } 436 } 437 438 func TestContext2Apply_sensitiveOutputPassthrough(t *testing.T) { 439 // Ensure we're not trying to double-mark values decoded from state 440 m := testModuleInline(t, map[string]string{ 441 "main.tf": ` 442 module "mod" { 443 source = "./mod" 444 } 445 446 resource "test_object" "a" { 447 test_string = module.mod.out 448 } 449 `, 450 451 "mod/main.tf": ` 452 variable "in" { 453 sensitive = true 454 default = "foo" 455 } 456 output "out" { 457 value = var.in 458 } 459 `, 460 }) 461 462 p := simpleMockProvider() 463 464 ctx := testContext2(t, &ContextOpts{ 465 Providers: map[addrs.Provider]providers.Factory{ 466 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 467 }, 468 }) 469 470 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 471 assertNoErrors(t, diags) 472 473 state, diags := ctx.Apply(plan, m) 474 if diags.HasErrors() { 475 t.Fatal(diags.ErrWithWarnings()) 476 } 477 478 obj := state.ResourceInstance(mustResourceInstanceAddr("test_object.a")) 479 if len(obj.Current.AttrSensitivePaths) != 1 { 480 t.Fatalf("Expected 1 sensitive mark for test_object.a, got %#v\n", obj.Current.AttrSensitivePaths) 481 } 482 483 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 484 assertNoErrors(t, diags) 485 486 // make sure the same marks are compared in the next plan as well 487 for _, c := range plan.Changes.Resources { 488 if c.Action != plans.NoOp { 489 t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr) 490 } 491 } 492 } 493 494 func TestContext2Apply_ignoreImpureFunctionChanges(t *testing.T) { 495 // The impure function call should not cause a planned change with 496 // ignore_changes 497 m := testModuleInline(t, map[string]string{ 498 "main.tf": ` 499 variable "pw" { 500 sensitive = true 501 default = "foo" 502 } 503 504 resource "test_object" "x" { 505 test_map = { 506 string = "X${bcrypt(var.pw)}" 507 } 508 lifecycle { 509 ignore_changes = [ test_map["string"] ] 510 } 511 } 512 513 resource "test_object" "y" { 514 test_map = { 515 string = "X${bcrypt(var.pw)}" 516 } 517 lifecycle { 518 ignore_changes = [ test_map ] 519 } 520 } 521 522 `, 523 }) 524 525 p := simpleMockProvider() 526 527 ctx := testContext2(t, &ContextOpts{ 528 Providers: map[addrs.Provider]providers.Factory{ 529 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 530 }, 531 }) 532 533 plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) 534 assertNoErrors(t, diags) 535 536 state, diags := ctx.Apply(plan, m) 537 assertNoErrors(t, diags) 538 539 // FINAL PLAN: 540 plan, diags = ctx.Plan(m, state, DefaultPlanOpts) 541 assertNoErrors(t, diags) 542 543 // make sure the same marks are compared in the next plan as well 544 for _, c := range plan.Changes.Resources { 545 if c.Action != plans.NoOp { 546 t.Logf("marks before: %#v", c.BeforeValMarks) 547 t.Logf("marks after: %#v", c.AfterValMarks) 548 t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr) 549 } 550 } 551 } 552 553 func TestContext2Apply_destroyWithDeposed(t *testing.T) { 554 m := testModuleInline(t, map[string]string{ 555 "main.tf": ` 556 resource "test_object" "x" { 557 test_string = "ok" 558 lifecycle { 559 create_before_destroy = true 560 } 561 }`, 562 }) 563 564 p := simpleMockProvider() 565 566 deposedKey := states.NewDeposedKey() 567 568 state := states.NewState() 569 root := state.EnsureModule(addrs.RootModuleInstance) 570 root.SetResourceInstanceDeposed( 571 mustResourceInstanceAddr("test_object.x").Resource, 572 deposedKey, 573 &states.ResourceInstanceObjectSrc{ 574 Status: states.ObjectTainted, 575 AttrsJSON: []byte(`{"test_string":"deposed"}`), 576 }, 577 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 578 ) 579 580 ctx := testContext2(t, &ContextOpts{ 581 Providers: map[addrs.Provider]providers.Factory{ 582 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 583 }, 584 }) 585 586 plan, diags := ctx.Plan(m, state, &PlanOpts{ 587 Mode: plans.DestroyMode, 588 }) 589 if diags.HasErrors() { 590 t.Fatalf("plan: %s", diags.Err()) 591 } 592 593 _, diags = ctx.Apply(plan, m) 594 if diags.HasErrors() { 595 t.Fatalf("apply: %s", diags.Err()) 596 } 597 598 } 599 600 func TestContext2Apply_nullableVariables(t *testing.T) { 601 m := testModule(t, "apply-nullable-variables") 602 state := states.NewState() 603 ctx := testContext2(t, &ContextOpts{}) 604 plan, diags := ctx.Plan(m, state, &PlanOpts{}) 605 if diags.HasErrors() { 606 t.Fatalf("plan: %s", diags.Err()) 607 } 608 state, diags = ctx.Apply(plan, m) 609 if diags.HasErrors() { 610 t.Fatalf("apply: %s", diags.Err()) 611 } 612 613 outputs := state.Module(addrs.RootModuleInstance).OutputValues 614 // we check for null outputs be seeing that they don't exists 615 if _, ok := outputs["nullable_null_default"]; ok { 616 t.Error("nullable_null_default: expected no output value") 617 } 618 if _, ok := outputs["nullable_non_null_default"]; ok { 619 t.Error("nullable_non_null_default: expected no output value") 620 } 621 if _, ok := outputs["nullable_no_default"]; ok { 622 t.Error("nullable_no_default: expected no output value") 623 } 624 625 if v := outputs["non_nullable_default"].Value; v.AsString() != "ok" { 626 t.Fatalf("incorrect 'non_nullable_default' output value: %#v\n", v) 627 } 628 if v := outputs["non_nullable_no_default"].Value; v.AsString() != "ok" { 629 t.Fatalf("incorrect 'non_nullable_no_default' output value: %#v\n", v) 630 } 631 }