| package terraform |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "log" |
| "reflect" |
| "runtime" |
| "sort" |
| "strings" |
| "sync" |
| "sync/atomic" |
| "testing" |
| "time" |
| |
| "github.com/davecgh/go-spew/spew" |
| "github.com/go-test/deep" |
| "github.com/google/go-cmp/cmp" |
| "github.com/zclconf/go-cty/cty" |
| "github.com/zclconf/go-cty/cty/gocty" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/configs" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/configs/hcl2shim" |
| "github.com/hashicorp/terraform/internal/lang/marks" |
| "github.com/hashicorp/terraform/internal/plans" |
| "github.com/hashicorp/terraform/internal/providers" |
| "github.com/hashicorp/terraform/internal/provisioners" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| func TestContext2Apply_basic(t *testing.T) { |
| m := testModule(t, "apply-good") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| mod := state.RootModule() |
| if len(mod.Resources) < 2 { |
| t.Fatalf("bad: %#v", mod.Resources) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_unstable(t *testing.T) { |
| // This tests behavior when the configuration contains an unstable value, |
| // such as the result of uuid() or timestamp(), where each call produces |
| // a different result. |
| // |
| // This is an important case to test because we need to ensure that |
| // we don't re-call the function during the apply phase: the value should |
| // be fixed during plan |
| |
| m := testModule(t, "apply-unstable") |
| p := testProvider("test") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected error during Plan: %s", diags.Err()) |
| } |
| |
| addr := addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "foo", |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) |
| schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block |
| rds := plan.Changes.ResourceInstance(addr) |
| rd, err := rds.Decode(schema.ImpliedType()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rd.After.GetAttr("random").IsKnown() { |
| t.Fatalf("Attribute 'random' has known value %#v; should be unknown in plan", rd.After.GetAttr("random")) |
| } |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected error during Apply: %s", diags.Err()) |
| } |
| |
| mod := state.Module(addr.Module) |
| rss := state.ResourceInstance(addr) |
| |
| if len(mod.Resources) != 1 { |
| t.Fatalf("wrong number of resources %d; want 1", len(mod.Resources)) |
| } |
| |
| rs, err := rss.Current.Decode(schema.ImpliedType()) |
| if err != nil { |
| t.Fatalf("decode error: %v", err) |
| } |
| got := rs.Value.GetAttr("random") |
| if !got.IsKnown() { |
| t.Fatalf("random is still unknown after apply") |
| } |
| if got, want := len(got.AsString()), 36; got != want { |
| t.Fatalf("random string has wrong length %d; want %d", got, want) |
| } |
| } |
| |
| func TestContext2Apply_escape(t *testing.T) { |
| m := testModule(t, "apply-escape") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| checkStateString(t, state, ` |
| aws_instance.bar: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = "bar" |
| type = aws_instance |
| `) |
| } |
| |
| func TestContext2Apply_resourceCountOneList(t *testing.T) { |
| m := testModule(t, "apply-resource-count-one-list") |
| p := testProvider("null") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| assertNoDiagnostics(t, diags) |
| |
| got := strings.TrimSpace(state.String()) |
| want := strings.TrimSpace(`null_resource.foo.0: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/null"] |
| |
| Outputs: |
| |
| test = [foo]`) |
| if got != want { |
| t.Fatalf("got:\n%s\n\nwant:\n%s\n", got, want) |
| } |
| } |
| func TestContext2Apply_resourceCountZeroList(t *testing.T) { |
| m := testModule(t, "apply-resource-count-zero-list") |
| p := testProvider("null") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| got := strings.TrimSpace(state.String()) |
| want := strings.TrimSpace(`<no state> |
| Outputs: |
| |
| test = []`) |
| if got != want { |
| t.Fatalf("wrong state\n\ngot:\n%s\n\nwant:\n%s\n", got, want) |
| } |
| } |
| |
| func TestContext2Apply_resourceDependsOnModule(t *testing.T) { |
| m := testModule(t, "apply-resource-depends-on-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // verify the apply happens in the correct order |
| var mu sync.Mutex |
| var order []string |
| |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| ami := req.PlannedState.GetAttr("ami").AsString() |
| switch ami { |
| case "child": |
| |
| // make the child slower than the parent |
| time.Sleep(50 * time.Millisecond) |
| |
| mu.Lock() |
| order = append(order, "child") |
| mu.Unlock() |
| case "parent": |
| mu.Lock() |
| order = append(order, "parent") |
| mu.Unlock() |
| } |
| |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if !reflect.DeepEqual(order, []string{"child", "parent"}) { |
| t.Fatal("resources applied out of order") |
| } |
| |
| checkStateString(t, state, testTerraformApplyResourceDependsOnModuleStr) |
| } |
| |
| // Test that without a config, the Dependencies in the state are enough |
| // to maintain proper ordering. |
| func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) { |
| m := testModule(t, "apply-resource-depends-on-module-empty") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"parent"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.child")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.child").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"child"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| { |
| // verify the apply happens in the correct order |
| var mu sync.Mutex |
| var order []string |
| |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| id := req.PriorState.GetAttr("id") |
| if id.IsKnown() && id.AsString() == "parent" { |
| // make the dep slower than the parent |
| time.Sleep(50 * time.Millisecond) |
| |
| mu.Lock() |
| order = append(order, "child") |
| mu.Unlock() |
| } else { |
| mu.Lock() |
| order = append(order, "parent") |
| mu.Unlock() |
| } |
| |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| if !reflect.DeepEqual(order, []string{"child", "parent"}) { |
| t.Fatal("resources applied out of order") |
| } |
| |
| checkStateString(t, state, "<no state>") |
| } |
| } |
| |
| func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) { |
| m := testModule(t, "apply-resource-depends-on-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| var globalState *states.State |
| { |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| globalState = state |
| } |
| |
| { |
| // Wait for the dependency, sleep, and verify the graph never |
| // called a child. |
| var called int32 |
| var checked bool |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| ami := req.PriorState.GetAttr("ami").AsString() |
| if ami == "parent" { |
| checked = true |
| |
| // Sleep to allow parallel execution |
| time.Sleep(50 * time.Millisecond) |
| |
| // Verify that called is 0 (dep not called) |
| if atomic.LoadInt32(&called) != 0 { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("module child should not be called")) |
| return resp |
| } |
| } |
| |
| atomic.AddInt32(&called, 1) |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, globalState, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if !checked { |
| t.Fatal("should check") |
| } |
| |
| checkStateString(t, state, `<no state>`) |
| } |
| } |
| |
| func TestContext2Apply_resourceDependsOnModuleGrandchild(t *testing.T) { |
| m := testModule(t, "apply-resource-depends-on-module-deep") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| { |
| // Wait for the dependency, sleep, and verify the graph never |
| // called a child. |
| var called int32 |
| var checked bool |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| planned := req.PlannedState.AsValueMap() |
| if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { |
| checked = true |
| |
| // Sleep to allow parallel execution |
| time.Sleep(50 * time.Millisecond) |
| |
| // Verify that called is 0 (dep not called) |
| if atomic.LoadInt32(&called) != 0 { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("aws_instance.a should not be called")) |
| return resp |
| } |
| } |
| |
| atomic.AddInt32(&called, 1) |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if !checked { |
| t.Fatal("should check") |
| } |
| |
| checkStateString(t, state, testTerraformApplyResourceDependsOnModuleDeepStr) |
| } |
| } |
| |
| func TestContext2Apply_resourceDependsOnModuleInModule(t *testing.T) { |
| m := testModule(t, "apply-resource-depends-on-module-in-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| { |
| // Wait for the dependency, sleep, and verify the graph never |
| // called a child. |
| var called int32 |
| var checked bool |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| planned := req.PlannedState.AsValueMap() |
| if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" { |
| checked = true |
| |
| // Sleep to allow parallel execution |
| time.Sleep(50 * time.Millisecond) |
| |
| // Verify that called is 0 (dep not called) |
| if atomic.LoadInt32(&called) != 0 { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("something else was applied before grandchild; grandchild should be first")) |
| return resp |
| } |
| } |
| |
| atomic.AddInt32(&called, 1) |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if !checked { |
| t.Fatal("should check") |
| } |
| |
| checkStateString(t, state, testTerraformApplyResourceDependsOnModuleInModuleStr) |
| } |
| } |
| |
| func TestContext2Apply_mapVarBetweenModules(t *testing.T) { |
| m := testModule(t, "apply-map-var-through-module") |
| p := testProvider("null") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(`<no state> |
| Outputs: |
| |
| amis_from_module = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 } |
| |
| module.test: |
| null_resource.noop: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/null"] |
| |
| Outputs: |
| |
| amis_out = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }`) |
| if actual != expected { |
| t.Fatalf("expected: \n%s\n\ngot: \n%s\n", expected, actual) |
| } |
| } |
| |
| func TestContext2Apply_refCount(t *testing.T) { |
| m := testModule(t, "apply-ref-count") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| mod := state.RootModule() |
| if len(mod.Resources) < 2 { |
| t.Fatalf("bad: %#v", mod.Resources) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyRefCountStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_providerAlias(t *testing.T) { |
| m := testModule(t, "apply-provider-alias") |
| |
| // Each provider instance must be completely independent to ensure that we |
| // are verifying the correct state of each. |
| p := func() (providers.Interface, error) { |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| return p, nil |
| } |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): p, |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| mod := state.RootModule() |
| if len(mod.Resources) < 2 { |
| t.Fatalf("bad: %#v", mod.Resources) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyProviderAliasStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| // Two providers that are configured should both be configured prior to apply |
| func TestContext2Apply_providerAliasConfigure(t *testing.T) { |
| m := testModule(t, "apply-provider-alias-configure") |
| |
| // Each provider instance must be completely independent to ensure that we |
| // are verifying the correct state of each. |
| p := func() (providers.Interface, error) { |
| p := testProvider("another") |
| p.ApplyResourceChangeFn = testApplyFn |
| p.PlanResourceChangeFn = testDiffFn |
| return p, nil |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("another"): p, |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| // Configure to record calls AFTER Plan above |
| var configCount int32 |
| p = func() (providers.Interface, error) { |
| p := testProvider("another") |
| p.ApplyResourceChangeFn = testApplyFn |
| p.PlanResourceChangeFn = testDiffFn |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| atomic.AddInt32(&configCount, 1) |
| |
| foo := req.Config.GetAttr("foo").AsString() |
| if foo != "bar" { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("foo: %#v", foo)) |
| } |
| |
| return |
| } |
| return p, nil |
| } |
| |
| ctx = testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("another"): p, |
| }, |
| }) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if configCount != 2 { |
| t.Fatalf("provider config expected 2 calls, got: %d", configCount) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyProviderAliasConfigStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| // GH-2870 |
| func TestContext2Apply_providerWarning(t *testing.T) { |
| m := testModule(t, "apply-provider-warning") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { |
| resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("just a warning")) |
| return |
| } |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(` |
| aws_instance.foo: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| type = aws_instance |
| `) |
| if actual != expected { |
| t.Fatalf("got: \n%s\n\nexpected:\n%s", actual, expected) |
| } |
| |
| if !p.ConfigureProviderCalled { |
| t.Fatalf("provider Configure() was never called!") |
| } |
| } |
| |
| func TestContext2Apply_emptyModule(t *testing.T) { |
| // A module with only outputs (no resources) |
| m := testModule(t, "apply-empty-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| actual = strings.Replace(actual, " ", "", -1) |
| expected := strings.TrimSpace(testTerraformApplyEmptyModuleStr) |
| if actual != expected { |
| t.Fatalf("bad: \n%s\nexpect:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_createBeforeDestroy(t *testing.T) { |
| m := testModule(t, "apply-good-create-before") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| mod := state.RootModule() |
| if got, want := len(mod.Resources), 1; got != want { |
| t.Logf("state:\n%s", state) |
| t.Fatalf("wrong number of resources %d; want %d", got, want) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyCreateBeforeStr) |
| if actual != expected { |
| t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) |
| } |
| } |
| |
| func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { |
| m := testModule(t, "apply-good-create-before-update") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // signal that resource foo has started applying |
| fooChan := make(chan struct{}) |
| |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| id := req.PriorState.GetAttr("id").AsString() |
| switch id { |
| case "bar": |
| select { |
| case <-fooChan: |
| resp.Diagnostics = resp.Diagnostics.Append(errors.New("bar must be updated before foo is destroyed")) |
| return resp |
| case <-time.After(100 * time.Millisecond): |
| // wait a moment to ensure that foo is not going to be destroyed first |
| } |
| case "foo": |
| close(fooChan) |
| } |
| |
| return testApplyFn(req) |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| fooAddr := mustResourceInstanceAddr("aws_instance.foo") |
| root.SetResourceInstanceCurrent( |
| fooAddr.Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), |
| CreateBeforeDestroy: true, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`), |
| CreateBeforeDestroy: true, |
| Dependencies: []addrs.ConfigResource{fooAddr.ContainingResource().Config()}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| mod := state.RootModule() |
| if len(mod.Resources) != 1 { |
| t.Fatalf("bad: %s", state) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyCreateBeforeUpdateStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| // This tests that when a CBD resource depends on a non-CBD resource, |
| // we can still properly apply changes that require new for both. |
| func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) { |
| m := testModule(t, "apply-cbd-depends-non-cbd") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo", "require_new": "abc"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| checkStateString(t, state, ` |
| aws_instance.bar: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| require_new = yes |
| type = aws_instance |
| value = foo |
| |
| Dependencies: |
| aws_instance.foo |
| aws_instance.foo: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| require_new = yes |
| type = aws_instance |
| `) |
| } |
| |
| func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) { |
| h := new(MockHook) |
| m := testModule(t, "apply-good-create-before") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| var actual []cty.Value |
| var actualLock sync.Mutex |
| h.PostApplyFn = func(addr addrs.AbsResourceInstance, gen states.Generation, sv cty.Value, e error) (HookAction, error) { |
| actualLock.Lock() |
| |
| defer actualLock.Unlock() |
| actual = append(actual, sv) |
| return HookActionContinue, nil |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Hooks: []Hook{h}, |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| |
| expected := []cty.Value{ |
| cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("foo"), |
| "require_new": cty.StringVal("xyz"), |
| "type": cty.StringVal("aws_instance"), |
| }), |
| cty.NullVal(cty.DynamicPseudoType), |
| } |
| |
| cmpOpt := cmp.Transformer("ctyshim", hcl2shim.ConfigValueFromHCL2) |
| if !cmp.Equal(actual, expected, cmpOpt) { |
| t.Fatalf("wrong state snapshot sequence\n%s", cmp.Diff(expected, actual, cmpOpt)) |
| } |
| } |
| |
| // Test that we can perform an apply with CBD in a count with deposed instances. |
| func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { |
| m := testModule(t, "apply-cbd-count") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceDeposed( |
| mustResourceInstanceAddr("aws_instance.bar[0]").Resource, |
| states.NewDeposedKey(), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceDeposed( |
| mustResourceInstanceAddr("aws_instance.bar[1]").Resource, |
| states.NewDeposedKey(), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| checkStateString(t, state, ` |
| aws_instance.bar.0: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = bar |
| type = aws_instance |
| aws_instance.bar.1: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = bar |
| type = aws_instance |
| `) |
| } |
| |
| // Test that when we have a deposed instance but a good primary, we still |
| // destroy the deposed instance. |
| func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) { |
| m := testModule(t, "apply-cbd-deposed-only") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceDeposed( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| states.NewDeposedKey(), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| checkStateString(t, state, ` |
| aws_instance.bar: |
| ID = bar |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| type = aws_instance |
| `) |
| } |
| |
| func TestContext2Apply_destroyComputed(t *testing.T) { |
| m := testModule(t, "apply-destroy-computed") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo", "output": "value"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| if diags.HasErrors() { |
| logDiagnostics(t, diags) |
| t.Fatal("plan failed") |
| } else { |
| t.Logf("plan:\n\n%s", legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| logDiagnostics(t, diags) |
| t.Fatal("apply failed") |
| } |
| } |
| |
| // Test that the destroy operation uses depends_on as a source of ordering. |
| func TestContext2Apply_destroyDependsOn(t *testing.T) { |
| // It is possible for this to be racy, so we loop a number of times |
| // just to check. |
| for i := 0; i < 10; i++ { |
| testContext2Apply_destroyDependsOn(t) |
| } |
| } |
| |
| func testContext2Apply_destroyDependsOn(t *testing.T) { |
| m := testModule(t, "apply-destroy-depends-on") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| // Record the order we see Apply |
| var actual []string |
| var actualLock sync.Mutex |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| actualLock.Lock() |
| defer actualLock.Unlock() |
| id := req.PriorState.GetAttr("id").AsString() |
| actual = append(actual, id) |
| |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Parallelism: 1, // To check ordering |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| |
| expected := []string{"foo", "bar"} |
| if !reflect.DeepEqual(actual, expected) { |
| t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) |
| } |
| } |
| |
| // Test that destroy ordering is correct with dependencies only |
| // in the state. |
| func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) { |
| newState := states.NewState() |
| root := newState.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "aws_instance", |
| Name: "foo", |
| }.Instance(addrs.NoKey), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("aws"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| root.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "aws_instance", |
| Name: "bar", |
| }.Instance(addrs.NoKey), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| Dependencies: []addrs.ConfigResource{ |
| { |
| Resource: addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "aws_instance", |
| Name: "foo", |
| }, |
| Module: root.Addr.Module(), |
| }, |
| }, |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("aws"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| |
| // It is possible for this to be racy, so we loop a number of times |
| // just to check. |
| for i := 0; i < 10; i++ { |
| t.Run("new", func(t *testing.T) { |
| testContext2Apply_destroyDependsOnStateOnly(t, newState) |
| }) |
| } |
| } |
| |
| func testContext2Apply_destroyDependsOnStateOnly(t *testing.T, state *states.State) { |
| state = state.DeepCopy() |
| m := testModule(t, "empty") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| // Record the order we see Apply |
| var actual []string |
| var actualLock sync.Mutex |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| actualLock.Lock() |
| defer actualLock.Unlock() |
| id := req.PriorState.GetAttr("id").AsString() |
| actual = append(actual, id) |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Parallelism: 1, // To check ordering |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| |
| expected := []string{"bar", "foo"} |
| if !reflect.DeepEqual(actual, expected) { |
| t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) |
| } |
| } |
| |
| // Test that destroy ordering is correct with dependencies only |
| // in the state within a module (GH-11749) |
| func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { |
| newState := states.NewState() |
| child := newState.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "aws_instance", |
| Name: "foo", |
| }.Instance(addrs.NoKey), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| Dependencies: []addrs.ConfigResource{}, |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("aws"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| child.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "aws_instance", |
| Name: "bar", |
| }.Instance(addrs.NoKey), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| Dependencies: []addrs.ConfigResource{ |
| { |
| Resource: addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "aws_instance", |
| Name: "foo", |
| }, |
| Module: child.Addr.Module(), |
| }, |
| }, |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("aws"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| |
| // It is possible for this to be racy, so we loop a number of times |
| // just to check. |
| for i := 0; i < 10; i++ { |
| t.Run("new", func(t *testing.T) { |
| testContext2Apply_destroyDependsOnStateOnlyModule(t, newState) |
| }) |
| } |
| } |
| |
| func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T, state *states.State) { |
| state = state.DeepCopy() |
| m := testModule(t, "empty") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // Record the order we see Apply |
| var actual []string |
| var actualLock sync.Mutex |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| actualLock.Lock() |
| defer actualLock.Unlock() |
| id := req.PriorState.GetAttr("id").AsString() |
| actual = append(actual, id) |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Parallelism: 1, // To check ordering |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| |
| expected := []string{"bar", "foo"} |
| if !reflect.DeepEqual(actual, expected) { |
| t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_dataBasic(t *testing.T) { |
| m := testModule(t, "apply-data-basic") |
| p := testProvider("null") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("yo"), |
| "foo": cty.NullVal(cty.String), |
| }), |
| } |
| |
| hook := new(MockHook) |
| ctx := testContext2(t, &ContextOpts{ |
| Hooks: []Hook{hook}, |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| state, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyDataBasicStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| |
| if !hook.PreApplyCalled { |
| t.Fatal("PreApply not called for data source read") |
| } |
| if !hook.PostApplyCalled { |
| t.Fatal("PostApply not called for data source read") |
| } |
| } |
| |
| func TestContext2Apply_destroyData(t *testing.T) { |
| m := testModule(t, "apply-destroy-data-resource") |
| p := testProvider("null") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| State: req.Config, |
| } |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("data.null_data_source.testing").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"-"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/null"]`), |
| ) |
| |
| hook := &testHook{} |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("null"): testProviderFuncFixed(p), |
| }, |
| Hooks: []Hook{hook}, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| newState, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if got := len(newState.Modules); got != 1 { |
| t.Fatalf("state has %d modules after destroy; want 1", got) |
| } |
| |
| if got := len(newState.RootModule().Resources); got != 0 { |
| t.Fatalf("state has %d resources after destroy; want 0", got) |
| } |
| |
| wantHookCalls := []*testHookCall{ |
| {"PreApply", "data.null_data_source.testing"}, |
| {"PostApply", "data.null_data_source.testing"}, |
| {"PostStateUpdate", ""}, |
| } |
| if !reflect.DeepEqual(hook.Calls, wantHookCalls) { |
| t.Errorf("wrong hook calls\ngot: %swant: %s", spew.Sdump(hook.Calls), spew.Sdump(wantHookCalls)) |
| } |
| } |
| |
| // https://github.com/hashicorp/terraform/pull/5096 |
| func TestContext2Apply_destroySkipsCBD(t *testing.T) { |
| // Config contains CBD resource depending on non-CBD resource, which triggers |
| // a cycle if they are both replaced, but should _not_ trigger a cycle when |
| // just doing a `terraform destroy`. |
| m := testModule(t, "apply-destroy-cbd") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } else { |
| t.Logf(legacyDiffComparisonString(plan.Changes)) |
| } |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| } |
| |
| func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) { |
| m := testModule(t, "apply-destroy-mod-var-provider-config") |
| p := func() (providers.Interface, error) { |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| return p, nil |
| } |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): p, |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| _, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| } |
| |
| func TestContext2Apply_destroyCrossProviders(t *testing.T) { |
| m := testModule(t, "apply-destroy-cross-providers") |
| |
| p_aws := testProvider("aws") |
| p_aws.ApplyResourceChangeFn = testApplyFn |
| p_aws.PlanResourceChangeFn = testDiffFn |
| p_aws.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": { |
| Type: cty.String, |
| Computed: true, |
| }, |
| }, |
| }, |
| "aws_vpc": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": { |
| Type: cty.String, |
| Computed: true, |
| }, |
| "value": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| }, |
| }, |
| }, |
| }) |
| |
| providers := map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p_aws), |
| } |
| |
| ctx, m, state := getContextForApply_destroyCrossProviders(t, m, providers) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| logDiagnostics(t, diags) |
| t.Fatal("apply failed") |
| } |
| } |
| |
| func getContextForApply_destroyCrossProviders(t *testing.T, m *configs.Config, providerFactories map[addrs.Provider]providers.Factory) (*Context, *configs.Config, *states.State) { |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.shared").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"test"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_vpc.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id": "vpc-aaabbb12", "value":"test"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: providerFactories, |
| }) |
| |
| return ctx, m, state |
| } |
| |
| func TestContext2Apply_minimal(t *testing.T) { |
| m := testModule(t, "apply-minimal") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyMinimalStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_cancel(t *testing.T) { |
| stopped := false |
| |
| m := testModule(t, "apply-cancel") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| if !stopped { |
| stopped = true |
| go ctx.Stop() |
| |
| for { |
| if ctx.sh.Stopped() { |
| break |
| } |
| time.Sleep(10 * time.Millisecond) |
| } |
| } |
| return testApplyFn(req) |
| } |
| p.PlanResourceChangeFn = testDiffFn |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| // Start the Apply in a goroutine |
| var applyDiags tfdiags.Diagnostics |
| stateCh := make(chan *states.State) |
| go func() { |
| state, diags := ctx.Apply(plan, m) |
| applyDiags = diags |
| |
| stateCh <- state |
| }() |
| |
| state := <-stateCh |
| // only expecting an early exit error |
| if !applyDiags.HasErrors() { |
| t.Fatal("expected early exit error") |
| } |
| |
| for _, d := range applyDiags { |
| desc := d.Description() |
| if desc.Summary != "execution halted" { |
| t.Fatalf("unexpected error: %v", applyDiags.Err()) |
| } |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyCancelStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| |
| if !p.StopCalled { |
| t.Fatal("stop should be called") |
| } |
| } |
| |
| func TestContext2Apply_cancelBlock(t *testing.T) { |
| m := testModule(t, "apply-cancel-block") |
| p := testProvider("aws") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| applyCh := make(chan struct{}) |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| close(applyCh) |
| |
| for !ctx.sh.Stopped() { |
| // Wait for stop to be called. We call Gosched here so that |
| // the other goroutines can always be scheduled to set Stopped. |
| runtime.Gosched() |
| } |
| |
| // Sleep |
| time.Sleep(100 * time.Millisecond) |
| return testApplyFn(req) |
| } |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| // Start the Apply in a goroutine |
| var applyDiags tfdiags.Diagnostics |
| stateCh := make(chan *states.State) |
| go func() { |
| state, diags := ctx.Apply(plan, m) |
| applyDiags = diags |
| |
| stateCh <- state |
| }() |
| |
| stopDone := make(chan struct{}) |
| go func() { |
| defer close(stopDone) |
| <-applyCh |
| ctx.Stop() |
| }() |
| |
| // Make sure that stop blocks |
| select { |
| case <-stopDone: |
| t.Fatal("stop should block") |
| case <-time.After(10 * time.Millisecond): |
| } |
| |
| // Wait for stop |
| select { |
| case <-stopDone: |
| case <-time.After(500 * time.Millisecond): |
| t.Fatal("stop should be done") |
| } |
| |
| // Wait for apply to complete |
| state := <-stateCh |
| // only expecting an early exit error |
| if !applyDiags.HasErrors() { |
| t.Fatal("expected early exit error") |
| } |
| |
| for _, d := range applyDiags { |
| desc := d.Description() |
| if desc.Summary != "execution halted" { |
| t.Fatalf("unexpected error: %v", applyDiags.Err()) |
| } |
| } |
| |
| checkStateString(t, state, ` |
| aws_instance.foo: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| num = 2 |
| type = aws_instance |
| `) |
| } |
| |
| func TestContext2Apply_cancelProvisioner(t *testing.T) { |
| m := testModule(t, "apply-cancel-provisioner") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| |
| pr := testProvisioner() |
| pr.GetSchemaResponse = provisioners.GetSchemaResponse{ |
| Provisioner: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "foo": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| }, |
| }, |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| prStopped := make(chan struct{}) |
| pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { |
| // Start the stop process |
| go ctx.Stop() |
| |
| <-prStopped |
| return |
| } |
| pr.StopFn = func() error { |
| close(prStopped) |
| return nil |
| } |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| // Start the Apply in a goroutine |
| var applyDiags tfdiags.Diagnostics |
| stateCh := make(chan *states.State) |
| go func() { |
| state, diags := ctx.Apply(plan, m) |
| applyDiags = diags |
| |
| stateCh <- state |
| }() |
| |
| // Wait for completion |
| state := <-stateCh |
| |
| // we are expecting only an early exit error |
| if !applyDiags.HasErrors() { |
| t.Fatal("expected early exit error") |
| } |
| |
| for _, d := range applyDiags { |
| desc := d.Description() |
| if desc.Summary != "execution halted" { |
| t.Fatalf("unexpected error: %v", applyDiags.Err()) |
| } |
| } |
| |
| checkStateString(t, state, ` |
| aws_instance.foo: (tainted) |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| num = 2 |
| type = aws_instance |
| `) |
| |
| if !pr.StopCalled { |
| t.Fatal("stop should be called") |
| } |
| } |
| |
| func TestContext2Apply_compute(t *testing.T) { |
| m := testModule(t, "apply-compute") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "num": { |
| Type: cty.Number, |
| Optional: true, |
| }, |
| "compute": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| "compute_value": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| "foo": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| "id": { |
| Type: cty.String, |
| Computed: true, |
| }, |
| "type": { |
| Type: cty.String, |
| Computed: true, |
| }, |
| "value": { // Populated from compute_value because compute = "value" in the config fixture |
| Type: cty.String, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| SetVariables: InputValues{ |
| "value": &InputValue{ |
| Value: cty.NumberIntVal(1), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyComputeStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_countDecrease(t *testing.T) { |
| m := testModule(t, "apply-count-dec") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[2]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| s, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| actual := strings.TrimSpace(s.String()) |
| expected := strings.TrimSpace(testTerraformApplyCountDecStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_countDecreaseToOneX(t *testing.T) { |
| m := testModule(t, "apply-count-dec-one") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[1]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[2]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| s, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(s.String()) |
| expected := strings.TrimSpace(testTerraformApplyCountDecToOneStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| // https://github.com/PeoplePerHour/terraform/pull/11 |
| // |
| // This tests a rare but possible situation where we have both a no-key and |
| // a zero-key instance of the same resource in the configuration when we |
| // disable count. |
| // |
| // The main way to get here is for a provider to fail to destroy the zero-key |
| // instance but succeed in creating the no-key instance, since those two |
| // can typically happen concurrently. There are various other ways to get here |
| // that might be considered user error, such as using "terraform state mv" |
| // to create a strange combination of different key types on the same resource. |
| // |
| // This test indirectly exercises an intentional interaction between |
| // refactoring.ImpliedMoveStatements and refactoring.ApplyMoves: we'll first |
| // generate an implied move statement from aws_instance.foo[0] to |
| // aws_instance.foo, but then refactoring.ApplyMoves should notice that and |
| // ignore the statement, in the same way as it would if an explicit move |
| // statement specified the same situation. |
| func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { |
| m := testModule(t, "apply-count-dec-one") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"baz", "type": "aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| { |
| got := strings.TrimSpace(legacyPlanComparisonString(state, plan.Changes)) |
| want := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedPlanStr) |
| if got != want { |
| t.Fatalf("wrong plan result\ngot:\n%s\nwant:\n%s", got, want) |
| } |
| } |
| { |
| change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo[0]")) |
| if change == nil { |
| t.Fatalf("no planned change for instance zero") |
| } |
| if got, want := change.Action, plans.Delete; got != want { |
| t.Errorf("wrong action for instance zero %s; want %s", got, want) |
| } |
| if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want { |
| t.Errorf("wrong action reason for instance zero %s; want %s", got, want) |
| } |
| } |
| { |
| change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo")) |
| if change == nil { |
| t.Fatalf("no planned change for no-key instance") |
| } |
| if got, want := change.Action, plans.NoOp; got != want { |
| t.Errorf("wrong action for no-key instance %s; want %s", got, want) |
| } |
| if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { |
| t.Errorf("wrong action reason for no-key instance %s; want %s", got, want) |
| } |
| } |
| |
| s, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(s.String()) |
| expected := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedStr) |
| if actual != expected { |
| t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_countTainted(t *testing.T) { |
| m := testModule(t, "apply-count-tainted") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo[0]").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectTainted, |
| AttrsJSON: []byte(`{"id":"bar", "type": "aws_instance", "foo": "foo"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| { |
| got := strings.TrimSpace(legacyDiffComparisonString(plan.Changes)) |
| want := strings.TrimSpace(` |
| DESTROY/CREATE: aws_instance.foo[0] |
| foo: "foo" => "foo" |
| id: "bar" => "<computed>" |
| type: "aws_instance" => "<computed>" |
| CREATE: aws_instance.foo[1] |
| foo: "" => "foo" |
| id: "" => "<computed>" |
| type: "" => "<computed>" |
| `) |
| if got != want { |
| t.Fatalf("wrong plan\n\ngot:\n%s\n\nwant:\n%s", got, want) |
| } |
| } |
| |
| s, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| got := strings.TrimSpace(s.String()) |
| want := strings.TrimSpace(` |
| aws_instance.foo.0: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = foo |
| type = aws_instance |
| aws_instance.foo.1: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = foo |
| type = aws_instance |
| `) |
| if got != want { |
| t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", got, want) |
| } |
| } |
| |
| func TestContext2Apply_countVariable(t *testing.T) { |
| m := testModule(t, "apply-count-variable") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyCountVariableStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_countVariableRef(t *testing.T) { |
| m := testModule(t, "apply-count-variable-ref") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyCountVariableRefStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_provisionerInterpCount(t *testing.T) { |
| // This test ensures that a provisioner can interpolate a resource count |
| // even though the provisioner expression is evaluated during the plan |
| // walk. https://github.com/hashicorp/terraform/issues/16840 |
| |
| m, snap := testModuleWithSnapshot(t, "apply-provisioner-interp-count") |
| |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| pr := testProvisioner() |
| |
| Providers := map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| } |
| |
| provisioners := map[string]provisioners.Factory{ |
| "local-exec": testProvisionerFuncFixed(pr), |
| } |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: Providers, |
| Provisioners: provisioners, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| assertNoErrors(t, diags) |
| |
| // We'll marshal and unmarshal the plan here, to ensure that we have |
| // a clean new context as would be created if we separately ran |
| // terraform plan -out=tfplan && terraform apply tfplan |
| ctxOpts, m, plan, err := contextOptsForPlanViaFile(t, snap, plan) |
| if err != nil { |
| t.Fatal(err) |
| } |
| ctxOpts.Providers = Providers |
| ctxOpts.Provisioners = provisioners |
| ctx, diags = NewContext(ctxOpts) |
| if diags.HasErrors() { |
| t.Fatalf("failed to create context for plan: %s", diags.Err()) |
| } |
| |
| // Applying the plan should now succeed |
| _, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("apply failed unexpectedly: %s", diags.Err()) |
| } |
| |
| // Verify apply was invoked |
| if !pr.ProvisionResourceCalled { |
| t.Fatalf("provisioner was not called") |
| } |
| } |
| |
| func TestContext2Apply_foreachVariable(t *testing.T) { |
| m := testModule(t, "plan-for-each-unknown-value") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "foo": &InputValue{ |
| Value: cty.StringVal("hello"), |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyForEachVariableStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_moduleBasic(t *testing.T) { |
| m := testModule(t, "apply-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyModuleStr) |
| if actual != expected { |
| t.Fatalf("bad, expected:\n%s\n\nactual:\n%s", expected, actual) |
| } |
| } |
| |
| func TestContext2Apply_moduleDestroyOrder(t *testing.T) { |
| m := testModule(t, "apply-module-destroy-order") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // Create a custom apply function to track the order they were destroyed |
| var order []string |
| var orderLock sync.Mutex |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| id := req.PriorState.GetAttr("id").AsString() |
| |
| if id == "b" { |
| // Pause briefly to make any race conditions more visible, since |
| // missing edges here can cause undeterministic ordering. |
| time.Sleep(100 * time.Millisecond) |
| } |
| |
| orderLock.Lock() |
| defer orderLock.Unlock() |
| |
| order = append(order, id) |
| resp.NewState = req.PlannedState |
| return resp |
| } |
| |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Required: true}, |
| "blah": {Type: cty.String, Optional: true}, |
| "value": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.a").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"a"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.b").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"b"}`), |
| Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.a")}, |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| expected := []string{"b", "a"} |
| if !reflect.DeepEqual(order, expected) { |
| t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected) |
| } |
| |
| { |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyModuleDestroyOrderStr) |
| if actual != expected { |
| t.Errorf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| } |
| |
| func TestContext2Apply_moduleInheritAlias(t *testing.T) { |
| m := testModule(t, "apply-module-provider-inherit-alias") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| val := req.Config.GetAttr("value") |
| if val.IsNull() { |
| return |
| } |
| |
| root := req.Config.GetAttr("root") |
| if !root.IsNull() { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) |
| } |
| |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| checkStateString(t, state, ` |
| <no state> |
| module.child: |
| aws_instance.foo: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"].eu |
| type = aws_instance |
| `) |
| } |
| |
| func TestContext2Apply_orphanResource(t *testing.T) { |
| // This is a two-step test: |
| // 1. Apply a configuration with resources that have count set. |
| // This should place the empty resource object in the state to record |
| // that each exists, and record any instances. |
| // 2. Apply an empty configuration against the same state, which should |
| // then clean up both the instances and the containing resource objects. |
| p := testProvider("test") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_thing": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "foo": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| // Step 1: create the resources and instances |
| m := testModule(t, "apply-orphan-resource") |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| state, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| // At this point both resources should be recorded in the state, along |
| // with the single instance associated with test_thing.one. |
| want := states.BuildState(func(s *states.SyncState) { |
| providerAddr := addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| } |
| oneAddr := addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_thing", |
| Name: "one", |
| }.Absolute(addrs.RootModuleInstance) |
| s.SetResourceProvider(oneAddr, providerAddr) |
| s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo"}`), |
| }, providerAddr) |
| }) |
| |
| if state.String() != want.String() { |
| t.Fatalf("wrong state after step 1\n%s", cmp.Diff(want, state)) |
| } |
| |
| // Step 2: update with an empty config, to destroy everything |
| m = testModule(t, "empty") |
| ctx = testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| plan, diags = ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| { |
| addr := mustResourceInstanceAddr("test_thing.one[0]") |
| change := plan.Changes.ResourceInstance(addr) |
| if change == nil { |
| t.Fatalf("no planned change for %s", addr) |
| } |
| if got, want := change.Action, plans.Delete; got != want { |
| t.Errorf("wrong action for %s %s; want %s", addr, got, want) |
| } |
| if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { |
| t.Errorf("wrong action for %s %s; want %s", addr, got, want) |
| } |
| } |
| |
| state, diags = ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| // The state should now be _totally_ empty, with just an empty root module |
| // (since that always exists) and no resources at all. |
| want = states.NewState() |
| want.CheckResults = &states.CheckResults{} |
| if !cmp.Equal(state, want) { |
| t.Fatalf("wrong state after step 2\ngot: %swant: %s", spew.Sdump(state), spew.Sdump(want)) |
| } |
| |
| } |
| |
| func TestContext2Apply_moduleOrphanInheritAlias(t *testing.T) { |
| m := testModule(t, "apply-module-provider-inherit-alias-orphan") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| val := req.Config.GetAttr("value") |
| if val.IsNull() { |
| return |
| } |
| |
| root := req.Config.GetAttr("root") |
| if !root.IsNull() { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root")) |
| } |
| |
| return |
| } |
| |
| // Create a state with an orphan module |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| { |
| addr := mustResourceInstanceAddr("module.child.aws_instance.bar") |
| change := plan.Changes.ResourceInstance(addr) |
| if change == nil { |
| t.Fatalf("no planned change for %s", addr) |
| } |
| if got, want := change.Action, plans.Delete; got != want { |
| t.Errorf("wrong action for %s %s; want %s", addr, got, want) |
| } |
| // This should ideally be ResourceInstanceDeleteBecauseNoModule, but |
| // the codepath deciding this doesn't currently have enough information |
| // to differentiate, and so this is a compromise. |
| if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want { |
| t.Errorf("wrong action for %s %s; want %s", addr, got, want) |
| } |
| } |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if !p.ConfigureProviderCalled { |
| t.Fatal("must call configure") |
| } |
| |
| checkStateString(t, state, "<no state>") |
| } |
| |
| func TestContext2Apply_moduleOrphanProvider(t *testing.T) { |
| m := testModule(t, "apply-module-orphan-provider-inherit") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| val := req.Config.GetAttr("value") |
| if val.IsNull() { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) |
| } |
| |
| return |
| } |
| |
| // Create a state with an orphan module |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| } |
| |
| func TestContext2Apply_moduleOrphanGrandchildProvider(t *testing.T) { |
| m := testModule(t, "apply-module-orphan-provider-inherit") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| val := req.Config.GetAttr("value") |
| if val.IsNull() { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) |
| } |
| |
| return |
| } |
| |
| // Create a state with an orphan module that is nested (grandchild) |
| state := states.NewState() |
| child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child", addrs.NoKey)) |
| child.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| } |
| |
| func TestContext2Apply_moduleGrandchildProvider(t *testing.T) { |
| m := testModule(t, "apply-module-grandchild-provider-inherit") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| var callLock sync.Mutex |
| called := false |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| val := req.Config.GetAttr("value") |
| if val.IsNull() { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) |
| } |
| |
| callLock.Lock() |
| called = true |
| callLock.Unlock() |
| |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| |
| callLock.Lock() |
| defer callLock.Unlock() |
| if called != true { |
| t.Fatalf("err: configure never called") |
| } |
| } |
| |
| // This tests an issue where all the providers in a module but not |
| // in the root weren't being added to the root properly. In this test |
| // case: aws is explicitly added to root, but "test" should be added to. |
| // With the bug, it wasn't. |
| func TestContext2Apply_moduleOnlyProvider(t *testing.T) { |
| m := testModule(t, "apply-module-only-provider") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| pTest := testProvider("test") |
| pTest.ApplyResourceChangeFn = testApplyFn |
| pTest.PlanResourceChangeFn = testDiffFn |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyModuleOnlyProviderStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_moduleProviderAlias(t *testing.T) { |
| m := testModule(t, "apply-module-provider-alias") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyModuleProviderAliasStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_moduleProviderAliasTargets(t *testing.T) { |
| m := testModule(t, "apply-module-provider-alias") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: []addrs.Targetable{ |
| addrs.ConfigResource{ |
| Module: addrs.RootModule, |
| Resource: addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "nonexistent", |
| Name: "thing", |
| }, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(` |
| <no state> |
| `) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_moduleProviderCloseNested(t *testing.T) { |
| m := testModule(t, "apply-module-provider-close-nested") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| } |
| |
| // Tests that variables used as module vars that reference data that |
| // already exists in the state and requires no diff works properly. This |
| // fixes an issue faced where module variables were pruned because they were |
| // accessing "non-existent" resources (they existed, just not in the graph |
| // cause they weren't in the diff). |
| func TestContext2Apply_moduleVarRefExisting(t *testing.T) { |
| m := testModule(t, "apply-ref-existing") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.foo").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyModuleVarRefExistingStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_moduleVarResourceCount(t *testing.T) { |
| m := testModule(t, "apply-module-var-resource-count") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.DestroyMode, |
| SetVariables: InputValues{ |
| "num": &InputValue{ |
| Value: cty.NumberIntVal(2), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| ctx = testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags = ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "num": &InputValue{ |
| Value: cty.NumberIntVal(5), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| } |
| |
| // GH-819 |
| func TestContext2Apply_moduleBool(t *testing.T) { |
| m := testModule(t, "apply-module-bool") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyModuleBoolStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| // Tests that a module can be targeted and everything is properly created. |
| // This adds to the plan test to also just verify that apply works. |
| func TestContext2Apply_moduleTarget(t *testing.T) { |
| m := testModule(t, "plan-targeted-cross-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| Targets: []addrs.Targetable{ |
| addrs.RootModuleInstance.Child("B", addrs.NoKey), |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| checkStateString(t, state, ` |
| <no state> |
| module.A: |
| aws_instance.foo: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = bar |
| type = aws_instance |
| |
| Outputs: |
| |
| value = foo |
| module.B: |
| aws_instance.bar: |
| ID = foo |
| provider = provider["registry.terraform.io/hashicorp/aws"] |
| foo = foo |
| type = aws_instance |
| |
| Dependencies: |
| module.A.aws_instance.foo |
| `) |
| } |
| |
| func TestContext2Apply_multiProvider(t *testing.T) { |
| m := testModule(t, "apply-multi-provider") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| |
| pDO := testProvider("do") |
| pDO.ApplyResourceChangeFn = testApplyFn |
| pDO.PlanResourceChangeFn = testDiffFn |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| addrs.NewDefaultProvider("do"): testProviderFuncFixed(pDO), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| mod := state.RootModule() |
| if len(mod.Resources) < 2 { |
| t.Fatalf("bad: %#v", mod.Resources) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyMultiProviderStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_multiProviderDestroy(t *testing.T) { |
| m := testModule(t, "apply-multi-provider-destroy") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| Provider: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "addr": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "foo": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| p2 := testProvider("vault") |
| p2.ApplyResourceChangeFn = testApplyFn |
| p2.PlanResourceChangeFn = testDiffFn |
| p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "vault_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| var state *states.State |
| |
| // First, create the instances |
| { |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| s, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| state = s |
| } |
| |
| // Destroy them |
| { |
| // Verify that aws_instance.bar is destroyed first |
| var checked bool |
| var called int32 |
| var lock sync.Mutex |
| applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| lock.Lock() |
| defer lock.Unlock() |
| |
| if req.TypeName == "aws_instance" { |
| checked = true |
| |
| // Sleep to allow parallel execution |
| time.Sleep(50 * time.Millisecond) |
| |
| // Verify that called is 0 (dep not called) |
| if atomic.LoadInt32(&called) != 0 { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) |
| return resp |
| } |
| } |
| |
| atomic.AddInt32(&called, 1) |
| return testApplyFn(req) |
| } |
| |
| // Set the apply functions |
| p.ApplyResourceChangeFn = applyFn |
| p2.ApplyResourceChangeFn = applyFn |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| s, diags := ctx.Apply(plan, m) |
| assertNoErrors(t, diags) |
| |
| if !checked { |
| t.Fatal("should be checked") |
| } |
| |
| state = s |
| } |
| |
| checkStateString(t, state, `<no state>`) |
| } |
| |
| // This is like the multiProviderDestroy test except it tests that |
| // dependent resources within a child module that inherit provider |
| // configuration are still destroyed first. |
| func TestContext2Apply_multiProviderDestroyChild(t *testing.T) { |
| m := testModule(t, "apply-multi-provider-destroy-child") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| Provider: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "value": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| ResourceTypes: map[string]*configschema.Block{ |
| "aws_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "foo": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| p2 := testProvider("vault") |
| p2.ApplyResourceChangeFn = testApplyFn |
| p2.PlanResourceChangeFn = testDiffFn |
| p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| Provider: &configschema.Block{}, |
| ResourceTypes: map[string]*configschema.Block{ |
| "vault_instance": { |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| var state *states.State |
| |
| // First, create the instances |
| { |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| s, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| state = s |
| } |
| |
| // Destroy them |
| { |
| // Verify that aws_instance.bar is destroyed first |
| var checked bool |
| var called int32 |
| var lock sync.Mutex |
| applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| lock.Lock() |
| defer lock.Unlock() |
| |
| if req.TypeName == "aws_instance" { |
| checked = true |
| |
| // Sleep to allow parallel execution |
| time.Sleep(50 * time.Millisecond) |
| |
| // Verify that called is 0 (dep not called) |
| if atomic.LoadInt32(&called) != 0 { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) |
| return resp |
| } |
| } |
| |
| atomic.AddInt32(&called, 1) |
| return testApplyFn(req) |
| } |
| |
| // Set the apply functions |
| p.ApplyResourceChangeFn = applyFn |
| p2.ApplyResourceChangeFn = applyFn |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.DestroyMode, |
| }) |
| assertNoErrors(t, diags) |
| |
| s, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if !checked { |
| t.Fatal("should be checked") |
| } |
| |
| state = s |
| } |
| |
| checkStateString(t, state, ` |
| <no state> |
| `) |
| } |
| |
| func TestContext2Apply_multiVar(t *testing.T) { |
| m := testModule(t, "apply-multi-var") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // First, apply with a count of 3 |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "num": &InputValue{ |
| Value: cty.NumberIntVal(3), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := state.RootModule().OutputValues["output"] |
| expected := cty.StringVal("bar0,bar1,bar2") |
| if actual == nil || actual.Value != expected { |
| t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) |
| } |
| |
| t.Logf("Initial state: %s", state.String()) |
| |
| // Apply again, reduce the count to 1 |
| { |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "num": &InputValue{ |
| Value: cty.NumberIntVal(1), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| t.Logf("End state: %s", state.String()) |
| |
| actual := state.RootModule().OutputValues["output"] |
| if actual == nil { |
| t.Fatal("missing output") |
| } |
| |
| expected := cty.StringVal("bar0") |
| if actual.Value != expected { |
| t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) |
| } |
| } |
| } |
| |
| // This is a holistic test of multi-var (aka "splat variable") handling |
| // across several different Terraform subsystems. This is here because |
| // historically there were quirky differences in handling across different |
| // parts of Terraform and so here we want to assert the expected behavior and |
| // ensure that it remains consistent in future. |
| func TestContext2Apply_multiVarComprehensive(t *testing.T) { |
| m := testModule(t, "apply-multi-var-comprehensive") |
| p := testProvider("test") |
| |
| configs := map[string]cty.Value{} |
| var configsLock sync.Mutex |
| |
| p.ApplyResourceChangeFn = testApplyFn |
| p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| proposed := req.ProposedNewState |
| configsLock.Lock() |
| defer configsLock.Unlock() |
| key := proposed.GetAttr("key").AsString() |
| // This test was originally written using the legacy p.PlanResourceChangeFn interface, |
| // and so the assertions below expect an old-style ResourceConfig, which |
| // we'll construct via our shim for now to avoid rewriting all of the |
| // assertions. |
| configs[key] = req.ProposedNewState |
| |
| retVals := make(map[string]cty.Value) |
| for it := proposed.ElementIterator(); it.Next(); { |
| idxVal, val := it.Element() |
| idx := idxVal.AsString() |
| |
| switch idx { |
| case "id": |
| retVals[idx] = cty.UnknownVal(cty.String) |
| case "name": |
| retVals[idx] = cty.StringVal(key) |
| default: |
| retVals[idx] = val |
| } |
| } |
| |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: cty.ObjectVal(retVals), |
| } |
| } |
| |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_thing": { |
| Attributes: map[string]*configschema.Attribute{ |
| "key": {Type: cty.String, Required: true}, |
| |
| "source_id": {Type: cty.String, Optional: true}, |
| "source_name": {Type: cty.String, Optional: true}, |
| "first_source_id": {Type: cty.String, Optional: true}, |
| "first_source_name": {Type: cty.String, Optional: true}, |
| "source_ids": {Type: cty.List(cty.String), Optional: true}, |
| "source_names": {Type: cty.List(cty.String), Optional: true}, |
| "source_ids_from_func": {Type: cty.List(cty.String), Optional: true}, |
| "source_names_from_func": {Type: cty.List(cty.String), Optional: true}, |
| "source_ids_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, |
| "source_names_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true}, |
| |
| "id": {Type: cty.String, Computed: true}, |
| "name": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| // First, apply with a count of 3 |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "num": &InputValue{ |
| Value: cty.NumberIntVal(3), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| checkConfig := func(key string, want cty.Value) { |
| configsLock.Lock() |
| defer configsLock.Unlock() |
| |
| got, ok := configs[key] |
| if !ok { |
| t.Errorf("no config recorded for %s; expected a configuration", key) |
| return |
| } |
| |
| t.Run("config for "+key, func(t *testing.T) { |
| for _, problem := range deep.Equal(got, want) { |
| t.Errorf(problem) |
| } |
| }) |
| } |
| |
| checkConfig("multi_count_var.0", cty.ObjectVal(map[string]cty.Value{ |
| "source_id": cty.UnknownVal(cty.String), |
| "source_name": cty.StringVal("source.0"), |
| })) |
| checkConfig("multi_count_var.2", cty.ObjectVal(map[string]cty.Value{ |
| "source_id": cty.UnknownVal(cty.String), |
| "source_name": cty.StringVal("source.2"), |
| })) |
| checkConfig("multi_count_derived.0", cty.ObjectVal(map[string]cty.Value{ |
| "source_id": cty.UnknownVal(cty.String), |
| "source_name": cty.StringVal("source.0"), |
| })) |
| checkConfig("multi_count_derived.2", cty.ObjectVal(map[string]cty.Value{ |
| "source_id": cty.UnknownVal(cty.String), |
| "source_name": cty.StringVal("source.2"), |
| })) |
| checkConfig("whole_splat", cty.ObjectVal(map[string]cty.Value{ |
| "source_ids": cty.ListVal([]cty.Value{ |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| }), |
| "source_names": cty.ListVal([]cty.Value{ |
| cty.StringVal("source.0"), |
| cty.StringVal("source.1"), |
| cty.StringVal("source.2"), |
| }), |
| "source_ids_from_func": cty.UnknownVal(cty.String), |
| "source_names_from_func": cty.ListVal([]cty.Value{ |
| cty.StringVal("source.0"), |
| cty.StringVal("source.1"), |
| cty.StringVal("source.2"), |
| }), |
| "source_ids_wrapped": cty.ListVal([]cty.Value{ |
| cty.ListVal([]cty.Value{ |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| }), |
| }), |
| "source_names_wrapped": cty.ListVal([]cty.Value{ |
| cty.ListVal([]cty.Value{ |
| cty.StringVal("source.0"), |
| cty.StringVal("source.1"), |
| cty.StringVal("source.2"), |
| }), |
| }), |
| "first_source_id": cty.UnknownVal(cty.String), |
| "first_source_name": cty.StringVal("source.0"), |
| })) |
| checkConfig("child.whole_splat", cty.ObjectVal(map[string]cty.Value{ |
| "source_ids": cty.ListVal([]cty.Value{ |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| }), |
| "source_names": cty.ListVal([]cty.Value{ |
| cty.StringVal("source.0"), |
| cty.StringVal("source.1"), |
| cty.StringVal("source.2"), |
| }), |
| "source_ids_wrapped": cty.ListVal([]cty.Value{ |
| cty.ListVal([]cty.Value{ |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| cty.UnknownVal(cty.String), |
| }), |
| }), |
| "source_names_wrapped": cty.ListVal([]cty.Value{ |
| cty.ListVal([]cty.Value{ |
| cty.StringVal("source.0"), |
| cty.StringVal("source.1"), |
| cty.StringVal("source.2"), |
| }), |
| }), |
| })) |
| |
| t.Run("apply", func(t *testing.T) { |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("error during apply: %s", diags.Err()) |
| } |
| |
| want := map[string]interface{}{ |
| "source_ids": []interface{}{"foo", "foo", "foo"}, |
| "source_names": []interface{}{ |
| "source.0", |
| "source.1", |
| "source.2", |
| }, |
| } |
| got := map[string]interface{}{} |
| for k, s := range state.RootModule().OutputValues { |
| got[k] = hcl2shim.ConfigValueFromHCL2(s.Value) |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Errorf( |
| "wrong outputs\ngot: %s\nwant: %s", |
| spew.Sdump(got), spew.Sdump(want), |
| ) |
| } |
| }) |
| } |
| |
| // Test that multi-var (splat) access is ordered by count, not by |
| // value. |
| func TestContext2Apply_multiVarOrder(t *testing.T) { |
| m := testModule(t, "apply-multi-var-order") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // First, apply with a count of 3 |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| t.Logf("State: %s", state.String()) |
| |
| actual := state.RootModule().OutputValues["should-be-11"] |
| expected := cty.StringVal("index-11") |
| if actual == nil || actual.Value != expected { |
| t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) |
| } |
| } |
| |
| // Test that multi-var (splat) access is ordered by count, not by |
| // value, through interpolations. |
| func TestContext2Apply_multiVarOrderInterp(t *testing.T) { |
| m := testModule(t, "apply-multi-var-order-interp") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // First, apply with a count of 3 |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| t.Logf("State: %s", state.String()) |
| |
| actual := state.RootModule().OutputValues["should-be-11"] |
| expected := cty.StringVal("baz-index-11") |
| if actual == nil || actual.Value != expected { |
| t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected) |
| } |
| } |
| |
| // Based on GH-10440 where a graph edge wasn't properly being created |
| // between a modified resource and a count instance being destroyed. |
| func TestContext2Apply_multiVarCountDec(t *testing.T) { |
| var s *states.State |
| |
| // First create resources. Nothing sneaky here. |
| { |
| m := testModule(t, "apply-multi-var-count-dec") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| log.Print("\n========\nStep 1 Plan\n========") |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "num": &InputValue{ |
| Value: cty.NumberIntVal(2), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| log.Print("\n========\nStep 1 Apply\n========") |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| t.Logf("Step 1 state:\n%s", state) |
| |
| s = state |
| } |
| |
| // Decrease the count by 1 and verify that everything happens in the |
| // right order. |
| m := testModule(t, "apply-multi-var-count-dec") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| // Verify that aws_instance.bar is modified first and nothing |
| // else happens at the same time. |
| { |
| var checked bool |
| var called int32 |
| var lock sync.Mutex |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| lock.Lock() |
| defer lock.Unlock() |
| |
| if !req.PlannedState.IsNull() { |
| s := req.PlannedState.AsValueMap() |
| if ami, ok := s["ami"]; ok && !ami.IsNull() && ami.AsString() == "special" { |
| checked = true |
| |
| // Sleep to allow parallel execution |
| time.Sleep(50 * time.Millisecond) |
| |
| // Verify that called is 0 (dep not called) |
| if atomic.LoadInt32(&called) != 1 { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called")) |
| return |
| } |
| } |
| } |
| atomic.AddInt32(&called, 1) |
| return testApplyFn(req) |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| log.Print("\n========\nStep 2 Plan\n========") |
| plan, diags := ctx.Plan(m, s, &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "num": &InputValue{ |
| Value: cty.NumberIntVal(1), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| t.Logf("Step 2 plan:\n%s", legacyDiffComparisonString(plan.Changes)) |
| |
| log.Print("\n========\nStep 2 Apply\n========") |
| _, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| |
| if !checked { |
| t.Error("apply never called") |
| } |
| } |
| } |
| |
| // Test that we can resolve a multi-var (splat) for the first resource |
| // created in a non-root module, which happens when the module state doesn't |
| // exist yet. |
| // https://github.com/hashicorp/terraform/issues/14438 |
| func TestContext2Apply_multiVarMissingState(t *testing.T) { |
| m := testModule(t, "apply-multi-var-missing-state") |
| p := testProvider("test") |
| p.PlanResourceChangeFn = testDiffFn |
| p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ |
| ResourceTypes: map[string]*configschema.Block{ |
| "test_thing": { |
| Attributes: map[string]*configschema.Attribute{ |
| "a_ids": {Type: cty.String, Optional: true}, |
| "id": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }) |
| |
| // First, apply with a count of 3 |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| // Before the relevant bug was fixed, Terraform would panic during apply. |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply failed: %s", diags.Err()) |
| } |
| |
| // If we get here with no errors or panics then our test was successful. |
| } |
| |
| func TestContext2Apply_outputOrphan(t *testing.T) { |
| m := testModule(t, "apply-output-orphan") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetOutputValue("foo", cty.StringVal("bar"), false) |
| root.SetOutputValue("bar", cty.StringVal("baz"), false) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags = ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyOutputOrphanStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_outputOrphanModule(t *testing.T) { |
| m := testModule(t, "apply-output-orphan-module") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| state := states.NewState() |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| s, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(s.String()) |
| expected := strings.TrimSpace(testTerraformApplyOutputOrphanModuleStr) |
| if actual != expected { |
| t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) |
| } |
| |
| // now apply with no module in the config, which should remove the |
| // remaining output |
| ctx = testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| emptyConfig := configs.NewEmptyConfig() |
| |
| // NOTE: While updating this test to pass the state in as a Plan argument, |
| // rather than into the testContext2 call above, it previously said |
| // State: state.DeepCopy(), which is a little weird since we just |
| // created "s" above as the result of the previous apply, but I've preserved |
| // it to avoid changing the flow of this test in case that's important |
| // for some reason. |
| plan, diags = ctx.Plan(emptyConfig, state.DeepCopy(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags = ctx.Apply(plan, emptyConfig) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| if !state.Empty() { |
| t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(state)) |
| } |
| } |
| |
| func TestContext2Apply_providerComputedVar(t *testing.T) { |
| m := testModule(t, "apply-provider-computed") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| pTest := testProvider("test") |
| pTest.ApplyResourceChangeFn = testApplyFn |
| pTest.PlanResourceChangeFn = testDiffFn |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest), |
| }, |
| }) |
| |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| val := req.Config.GetAttr("value") |
| if val.IsNull() { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) |
| return |
| } |
| return |
| } |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| } |
| |
| func TestContext2Apply_providerConfigureDisabled(t *testing.T) { |
| m := testModule(t, "apply-provider-configure-disabled") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| |
| p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { |
| val := req.Config.GetAttr("value") |
| if val.IsNull() { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found")) |
| } |
| |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| if _, diags := ctx.Apply(plan, m); diags.HasErrors() { |
| t.Fatalf("apply errors: %s", diags.Err()) |
| } |
| |
| if !p.ConfigureProviderCalled { |
| t.Fatal("configure never called") |
| } |
| } |
| |
| func TestContext2Apply_provisionerModule(t *testing.T) { |
| m := testModule(t, "apply-provisioner-module") |
| |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| |
| pr := testProvisioner() |
| pr.GetSchemaResponse = provisioners.GetSchemaResponse{ |
| Provisioner: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "foo": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyProvisionerModuleStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| |
| // Verify apply was invoked |
| if !pr.ProvisionResourceCalled { |
| t.Fatalf("provisioner not invoked") |
| } |
| } |
| |
| func TestContext2Apply_Provisioner_compute(t *testing.T) { |
| m := testModule(t, "apply-provisioner-compute") |
| p := testProvider("aws") |
| pr := testProvisioner() |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { |
| |
| val := req.Config.GetAttr("command").AsString() |
| if val != "computed_value" { |
| t.Fatalf("bad value for foo: %q", val) |
| } |
| req.UIOutput.Output(fmt.Sprintf("Executing: %q", val)) |
| |
| return |
| } |
| h := new(MockHook) |
| ctx := testContext2(t, &ContextOpts{ |
| Hooks: []Hook{h}, |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ |
| Mode: plans.NormalMode, |
| SetVariables: InputValues{ |
| "value": &InputValue{ |
| Value: cty.NumberIntVal(1), |
| SourceType: ValueFromCaller, |
| }, |
| }, |
| }) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags.HasErrors() { |
| t.Fatalf("diags: %s", diags.Err()) |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyProvisionerStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| |
| // Verify apply was invoked |
| if !pr.ProvisionResourceCalled { |
| t.Fatalf("provisioner not invoked") |
| } |
| |
| // Verify output was rendered |
| if !h.ProvisionOutputCalled { |
| t.Fatalf("ProvisionOutput hook not called") |
| } |
| if got, want := h.ProvisionOutputMessage, `Executing: "computed_value"`; got != want { |
| t.Errorf("expected output to be %q, but was %q", want, got) |
| } |
| } |
| |
| func TestContext2Apply_provisionerCreateFail(t *testing.T) { |
| m := testModule(t, "apply-provisioner-fail-create") |
| p := testProvider("aws") |
| pr := testProvisioner() |
| p.PlanResourceChangeFn = testDiffFn |
| |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| resp := testApplyFn(req) |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) |
| |
| return resp |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags == nil { |
| t.Fatal("should error") |
| } |
| |
| got := strings.TrimSpace(state.String()) |
| want := strings.TrimSpace(testTerraformApplyProvisionerFailCreateStr) |
| if got != want { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want) |
| } |
| } |
| |
| func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) { |
| m := testModule(t, "apply-provisioner-fail-create") |
| p := testProvider("aws") |
| pr := testProvisioner() |
| p.PlanResourceChangeFn = testDiffFn |
| |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags == nil { |
| t.Fatal("should error") |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateNoIdStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_provisionerFail(t *testing.T) { |
| m := testModule(t, "apply-provisioner-fail") |
| p := testProvider("aws") |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| pr := testProvisioner() |
| pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) |
| return |
| } |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags := ctx.Apply(plan, m) |
| if diags == nil { |
| t.Fatal("should error") |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyProvisionerFailStr) |
| if actual != expected { |
| t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) |
| } |
| } |
| |
| func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) { |
| m := testModule(t, "apply-provisioner-fail-create-before") |
| p := testProvider("aws") |
| pr := testProvisioner() |
| p.PlanResourceChangeFn = testDiffFn |
| p.ApplyResourceChangeFn = testApplyFn |
| pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION")) |
| return |
| } |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar","require_new":"abc"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| Provisioners: map[string]provisioners.Factory{ |
| "shell": testProvisionerFuncFixed(pr), |
| }, |
| }) |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags = ctx.Apply(plan, m) |
| if !diags.HasErrors() { |
| t.Fatal("should error") |
| } |
| |
| actual := strings.TrimSpace(state.String()) |
| expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateBeforeDestroyStr) |
| if actual != expected { |
| t.Fatalf("expected:\n%s\n:got\n%s", expected, actual) |
| } |
| } |
| |
| func TestContext2Apply_error_createBeforeDestroy(t *testing.T) { |
| m := testModule(t, "apply-error-create-before") |
| p := testProvider("aws") |
| |
| state := states.NewState() |
| root := state.EnsureModule(addrs.RootModuleInstance) |
| root.SetResourceInstanceCurrent( |
| mustResourceInstanceAddr("aws_instance.bar").Resource, |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"id":"bar", "require_new": "abc","type":"aws_instance"}`), |
| }, |
| mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), |
| ) |
| |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), |
| }, |
| }) |
| p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { |
| resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("placeholder error from ApplyFn")) |
| return |
| } |
| p.PlanResourceChangeFn = testDiffFn |
| |
| plan, diags := ctx.Plan(m, state, DefaultPlanOpts) |
| assertNoErrors(t, diags) |
| |
| state, diags = ctx.Apply(plan, m) |
| if !diags.HasErrors() { |
| t.Fatal("should have error") |