package terraform import ( "errors" "testing" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/states" "github.com/zclconf/go-cty/cty" ) func TestContext2Plan_removedDuringRefresh(t *testing.T) { // The resource was added to state but actually failed to create and was // left tainted. This should be removed during plan and result in a Create // action. m := testModuleInline(t, map[string]string{ "main.tf": ` resource "test_object" "a" { } `, }) p := simpleMockProvider() p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { resp.NewState = cty.NullVal(req.PriorState.Type()) return resp } addr := mustResourceInstanceAddr("test_object.a") state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"test_string":"foo"}`), Status: states.ObjectTainted, }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) }) ctx := testContext2(t, &ContextOpts{ Config: m, State: state, Providers: map[addrs.Provider]providers.Factory{ addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatal(diags.Err()) } for _, c := range plan.Changes.Resources { if c.Action != plans.Create { t.Fatalf("expected Create action for missing %s, got %s", c.Addr, c.Action) } } } func TestContext2Plan_noChangeDataSourceSensitiveNestedSet(t *testing.T) { m := testModuleInline(t, map[string]string{ "main.tf": ` variable "bar" { sensitive = true default = "baz" } data "test_data_source" "foo" { foo { bar = var.bar } } `, }) p := new(MockProvider) p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{ DataSources: map[string]*configschema.Block{ "test_data_source": { Attributes: map[string]*configschema.Attribute{ "id": { Type: cty.String, Computed: true, }, }, BlockTypes: map[string]*configschema.NestedBlock{ "foo": { Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": {Type: cty.String, Optional: true}, }, }, Nesting: configschema.NestingSet, }, }, }, }, }) p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ State: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("data_id"), "foo": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}), }), } state := states.NewState() root := state.EnsureModule(addrs.RootModuleInstance) root.SetResourceInstanceCurrent( mustResourceInstanceAddr("data.test_data_source.foo").Resource, &states.ResourceInstanceObjectSrc{ Status: states.ObjectReady, AttrsJSON: []byte(`{"id":"data_id", "foo":[{"bar":"baz"}]}`), AttrSensitivePaths: []cty.PathValueMarks{ { Path: cty.GetAttrPath("foo"), Marks: cty.NewValueMarks("sensitive"), }, }, }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), ) ctx := testContext2(t, &ContextOpts{ Config: m, Providers: map[addrs.Provider]providers.Factory{ addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), }, State: state, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatal(diags.ErrWithWarnings()) } for _, res := range plan.Changes.Resources { if res.Action != plans.NoOp { t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action) } } } func TestContext2Plan_orphanDataInstance(t *testing.T) { // ensure the planned replacement of the data source is evaluated properly m := testModuleInline(t, map[string]string{ "main.tf": ` data "test_object" "a" { for_each = { new = "ok" } } output "out" { value = [ for k, _ in data.test_object.a: k ] } `, }) p := simpleMockProvider() p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { resp.State = req.Config return resp } state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a["old"]`), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"test_string":"foo"}`), Status: states.ObjectReady, }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) }) ctx := testContext2(t, &ContextOpts{ Config: m, State: state, Providers: map[addrs.Provider]providers.Factory{ addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), }, }) plan, diags := ctx.Plan() if diags.HasErrors() { t.Fatal(diags.Err()) } change, err := plan.Changes.Outputs[0].Decode() if err != nil { t.Fatal(err) } expected := cty.TupleVal([]cty.Value{cty.StringVal("new")}) if change.After.Equals(expected).False() { t.Fatalf("expected %#v, got %#v\n", expected, change.After) } } func TestContext2Plan_basicConfigurationAliases(t *testing.T) { m := testModuleInline(t, map[string]string{ "main.tf": ` provider "test" { alias = "z" test_string = "config" } module "mod" { source = "./mod" providers = { test.x = test.z } } `, "mod/main.tf": ` terraform { required_providers { test = { source = "registry.terraform.io/hashicorp/test" configuration_aliases = [ test.x ] } } } resource "test_object" "a" { provider = test.x } `, }) p := simpleMockProvider() // The resource within the module should be using the provider configured // from the root module. We should never see an empty configuration. p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { if req.Config.GetAttr("test_string").IsNull() { resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing test_string value")) } return resp } ctx := testContext2(t, &ContextOpts{ Config: m, Providers: map[addrs.Provider]providers.Factory{ addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), }, }) _, diags := ctx.Plan() if diags.HasErrors() { t.Fatal(diags.Err()) } }