diff --git a/internal/states/instance_object_src.go b/internal/states/instance_object_src.go index 49600d9552dd..63b7963c2249 100644 --- a/internal/states/instance_object_src.go +++ b/internal/states/instance_object_src.go @@ -117,7 +117,7 @@ func (os *ResourceInstanceObjectSrc) Decode(schema providers.Schema) (*ResourceI var identity cty.Value if os.decodeIdentityCache != cty.NilVal { identity = os.decodeIdentityCache - } else if os.IdentityJSON != nil { + } else if os.IdentityJSON != nil && schema.Identity != nil { identity, err = ctyjson.Unmarshal(os.IdentityJSON, schema.Identity.ImpliedType()) if err != nil { return nil, fmt.Errorf("failed to decode identity: %s. This is most likely a bug in the Provider, providers must not change the identity schema without updating the identity schema version", err.Error()) diff --git a/internal/states/instance_object_test.go b/internal/states/instance_object_test.go index 7b5a1a4bde8d..ba8cccb8efdf 100644 --- a/internal/states/instance_object_test.go +++ b/internal/states/instance_object_test.go @@ -61,22 +61,22 @@ func TestResourceInstanceObject_encode(t *testing.T) { // multiple instances may have been assigned the same deps slice objs := []*ResourceInstanceObject{ - &ResourceInstanceObject{ + { Value: value, Status: ObjectPlanned, Dependencies: depsOne, }, - &ResourceInstanceObject{ + { Value: value, Status: ObjectPlanned, Dependencies: depsTwo, }, - &ResourceInstanceObject{ + { Value: value, Status: ObjectPlanned, Dependencies: depsOne, }, - &ResourceInstanceObject{ + { Value: value, Status: ObjectPlanned, Dependencies: depsOne, @@ -91,10 +91,7 @@ func TestResourceInstanceObject_encode(t *testing.T) { var mu sync.Mutex for _, obj := range objs { - obj := obj - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { rios, err := obj.Encode(schema) if err != nil { t.Errorf("unexpected error: %s", err) @@ -102,7 +99,7 @@ func TestResourceInstanceObject_encode(t *testing.T) { mu.Lock() encoded = append(encoded, rios) mu.Unlock() - }() + }) } wg.Wait() diff --git a/internal/terraform/context_plan_identity_test.go b/internal/terraform/context_plan_identity_test.go index 1c6a573f7091..aa8535edc05a 100644 --- a/internal/terraform/context_plan_identity_test.go +++ b/internal/terraform/context_plan_identity_test.go @@ -309,6 +309,76 @@ func TestContext2Plan_resource_identity_refresh(t *testing.T) { } } +func TestContext2Plan_resource_identity_refresh_downgrade(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-basic") + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{ + ResourceTypes: map[string]*configschema.Block{ + "aws_instance": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "foo": { + Type: cty.String, + Optional: true, + Computed: true, + }, + }, + }, + }, + }) + + state := states.NewState() + root := state.EnsureModule(addrs.RootModuleInstance) + + root.SetResourceInstanceCurrent( + mustResourceInstanceAddr("aws_instance.web").Resource, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []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), + }, + }) + + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] + + p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { + return providers.ReadResourceResponse{ + NewState: req.PriorState, + } + } + + s, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.RefreshOnlyMode}) + + if diags.HasErrors() { + t.Fatal(diags.Err()) + } + + if !p.ReadResourceCalled { + t.Fatal("ReadResource should be called") + } + + mod := s.PriorState.RootModule() + fromState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema) + if err != nil { + t.Fatal(err) + } + + if !fromState.Identity.IsNull() { + t.Fatalf("wrong identity\nwant: null\ngot: %s", fromState.Identity.GoString()) + } +} + // This test validates if a resource identity that is deposed and will be destroyed // can be refreshed with an identity during the plan. func TestContext2Plan_resource_identity_refresh_destroy_deposed(t *testing.T) {