send resource identities to provider calls

This commit is contained in:
Daniel Schmidt 2025-02-27 12:17:00 +01:00
parent b2b42c0fb4
commit fec6e4b552
35 changed files with 2214 additions and 466 deletions

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
}
})

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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,
}
}
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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{

View file

@ -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)
}

View file

@ -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

View file

@ -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())
}
}

View file

@ -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())
}
}
})
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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)

View file

@ -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())
}
}
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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,
),
))

View file

@ -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)))
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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