diff --git a/internal/command/e2etest/unmanaged_test.go b/internal/command/e2etest/unmanaged_test.go index 666fdcda2a..e3e2335f71 100644 --- a/internal/command/e2etest/unmanaged_test.go +++ b/internal/command/e2etest/unmanaged_test.go @@ -116,6 +116,7 @@ func (p *providerServer5) ApplyResourceChange(ctx context.Context, req *proto5.A defer p.Unlock() p.applyResourceChangeCalled = true + return p.ProviderServer.ApplyResourceChange(ctx, req) } diff --git a/internal/command/jsonformat/plan_test.go b/internal/command/jsonformat/plan_test.go index 2115fb9236..47a7476899 100644 --- a/internal/command/jsonformat/plan_test.go +++ b/internal/command/jsonformat/plan_test.go @@ -8234,7 +8234,9 @@ func TestResourceChange_deferredActions(t *testing.T) { } var changes []*plans.DeferredResourceInstanceChangeSrc for _, change := range tc.changes { - changeSrc, err := change.Encode(blockSchema.ImpliedType()) + changeSrc, err := change.Encode(providers.Schema{ + Body: blockSchema, + }) if err != nil { t.Fatalf("Failed to encode change: %s", err) } diff --git a/internal/command/jsonplan/plan.go b/internal/command/jsonplan/plan.go index 4cf788b70e..69323379e3 100644 --- a/internal/command/jsonplan/plan.go +++ b/internal/command/jsonplan/plan.go @@ -433,7 +433,7 @@ func marshalResourceChange(rc *plans.ResourceInstanceChangeSrc, schemas *terrafo return r, fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider) } - changeV, err := rc.Decode(schema.Body.ImpliedType()) + changeV, err := rc.Decode(schema) if err != nil { return r, err } diff --git a/internal/command/jsonplan/values.go b/internal/command/jsonplan/values.go index fded2ef5a4..e13c372eea 100644 --- a/internal/command/jsonplan/values.go +++ b/internal/command/jsonplan/values.go @@ -204,7 +204,7 @@ func marshalPlanResources(changes *plans.ChangesSrc, ris []addrs.AbsResourceInst return nil, fmt.Errorf("no schema found for %s", r.Addr.String()) } resource.SchemaVersion = uint64(schema.Version) - changeV, err := r.Decode(schema.Body.ImpliedType()) + changeV, err := r.Decode(schema) if err != nil { return nil, err } diff --git a/internal/command/views/operation_test.go b/internal/command/views/operation_test.go index aec9fca1b3..e3a933b7c3 100644 --- a/internal/command/views/operation_test.go +++ b/internal/command/views/operation_test.go @@ -139,7 +139,7 @@ func TestOperation_planNoChanges(t *testing.T) { }), }, } - rcs, err := rc.Encode(ty) + rcs, err := rc.Encode(schema) if err != nil { panic(err) } @@ -180,7 +180,7 @@ func TestOperation_planNoChanges(t *testing.T) { }), }, } - rcs, err := rc.Encode(ty) + rcs, err := rc.Encode(schema) if err != nil { panic(err) } @@ -227,7 +227,7 @@ func TestOperation_planNoChanges(t *testing.T) { }), }, } - rcs, err := rc.Encode(ty) + rcs, err := rc.Encode(schema) if err != nil { panic(err) } @@ -257,7 +257,6 @@ func TestOperation_planNoChanges(t *testing.T) { addr.Resource.Resource.Mode, addr.Resource.Resource.Type, ) - ty := schema.Body.ImpliedType() rc := &plans.ResourceInstanceChange{ Addr: addr, PrevRunAddr: addrPrev, @@ -276,7 +275,7 @@ func TestOperation_planNoChanges(t *testing.T) { }), }, } - rcs, err := rc.Encode(ty) + rcs, err := rc.Encode(schema) if err != nil { panic(err) } diff --git a/internal/grpcwrap/provider.go b/internal/grpcwrap/provider.go index 813ce2e2c2..9165de4863 100644 --- a/internal/grpcwrap/provider.go +++ b/internal/grpcwrap/provider.go @@ -219,7 +219,8 @@ func (p *provider) Configure(_ context.Context, req *tfplugin5.Configure_Request func (p *provider) ReadResource(_ context.Context, req *tfplugin5.ReadResource_Request) (*tfplugin5.ReadResource_Response, error) { resp := &tfplugin5.ReadResource_Response{} - ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType() + resSchema := p.schema.ResourceTypes[req.TypeName] + ty := resSchema.Body.ImpliedType() stateVal, err := decodeDynamicValue(req.CurrentState, ty) if err != nil { @@ -234,11 +235,24 @@ func (p *provider) ReadResource(_ context.Context, req *tfplugin5.ReadResource_R return resp, nil } + var currentIdentity cty.Value + if req.CurrentIdentity != nil && req.CurrentIdentity.IdentityData != nil { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + currentIdentity, err = decodeDynamicValue(req.CurrentIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } + readResp := p.provider.ReadResource(providers.ReadResourceRequest{ - TypeName: req.TypeName, - PriorState: stateVal, - Private: req.Private, - ProviderMeta: metaVal, + TypeName: req.TypeName, + PriorState: stateVal, + Private: req.Private, + ProviderMeta: metaVal, + CurrentIdentity: currentIdentity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics) if readResp.Diagnostics.HasErrors() { @@ -253,12 +267,27 @@ func (p *provider) ReadResource(_ context.Context, req *tfplugin5.ReadResource_R } resp.NewState = dv + if !readResp.Identity.IsNull() { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + identity, err := encodeDynamicValue(readResp.Identity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + resp.NewIdentity = &tfplugin5.ResourceIdentityData{ + IdentityData: identity, + } + } return resp, nil } func (p *provider) PlanResourceChange(_ context.Context, req *tfplugin5.PlanResourceChange_Request) (*tfplugin5.PlanResourceChange_Response, error) { resp := &tfplugin5.PlanResourceChange_Response{} - ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType() + resSchema := p.schema.ResourceTypes[req.TypeName] + ty := resSchema.Body.ImpliedType() priorStateVal, err := decodeDynamicValue(req.PriorState, ty) if err != nil { @@ -285,6 +314,20 @@ func (p *provider) PlanResourceChange(_ context.Context, req *tfplugin5.PlanReso return resp, nil } + var priorIdentity cty.Value + if req.PriorIdentity != nil && req.PriorIdentity.IdentityData != nil { + + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + priorIdentity, err = decodeDynamicValue(req.PriorIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } + planResp := p.provider.PlanResourceChange(providers.PlanResourceChangeRequest{ TypeName: req.TypeName, PriorState: priorStateVal, @@ -292,6 +335,7 @@ func (p *provider) PlanResourceChange(_ context.Context, req *tfplugin5.PlanReso Config: configVal, PriorPrivate: req.PriorPrivate, ProviderMeta: metaVal, + PriorIdentity: priorIdentity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics) if planResp.Diagnostics.HasErrors() { @@ -310,12 +354,30 @@ func (p *provider) PlanResourceChange(_ context.Context, req *tfplugin5.PlanReso resp.RequiresReplace = append(resp.RequiresReplace, convert.PathToAttributePath(path)) } + if !planResp.PlannedIdentity.IsNull() { + + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + plannedIdentity, err := encodeDynamicValue(planResp.PlannedIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + resp.PlannedIdentity = &tfplugin5.ResourceIdentityData{ + IdentityData: plannedIdentity, + } + } + return resp, nil } func (p *provider) ApplyResourceChange(_ context.Context, req *tfplugin5.ApplyResourceChange_Request) (*tfplugin5.ApplyResourceChange_Response, error) { resp := &tfplugin5.ApplyResourceChange_Response{} - ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType() + resSchema := p.schema.ResourceTypes[req.TypeName] + ty := resSchema.Body.ImpliedType() priorStateVal, err := decodeDynamicValue(req.PriorState, ty) if err != nil { @@ -342,13 +404,27 @@ func (p *provider) ApplyResourceChange(_ context.Context, req *tfplugin5.ApplyRe return resp, nil } + var plannedIdentity cty.Value + if req.PlannedIdentity != nil && req.PlannedIdentity.IdentityData != nil { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + plannedIdentity, err = decodeDynamicValue(req.PlannedIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } + applyResp := p.provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ - TypeName: req.TypeName, - PriorState: priorStateVal, - PlannedState: plannedStateVal, - Config: configVal, - PlannedPrivate: req.PlannedPrivate, - ProviderMeta: metaVal, + TypeName: req.TypeName, + PriorState: priorStateVal, + PlannedState: plannedStateVal, + Config: configVal, + PlannedPrivate: req.PlannedPrivate, + ProviderMeta: metaVal, + PlannedIdentity: plannedIdentity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, applyResp.Diagnostics) @@ -356,38 +432,85 @@ func (p *provider) ApplyResourceChange(_ context.Context, req *tfplugin5.ApplyRe return resp, nil } resp.Private = applyResp.Private - resp.NewState, err = encodeDynamicValue(applyResp.NewState, ty) if err != nil { resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) return resp, nil } + if !applyResp.NewIdentity.IsNull() { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + newIdentity, err := encodeDynamicValue(applyResp.NewIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + resp.NewIdentity = &tfplugin5.ResourceIdentityData{ + IdentityData: newIdentity, + } + } + return resp, nil } func (p *provider) ImportResourceState(_ context.Context, req *tfplugin5.ImportResourceState_Request) (*tfplugin5.ImportResourceState_Response, error) { resp := &tfplugin5.ImportResourceState_Response{} + var identity cty.Value + var err error + if req.Identity != nil && req.Identity.IdentityData != nil { + resSchema := p.schema.ResourceTypes[req.TypeName] + + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + identity, err = decodeDynamicValue(req.Identity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } importResp := p.provider.ImportResourceState(providers.ImportResourceStateRequest{ TypeName: req.TypeName, ID: req.Id, + Identity: identity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, importResp.Diagnostics) for _, res := range importResp.ImportedResources { - ty := p.schema.ResourceTypes[res.TypeName].Body.ImpliedType() + importSchema := p.schema.ResourceTypes[res.TypeName] + ty := importSchema.Body.ImpliedType() state, err := encodeDynamicValue(res.State, ty) if err != nil { resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) continue } - resp.ImportedResources = append(resp.ImportedResources, &tfplugin5.ImportResourceState_ImportedResource{ + resource := &tfplugin5.ImportResourceState_ImportedResource{ TypeName: res.TypeName, State: state, Private: res.Private, - }) + } + + if !res.Identity.IsNull() { + if importSchema.Identity == nil { + return nil, fmt.Errorf("identity schema not found for type %s", res.TypeName) + } + identity, err := encodeDynamicValue(res.Identity, importSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + continue + } + resource.Identity = &tfplugin5.ResourceIdentityData{ + IdentityData: identity, + } + } + + resp.ImportedResources = append(resp.ImportedResources, resource) } return resp, nil @@ -396,12 +519,19 @@ func (p *provider) ImportResourceState(_ context.Context, req *tfplugin5.ImportR func (p *provider) MoveResourceState(_ context.Context, request *tfplugin5.MoveResourceState_Request) (*tfplugin5.MoveResourceState_Response, error) { resp := &tfplugin5.MoveResourceState_Response{} + var sourceIdentity []byte + var err error + if request.SourceIdentity != nil && len(request.SourceIdentity.Json) > 0 { + sourceIdentity = request.SourceIdentity.Json + } + moveResp := p.provider.MoveResourceState(providers.MoveResourceStateRequest{ SourceProviderAddress: request.SourceProviderAddress, SourceTypeName: request.SourceTypeName, SourceSchemaVersion: request.SourceSchemaVersion, SourceStateJSON: request.SourceState.Json, SourcePrivate: request.SourcePrivate, + SourceIdentity: sourceIdentity, TargetTypeName: request.TargetTypeName, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, moveResp.Diagnostics) @@ -409,7 +539,8 @@ func (p *provider) MoveResourceState(_ context.Context, request *tfplugin5.MoveR return resp, nil } - targetType := p.schema.ResourceTypes[request.TargetTypeName].Body.ImpliedType() + targetSchema := p.schema.ResourceTypes[request.TargetTypeName] + targetType := targetSchema.Body.ImpliedType() targetState, err := encodeDynamicValue(moveResp.TargetState, targetType) if err != nil { resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) @@ -417,6 +548,21 @@ func (p *provider) MoveResourceState(_ context.Context, request *tfplugin5.MoveR } resp.TargetState = targetState resp.TargetPrivate = moveResp.TargetPrivate + + if !moveResp.TargetIdentity.IsNull() { + if targetSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", request.TargetTypeName) + } + targetIdentity, err := encodeDynamicValue(moveResp.TargetIdentity, targetSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + resp.TargetIdentity = &tfplugin5.ResourceIdentityData{ + IdentityData: targetIdentity, + } + } + return resp, nil } @@ -558,11 +704,11 @@ func (p *provider) GetResourceIdentitySchemas(_ context.Context, req *tfplugin5. func (p *provider) UpgradeResourceIdentity(_ context.Context, req *tfplugin5.UpgradeResourceIdentity_Request) (*tfplugin5.UpgradeResourceIdentity_Response, error) { resp := &tfplugin5.UpgradeResourceIdentity_Response{} - resource, ok := p.identitySchemas.IdentityTypes[req.TypeName] + resource, ok := p.schema.ResourceTypes[req.TypeName] if !ok { return nil, fmt.Errorf("resource identity schema not found for type %q", req.TypeName) } - ty := resource.Body.ImpliedType() + ty := resource.Identity.ImpliedType() upgradeResp := p.provider.UpgradeResourceIdentity(providers.UpgradeResourceIdentityRequest{ TypeName: req.TypeName, Version: req.Version, diff --git a/internal/grpcwrap/provider6.go b/internal/grpcwrap/provider6.go index 185a9f23fe..0fe69c2744 100644 --- a/internal/grpcwrap/provider6.go +++ b/internal/grpcwrap/provider6.go @@ -223,7 +223,8 @@ func (p *provider6) ConfigureProvider(_ context.Context, req *tfplugin6.Configur func (p *provider6) ReadResource(_ context.Context, req *tfplugin6.ReadResource_Request) (*tfplugin6.ReadResource_Response, error) { resp := &tfplugin6.ReadResource_Response{} - ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType() + resSchema := p.schema.ResourceTypes[req.TypeName] + ty := resSchema.Body.ImpliedType() stateVal, err := decodeDynamicValue6(req.CurrentState, ty) if err != nil { @@ -238,11 +239,25 @@ func (p *provider6) ReadResource(_ context.Context, req *tfplugin6.ReadResource_ return resp, nil } + var currentIdentity cty.Value + if req.CurrentIdentity != nil && req.CurrentIdentity.IdentityData != nil { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + currentIdentity, err = decodeDynamicValue6(req.CurrentIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } + readResp := p.provider.ReadResource(providers.ReadResourceRequest{ - TypeName: req.TypeName, - PriorState: stateVal, - Private: req.Private, - ProviderMeta: metaVal, + TypeName: req.TypeName, + PriorState: stateVal, + Private: req.Private, + ProviderMeta: metaVal, + CurrentIdentity: currentIdentity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics) if readResp.Diagnostics.HasErrors() { @@ -257,12 +272,27 @@ func (p *provider6) ReadResource(_ context.Context, req *tfplugin6.ReadResource_ } resp.NewState = dv + if !readResp.Identity.IsNull() { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + newIdentity, err := encodeDynamicValue6(readResp.Identity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + resp.NewIdentity = &tfplugin6.ResourceIdentityData{ + IdentityData: newIdentity, + } + } + return resp, nil } func (p *provider6) PlanResourceChange(_ context.Context, req *tfplugin6.PlanResourceChange_Request) (*tfplugin6.PlanResourceChange_Response, error) { resp := &tfplugin6.PlanResourceChange_Response{} - ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType() + resSchema := p.schema.ResourceTypes[req.TypeName] + ty := resSchema.Body.ImpliedType() priorStateVal, err := decodeDynamicValue6(req.PriorState, ty) if err != nil { @@ -289,6 +319,19 @@ func (p *provider6) PlanResourceChange(_ context.Context, req *tfplugin6.PlanRes return resp, nil } + var priorIdentity cty.Value + if req.PriorIdentity != nil && req.PriorIdentity.IdentityData != nil { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + priorIdentity, err = decodeDynamicValue6(req.PriorIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } + planResp := p.provider.PlanResourceChange(providers.PlanResourceChangeRequest{ TypeName: req.TypeName, PriorState: priorStateVal, @@ -296,6 +339,7 @@ func (p *provider6) PlanResourceChange(_ context.Context, req *tfplugin6.PlanRes Config: configVal, PriorPrivate: req.PriorPrivate, ProviderMeta: metaVal, + PriorIdentity: priorIdentity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics) if planResp.Diagnostics.HasErrors() { @@ -314,12 +358,29 @@ func (p *provider6) PlanResourceChange(_ context.Context, req *tfplugin6.PlanRes resp.RequiresReplace = append(resp.RequiresReplace, convert.PathToAttributePath(path)) } + if !planResp.PlannedIdentity.IsNull() { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + plannedIdentityVal, err := encodeDynamicValue6(planResp.PlannedIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + resp.PlannedIdentity = &tfplugin6.ResourceIdentityData{ + IdentityData: plannedIdentityVal, + } + } + return resp, nil } func (p *provider6) ApplyResourceChange(_ context.Context, req *tfplugin6.ApplyResourceChange_Request) (*tfplugin6.ApplyResourceChange_Response, error) { resp := &tfplugin6.ApplyResourceChange_Response{} - ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType() + resSchema := p.schema.ResourceTypes[req.TypeName] + ty := resSchema.Body.ImpliedType() priorStateVal, err := decodeDynamicValue6(req.PriorState, ty) if err != nil { @@ -346,13 +407,27 @@ func (p *provider6) ApplyResourceChange(_ context.Context, req *tfplugin6.ApplyR return resp, nil } + var plannedIdentity cty.Value + if req.PlannedIdentity != nil && req.PlannedIdentity.IdentityData != nil { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + + plannedIdentity, err = decodeDynamicValue6(req.PlannedIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } + applyResp := p.provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ - TypeName: req.TypeName, - PriorState: priorStateVal, - PlannedState: plannedStateVal, - Config: configVal, - PlannedPrivate: req.PlannedPrivate, - ProviderMeta: metaVal, + TypeName: req.TypeName, + PriorState: priorStateVal, + PlannedState: plannedStateVal, + Config: configVal, + PlannedPrivate: req.PlannedPrivate, + ProviderMeta: metaVal, + PlannedIdentity: plannedIdentity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, applyResp.Diagnostics) @@ -367,31 +442,78 @@ func (p *provider6) ApplyResourceChange(_ context.Context, req *tfplugin6.ApplyR return resp, nil } + if !applyResp.NewIdentity.IsNull() { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + newIdentity, err := encodeDynamicValue6(applyResp.NewIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + resp.NewIdentity = &tfplugin6.ResourceIdentityData{ + IdentityData: newIdentity, + } + } + return resp, nil } func (p *provider6) ImportResourceState(_ context.Context, req *tfplugin6.ImportResourceState_Request) (*tfplugin6.ImportResourceState_Response, error) { resp := &tfplugin6.ImportResourceState_Response{} + resSchema := p.schema.ResourceTypes[req.TypeName] + + var identity cty.Value + var err error + if req.Identity != nil && req.Identity.IdentityData != nil { + if resSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", req.TypeName) + } + identity, err = decodeDynamicValue6(req.Identity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + } + importResp := p.provider.ImportResourceState(providers.ImportResourceStateRequest{ TypeName: req.TypeName, ID: req.Id, + Identity: identity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, importResp.Diagnostics) for _, res := range importResp.ImportedResources { - ty := p.schema.ResourceTypes[res.TypeName].Body.ImpliedType() + importSchema := p.schema.ResourceTypes[res.TypeName] + ty := importSchema.Body.ImpliedType() state, err := encodeDynamicValue6(res.State, ty) if err != nil { resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) continue } - - resp.ImportedResources = append(resp.ImportedResources, &tfplugin6.ImportResourceState_ImportedResource{ + importedResource := &tfplugin6.ImportResourceState_ImportedResource{ TypeName: res.TypeName, State: state, Private: res.Private, - }) + } + if !res.Identity.IsNull() { + if importSchema.Identity == nil { + return nil, fmt.Errorf("identity schema not found for type %s", res.TypeName) + } + + identity, err := encodeDynamicValue6(res.Identity, importSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + continue + } + + importedResource.Identity = &tfplugin6.ResourceIdentityData{ + IdentityData: identity, + } + } + + resp.ImportedResources = append(resp.ImportedResources, importedResource) } return resp, nil @@ -400,6 +522,12 @@ func (p *provider6) ImportResourceState(_ context.Context, req *tfplugin6.Import func (p *provider6) MoveResourceState(_ context.Context, request *tfplugin6.MoveResourceState_Request) (*tfplugin6.MoveResourceState_Response, error) { resp := &tfplugin6.MoveResourceState_Response{} + var sourceIdentity []byte + var err error + if request.SourceIdentity != nil && len(request.SourceIdentity.Json) > 0 { + sourceIdentity = request.SourceIdentity.Json + } + moveResp := p.provider.MoveResourceState(providers.MoveResourceStateRequest{ SourceProviderAddress: request.SourceProviderAddress, SourceTypeName: request.SourceTypeName, @@ -407,18 +535,37 @@ func (p *provider6) MoveResourceState(_ context.Context, request *tfplugin6.Move SourceStateJSON: request.SourceState.Json, SourcePrivate: request.SourcePrivate, TargetTypeName: request.TargetTypeName, + SourceIdentity: sourceIdentity, }) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, moveResp.Diagnostics) if moveResp.Diagnostics.HasErrors() { return resp, nil } - targetType := p.schema.ResourceTypes[request.TargetTypeName].Body.ImpliedType() + targetSchema := p.schema.ResourceTypes[request.TargetTypeName] + targetType := targetSchema.Body.ImpliedType() targetState, err := encodeDynamicValue6(moveResp.TargetState, targetType) if err != nil { resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) return resp, nil } + + if !moveResp.TargetIdentity.IsNull() { + if targetSchema.Identity == nil { + return resp, fmt.Errorf("identity schema not found for type %s", request.TargetTypeName) + } + + targetIdentity, err := encodeDynamicValue6(moveResp.TargetIdentity, targetSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) + return resp, nil + } + + resp.TargetIdentity = &tfplugin6.ResourceIdentityData{ + IdentityData: targetIdentity, + } + } + resp.TargetState = targetState resp.TargetPrivate = moveResp.TargetPrivate return resp, nil diff --git a/internal/plans/changes.go b/internal/plans/changes.go index 25dfe2c273..ff93a06dab 100644 --- a/internal/plans/changes.go +++ b/internal/plans/changes.go @@ -63,8 +63,7 @@ func (c *Changes) Encode(schemas *schemarepo.Schemas) (*ChangesSrc, error) { if schema.Body == nil { return changesSrc, fmt.Errorf("Changes.Encode: missing schema for %s", rc.Addr) } - - rcs, err := rc.Encode(schema.Body.ImpliedType()) + rcs, err := rc.Encode(schema) if err != nil { return changesSrc, fmt.Errorf("Changes.Encode: %w", err) } @@ -280,8 +279,8 @@ type ResourceInstanceChange struct { // Encode produces a variant of the reciever that has its change values // serialized so it can be written to a plan file. Pass the implied type of the // corresponding resource type schema for correct operation. -func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSrc, error) { - cs, err := rc.Change.Encode(ty) +func (rc *ResourceInstanceChange) Encode(schema providers.Schema) (*ResourceInstanceChangeSrc, error) { + cs, err := rc.Change.Encode(&schema) if err != nil { return nil, err } @@ -292,6 +291,7 @@ func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSr prevRunAddr = rc.Addr } return &ResourceInstanceChangeSrc{ + Addr: rc.Addr, PrevRunAddr: prevRunAddr, DeposedKey: rc.DeposedKey, @@ -347,7 +347,9 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Change: Change{ Action: Delete, Before: rc.Before, + BeforeIdentity: rc.BeforeIdentity, After: cty.NullVal(rc.Before.Type()), + AfterIdentity: cty.NullVal(rc.BeforeIdentity.Type()), Importing: rc.Importing, GeneratedConfig: rc.GeneratedConfig, }, @@ -361,7 +363,9 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Change: Change{ Action: NoOp, Before: rc.Before, + BeforeIdentity: rc.BeforeIdentity, After: rc.Before, + AfterIdentity: rc.BeforeIdentity, Importing: rc.Importing, GeneratedConfig: rc.GeneratedConfig, }, @@ -378,7 +382,9 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Change: Change{ Action: NoOp, Before: rc.Before, + BeforeIdentity: rc.BeforeIdentity, After: rc.Before, + AfterIdentity: rc.BeforeIdentity, Importing: rc.Importing, GeneratedConfig: rc.GeneratedConfig, }, @@ -392,7 +398,9 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Change: Change{ Action: Create, Before: cty.NullVal(rc.After.Type()), + BeforeIdentity: cty.NullVal(rc.AfterIdentity.Type()), After: rc.After, + AfterIdentity: rc.AfterIdentity, Importing: rc.Importing, GeneratedConfig: rc.GeneratedConfig, }, @@ -526,7 +534,7 @@ type OutputChange struct { // Encode produces a variant of the reciever that has its change values // serialized so it can be written to a plan file. func (oc *OutputChange) Encode() (*OutputChangeSrc, error) { - cs, err := oc.Change.Encode(cty.DynamicPseudoType) + cs, err := oc.Change.Encode(nil) if err != nil { return nil, err } @@ -584,6 +592,9 @@ type Change struct { // collections/structures. Before, After cty.Value + // Keeping track of how the identity of the resource has changed. + BeforeIdentity, AfterIdentity cty.Value + // Importing is present if the resource is being imported as part of this // change. // @@ -606,7 +617,7 @@ type Change struct { // Where a Change is embedded in some other struct, it's generally better // to call the corresponding Encode method of that struct rather than working // directly with its embedded Change. -func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { +func (c *Change) Encode(schema *providers.Schema) (*ChangeSrc, error) { // We can't serialize value marks directly so we'll need to extract the // sensitive marks and store them in a separate field. // @@ -632,6 +643,11 @@ func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { ) } + ty := cty.DynamicPseudoType + if schema != nil { + ty = schema.Body.ImpliedType() + } + beforeDV, err := NewDynamicValue(unmarkedBefore, ty) if err != nil { return nil, err @@ -641,12 +657,35 @@ func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { return nil, err } + var beforeIdentityDV DynamicValue + var afterIdentityDV DynamicValue + + identityTy := cty.DynamicPseudoType + if schema != nil { + identityTy = schema.Identity.ImpliedType() + } + + if !c.BeforeIdentity.IsNull() { + beforeIdentityDV, err = NewDynamicValue(c.BeforeIdentity, identityTy) + if err != nil { + return nil, err + } + } + + if !c.AfterIdentity.IsNull() { + afterIdentityDV, err = NewDynamicValue(c.AfterIdentity, identityTy) + if err != nil { + return nil, err + } + } return &ChangeSrc{ Action: c.Action, Before: beforeDV, After: afterDV, BeforeSensitivePaths: sensitiveAttrsBefore, AfterSensitivePaths: sensitiveAttrsAfter, + BeforeIdentity: beforeIdentityDV, + AfterIdentity: afterIdentityDV, Importing: c.Importing.Encode(), GeneratedConfig: c.GeneratedConfig, }, nil diff --git a/internal/plans/changes_src.go b/internal/plans/changes_src.go index 98130fec4e..d25cb97de3 100644 --- a/internal/plans/changes_src.go +++ b/internal/plans/changes_src.go @@ -122,7 +122,7 @@ func (c *ChangesSrc) Decode(schemas *schemarepo.Schemas) (*Changes, error) { return nil, fmt.Errorf("ChangesSrc.Decode: missing schema for %s", rcs.Addr) } - rc, err := rcs.Decode(schema.Body.ImpliedType()) + rc, err := rcs.Decode(schema) if err != nil { return nil, err } @@ -232,8 +232,8 @@ func (rcs *ResourceInstanceChangeSrc) ObjectAddr() addrs.AbsResourceInstanceObje // Decode unmarshals the raw representation of the instance object being // changed. Pass the implied type of the corresponding resource type schema // for correct operation. -func (rcs *ResourceInstanceChangeSrc) Decode(ty cty.Type) (*ResourceInstanceChange, error) { - change, err := rcs.ChangeSrc.Decode(ty) +func (rcs *ResourceInstanceChangeSrc) Decode(schema providers.Schema) (*ResourceInstanceChange, error) { + change, err := rcs.ChangeSrc.Decode(&schema) if err != nil { return nil, err } @@ -309,7 +309,7 @@ type OutputChangeSrc struct { // Decode unmarshals the raw representation of the output value being // changed. func (ocs *OutputChangeSrc) Decode() (*OutputChange, error) { - change, err := ocs.ChangeSrc.Decode(cty.DynamicPseudoType) + change, err := ocs.ChangeSrc.Decode(nil) if err != nil { return nil, err } @@ -380,6 +380,11 @@ type ChangeSrc struct { // storage. Before, After DynamicValue + // BeforeIdentity and AfterIdentity correspond to the fields of the same name in Change, + // but have not yet been decoded from the serialized value used for + // storage. + BeforeIdentity, AfterIdentity DynamicValue + // BeforeSensitivePaths and AfterSensitivePaths are the paths for any // values in Before or After (respectively) that are considered to be // sensitive. The sensitive marks are removed from the in-memory values @@ -409,10 +414,20 @@ type ChangeSrc struct { // Where a ChangeSrc is embedded in some other struct, it's generally better // to call the corresponding Decode method of that struct rather than working // directly with its embedded Change. -func (cs *ChangeSrc) Decode(ty cty.Type) (*Change, error) { +func (cs *ChangeSrc) Decode(schema *providers.Schema) (*Change, error) { var err error + + ty := cty.DynamicPseudoType + identityType := cty.DynamicPseudoType + if schema != nil { + ty = schema.Body.ImpliedType() + identityType = schema.Identity.ImpliedType() + } + before := cty.NullVal(ty) + beforeIdentity := cty.NullVal(identityType) after := cty.NullVal(ty) + afterIdentity := cty.NullVal(identityType) if len(cs.Before) > 0 { before, err = cs.Before.Decode(ty) @@ -420,17 +435,31 @@ func (cs *ChangeSrc) Decode(ty cty.Type) (*Change, error) { return nil, fmt.Errorf("error decoding 'before' value: %s", err) } } + if len(cs.BeforeIdentity) > 0 { + beforeIdentity, err = cs.BeforeIdentity.Decode(identityType) + if err != nil { + return nil, fmt.Errorf("error decoding 'beforeIdentity' value: %s", err) + } + } if len(cs.After) > 0 { after, err = cs.After.Decode(ty) if err != nil { return nil, fmt.Errorf("error decoding 'after' value: %s", err) } } + if len(cs.AfterIdentity) > 0 { + afterIdentity, err = cs.AfterIdentity.Decode(identityType) + if err != nil { + return nil, fmt.Errorf("error decoding 'afterIdentity' value: %s", err) + } + } return &Change{ Action: cs.Action, Before: marks.MarkPaths(before, marks.Sensitive, cs.BeforeSensitivePaths), + BeforeIdentity: beforeIdentity, After: marks.MarkPaths(after, marks.Sensitive, cs.AfterSensitivePaths), + AfterIdentity: afterIdentity, Importing: cs.Importing.Decode(), GeneratedConfig: cs.GeneratedConfig, }, nil diff --git a/internal/plans/changes_test.go b/internal/plans/changes_test.go index e400d4064a..b20fd4f045 100644 --- a/internal/plans/changes_test.go +++ b/internal/plans/changes_test.go @@ -7,37 +7,82 @@ import ( "fmt" "testing" + "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang/marks" + "github.com/hashicorp/terraform/internal/providers" "github.com/zclconf/go-cty/cty" ) func TestChangeEncodeSensitive(t *testing.T) { - testVals := []cty.Value{ - cty.ObjectVal(map[string]cty.Value{ - "ding": cty.StringVal("dong").Mark(marks.Sensitive), - }), - cty.StringVal("bleep").Mark(marks.Sensitive), - cty.ListVal([]cty.Value{cty.UnknownVal(cty.String).Mark(marks.Sensitive)}), + testVals := []struct { + val cty.Value + schema providers.Schema + }{ + { + val: cty.ObjectVal(map[string]cty.Value{ + "ding": cty.StringVal("dong").Mark(marks.Sensitive), + }), + schema: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "ding": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + { + val: cty.ObjectVal(map[string]cty.Value{ + "ding": cty.StringVal("bleep").Mark(marks.Sensitive), + }), + schema: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "ding": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + { + val: cty.ObjectVal(map[string]cty.Value{ + "ding": cty.ListVal([]cty.Value{cty.UnknownVal(cty.String).Mark(marks.Sensitive)}), + }), + schema: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "ding": { + Type: cty.List(cty.String), + Required: false, + }, + }, + }, + }, + }, } for _, v := range testVals { t.Run(fmt.Sprintf("%#v", v), func(t *testing.T) { change := Change{ - Before: cty.NullVal(v.Type()), - After: v, + Before: cty.NullVal(v.val.Type()), + After: v.val, } - encoded, err := change.Encode(v.Type()) + encoded, err := change.Encode(&v.schema) if err != nil { t.Fatal(err) } - decoded, err := encoded.Decode(v.Type()) + decoded, err := encoded.Decode(&v.schema) if err != nil { t.Fatal(err) } - if !v.RawEquals(decoded.After) { + if !v.val.RawEquals(decoded.After) { t.Fatalf("%#v != %#v\n", decoded.After, v) } }) diff --git a/internal/plans/deferring.go b/internal/plans/deferring.go index f576e19b9d..826a4ff362 100644 --- a/internal/plans/deferring.go +++ b/internal/plans/deferring.go @@ -5,7 +5,6 @@ package plans import ( "github.com/hashicorp/terraform/internal/providers" - "github.com/zclconf/go-cty/cty" ) // DeferredResourceInstanceChangeSrc tracks information about a resource that @@ -19,8 +18,8 @@ type DeferredResourceInstanceChangeSrc struct { ChangeSrc *ResourceInstanceChangeSrc } -func (rcs *DeferredResourceInstanceChangeSrc) Decode(ty cty.Type) (*DeferredResourceInstanceChange, error) { - change, err := rcs.ChangeSrc.Decode(ty) +func (rcs *DeferredResourceInstanceChangeSrc) Decode(schema providers.Schema) (*DeferredResourceInstanceChange, error) { + change, err := rcs.ChangeSrc.Decode(schema) if err != nil { return nil, err } @@ -42,8 +41,8 @@ type DeferredResourceInstanceChange struct { Change *ResourceInstanceChange } -func (rcs *DeferredResourceInstanceChange) Encode(ty cty.Type) (*DeferredResourceInstanceChangeSrc, error) { - change, err := rcs.Change.Encode(ty) +func (rcs *DeferredResourceInstanceChange) Encode(schema providers.Schema) (*DeferredResourceInstanceChangeSrc, error) { + change, err := rcs.Change.Encode(schema) if err != nil { return nil, err } diff --git a/internal/plans/planfile/tfplan_test.go b/internal/plans/planfile/tfplan_test.go index 53c73661b3..a596864c7d 100644 --- a/internal/plans/planfile/tfplan_test.go +++ b/internal/plans/planfile/tfplan_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/checks" "github.com/hashicorp/terraform/internal/collections" + "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang/globalref" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/providers" @@ -381,6 +382,17 @@ func TestTFPlanRoundTripDestroy(t *testing.T) { "id": cty.String, }) + objSchema := providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Required: true, + }, + }, + }, + } + plan := &plans.Plan{ Changes: &plans.ChangesSrc{ Outputs: []*plans.OutputChangeSrc{ @@ -453,7 +465,7 @@ func TestTFPlanRoundTripDestroy(t *testing.T) { } for _, rics := range newPlan.Changes.Resources { - ric, err := rics.Decode(objTy) + ric, err := rics.Decode(objSchema) if err != nil { t.Fatal(err) } diff --git a/internal/plugin/grpc_provider.go b/internal/plugin/grpc_provider.go index ed916ea3d8..87516f8d94 100644 --- a/internal/plugin/grpc_provider.go +++ b/internal/plugin/grpc_provider.go @@ -510,6 +510,21 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi protoReq.ProviderMeta = &proto.DynamicValue{Msgpack: metaMP} } + if !r.CurrentIdentity.IsNull() { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("identity type not found for resource type %s", r.TypeName)) + return resp + } + currentIdentityMP, err := msgpack.Marshal(r.CurrentIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + protoReq.CurrentIdentity = &proto.ResourceIdentityData{ + IdentityData: &proto.DynamicValue{Msgpack: currentIdentityMP}, + } + } + protoResp, err := p.client.ReadResource(p.ctx, protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) @@ -526,6 +541,18 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi resp.NewState = state resp.Private = protoResp.Private + if protoResp.NewIdentity != nil && protoResp.NewIdentity.IdentityData != nil { + + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %q", r.TypeName)) + } + + resp.Identity, err = decodeDynamicValue(protoResp.NewIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + } + } + return resp } @@ -596,6 +623,21 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) protoReq.ProviderMeta = &proto.DynamicValue{Msgpack: metaMP} } + if !r.PriorIdentity.IsNull() { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("identity type not found for resource type %q", r.TypeName)) + return resp + } + priorIdentityMP, err := msgpack.Marshal(r.PriorIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + protoReq.PriorIdentity = &proto.ResourceIdentityData{ + IdentityData: &proto.DynamicValue{Msgpack: priorIdentityMP}, + } + } + protoResp, err := p.client.PlanResourceChange(p.ctx, protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) @@ -620,6 +662,19 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) resp.Deferred = convert.ProtoToDeferred(protoResp.Deferred) + if protoResp.PlannedIdentity != nil && protoResp.PlannedIdentity.IdentityData != nil { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %s", r.TypeName)) + return resp + } + + resp.PlannedIdentity, err = decodeDynamicValue(protoResp.PlannedIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + } + return resp } @@ -678,6 +733,21 @@ func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques protoReq.ProviderMeta = &proto.DynamicValue{Msgpack: metaMP} } + if !r.PlannedIdentity.IsNull() { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("identity type not found for resource type %s", r.TypeName)) + return resp + } + identityMP, err := msgpack.Marshal(r.PlannedIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + protoReq.PlannedIdentity = &proto.ResourceIdentityData{ + IdentityData: &proto.DynamicValue{Msgpack: identityMP}, + } + } + protoResp, err := p.client.ApplyResourceChange(p.ctx, protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) @@ -696,6 +766,19 @@ func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques resp.LegacyTypeSystem = protoResp.LegacyTypeSystem + if protoResp.NewIdentity != nil && protoResp.NewIdentity.IdentityData != nil { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("identity type not found for resource type %s", r.TypeName)) + return resp + } + newIdentity, err := decodeDynamicValue(protoResp.NewIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resp.NewIdentity = newIdentity + } + return resp } @@ -714,6 +797,26 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques ClientCapabilities: clientCapabilitiesToProto(r.ClientCapabilities), } + if !r.Identity.IsNull() { + resSchema := schema.ResourceTypes[r.TypeName] + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %q", r.TypeName)) + return resp + } + + mp, err := msgpack.Marshal(r.Identity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + + protoReq.Identity = &proto.ResourceIdentityData{ + IdentityData: &proto.DynamicValue{ + Msgpack: mp, + }, + } + } + protoResp, err := p.client.ImportResourceState(p.ctx, protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) @@ -740,6 +843,21 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques return resp } resource.State = state + + if imported.Identity != nil && imported.Identity.IdentityData != nil { + importedIdentitySchema, ok := schema.ResourceTypes[imported.TypeName] + if !ok { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown resource type %q", imported.TypeName)) + continue + } + importedIdentity, err := decodeDynamicValue(imported.Identity.IdentityData, importedIdentitySchema.Body.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resource.Identity = importedIdentity + } + resp.ImportedResources = append(resp.ImportedResources, resource) } @@ -760,6 +878,18 @@ func (p *GRPCProvider) MoveResourceState(r providers.MoveResourceStateRequest) ( TargetTypeName: r.TargetTypeName, } + schema := p.GetProviderSchema() + if schema.Diagnostics.HasErrors() { + resp.Diagnostics = schema.Diagnostics + return resp + } + + if len(r.SourceIdentity) > 0 { + protoReq.SourceIdentity = &proto.RawState{ + Json: r.SourceIdentity, + } + } + protoResp, err := p.client.MoveResourceState(p.ctx, protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) @@ -770,12 +900,6 @@ func (p *GRPCProvider) MoveResourceState(r providers.MoveResourceStateRequest) ( return resp } - schema := p.GetProviderSchema() - if schema.Diagnostics.HasErrors() { - resp.Diagnostics = schema.Diagnostics - return resp - } - targetType, ok := schema.ResourceTypes[r.TargetTypeName] if !ok { // We should have validated this earlier in the process, but we'll @@ -792,6 +916,20 @@ func (p *GRPCProvider) MoveResourceState(r providers.MoveResourceStateRequest) ( resp.TargetPrivate = protoResp.TargetPrivate + if protoResp.TargetIdentity != nil && protoResp.TargetIdentity.IdentityData != nil { + targetResSchema := schema.ResourceTypes[r.TargetTypeName] + + if targetResSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %s", r.TargetTypeName)) + return resp + } + resp.TargetIdentity, err = decodeDynamicValue(protoResp.TargetIdentity.IdentityData, targetResSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + } + return resp } diff --git a/internal/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index d4495f7851..bc094ad2a2 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -485,11 +485,29 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi return resp } + var currentIdentity *proto6.ResourceIdentityData + if !r.CurrentIdentity.IsNull() { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %s", r.TypeName)) + return resp + } + + mp, err := msgpack.Marshal(r.CurrentIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + currentIdentity.IdentityData = &proto6.DynamicValue{ + Msgpack: mp, + } + } + protoReq := &proto6.ReadResource_Request{ TypeName: r.TypeName, CurrentState: &proto6.DynamicValue{Msgpack: mp}, Private: r.Private, ClientCapabilities: clientCapabilitiesToProto(r.ClientCapabilities), + CurrentIdentity: currentIdentity, } if metaSchema.Body != nil { @@ -517,6 +535,17 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi resp.Private = protoResp.Private resp.Deferred = convert.ProtoToDeferred(protoResp.Deferred) + if protoResp.NewIdentity != nil && protoResp.NewIdentity.IdentityData != nil { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %q", r.TypeName)) + } + + resp.Identity, err = decodeDynamicValue(protoResp.NewIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + } + } + return resp } @@ -587,6 +616,25 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) protoReq.ProviderMeta = &proto6.DynamicValue{Msgpack: metaMP} } + if !r.PriorIdentity.IsNull() { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("identity type not found for resource type %s", r.TypeName)) + return resp + } + + mp, err := msgpack.Marshal(r.PriorIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + + protoReq.PriorIdentity = &proto6.ResourceIdentityData{ + IdentityData: &proto6.DynamicValue{ + Msgpack: mp, + }, + } + } + protoResp, err := p.client.PlanResourceChange(p.ctx, protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) @@ -611,6 +659,20 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) resp.Deferred = convert.ProtoToDeferred(protoResp.Deferred) + if protoResp.PlannedIdentity != nil && protoResp.PlannedIdentity.IdentityData != nil { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %s", r.TypeName)) + return resp + } + + plannedIdentity, err := decodeDynamicValue(protoResp.PlannedIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resp.PlannedIdentity = plannedIdentity + } + return resp } @@ -669,6 +731,21 @@ func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques protoReq.ProviderMeta = &proto6.DynamicValue{Msgpack: metaMP} } + if !r.PlannedIdentity.IsNull() { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("identity type not found for resource type %s", r.TypeName)) + return resp + } + plannedIdentityMP, err := msgpack.Marshal(r.PlannedIdentity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + protoReq.PlannedIdentity = &proto6.ResourceIdentityData{ + IdentityData: &proto6.DynamicValue{Msgpack: plannedIdentityMP}, + } + } + protoResp, err := p.client.ApplyResourceChange(p.ctx, protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) @@ -687,6 +764,20 @@ func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques resp.LegacyTypeSystem = protoResp.LegacyTypeSystem + if protoResp.NewIdentity != nil && protoResp.NewIdentity.IdentityData != nil { + + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("identity type not found for resource type %s", r.TypeName)) + return resp + } + newIdentity, err := decodeDynamicValue(protoResp.NewIdentity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resp.NewIdentity = newIdentity + } + return resp } @@ -699,6 +790,12 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques return resp } + resSchema, ok := schema.ResourceTypes[r.TypeName] + if !ok { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("resource type not found: %s", r.TypeName)) + return resp + } + protoReq := &proto6.ImportResourceState_Request{ TypeName: r.TypeName, Id: r.ID, @@ -713,6 +810,22 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) resp.Deferred = convert.ProtoToDeferred(protoResp.Deferred) + if !r.Identity.IsNull() { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %s", r.TypeName)) + return resp + } + + identityMP, err := msgpack.Marshal(r.Identity, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + protoReq.Identity = &proto6.ResourceIdentityData{ + IdentityData: &proto6.DynamicValue{Msgpack: identityMP}, + } + } + for _, imported := range protoResp.ImportedResources { resource := providers.ImportedResource{ TypeName: imported.TypeName, @@ -731,6 +844,21 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques return resp } resource.State = state + + if imported.Identity != nil && imported.Identity.IdentityData != nil { + if resSchema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %s", r.TypeName)) + return resp + } + + identity, err := decodeDynamicValue(imported.Identity.IdentityData, resSchema.Identity.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resource.Identity = identity + } + resp.ImportedResources = append(resp.ImportedResources, resource) } @@ -740,6 +868,13 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques func (p *GRPCProvider) MoveResourceState(r providers.MoveResourceStateRequest) (resp providers.MoveResourceStateResponse) { logger.Trace("GRPCProvider: MoveResourceState") + var sourceIdentity *proto6.RawState + if len(r.SourceIdentity) > 0 { + sourceIdentity = &proto6.RawState{ + Json: r.SourceIdentity, + } + } + protoReq := &proto6.MoveResourceState_Request{ SourceProviderAddress: r.SourceProviderAddress, SourceTypeName: r.SourceTypeName, @@ -748,6 +883,7 @@ func (p *GRPCProvider) MoveResourceState(r providers.MoveResourceStateRequest) ( Json: r.SourceStateJSON, }, SourcePrivate: r.SourcePrivate, + SourceIdentity: sourceIdentity, TargetTypeName: r.TargetTypeName, } @@ -783,6 +919,19 @@ func (p *GRPCProvider) MoveResourceState(r providers.MoveResourceStateRequest) ( resp.TargetPrivate = protoResp.TargetPrivate + if protoResp.TargetIdentity != nil && protoResp.TargetIdentity.IdentityData != nil { + targetIdentitySchema := targetType.Identity + if targetIdentitySchema == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown identity type %s", protoReq.TargetTypeName)) + return resp + } + resp.TargetIdentity, err = decodeDynamicValue(protoResp.TargetIdentity.IdentityData, targetIdentitySchema.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + } + return resp } diff --git a/internal/provider-simple-v6/provider.go b/internal/provider-simple-v6/provider.go index 01186a20fd..065121bbbc 100644 --- a/internal/provider-simple-v6/provider.go +++ b/internal/provider-simple-v6/provider.go @@ -139,6 +139,7 @@ func (s simple) Stop() error { func (s simple) ReadResource(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { // just return the same state we received resp.NewState = req.PriorState + resp.Identity = req.CurrentIdentity return resp } @@ -171,6 +172,7 @@ func (s simple) ApplyResourceChange(req providers.ApplyResourceChangeRequest) (r } resp.NewState = req.PlannedState + resp.NewIdentity = req.PlannedIdentity return resp } diff --git a/internal/provider-simple/provider.go b/internal/provider-simple/provider.go index 2b7e4fc892..db75f392a7 100644 --- a/internal/provider-simple/provider.go +++ b/internal/provider-simple/provider.go @@ -116,6 +116,7 @@ func (s simple) Stop() error { func (s simple) ReadResource(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { // just return the same state we received resp.NewState = req.PriorState + resp.Identity = req.CurrentIdentity return resp } @@ -149,6 +150,7 @@ func (s simple) ApplyResourceChange(req providers.ApplyResourceChangeRequest) (r m["id"] = cty.StringVal(time.Now().String()) } resp.NewState = cty.ObjectVal(m) + resp.NewIdentity = req.PlannedIdentity return resp } diff --git a/internal/providers/mock.go b/internal/providers/mock.go index 324159a161..9e560bf63c 100644 --- a/internal/providers/mock.go +++ b/internal/providers/mock.go @@ -194,6 +194,7 @@ func (m *Mock) ReadResource(request ReadResourceRequest) ReadResourceResponse { // state. So we'll return what we have. return ReadResourceResponse{ NewState: request.PriorState, + Identity: request.CurrentIdentity, } } @@ -287,13 +288,15 @@ func (m *Mock) ApplyResourceChange(request ApplyResourceChangeRequest) ApplyReso value, diags := mocking.ApplyComputedValuesForResource(request.PlannedState, replacement, resource.Body) response.Diagnostics = response.Diagnostics.Append(diags) response.NewState = value + response.NewIdentity = request.PlannedIdentity return response default: // For update or destroy operations, we don't have to create any values // so we'll just return the planned state directly. return ApplyResourceChangeResponse{ - NewState: request.PlannedState, + NewState: request.PlannedState, + NewIdentity: request.PlannedIdentity, } } } diff --git a/internal/providers/provider.go b/internal/providers/provider.go index ac5d22b6cd..6fa57a511a 100644 --- a/internal/providers/provider.go +++ b/internal/providers/provider.go @@ -343,6 +343,9 @@ type ReadResourceRequest struct { // ClientCapabilities contains information about the client's capabilities. ClientCapabilities ClientCapabilities + + // CurrentIdentity is the current identity data of the resource. + CurrentIdentity cty.Value } // DeferredReason is a string that describes why a resource was deferred. @@ -433,6 +436,9 @@ type PlanResourceChangeRequest struct { // ClientCapabilities contains information about the client's capabilities. ClientCapabilities ClientCapabilities + + // PriorIdentity is the current identity data of the resource. + PriorIdentity cty.Value } type PlanResourceChangeResponse struct { @@ -462,6 +468,9 @@ type PlanResourceChangeResponse struct { // Deferred if present signals that the provider was not able to fully // complete this operation and a susequent run is required. Deferred *Deferred + + // PlannedIdentity is the planned identity data of the resource. + PlannedIdentity cty.Value } type ApplyResourceChangeRequest struct { @@ -489,6 +498,9 @@ type ApplyResourceChangeRequest struct { // each provider, and it should not be used without coordination with // HashiCorp. It is considered experimental and subject to change. ProviderMeta cty.Value + + // PlannedIdentity is the planned identity data of the resource. + PlannedIdentity cty.Value } type ApplyResourceChangeResponse struct { @@ -510,6 +522,9 @@ type ApplyResourceChangeResponse struct { // otherwise fail due to this imprecise mapping. No other provider or SDK // implementation is permitted to set this. LegacyTypeSystem bool + + // NewIdentity is the new identity data of the resource. + NewIdentity cty.Value } type ImportResourceStateRequest struct { @@ -522,6 +537,9 @@ type ImportResourceStateRequest struct { // ClientCapabilities contains information about the client's capabilities. ClientCapabilities ClientCapabilities + + // Identity is the identity data of the resource. + Identity cty.Value } type ImportResourceStateResponse struct { @@ -556,6 +574,9 @@ type ImportedResource struct { // Private is an opaque blob that will be stored in state along with the // resource. It is intended only for interpretation by the provider itself. Private []byte + + // Identity is the identity data of the resource. + Identity cty.Value } type MoveResourceStateRequest struct { @@ -583,6 +604,9 @@ type MoveResourceStateRequest struct { // TargetTypeName is the name of the resource type that the resource is // being moved to. TargetTypeName string + + // SourceIdentity is the identity data of the resource that is being moved. + SourceIdentity []byte } type MoveResourceStateResponse struct { @@ -596,6 +620,9 @@ type MoveResourceStateResponse struct { // Diagnostics contains any warnings or errors from the method call. Diagnostics tfdiags.Diagnostics + + // TargetIdentity is the identity data of the resource that is being moved. + TargetIdentity cty.Value } type ReadDataSourceRequest struct { diff --git a/internal/providers/testing/provider_mock.go b/internal/providers/testing/provider_mock.go index 1e83280f9d..099ff83efb 100644 --- a/internal/providers/testing/provider_mock.go +++ b/internal/providers/testing/provider_mock.go @@ -352,13 +352,14 @@ func (p *MockProvider) UpgradeResourceIdentity(r providers.UpgradeResourceIdenti return *p.UpgradeResourceIdentityResponse } - identitySchema, ok := p.getResourceIdentitySchemas().IdentityTypes[r.TypeName] - if !ok { - resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName)) + schema, ok := p.getProviderSchema().ResourceTypes[r.TypeName] + + if !ok || schema.Identity == nil { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no identity schema found for %q", r.TypeName)) return resp } - identityType := identitySchema.Body.ImpliedType() + identityType := schema.Identity.ImpliedType() v, err := ctyjson.Unmarshal(r.RawIdentityJSON, identityType) @@ -435,6 +436,10 @@ func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) (resp provi resp.Diagnostics = resp.Diagnostics.Append(err) } resp.NewState = newState + if resp.Identity.IsNull() { + resp.Identity = r.CurrentIdentity + } + return resp } @@ -470,6 +475,7 @@ func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) (resp provi resp.NewState = r.PriorState } + resp.Identity = r.CurrentIdentity resp.Private = r.Private return resp } @@ -587,6 +593,7 @@ func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques // if the value is nil, we return that directly to correspond to a delete if r.PlannedState.IsNull() { resp.NewState = r.PlannedState + resp.NewIdentity = r.PlannedIdentity return resp } @@ -616,6 +623,7 @@ func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques resp.NewState = val resp.Private = r.PlannedPrivate + resp.NewIdentity = r.PlannedIdentity return resp } diff --git a/internal/refactoring/cross_provider_move.go b/internal/refactoring/cross_provider_move.go index 1d60aa9c1a..097cc3832a 100644 --- a/internal/refactoring/cross_provider_move.go +++ b/internal/refactoring/cross_provider_move.go @@ -99,11 +99,11 @@ func (m *crossTypeMover) prepareCrossTypeMove(stmt *MoveStatement, source, targe }) return nil, diags } - schema := targetSchema.SchemaForResourceAddr(target.Resource) + targetResourceSchema := targetSchema.SchemaForResourceAddr(target.Resource) return &crossTypeMove{ targetProvider: targetProvider, targetProviderAddr: *targetProviderAddr, - targetResourceSchema: schema, + targetResourceSchema: targetResourceSchema, sourceProviderAddr: sourceProviderAddr, }, diags } @@ -128,9 +128,14 @@ func (move *crossTypeMove) applyCrossTypeMove(stmt *MoveStatement, source, targe var diags tfdiags.Diagnostics - // First, build the request. - + var sourceIdentity []byte src := state.ResourceInstance(source).Current + if src != nil { + sourceIdentity = src.IdentityJSON + } + + // Build the request. + request := providers.MoveResourceStateRequest{ SourceProviderAddress: move.sourceProviderAddr.Provider.String(), SourceTypeName: source.Resource.Resource.Type, @@ -138,9 +143,10 @@ func (move *crossTypeMove) applyCrossTypeMove(stmt *MoveStatement, source, targe SourceStateJSON: src.AttrsJSON, SourcePrivate: src.Private, TargetTypeName: target.Resource.Resource.Type, + SourceIdentity: sourceIdentity, } - // Second, ask the provider to transform the value into the type expected by + // Ask the provider to transform the value into the type expected by // the new resource type. resp := move.targetProvider.MoveResourceState(request) @@ -167,6 +173,32 @@ func (move *crossTypeMove) applyCrossTypeMove(stmt *MoveStatement, source, targe return diags } + if !resp.TargetIdentity.IsNull() { + // Identities can not contain unknown values + if !resp.TargetIdentity.IsWhollyKnown() { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Provider produced invalid identity", + fmt.Sprintf( + "Provider %q planned an identity with unknown values for the move from %s to %s. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", + move.targetProviderAddr, source, target, + ), + )) + } + + // Identities can not contain marks + if _, marks := resp.TargetIdentity.UnmarkDeep(); len(marks) > 0 { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Provider produced invalid identity", + fmt.Sprintf( + "Provider %q planned an identity with marks for the move from %s to %s. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", + move.targetProviderAddr, source, target, + ), + )) + } + } + if resp.TargetState == cty.NilVal { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, @@ -194,9 +226,9 @@ func (move *crossTypeMove) applyCrossTypeMove(stmt *MoveStatement, source, targe Status: src.Status, Dependencies: src.Dependencies, CreateBeforeDestroy: src.CreateBeforeDestroy, + Identity: resp.TargetIdentity, } - // TODO: We need to handle identity data in move scenarios. data, err := newValue.Encode(move.targetResourceSchema) if err != nil { diags = diags.Append(&hcl.Diagnostic{ diff --git a/internal/stacks/stackruntime/internal/stackeval/planning_test.go b/internal/stacks/stackruntime/internal/stackeval/planning_test.go index 40dc736129..38b1ec1fe4 100644 --- a/internal/stacks/stackruntime/internal/stackeval/planning_test.go +++ b/internal/stacks/stackruntime/internal/stackeval/planning_test.go @@ -271,7 +271,9 @@ func TestPlanning_DestroyMode(t *testing.T) { if planSrc := aPlan.Changes.ResourceInstance(aResourceInstAddr.Item); planSrc != nil { rAddr := aResourceInstAddr - plan, err := planSrc.Decode(resourceTypeSchema.ImpliedType()) + plan, err := planSrc.Decode(providers.Schema{ + Body: resourceTypeSchema, + }) if err != nil { t.Fatalf("can't decode change for %s: %s", rAddr, err) } @@ -295,7 +297,9 @@ func TestPlanning_DestroyMode(t *testing.T) { if planSrc := bPlan.Changes.ResourceInstance(bResourceInstAddr.Item); planSrc != nil { rAddr := bResourceInstAddr - plan, err := planSrc.Decode(resourceTypeSchema.ImpliedType()) + plan, err := planSrc.Decode(providers.Schema{ + Body: resourceTypeSchema, + }) if err != nil { t.Fatalf("can't decode change for %s: %s", rAddr, err) } diff --git a/internal/states/instance_object.go b/internal/states/instance_object.go index 8631377ae1..ece716058d 100644 --- a/internal/states/instance_object.go +++ b/internal/states/instance_object.go @@ -142,7 +142,8 @@ func (o *ResourceInstanceObject) Encode(schema providers.Schema) (*ResourceInsta } var idJSON []byte - if !o.Identity.IsNull() && schema.Identity != nil { + // If the Identity is known and not null we can marshal it. + if !o.Identity.IsNull() && o.Identity.IsWhollyKnown() && schema.Identity != nil { idJSON, err = ctyjson.Marshal(o.Identity, schema.Identity.ImpliedType()) if err != nil { return nil, err diff --git a/internal/states/instance_object_src.go b/internal/states/instance_object_src.go index a108829010..49600d9552 100644 --- a/internal/states/instance_object_src.go +++ b/internal/states/instance_object_src.go @@ -120,7 +120,7 @@ func (os *ResourceInstanceObjectSrc) Decode(schema providers.Schema) (*ResourceI } else if os.IdentityJSON != nil { identity, err = ctyjson.Unmarshal(os.IdentityJSON, schema.Identity.ImpliedType()) if err != nil { - return nil, fmt.Errorf("failed to decode identity schema: %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()) + 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/terraform/context_apply_identity_test.go b/internal/terraform/context_apply_identity_test.go new file mode 100644 index 0000000000..3919894f2c --- /dev/null +++ b/internal/terraform/context_apply_identity_test.go @@ -0,0 +1,189 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package terraform + +import ( + "testing" + + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/plans" + "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/states" +) + +func TestContext2Apply_identity(t *testing.T) { + for name, tc := range map[string]struct { + mode plans.Mode + prevRunState *states.State + requiresReplace []cty.Path + plannedIdentity cty.Value + + expectedIdentity cty.Value + }{ + "create": { + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo"), + }), + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo"), + }), + }, + + "update": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + }, + + "delete": { + mode: plans.DestroyMode, + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"bar"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + plannedIdentity: cty.NilVal, + expectedIdentity: cty.NullVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + })), + }, + "replace": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"foo"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + requiresReplace: []cty.Path{cty.GetAttrPath("id")}, + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + }, + } { + t.Run(name, func(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` + resource "test_resource" "test" { + id = "bar" + } + `, + }) + p := testProvider("test") + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_resource": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Optional: true, + }, + }, + }, + }, + IdentityTypes: map[string]*configschema.Object{ + "test_resource": &configschema.Object{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Required: true, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + IdentityTypeSchemaVersions: map[string]uint64{ + "test_resource": 0, + }, + }) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { + return providers.PlanResourceChangeResponse{ + PlannedState: req.ProposedNewState, + PlannedIdentity: tc.plannedIdentity, + RequiresReplace: tc.requiresReplace, + } + } + + plan, diags := ctx.Plan(m, tc.prevRunState, &PlanOpts{Mode: tc.mode}) + assertNoDiagnostics(t, diags) + + state, diags := ctx.Apply(plan, m, nil) + assertNoDiagnostics(t, diags) + + if !tc.expectedIdentity.IsNull() { + schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"] + + resourceInstanceStateSrc := state.Modules[""].Resources["test_resource.test"].Instance(addrs.NoKey).Current + + resourceInstanceState, err := resourceInstanceStateSrc.Decode(schema) + if err != nil { + t.Fatalf("failed to decode resource instance state: %s", err) + } + + if !resourceInstanceState.Identity.RawEquals(tc.expectedIdentity) { + t.Fatalf("unexpected identity: \n expected: %s\n got: %s", tc.expectedIdentity.GoString(), resourceInstanceState.Identity.GoString()) + } + } + }) + } +} diff --git a/internal/terraform/context_apply_test.go b/internal/terraform/context_apply_test.go index aeeb0eb39a..43396f7a7d 100644 --- a/internal/terraform/context_apply_test.go +++ b/internal/terraform/context_apply_test.go @@ -275,7 +275,7 @@ func TestContext2Apply_unstable(t *testing.T) { }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"] rds := plan.Changes.ResourceInstance(addr) - rd, err := rds.Decode(schema.Body.ImpliedType()) + rd, err := rds.Decode(schema) if err != nil { t.Fatal(err) } diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index e2104c2662..46645eacea 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -869,8 +869,7 @@ func (c *Context) deferredResources(config *configs.Config, deferrals []*plans.D deferral.Change.Addr.Resource.Resource.Mode, deferral.Change.Addr.Resource.Resource.Type) - ty := schema.Body.ImpliedType() - deferralSrc, err := deferral.Encode(ty) + deferralSrc, err := deferral.Encode(schema) if err != nil { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, @@ -1084,7 +1083,7 @@ func (c *Context) driftedResources(config *configs.Config, oldState, newState *s }, } - changeSrc, err := change.Encode(ty) + changeSrc, err := change.Encode(schema) if err != nil { diags = diags.Append(err) return nil, diags diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index 66817a4bf3..9788061c18 100644 --- a/internal/terraform/context_plan2_test.go +++ b/internal/terraform/context_plan2_test.go @@ -1770,6 +1770,104 @@ The -target option is not for routine use, and is provided only for exceptional }) } +func TestContext2Plan_movedResourceWithIdentity(t *testing.T) { + addrA := mustResourceInstanceAddr("test_object.a") + addrB := mustResourceInstanceAddr("test_object.b") + m := testModuleInline(t, map[string]string{ + "main.tf": ` + resource "test_object" "b" { + } + + moved { + from = test_object.a + to = test_object.b + } + `, + }) + + state := states.BuildState(func(s *states.SyncState) { + // The prior state tracks test_object.a, which we should treat as + // test_object.b because of the "moved" block in the config. + s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{}`), + Status: states.ObjectReady, + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id": "before"}`), + }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) + }) + + p := simpleMockProvider() + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_object": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Optional: true, + }, + }, + }, + }, + IdentityTypes: map[string]*configschema.Object{ + "test_object": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Required: true, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + IdentityTypeSchemaVersions: map[string]uint64{ + "test_object": 0, + }, + }) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + plan, diags := ctx.Plan(m, state, &PlanOpts{ + Mode: plans.NormalMode, + }) + if diags.HasErrors() { + t.Fatalf("unexpected errors\n%s", diags.Err().Error()) + } + + t.Run(addrA.String(), func(t *testing.T) { + instPlan := plan.Changes.ResourceInstance(addrA) + if instPlan != nil { + t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB) + } + }) + t.Run(addrB.String(), func(t *testing.T) { + instPlan := plan.Changes.ResourceInstance(addrB) + if instPlan == nil { + t.Fatalf("no plan for %s at all", addrB) + } + + beforeIdentity, err := instPlan.BeforeIdentity.Decode(cty.Object(map[string]cty.Type{ + "id": cty.String, + })) + if err != nil { + t.Fatalf("unexpected error decoding before identity: %s", err) + } + + if beforeIdentity.IsNull() { + t.Fatalf("after identity is null") + } + expectedIdentity := cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("before"), + }) + if !beforeIdentity.RawEquals(expectedIdentity) { + t.Fatalf("after identity doesn't match expected: expected %s, got %s", expectedIdentity.GoString(), beforeIdentity.GoString()) + } + }) +} + func TestContext2Plan_crossResourceMoveBasic(t *testing.T) { addrA := mustResourceInstanceAddr("test_object_one.a") addrB := mustResourceInstanceAddr("test_object_two.a") @@ -1973,6 +2071,156 @@ func TestContext2Plan_crossProviderMove(t *testing.T) { }) } +func TestContext2Plan_crossProviderMoveWithIdentity(t *testing.T) { + addrA := mustResourceInstanceAddr("one_object.a") + addrB := mustResourceInstanceAddr("two_object.a") + m := testModuleInline(t, map[string]string{ + "main.tf": ` + resource "two_object" "a" { + } + + moved { + from = one_object.a + to = two_object.a + } + `, + }) + + state := states.BuildState(func(s *states.SyncState) { + // The prior state tracks test_object.a, which we should treat as + // test_object.b because of the "moved" block in the config. + s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"value":"before"}`), + Status: states.ObjectReady, + IdentityJSON: []byte(`{"id":"42"}`), + }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/one"]`)) + }) + + one := &testing_provider.MockProvider{} + one.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ + ResourceTypes: map[string]providers.Schema{ + "one_object": { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + Identity: &configschema.Object{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Required: true, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + }, + } + + two := &testing_provider.MockProvider{} + two.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ + ResourceTypes: map[string]providers.Schema{ + "two_object": { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + Identity: &configschema.Object{ + Attributes: map[string]*configschema.Attribute{ + "arn": { + Type: cty.String, + Required: true, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + }, + ServerCapabilities: providers.ServerCapabilities{ + MoveResourceState: true, + }, + } + + expectedTargetIdentity := cty.ObjectVal(map[string]cty.Value{ + "arn": cty.StringVal("arn:4223"), + }) + + var receivedSourceIdentity []byte + two.MoveResourceStateFn = func(req providers.MoveResourceStateRequest) providers.MoveResourceStateResponse { + receivedSourceIdentity = req.SourceIdentity + return providers.MoveResourceStateResponse{ + TargetState: cty.ObjectVal(map[string]cty.Value{ + "value": cty.StringVal("after"), + }), + TargetIdentity: expectedTargetIdentity, + } + } + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("one"): testProviderFuncFixed(one), + addrs.NewDefaultProvider("two"): testProviderFuncFixed(two), + }, + }) + + plan, diags := ctx.Plan(m, state, &PlanOpts{ + Mode: plans.NormalMode, + }) + if diags.HasErrors() { + t.Fatalf("unexpected errors\n%s", diags.Err().Error()) + } + + t.Run(addrA.String(), func(t *testing.T) { + instPlan := plan.Changes.ResourceInstance(addrA) + if instPlan != nil { + t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB) + } + + expectedSourceIdentity := `{"id":"42"}` + if string(receivedSourceIdentity) != string(expectedSourceIdentity) { + t.Errorf("wrong source identity\ngot: %s\nwant: %s", string(receivedSourceIdentity), string(expectedSourceIdentity)) + } + }) + t.Run(addrB.String(), func(t *testing.T) { + instPlan := plan.Changes.ResourceInstance(addrB) + if instPlan == nil { + t.Fatalf("no plan for %s at all", addrB) + } + + if got, want := instPlan.Addr, addrB; !got.Equal(want) { + t.Errorf("wrong current address\ngot: %s\nwant: %s", got, want) + } + if got, want := instPlan.PrevRunAddr, addrA; !got.Equal(want) { + t.Errorf("wrong previous run address\ngot: %s\nwant: %s", got, want) + } + if got, want := instPlan.Action, plans.Update; got != want { + t.Errorf("wrong planned action\ngot: %s\nwant: %s", got, want) + } + if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { + t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) + } + + targetIdentity, err := instPlan.BeforeIdentity.Decode(cty.Object(map[string]cty.Type{ + "arn": cty.String, + })) + if err != nil { + t.Fatalf("failed to decode after identity: %s", err) + } + + if !targetIdentity.RawEquals(expectedTargetIdentity) { + t.Errorf("wrong target identity\ngot: %s\nwant: %s", targetIdentity.GoString(), expectedTargetIdentity.GoString()) + } + }) +} + func TestContext2Plan_crossResourceMoveMissingConfig(t *testing.T) { addrA := mustResourceInstanceAddr("test_object_one.a") addrB := mustResourceInstanceAddr("test_object_two.a") @@ -2066,6 +2314,138 @@ func TestContext2Plan_crossResourceMoveMissingConfig(t *testing.T) { }) } +func TestContext2Plan_crossResourceMoveWithIdentity(t *testing.T) { + addrA := mustResourceInstanceAddr("test_object_one.a") + addrB := mustResourceInstanceAddr("test_object_two.a") + m := testModuleInline(t, map[string]string{ + "main.tf": ` + resource "test_object_two" "a" { + } + + moved { + from = test_object_one.a + to = test_object_two.a + } + `, + }) + + state := states.BuildState(func(s *states.SyncState) { + // The prior state tracks test_object.a, which we should treat as + // test_object.b because of the "moved" block in the config. + s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"value":"before"}`), + Status: states.ObjectReady, + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"oneId": "before"}`), + }, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`)) + }) + + expectedSourceIdentity := `{"oneId": "before"}` + expectedTargetIdentity := cty.ObjectVal(map[string]cty.Value{ + "twoId": cty.StringVal("after"), + }) + + p := &testing_provider.MockProvider{} + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_object_one": &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + "test_object_two": &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + }, + IdentityTypes: map[string]*configschema.Object{ + "test_object_one": { + Attributes: map[string]*configschema.Attribute{ + "oneId": { + Type: cty.String, + Required: true, + }, + }, + Nesting: configschema.NestingSingle, + }, + + "test_object_two": { + Attributes: map[string]*configschema.Attribute{ + "twoId": { + Type: cty.String, + Required: true, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + IdentityTypeSchemaVersions: map[string]uint64{ + "test_object_one": 0, + "test_object_two": 2, + }, + }) + p.GetProviderSchemaResponse.ServerCapabilities = providers.ServerCapabilities{ + MoveResourceState: true, + } + + var sourceIdentity []byte + p.MoveResourceStateFn = func(req providers.MoveResourceStateRequest) providers.MoveResourceStateResponse { + sourceIdentity = req.SourceIdentity + return providers.MoveResourceStateResponse{ + TargetState: cty.ObjectVal(map[string]cty.Value{ + "value": cty.StringVal("after"), + }), + TargetIdentity: expectedTargetIdentity, + } + } + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + plan, diags := ctx.Plan(m, state, &PlanOpts{ + Mode: plans.NormalMode, + }) + assertNoDiagnostics(t, diags) + + if string(expectedSourceIdentity) != string(sourceIdentity) { + t.Fatalf("unexpected source identity; expected %s, got %s", string(expectedSourceIdentity), string(sourceIdentity)) + } + + t.Run(addrA.String(), func(t *testing.T) { + instPlan := plan.Changes.ResourceInstance(addrA) + if instPlan != nil { + t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB) + } + }) + t.Run(addrB.String(), func(t *testing.T) { + instPlan := plan.Changes.ResourceInstance(addrB) + if instPlan == nil { + t.Fatalf("no plan for %s at all", addrB) + } + + identity, err := instPlan.BeforeIdentity.Decode(cty.Object(map[string]cty.Type{ + "twoId": cty.String, + })) + if err != nil { + t.Fatalf("unexpected error decoding identity: %s", err) + } + + if !identity.RawEquals(expectedTargetIdentity) { + t.Fatalf("unexpected target identity; expected %s, got %s", expectedTargetIdentity.GoString(), identity.GoString()) + } + }) +} + func TestContext2Plan_untargetedResourceSchemaChange(t *testing.T) { // an untargeted resource which requires a schema migration should not // block planning due external changes in the plan. @@ -5894,9 +6274,11 @@ resource "test_object" "obj" { Before: cty.ObjectVal(map[string]cty.Value{ "value": cty.NullVal(cty.String), }), + BeforeIdentity: cty.NullVal(cty.EmptyObject), After: cty.ObjectVal(map[string]cty.Value{ "value": cty.NullVal(cty.String), }), + AfterIdentity: cty.NullVal(cty.EmptyObject), }, ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, RequiredReplace: cty.NewPathSet(cty.GetAttrPath("value")), @@ -5911,6 +6293,7 @@ resource "test_object" "obj" { if err != nil { t.Fatal(err) } + if diff := cmp.Diff(expectedChanges, changes, ctydebug.CmpOptions); diff != "" { t.Fatalf("unexpected changes: %s", diff) } @@ -5989,9 +6372,11 @@ resource "test_object" "obj" { Before: cty.ObjectVal(map[string]cty.Value{ "value": cty.NullVal(cty.String), }), + BeforeIdentity: cty.NullVal(cty.EmptyObject), After: cty.ObjectVal(map[string]cty.Value{ "value": cty.NullVal(cty.String), }), + AfterIdentity: cty.NullVal(cty.EmptyObject), }, ActionReason: plans.ResourceInstanceReplaceBecauseCannotUpdate, RequiredReplace: cty.NewPathSet(cty.GetAttrPath("value")), @@ -6163,17 +6548,21 @@ output "staying" { { Addr: mustAbsOutputValue("output.old"), Change: plans.Change{ - Action: plans.Delete, - Before: cty.StringVal("old_value"), - After: cty.NullVal(cty.DynamicPseudoType), + Action: plans.Delete, + Before: cty.StringVal("old_value"), + BeforeIdentity: cty.NullVal(cty.DynamicPseudoType), + After: cty.NullVal(cty.DynamicPseudoType), + AfterIdentity: cty.NullVal(cty.DynamicPseudoType), }, }, { Addr: mustAbsOutputValue("output.staying"), Change: plans.Change{ - Action: plans.NoOp, - Before: cty.StringVal("foo"), - After: cty.StringVal("foo"), + Action: plans.NoOp, + Before: cty.StringVal("foo"), + BeforeIdentity: cty.NullVal(cty.DynamicPseudoType), + After: cty.StringVal("foo"), + AfterIdentity: cty.NullVal(cty.DynamicPseudoType), }, }, }, @@ -6343,7 +6732,7 @@ resource "test_object" "a" { plan, diags := ctx.Plan(m, state, DefaultPlanOpts) assertNoErrors(t, diags) - resourceType := p.GetProviderSchemaResponse.ResourceTypes["test_object"].Body.ImpliedType() + resourceType := p.GetProviderSchemaResponse.ResourceTypes["test_object"] change, err := plan.Changes.ResourceInstance(mustResourceInstanceAddr(`test_object.a["old"]`)).Decode(resourceType) if err != nil { t.Fatal(err) diff --git a/internal/terraform/context_plan_identity_test.go b/internal/terraform/context_plan_identity_test.go index cafc3531fe..d5e036ea90 100644 --- a/internal/terraform/context_plan_identity_test.go +++ b/internal/terraform/context_plan_identity_test.go @@ -92,7 +92,7 @@ func TestContext2Plan_resource_identity_refresh(t *testing.T) { ExpectedIdentity: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("foo"), }), - ExpectedError: fmt.Errorf("failed to decode identity schema: unsupported attribute \"arn\". This is most likely a bug in the Provider, providers must not change the identity schema without updating the identity schema version"), + ExpectedError: fmt.Errorf("failed to decode identity: unsupported attribute \"arn\". This is most likely a bug in the Provider, providers must not change the identity schema without updating the identity schema version"), }, "identity upgrade succeeds": { StoredIdentitySchemaVersion: 1, @@ -173,7 +173,7 @@ func TestContext2Plan_resource_identity_refresh(t *testing.T) { ExpectedIdentity: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("foo"), }), - ExpectedError: fmt.Errorf("Provider produced different identity: Provider \"registry.terraform.io/hashicorp/aws\" planned an different identity for aws_instance.web during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), + ExpectedError: fmt.Errorf("Provider produced different identity: Provider \"registry.terraform.io/hashicorp/aws\" returned a different identity for aws_instance.web than the previously stored one. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), }, "identity with unknowns": { IdentitySchema: providers.IdentitySchema{ @@ -191,7 +191,7 @@ func TestContext2Plan_resource_identity_refresh(t *testing.T) { IdentityData: cty.ObjectVal(map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), - ExpectedError: fmt.Errorf("Provider produced invalid identity: Provider \"registry.terraform.io/hashicorp/aws\" planned an identity with unknown values for aws_instance.web during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), + ExpectedError: fmt.Errorf("Provider produced invalid identity: Provider \"registry.terraform.io/hashicorp/aws\" returned an identity with unknown values for aws_instance.web. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), }, "identity with marks": { @@ -210,7 +210,7 @@ func TestContext2Plan_resource_identity_refresh(t *testing.T) { IdentityData: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("marked value").Mark(marks.Sensitive), }), - ExpectedError: fmt.Errorf("Provider produced invalid identity: Provider \"registry.terraform.io/hashicorp/aws\" planned an identity with marks for aws_instance.web during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), + ExpectedError: fmt.Errorf("Provider produced invalid identity: Provider \"registry.terraform.io/hashicorp/aws\" returned an identity with marks for aws_instance.web. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), }, } { t.Run(name, func(t *testing.T) { @@ -261,17 +261,13 @@ func TestContext2Plan_resource_identity_refresh(t *testing.T) { }) schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] - ty := schema.Body.ImpliedType() - readState, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"id": "foo", "foo": "baz"}, ty) - if err != nil { - t.Fatal(err) - } - p.ReadResourceResponse = &providers.ReadResourceResponse{ - NewState: readState, - Identity: tc.IdentityData, + p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { + return providers.ReadResourceResponse{ + NewState: req.PriorState, + Identity: tc.IdentityData, + } } - p.UpgradeResourceIdentityResponse = &tc.UpgradeResourceIdentityResponse s, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.RefreshOnlyMode}) @@ -306,15 +302,6 @@ func TestContext2Plan_resource_identity_refresh(t *testing.T) { t.Fatal(err) } - newState, err := schema.Body.CoerceValue(fromState.Value) - if err != nil { - t.Fatal(err) - } - - if !cmp.Equal(readState, newState, valueComparer) { - t.Fatal(cmp.Diff(readState, newState, valueComparer, equateEmpty)) - } - if tc.ExpectedIdentity.Equals(fromState.Identity).False() { t.Fatalf("wrong identity\nwant: %s\ngot: %s", tc.ExpectedIdentity.GoString(), fromState.Identity.GoString()) } @@ -425,3 +412,399 @@ func TestContext2Plan_resource_identity_refresh_destroy_deposed(t *testing.T) { } } + +func TestContext2Plan_resource_identity_plan(t *testing.T) { + for name, tc := range map[string]struct { + mode plans.Mode + prevRunState *states.State + requiresReplace []cty.Path + identitySchemaVersion int64 + + readResourceIdentity cty.Value + upgradedIdentity cty.Value + + plannedIdentity cty.Value + expectedIdentity cty.Value + expectedPriorIdentity cty.Value + + expectDiagnostics tfdiags.Diagnostics + }{ + "create": { + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo"), + }), + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo"), + }), + }, + "update": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"bar"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + }, + "delete": { + mode: plans.DestroyMode, + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"bar"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + plannedIdentity: cty.NilVal, + expectedIdentity: cty.NullVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + })), + }, + "replace": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"foo"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + requiresReplace: []cty.Path{cty.GetAttrPath("id")}, + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + }, + + "update - changing identity": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"bar"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("foo"), + }), + + expectDiagnostics: tfdiags.Diagnostics{ + tfdiags.Sourceless(tfdiags.Error, "Provider produced different identity", "Provider \"registry.terraform.io/hashicorp/test\" returned a different identity for test_resource.test than the previously stored one. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), + }, + }, + + "update - updating identity schema version": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"foo"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + upgradedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + identitySchemaVersion: 1, + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + }, + + "update - downgrading identity schema version": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + IdentitySchemaVersion: 2, + IdentityJSON: []byte(`{"id":"foo"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "arn": cty.StringVal("arn:foo"), + }), + identitySchemaVersion: 1, + expectDiagnostics: tfdiags.Diagnostics{ + tfdiags.Sourceless(tfdiags.Error, "Resource instance managed by newer provider version", "The current state of test_resource.test was created by a newer provider version than is currently selected. Upgrade the test provider to work with this state."), + }, + }, + + "read and update": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + readResourceIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + plannedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + + expectedPriorIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + expectedIdentity: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + }, + "create with unknown identity": { + plannedIdentity: cty.UnknownVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + })), + expectedIdentity: cty.UnknownVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + })), + }, + "update with unknown identity": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"bar"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"bar"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + plannedIdentity: cty.UnknownVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + })), + + expectDiagnostics: tfdiags.Diagnostics{ + tfdiags.Sourceless(tfdiags.Error, "Provider produced invalid identity", "Provider \"registry.terraform.io/hashicorp/test\" returned an identity with unknown values for test_resource.test. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."), + }, + }, + "replace with unknown identity": { + prevRunState: states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_resource", + Name: "test", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"id":"foo"}`), + IdentitySchemaVersion: 0, + IdentityJSON: []byte(`{"id":"foo"}`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }), + requiresReplace: []cty.Path{cty.GetAttrPath("id")}, + plannedIdentity: cty.UnknownVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + })), + + expectedIdentity: cty.UnknownVal(cty.Object(map[string]cty.Type{ + "id": cty.String, + })), + }, + } { + t.Run(name, func(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` + resource "test_resource" "test" { + id = "newValue" + } + `, + }) + p := testProvider("test") + p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_resource": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Optional: true, + }, + }, + }, + }, + IdentityTypes: map[string]*configschema.Object{ + "test_resource": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Required: true, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + IdentityTypeSchemaVersions: map[string]uint64{ + "test_resource": uint64(tc.identitySchemaVersion), + }, + }) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { + identity := req.CurrentIdentity + if !tc.readResourceIdentity.IsNull() { + identity = tc.readResourceIdentity + } + + return providers.ReadResourceResponse{ + NewState: req.PriorState, + Identity: identity, + } + } + var plannedPriorIdentity cty.Value + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { + plannedPriorIdentity = req.PriorIdentity + return providers.PlanResourceChangeResponse{ + PlannedState: req.ProposedNewState, + PlannedIdentity: tc.plannedIdentity, + RequiresReplace: tc.requiresReplace, + } + } + + p.UpgradeResourceIdentityFn = func(req providers.UpgradeResourceIdentityRequest) providers.UpgradeResourceIdentityResponse { + + return providers.UpgradeResourceIdentityResponse{ + UpgradedIdentity: tc.upgradedIdentity, + } + } + + plan, diags := ctx.Plan(m, tc.prevRunState, &PlanOpts{Mode: plans.NormalMode}) + + if tc.expectDiagnostics != nil { + tfdiags.AssertDiagnosticsMatch(t, diags, tc.expectDiagnostics) + } else { + assertNoDiagnostics(t, diags) + + if !tc.expectedPriorIdentity.IsNull() { + if !p.PlanResourceChangeCalled { + t.Fatal("PlanResourceChangeFn was not called") + } + + if !plannedPriorIdentity.RawEquals(tc.expectedPriorIdentity) { + t.Fatalf("wrong prior identity\nwant: %s\ngot: %s", tc.expectedPriorIdentity.GoString(), plannedPriorIdentity.GoString()) + } + } + + schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"] + + change, err := plan.Changes.Resources[0].Decode(schema) + + if err != nil { + t.Fatal(err) + } + + if !tc.expectedIdentity.RawEquals(change.AfterIdentity) { + t.Fatalf("wrong identity\nwant: %s\ngot: %s", tc.expectedIdentity.GoString(), change.AfterIdentity.GoString()) + } + } + }) + } +} diff --git a/internal/terraform/context_plan_test.go b/internal/terraform/context_plan_test.go index a680bb81aa..74c20882b4 100644 --- a/internal/terraform/context_plan_test.go +++ b/internal/terraform/context_plan_test.go @@ -50,10 +50,9 @@ func TestContext2Plan_basic(t *testing.T) { t.Fatalf("wrong number of resources %d; want fewer than two\n%s", l, spew.Sdump(plan.Changes.Resources)) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] for _, r := range plan.Changes.Resources { - ric, err := r.Decode(ty) + ric, err := r.Decode(schema) if err != nil { t.Fatal(err) } @@ -129,8 +128,7 @@ func TestContext2Plan_createBefore_deposed(t *testing.T) { t.Fatalf("\nexpected: %q\ngot: %q\n", expectedState, plan.PriorState.String()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] type InstanceGen struct { Addr string @@ -161,7 +159,7 @@ func TestContext2Plan_createBefore_deposed(t *testing.T) { } { - ric, err := changes[InstanceGen{Addr: "aws_instance.foo"}].Decode(ty) + ric, err := changes[InstanceGen{Addr: "aws_instance.foo"}].Decode(schema) if err != nil { t.Fatal(err) } @@ -171,7 +169,7 @@ func TestContext2Plan_createBefore_deposed(t *testing.T) { } // the existing instance should only have an unchanged id - expected, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + expected, err := schema.Body.CoerceValue(cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("baz"), "type": cty.StringVal("aws_instance"), })) @@ -183,7 +181,7 @@ func TestContext2Plan_createBefore_deposed(t *testing.T) { } { - ric, err := changes[InstanceGen{Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001")}].Decode(ty) + ric, err := changes[InstanceGen{Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001")}].Decode(schema) if err != nil { t.Fatal(err) } @@ -290,15 +288,14 @@ func TestContext2Plan_escapedVar(t *testing.T) { t.Fatalf("expected resource creation, got %s", res.Action) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } - expected := objectVal(t, schema, map[string]cty.Value{ + expected := objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar-${baz}"), "type": cty.UnknownVal(cty.String), @@ -363,16 +360,15 @@ func TestContext2Plan_modules(t *testing.T) { t.Error("expected 3 resource in plan, got", len(plan.Changes.Resources)) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] - expectFoo := objectVal(t, schema, map[string]cty.Value{ + expectFoo := objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.UnknownVal(cty.String), }) - expectNum := objectVal(t, schema, map[string]cty.Value{ + expectNum := objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.UnknownVal(cty.String), @@ -382,7 +378,7 @@ func TestContext2Plan_modules(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -417,8 +413,7 @@ func TestContext2Plan_moduleExpand(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] expected := map[string]struct{}{ `aws_instance.foo["a"]`: {}, @@ -434,7 +429,7 @@ func TestContext2Plan_moduleExpand(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -477,8 +472,7 @@ func TestContext2Plan_moduleCycle(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -488,7 +482,7 @@ func TestContext2Plan_moduleCycle(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -496,12 +490,12 @@ func TestContext2Plan_moduleCycle(t *testing.T) { var expected cty.Value switch i := ric.Addr.String(); i { case "aws_instance.b": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }) case "aws_instance.c": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "some_input": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), @@ -531,19 +525,18 @@ func TestContext2Plan_moduleDeadlock(t *testing.T) { t.Fatalf("err: %s", err) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] for _, res := range plan.Changes.Resources { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } - expected := objectVal(t, schema, map[string]cty.Value{ + expected := objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }) @@ -575,8 +568,7 @@ func TestContext2Plan_moduleInput(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -586,7 +578,7 @@ func TestContext2Plan_moduleInput(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -595,13 +587,13 @@ func TestContext2Plan_moduleInput(t *testing.T) { switch i := ric.Addr.String(); i { case "aws_instance.bar": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.UnknownVal(cty.String), }) case "module.child.aws_instance.foo": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("42"), "type": cty.UnknownVal(cty.String), @@ -629,8 +621,7 @@ func TestContext2Plan_moduleInputComputed(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -640,21 +631,21 @@ func TestContext2Plan_moduleInputComputed(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), "compute": cty.StringVal("foo"), }), ric.After) case "module.child.aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), @@ -688,8 +679,7 @@ func TestContext2Plan_moduleInputFromVar(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -699,20 +689,20 @@ func TestContext2Plan_moduleInputFromVar(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("52"), "type": cty.UnknownVal(cty.String), @@ -749,8 +739,7 @@ func TestContext2Plan_moduleMultiVar(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 5 { t.Fatal("expected 5 changes, got", len(plan.Changes.Resources)) @@ -761,32 +750,32 @@ func TestContext2Plan_moduleMultiVar(t *testing.T) { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.parent[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.parent[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.bar[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "baz": cty.StringVal("baz"), }), ric.After) case "module.child.aws_instance.bar[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "baz": cty.StringVal("baz"), }), ric.After) case "module.child.aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("baz,baz"), }), ric.After) @@ -822,8 +811,7 @@ func TestContext2Plan_moduleOrphans(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -831,7 +819,7 @@ func TestContext2Plan_moduleOrphans(t *testing.T) { for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -841,7 +829,7 @@ func TestContext2Plan_moduleOrphans(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.UnknownVal(cty.String), @@ -916,8 +904,7 @@ func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 3 { t.Error("expected 3 planned resources, got", len(plan.Changes.Resources)) @@ -925,7 +912,7 @@ func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -1187,8 +1174,7 @@ func TestContext2Plan_moduleProviderVar(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) @@ -1198,14 +1184,14 @@ func TestContext2Plan_moduleProviderVar(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.aws_instance.test": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "value": cty.StringVal("hello"), }), ric.After) default: @@ -1229,8 +1215,7 @@ func TestContext2Plan_moduleVar(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -1240,7 +1225,7 @@ func TestContext2Plan_moduleVar(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -1249,13 +1234,13 @@ func TestContext2Plan_moduleVar(t *testing.T) { switch i := ric.Addr.String(); i { case "aws_instance.bar": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.UnknownVal(cty.String), }) case "module.child.aws_instance.foo": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.UnknownVal(cty.String), @@ -1330,8 +1315,7 @@ func TestContext2Plan_moduleVarComputed(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -1341,20 +1325,20 @@ func TestContext2Plan_moduleVarComputed(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), @@ -1660,8 +1644,7 @@ func TestContext2Plan_computed(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -1671,20 +1654,20 @@ func TestContext2Plan_computed(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), @@ -1804,8 +1787,7 @@ func TestContext2Plan_computedDataResource(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.DataSources["aws_vpc"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.DataSources["aws_vpc"] if rc := plan.Changes.ResourceInstance(addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)); rc == nil { t.Fatalf("missing diff for aws_instance.foo") @@ -1819,7 +1801,7 @@ func TestContext2Plan_computedDataResource(t *testing.T) { t.Fatalf("missing diff for data.aws_vpc.bar") } - rc, err := rcs.Decode(ty) + rc, err := rcs.Decode(schema) if err != nil { t.Fatal(err) } @@ -1988,8 +1970,7 @@ func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) { } } - schema := p.GetProviderSchemaResponse.DataSources["aws_data_source"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.DataSources["aws_data_source"] p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ // This should not be called, because the configuration for the @@ -2029,7 +2010,7 @@ func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) { t.Fatalf("missing diff for data.aws_data_resource.foo") } - rc, err := rcs.Decode(ty) + rc, err := rcs.Decode(schema) if err != nil { t.Fatal(err) } @@ -2073,9 +2054,7 @@ func TestContext2Plan_computedList(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() - + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } @@ -2084,18 +2063,18 @@ func TestContext2Plan_computedList(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "foo": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "list": cty.UnknownVal(cty.List(cty.String)), "num": cty.NumberIntVal(2), "compute": cty.StringVal("list.#"), @@ -2136,8 +2115,7 @@ func TestContext2Plan_computedMultiIndex(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) @@ -2147,26 +2125,26 @@ func TestContext2Plan_computedMultiIndex(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "ip": cty.UnknownVal(cty.List(cty.String)), "foo": cty.NullVal(cty.List(cty.String)), "compute": cty.StringVal("ip.#"), }), ric.After) case "aws_instance.foo[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "ip": cty.UnknownVal(cty.List(cty.String)), "foo": cty.NullVal(cty.List(cty.String)), "compute": cty.StringVal("ip.#"), }), ric.After) case "aws_instance.bar[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "foo": cty.UnknownVal(cty.List(cty.String)), }), ric.After) default: @@ -2190,8 +2168,7 @@ func TestContext2Plan_count(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 6 { t.Fatal("expected 6 changes, got", len(plan.Changes.Resources)) @@ -2201,44 +2178,44 @@ func TestContext2Plan_count(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo,foo,foo,foo,foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[2]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[3]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[4]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2298,8 +2275,7 @@ func TestContext2Plan_countModuleStatic(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) @@ -2309,24 +2285,24 @@ func TestContext2Plan_countModuleStatic(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.aws_instance.foo[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.foo[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.child.aws_instance.foo[2]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) @@ -2351,8 +2327,7 @@ func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) @@ -2362,24 +2337,24 @@ func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.module.child.aws_instance.foo[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.child.module.child.aws_instance.foo[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.child.module.child.aws_instance.foo[2]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) @@ -2404,8 +2379,7 @@ func TestContext2Plan_countIndex(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -2415,20 +2389,20 @@ func TestContext2Plan_countIndex(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("0"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("1"), "type": cty.UnknownVal(cty.String), @@ -2461,8 +2435,7 @@ func TestContext2Plan_countVar(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) @@ -2472,32 +2445,32 @@ func TestContext2Plan_countVar(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo,foo,foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[2]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2538,8 +2511,7 @@ func TestContext2Plan_countZero(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) @@ -2550,7 +2522,7 @@ func TestContext2Plan_countZero(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -2579,8 +2551,7 @@ func TestContext2Plan_countOneIndex(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -2590,20 +2561,20 @@ func TestContext2Plan_countOneIndex(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), }), ric.After) case "aws_instance.foo[0]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2656,8 +2627,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) @@ -2665,7 +2635,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -2675,7 +2645,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.UnknownVal(cty.String), @@ -2740,8 +2710,7 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) @@ -2749,7 +2718,7 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -2759,7 +2728,7 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.UnknownVal(cty.String), @@ -2772,7 +2741,7 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2781,7 +2750,7 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2817,16 +2786,14 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() - + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 4 { t.Fatal("expected 4 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -2836,7 +2803,7 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.UnknownVal(cty.String), @@ -2849,7 +2816,7 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2858,7 +2825,7 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2908,8 +2875,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 5 { t.Fatal("expected 5 changes, got", len(plan.Changes.Resources)) @@ -2917,7 +2883,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -2927,7 +2893,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.UnknownVal(cty.String), @@ -2944,7 +2910,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -2953,7 +2919,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource create, got %s", res.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -3033,15 +2999,14 @@ func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 6 { t.Fatal("expected 6 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3087,8 +3052,7 @@ func TestContext2Plan_forEach(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 8 { t.Fatal("expected 8 changes, got", len(plan.Changes.Resources)) @@ -3098,7 +3062,7 @@ func TestContext2Plan_forEach(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - _, err := res.Decode(ty) + _, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3183,15 +3147,14 @@ func TestContext2Plan_destroy(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3244,15 +3207,14 @@ func TestContext2Plan_moduleDestroy(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3307,15 +3269,14 @@ func TestContext2Plan_moduleDestroyCycle(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3368,15 +3329,14 @@ func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3424,15 +3384,14 @@ func TestContext2Plan_pathVar(t *testing.T) { t.Fatalf("err: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3442,7 +3401,7 @@ func TestContext2Plan_pathVar(t *testing.T) { if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "cwd": cty.StringVal(cwd + "/barpath"), "module": cty.StringVal(m.Module.SourceDir + "/foopath"), "root": cty.StringVal(m.Module.SourceDir + "/barpath"), @@ -3479,15 +3438,14 @@ func TestContext2Plan_diffVar(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3497,7 +3455,7 @@ func TestContext2Plan_diffVar(t *testing.T) { if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(3), "type": cty.UnknownVal(cty.String), @@ -3506,12 +3464,12 @@ func TestContext2Plan_diffVar(t *testing.T) { if res.Action != plans.Update { t.Fatalf("resource %s should be updated", i) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NumberIntVal(2), "type": cty.StringVal("aws_instance"), }), ric.Before) - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NumberIntVal(3), "type": cty.StringVal("aws_instance"), @@ -3594,15 +3552,14 @@ func TestContext2Plan_orphan(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3622,7 +3579,7 @@ func TestContext2Plan_orphan(t *testing.T) { if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.UnknownVal(cty.String), @@ -3678,15 +3635,14 @@ func TestContext2Plan_state(t *testing.T) { if len(plan.Changes.Resources) < 2 { t.Fatalf("bad: %#v", plan.Changes.Resources) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3699,7 +3655,7 @@ func TestContext2Plan_state(t *testing.T) { if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.UnknownVal(cty.String), @@ -3711,12 +3667,12 @@ func TestContext2Plan_state(t *testing.T) { if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NullVal(cty.Number), "type": cty.NullVal(cty.String), }), ric.Before) - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("bar"), "num": cty.NumberIntVal(2), "type": cty.UnknownVal(cty.String), @@ -3778,8 +3734,7 @@ func TestContext2Plan_requiresReplace(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["test_thing"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["test_thing"] if got, want := len(plan.Changes.Resources), 1; got != want { t.Fatalf("got %d changes; want %d", got, want) @@ -3787,7 +3742,7 @@ func TestContext2Plan_requiresReplace(t *testing.T) { for _, res := range plan.Changes.Resources { t.Run(res.Addr.String(), func(t *testing.T) { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3800,7 +3755,7 @@ func TestContext2Plan_requiresReplace(t *testing.T) { if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseCannotUpdate; got != want { t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "v": cty.StringVal("goodbye"), }), ric.After) default: @@ -3844,8 +3799,7 @@ func TestContext2Plan_taint(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -3853,7 +3807,7 @@ func TestContext2Plan_taint(t *testing.T) { for _, res := range plan.Changes.Resources { t.Run(res.Addr.String(), func(t *testing.T) { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3866,7 +3820,7 @@ func TestContext2Plan_taint(t *testing.T) { if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("2"), "type": cty.UnknownVal(cty.String), @@ -3922,15 +3876,14 @@ func TestContext2Plan_taintIgnoreChanges(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -3943,12 +3896,12 @@ func TestContext2Plan_taintIgnoreChanges(t *testing.T) { if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("foo"), "vars": cty.StringVal("foo"), "type": cty.StringVal("aws_instance"), }), ric.Before) - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "vars": cty.StringVal("foo"), "type": cty.UnknownVal(cty.String), @@ -4003,15 +3956,14 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 3 { t.Fatal("expected 3 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4024,11 +3976,11 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want { t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("bar"), "type": cty.StringVal("aws_instance"), }), ric.Before) - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) @@ -4069,15 +4021,14 @@ func TestContext2Plan_targeted(t *testing.T) { t.Error("plan marked as complete; should not be because it used targeting") } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4087,7 +4038,7 @@ func TestContext2Plan_targeted(t *testing.T) { if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.UnknownVal(cty.String), @@ -4124,15 +4075,14 @@ func TestContext2Plan_targetedCrossModule(t *testing.T) { t.Error("plan marked as complete; should not be because it used targeting") } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4141,13 +4091,13 @@ func TestContext2Plan_targetedCrossModule(t *testing.T) { } switch i := ric.Addr.String(); i { case "module.A.aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.StringVal("bar"), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.B.aws_instance.bar": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "foo": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), @@ -4194,15 +4144,14 @@ func TestContext2Plan_targetedModuleWithProvider(t *testing.T) { t.Error("plan marked as complete; should not be because it used targeting") } - schema := p.GetProviderSchemaResponse.ResourceTypes["null_resource"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["null_resource"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4257,15 +4206,14 @@ func TestContext2Plan_targetedOrphan(t *testing.T) { t.Error("plan marked as complete; should not be because it used targeting") } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4327,15 +4275,14 @@ func TestContext2Plan_targetedModuleOrphan(t *testing.T) { t.Error("plan marked as complete; should not be because it used targeting") } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4374,15 +4321,14 @@ func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) { t.Error("plan marked as complete; should not be because it used targeting") } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4391,12 +4337,12 @@ func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) { } switch i := ric.Addr.String(); i { case "aws_instance.blue": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), }), ric.After) case "module.blue_mod.aws_instance.mod": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "value": cty.UnknownVal(cty.String), "type": cty.UnknownVal(cty.String), @@ -4479,11 +4425,10 @@ func TestContext2Plan_targetedOverTen(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4578,15 +4523,14 @@ func TestContext2Plan_ignoreChanges(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4595,7 +4539,7 @@ func TestContext2Plan_ignoreChanges(t *testing.T) { t.Fatalf("unexpected resource: %s", ric.Addr) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("bar"), "ami": cty.StringVal("ami-abcd1234"), "type": cty.StringVal("aws_instance"), @@ -4713,15 +4657,14 @@ func TestContext2Plan_ignoreChangesInMap(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["test_ignore_changes_map"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["test_ignore_changes_map"] if got, want := len(plan.Changes.Resources), 1; got != want { t.Fatalf("wrong number of changes %d; want %d", got, want) } res := plan.Changes.Resources[0] - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4733,7 +4676,7 @@ func TestContext2Plan_ignoreChangesInMap(t *testing.T) { t.Fatalf("unexpected resource address %s; want %s", got, want) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "tags": cty.MapVal(map[string]cty.Value{ "ignored": cty.StringVal("from state"), "other": cty.StringVal("from config"), @@ -4776,15 +4719,14 @@ func TestContext2Plan_ignoreChangesSensitive(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } res := plan.Changes.Resources[0] - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4793,7 +4735,7 @@ func TestContext2Plan_ignoreChangesSensitive(t *testing.T) { t.Fatalf("unexpected resource: %s", ric.Addr) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.StringVal("bar"), "ami": cty.StringVal("ami-abcd1234"), "type": cty.StringVal("aws_instance"), @@ -4885,9 +4827,9 @@ func TestContext2Plan_computedValueInMap(t *testing.T) { } for _, res := range plan.Changes.Resources { - schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Body + schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type] - ric, err := res.Decode(schema.ImpliedType()) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4898,11 +4840,11 @@ func TestContext2Plan_computedValueInMap(t *testing.T) { switch i := ric.Addr.String(); i { case "aws_computed_source.intermediates": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "computed_read_only": cty.UnknownVal(cty.String), }), ric.After) case "module.test_mod.aws_instance.inner2": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "looked_up": cty.UnknownVal(cty.String), }), ric.After) default: @@ -4940,9 +4882,9 @@ func TestContext2Plan_moduleVariableFromSplat(t *testing.T) { } for _, res := range plan.Changes.Resources { - schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Body + schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type] - ric, err := res.Decode(schema.ImpliedType()) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -4956,7 +4898,7 @@ func TestContext2Plan_moduleVariableFromSplat(t *testing.T) { "module.mod1.aws_instance.test[1]", "module.mod2.aws_instance.test[0]", "module.mod2.aws_instance.test[1]": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "thing": cty.StringVal("doesnt"), }), ric.After) default: @@ -5019,15 +4961,15 @@ func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) { seenAddrs := make(map[string]struct{}) for _, res := range plan.Changes.Resources { - var schema *configschema.Block + var schema providers.Schema switch res.Addr.Resource.Resource.Mode { case addrs.DataResourceMode: - schema = p.GetProviderSchemaResponse.DataSources[res.Addr.Resource.Resource.Type].Body + schema = p.GetProviderSchemaResponse.DataSources[res.Addr.Resource.Resource.Type] case addrs.ManagedResourceMode: - schema = p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Body + schema = p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type] } - ric, err := res.Decode(schema.ImpliedType()) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -5040,7 +4982,7 @@ func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) { if res.Action != plans.Create { t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "num": cty.StringVal("2"), "computed": cty.StringVal("data_id"), }), ric.After) @@ -5048,7 +4990,7 @@ func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) { if res.Action != plans.Create { t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "num": cty.StringVal("2"), "computed": cty.StringVal("data_id"), }), ric.After) @@ -5164,9 +5106,8 @@ func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) { } res := plan.Changes.Resources[0] - schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Body - - ric, err := res.Decode(schema.ImpliedType()) + schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type] + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -5179,7 +5120,7 @@ func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) { t.Fatalf("unknown resource: %s", ric.Addr) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "lst": cty.ListVal([]cty.Value{ cty.StringVal("j"), cty.StringVal("k"), @@ -5588,8 +5529,7 @@ func TestContext2Plan_variableSensitivity(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) @@ -5599,14 +5539,14 @@ func TestContext2Plan_variableSensitivity(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "foo": cty.StringVal("foo").Mark(marks.Sensitive), }), ric.After) if len(res.ChangeSrc.BeforeSensitivePaths) != 0 { @@ -5654,8 +5594,7 @@ func TestContext2Plan_variableSensitivityModule(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) @@ -5665,14 +5604,14 @@ func TestContext2Plan_variableSensitivityModule(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } switch i := ric.Addr.String(); i { case "module.child.aws_instance.foo": - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "foo": cty.StringVal("foo").Mark(marks.Sensitive), "value": cty.StringVal("boop").Mark(marks.Sensitive), }), ric.After) @@ -5754,8 +5693,7 @@ func TestContext2Plan_requiredModuleOutput(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -5766,7 +5704,7 @@ func TestContext2Plan_requiredModuleOutput(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -5774,12 +5712,12 @@ func TestContext2Plan_requiredModuleOutput(t *testing.T) { var expected cty.Value switch i := ric.Addr.String(); i { case "test_resource.root": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.UnknownVal(cty.String), }) case "module.mod.test_resource.for_output": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.StringVal("val"), }) @@ -5817,8 +5755,7 @@ func TestContext2Plan_requiredModuleObject(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"] if len(plan.Changes.Resources) != 2 { t.Fatal("expected 2 changes, got", len(plan.Changes.Resources)) @@ -5829,7 +5766,7 @@ func TestContext2Plan_requiredModuleObject(t *testing.T) { if res.Action != plans.Create { t.Fatalf("expected resource creation, got %s", res.Action) } - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -5837,12 +5774,12 @@ func TestContext2Plan_requiredModuleObject(t *testing.T) { var expected cty.Value switch i := ric.Addr.String(); i { case "test_resource.root": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.UnknownVal(cty.String), }) case "module.mod.test_resource.for_output": - expected = objectVal(t, schema, map[string]cty.Value{ + expected = objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "required": cty.StringVal("val"), }) @@ -6286,15 +6223,14 @@ func TestContext2Plan_targetedModuleInstance(t *testing.T) { if diags.HasErrors() { t.Fatalf("unexpected errors: %s", diags.Err()) } - schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body - ty := schema.ImpliedType() + schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"] if len(plan.Changes.Resources) != 1 { t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) } for _, res := range plan.Changes.Resources { - ric, err := res.Decode(ty) + ric, err := res.Decode(schema) if err != nil { t.Fatal(err) } @@ -6304,7 +6240,7 @@ func TestContext2Plan_targetedModuleInstance(t *testing.T) { if res.Action != plans.Create { t.Fatalf("resource %s should be created", i) } - checkVals(t, objectVal(t, schema, map[string]cty.Value{ + checkVals(t, objectVal(t, schema.Body, map[string]cty.Value{ "id": cty.UnknownVal(cty.String), "num": cty.NumberIntVal(2), "type": cty.UnknownVal(cty.String), diff --git a/internal/terraform/node_resource_abstract.go b/internal/terraform/node_resource_abstract.go index 94701bb578..a5cf2e11a7 100644 --- a/internal/terraform/node_resource_abstract.go +++ b/internal/terraform/node_resource_abstract.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/dag" "github.com/hashicorp/terraform/internal/lang/langrefs" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -53,8 +54,7 @@ type NodeAbstractResource struct { // interfaces if you're running those transforms, but also be explicitly // set if you already have that information. - Schema *configschema.Block // Schema for processing the configuration body - SchemaVersion uint64 // Schema version of "Schema", as decided by the provider + Schema *providers.Schema // Schema for processing the configuration body // Config and RemovedConfig are mutally-exclusive, because a // resource can't be both declared and removed at the same time. @@ -189,7 +189,7 @@ func (n *NodeAbstractResource) References() []*addrs.Reference { // ReferencesInBlock() requires a schema if n.Schema != nil { - refs, _ = langrefs.ReferencesInBlock(addrs.ParseRef, c.Config, n.Schema) + refs, _ = langrefs.ReferencesInBlock(addrs.ParseRef, c.Config, n.Schema.Body) result = append(result, refs...) } @@ -388,9 +388,8 @@ func (n *NodeAbstractResource) AttachResourceConfig(c *configs.Resource, rc *con } // GraphNodeAttachResourceSchema impl -func (n *NodeAbstractResource) AttachResourceSchema(schema *configschema.Block, version uint64) { +func (n *NodeAbstractResource) AttachResourceSchema(schema *providers.Schema) { n.Schema = schema - n.SchemaVersion = version } // GraphNodeAttachProviderMetaConfigs impl diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index 7b2f64975e..3ebd40eba8 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -440,6 +440,7 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState PriorPrivate: currentState.Private, ProviderMeta: metaConfigVal, ClientCapabilities: ctx.ClientCapabilities(), + PriorIdentity: currentState.Identity, }) deferred = resp.Deferred @@ -449,6 +450,12 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState diags = diags.Append(deferring.UnexpectedProviderDeferralDiagnostic(n.Addr)) } + if !resp.PlannedIdentity.IsNull() { + // Destroying is an operation where we allow identity changes. + diags = diags.Append(n.validateIdentityKnown(resp.PlannedIdentity)) + diags = diags.Append(n.validateIdentity(resp.PlannedIdentity)) + } + // We may not have a config for all destroys, but we want to reference // it in the diagnostics if we do. if n.Config != nil { @@ -480,9 +487,11 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState PrevRunAddr: n.prevRunAddr(ctx), DeposedKey: deposedKey, Change: plans.Change{ - Action: plans.Delete, - Before: currentState.Value, - After: nullVal, + Action: plans.Delete, + Before: currentState.Value, + BeforeIdentity: currentState.Identity, + After: nullVal, + AfterIdentity: resp.PlannedIdentity, }, Private: resp.PlannedPrivate, ProviderAddr: n.ResolvedProvider, @@ -639,6 +648,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state Private: state.Private, ProviderMeta: metaConfigVal, ClientCapabilities: ctx.ClientCapabilities(), + CurrentIdentity: state.Identity, }) // If we don't support deferrals, but the provider reports a deferral and does not @@ -647,6 +657,11 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state diags = diags.Append(deferring.UnexpectedProviderDeferralDiagnostic(n.Addr)) } + if !resp.Identity.IsNull() { + diags = diags.Append(n.validateIdentityKnown(resp.Identity)) + diags = diags.Append(n.validateIdentity(resp.Identity)) + diags = diags.Append(n.validateIdentityDidNotChange(state, resp.Identity)) + } if resp.Deferred != nil { deferred = resp.Deferred } @@ -711,8 +726,6 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state if writeOnlyDiags.HasErrors() { return state, deferred, diags } - - diags = diags.Append(n.validateIdentity(state, resp.Identity, false)) if diags.HasErrors() { return state, deferred, diags } @@ -844,10 +857,12 @@ func (n *NodeAbstractResourceInstance) plan( var priorVal cty.Value var priorValTainted cty.Value var priorPrivate []byte + var priorIdentity cty.Value if currentState != nil { if currentState.Status != states.ObjectTainted { priorVal = currentState.Value priorPrivate = currentState.Private + priorIdentity = currentState.Identity } else { // If the prior state is tainted then we'll proceed below like // we're creating an entirely new object, but then turn it into @@ -947,6 +962,7 @@ func (n *NodeAbstractResourceInstance) plan( PriorPrivate: priorPrivate, ProviderMeta: metaConfigVal, ClientCapabilities: ctx.ClientCapabilities(), + PriorIdentity: priorIdentity, }) // If we don't support deferrals, but the provider reports a deferral and does not // emit any error level diagnostics, we should emit an error. @@ -966,6 +982,7 @@ func (n *NodeAbstractResourceInstance) plan( plannedNewVal := resp.PlannedState plannedPrivate := resp.PlannedPrivate + plannedIdentity := resp.PlannedIdentity // These checks are only relevant if the provider is not deferring the // change. @@ -1091,6 +1108,21 @@ func (n *NodeAbstractResourceInstance) plan( woPathSet := cty.NewPathSet(writeOnlyPaths...) action, actionReason := getAction(n.Addr, unmarkedPriorVal, unmarkedPlannedNewVal, createBeforeDestroy, woPathSet, forceReplace, reqRep) + if !plannedIdentity.IsNull() { + if !action.IsReplace() && action != plans.Create { + diags = diags.Append(n.validateIdentityKnown(plannedIdentity)) + // If the identity is not known we can not validate it did not change + if !diags.HasErrors() { + diags = diags.Append(n.validateIdentityDidNotChange(currentState, plannedIdentity)) + } + } + + diags = diags.Append(n.validateIdentity(plannedIdentity)) + } + if diags.HasErrors() { + return nil, nil, deferred, keyData, diags + } + if action.IsReplace() { // In this strange situation we want to produce a change object that // shows our real prior object but has a _new_ object that is built @@ -1136,6 +1168,7 @@ func (n *NodeAbstractResourceInstance) plan( PriorPrivate: plannedPrivate, ProviderMeta: metaConfigVal, ClientCapabilities: ctx.ClientCapabilities(), + PriorIdentity: plannedIdentity, }) // If we don't support deferrals, but the provider reports a deferral and does not @@ -1143,6 +1176,11 @@ func (n *NodeAbstractResourceInstance) plan( if resp.Deferred != nil && !deferralAllowed && !resp.Diagnostics.HasErrors() { diags = diags.Append(deferring.UnexpectedProviderDeferralDiagnostic(n.Addr)) } + + if !resp.PlannedIdentity.IsNull() { + // On replace the identity is allowed to change and be unknown. + diags = diags.Append(n.validateIdentity(resp.PlannedIdentity)) + } } // We need to tread carefully here, since if there are any warnings // in here they probably also came out of our previous call to @@ -1160,6 +1198,7 @@ func (n *NodeAbstractResourceInstance) plan( plannedNewVal = resp.PlannedState plannedPrivate = resp.PlannedPrivate + plannedIdentity = resp.PlannedIdentity if len(unmarkedPaths) > 0 { plannedNewVal = plannedNewVal.MarkWithPaths(unmarkedPaths) @@ -1253,12 +1292,14 @@ func (n *NodeAbstractResourceInstance) plan( Private: plannedPrivate, ProviderAddr: n.ResolvedProvider, Change: plans.Change{ - Action: action, - Before: priorVal, + Action: action, + Before: priorVal, + BeforeIdentity: priorIdentity, // Pass the marked planned value through in our change // to propogate through evaluation. // Marks will be removed when encoding. After: plannedNewVal, + AfterIdentity: plannedIdentity, GeneratedConfig: n.generatedConfigHCL, }, ActionReason: actionReason, @@ -1273,9 +1314,10 @@ func (n *NodeAbstractResourceInstance) plan( // must _also_ record the returned change in the active plan, // which the expression evaluator will use in preference to this // incomplete value recorded in the state. - Status: states.ObjectPlanned, - Value: plannedNewVal, - Private: plannedPrivate, + Status: states.ObjectPlanned, + Value: plannedNewVal, + Private: plannedPrivate, + Identity: resp.PlannedIdentity, } return plan, state, deferred, keyData, diags @@ -2583,13 +2625,22 @@ func (n *NodeAbstractResourceInstance) apply( } } else { resp = provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ - TypeName: n.Addr.Resource.Resource.Type, - PriorState: unmarkedBefore, - Config: unmarkedConfigVal, - PlannedState: unmarkedAfter, - PlannedPrivate: change.Private, - ProviderMeta: metaConfigVal, + TypeName: n.Addr.Resource.Resource.Type, + PriorState: unmarkedBefore, + Config: unmarkedConfigVal, + PlannedState: unmarkedAfter, + PlannedPrivate: change.Private, + ProviderMeta: metaConfigVal, + PlannedIdentity: change.AfterIdentity, }) + + if !resp.NewIdentity.IsNull() { + diags = diags.Append(n.validateIdentityKnown(resp.NewIdentity)) + diags = diags.Append(n.validateIdentity(resp.NewIdentity)) + if !change.Action.IsReplace() { + diags = diags.Append(n.validateIdentityDidNotChange(state, resp.NewIdentity)) + } + } } applyDiags := resp.Diagnostics if applyConfig != nil { @@ -2825,6 +2876,7 @@ func (n *NodeAbstractResourceInstance) apply( Value: newVal, Private: resp.Private, CreateBeforeDestroy: createBeforeDestroy, + Identity: resp.NewIdentity, } return newState, diags @@ -2838,39 +2890,43 @@ func (n *NodeAbstractResourceInstance) prevRunAddr(ctx EvalContext) addrs.AbsRes return resourceInstancePrevRunAddr(ctx, n.Addr) } -func (n *NodeAbstractResourceInstance) validateIdentity(state *states.ResourceInstanceObject, newIdentity cty.Value, isAllowedToChange bool) (diags tfdiags.Diagnostics) { - - // Identities can not contain unknown values +func (n *NodeAbstractResourceInstance) validateIdentityKnown(newIdentity cty.Value) (diags tfdiags.Diagnostics) { if !newIdentity.IsWhollyKnown() { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Provider produced invalid identity", fmt.Sprintf( - "Provider %q planned an identity with unknown values for %s during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", + "Provider %q returned an identity with unknown values for %s. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", n.ResolvedProvider.Provider, n.Addr, ), )) } - // Identities can not contain marks + return diags +} + +func (n *NodeAbstractResourceInstance) validateIdentityDidNotChange(state *states.ResourceInstanceObject, newIdentity cty.Value) (diags tfdiags.Diagnostics) { + if state != nil && !state.Identity.IsNull() && state.Identity.Equals(newIdentity).False() { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Provider produced different identity", + fmt.Sprintf( + "Provider %q returned a different identity for %s than the previously stored one. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", + n.ResolvedProvider.Provider, n.Addr, + ), + )) + } + + return diags +} + +func (n *NodeAbstractResourceInstance) validateIdentity(newIdentity cty.Value) (diags tfdiags.Diagnostics) { if _, marks := newIdentity.UnmarkDeep(); len(marks) > 0 { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Provider produced invalid identity", fmt.Sprintf( - "Provider %q planned an identity with marks for %s during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", - n.ResolvedProvider.Provider, n.Addr, - ), - )) - } - - // Identities can not change (except if they are re-created or initially recorded) - if !isAllowedToChange && !state.Identity.IsNull() && state.Identity.Equals(newIdentity).False() { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Provider produced different identity", - fmt.Sprintf( - "Provider %q planned an different identity for %s during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", + "Provider %q returned an identity with marks for %s. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", n.ResolvedProvider.Provider, n.Addr, ), )) diff --git a/internal/terraform/node_resource_apply_deferred.go b/internal/terraform/node_resource_apply_deferred.go index c62dab70ad..1a51172784 100644 --- a/internal/terraform/node_resource_apply_deferred.go +++ b/internal/terraform/node_resource_apply_deferred.go @@ -29,7 +29,12 @@ type nodeApplyableDeferredInstance struct { func (n *nodeApplyableDeferredInstance) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics { var diags tfdiags.Diagnostics - change, err := n.ChangeSrc.Decode(n.Schema.ImpliedType()) + if n.Schema == nil { + diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Failed to decode", "Terraform failed to decode a deferred change due to the schema not being present. This is a bug in Terraform; please report it!")) + return diags + } + + change, err := n.ChangeSrc.Decode(*n.Schema) if err != nil { diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Failed to decode ", fmt.Sprintf("Terraform failed to decode a deferred change: %v\n\nThis is a bug in Terraform; please report it!", err))) } @@ -55,7 +60,12 @@ type nodeApplyableDeferredPartialInstance struct { func (n *nodeApplyableDeferredPartialInstance) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics { var diags tfdiags.Diagnostics - change, err := n.ChangeSrc.Decode(n.Schema.ImpliedType()) + if n.Schema == nil { + diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Failed to decode", "Terraform failed to decode a deferred change due to the schema not being present. This is a bug in Terraform; please report it!")) + return diags + } + + change, err := n.ChangeSrc.Decode(*n.Schema) if err != nil { diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Failed to decode ", fmt.Sprintf("Terraform failed to decode a deferred change: %v\n\nThis is a bug in Terraform; please report it!", err))) } diff --git a/internal/terraform/node_resource_plan_instance.go b/internal/terraform/node_resource_plan_instance.go index e2f2cd680a..c520535fe2 100644 --- a/internal/terraform/node_resource_plan_instance.go +++ b/internal/terraform/node_resource_plan_instance.go @@ -318,6 +318,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) if deferred == nil { diags = diags.Append(n.writeResourceInstanceState(ctx, instanceRefreshState, refreshState)) } + if diags.HasErrors() { return diags } diff --git a/internal/terraform/transform_attach_schema.go b/internal/terraform/transform_attach_schema.go index 8d70e72858..3a56b036cc 100644 --- a/internal/terraform/transform_attach_schema.go +++ b/internal/terraform/transform_attach_schema.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/dag" + "github.com/hashicorp/terraform/internal/providers" ) // GraphNodeAttachResourceSchema is an interface implemented by node types @@ -18,7 +19,7 @@ type GraphNodeAttachResourceSchema interface { GraphNodeConfigResource GraphNodeProviderConsumer - AttachResourceSchema(schema *configschema.Block, version uint64) + AttachResourceSchema(schema *providers.Schema) } // GraphNodeAttachProviderConfigSchema is an interface implemented by node types @@ -74,7 +75,7 @@ func (t *AttachSchemaTransformer) Transform(g *Graph) error { continue } log.Printf("[TRACE] AttachSchemaTransformer: attaching resource schema to %s", dag.VertexName(v)) - tv.AttachResourceSchema(schema.Body, uint64(schema.Version)) + tv.AttachResourceSchema(&schema) } if tv, ok := v.(GraphNodeAttachProviderConfigSchema); ok { diff --git a/internal/terraform/transform_resource_count.go b/internal/terraform/transform_resource_count.go index e511d31d56..1b9706d0a9 100644 --- a/internal/terraform/transform_resource_count.go +++ b/internal/terraform/transform_resource_count.go @@ -7,8 +7,8 @@ import ( "log" "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/dag" + "github.com/hashicorp/terraform/internal/providers" ) // ResourceCountTransformer is a GraphTransformer that expands the count @@ -17,7 +17,7 @@ import ( // This assumes that the count is already interpolated. type ResourceCountTransformer struct { Concrete ConcreteResourceInstanceNodeFunc - Schema *configschema.Block + Schema *providers.Schema Addr addrs.ConfigResource InstanceAddrs []addrs.AbsResourceInstance