add provider implementation for actions

pull/37317/head
Daniel Schmidt 10 months ago
parent e6b848d5ab
commit ac132d1b15

@ -74,6 +74,7 @@ func (p *Provider) GetProviderSchema() providers.GetProviderSchemaResponse {
},
},
StateStores: map[string]providers.Schema{},
Actions: map[string]providers.ActionSchema{},
}
providers.SchemaCache.Set(tfaddr.NewProvider(tfaddr.BuiltInProviderHost, tfaddr.BuiltInProviderNamespace, "terraform"), resp)
return resp
@ -302,6 +303,28 @@ func (p *Provider) DeleteState(req providers.DeleteStateRequest) providers.Delet
return resp
}
func (p *Provider) PlanAction(req providers.PlanActionRequest) providers.PlanActionResponse {
var resp providers.PlanActionResponse
switch req.ActionType {
default:
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unsupported action %q", req.ActionType))
}
return resp
}
func (p *Provider) InvokeAction(req providers.InvokeActionRequest) providers.InvokeActionResponse {
var resp providers.InvokeActionResponse
switch req.ActionType {
default:
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unsupported action %q", req.ActionType))
}
return resp
}
// Close is a noop for this provider, since it's run in-process.
func (p *Provider) Close() error {
return nil

@ -46,6 +46,7 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
DataSourceSchemas: make(map[string]*tfplugin5.Schema),
EphemeralResourceSchemas: make(map[string]*tfplugin5.Schema),
ListResourceSchemas: make(map[string]*tfplugin5.Schema),
ActionSchemas: make(map[string]*tfplugin5.ActionSchema),
}
resp.Provider = &tfplugin5.Schema{
@ -93,6 +94,32 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
return resp, nil
}
for typ, act := range p.schema.Actions {
newAct := tfplugin5.ActionSchema{
Schema: &tfplugin5.Schema{
Block: convert.ConfigSchemaToProto(act.ConfigSchema),
},
}
if act.Unlinked != nil {
newAct.Type = &tfplugin5.ActionSchema_Unlinked_{}
} else if act.Lifecycle != nil {
newAct.Type = &tfplugin5.ActionSchema_Lifecycle_{
Lifecycle: &tfplugin5.ActionSchema_Lifecycle{
Executes: convert.ExecutionOrderToProto(act.Lifecycle.Exectues),
LinkedResource: convert.LinkedResourceToProto(act.Lifecycle.LinkedResource),
},
}
} else if act.Linked != nil {
newAct.Type = &tfplugin5.ActionSchema_Linked_{
Linked: &tfplugin5.ActionSchema_Linked{
LinkedResources: convert.LinkedResourcesToProto(act.Linked.LinkedResources),
},
}
}
resp.ActionSchemas[typ] = &newAct
}
resp.ServerCapabilities = &tfplugin5.ServerCapabilities{
GetProviderSchemaOptional: p.schema.ServerCapabilities.GetProviderSchemaOptional,
PlanDestroy: p.schema.ServerCapabilities.PlanDestroy,
@ -828,6 +855,218 @@ func (p *provider) ListResource(req *tfplugin5.ListResource_Request, res tfplugi
return nil
}
func (p *provider) PlanAction(_ context.Context, req *tfplugin5.PlanAction_Request) (*tfplugin5.PlanAction_Response, error) {
resp := &tfplugin5.PlanAction_Response{}
actionSchema, ok := p.schema.Actions[req.ActionType]
if !ok {
return nil, fmt.Errorf("action schema not found for action %q", req.ActionType)
}
ty := actionSchema.ConfigSchema.ImpliedType()
configVal, err := decodeDynamicValue(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
linkedResouceSchemas := actionSchema.LinkedResources()
inputLinkedResources := make([]providers.LinkedResourcePlanData, 0, len(req.LinkedResources))
for i, lr := range req.LinkedResources {
resourceSchema, ok := p.schema.ResourceTypes[linkedResouceSchemas[i].TypeName]
if !ok {
return nil, fmt.Errorf("linked resource schema not found for type %q in action %q", linkedResouceSchemas[i].TypeName, req.ActionType)
}
linkedResourceTy := resourceSchema.Body.ImpliedType()
linkedResourceIdentityTy := resourceSchema.Identity.ImpliedType()
priorState, err := decodeDynamicValue(lr.PriorState, linkedResourceTy)
if err != nil {
return nil, fmt.Errorf("failed to decode prior state for linked resource #%d (%q) in action %q: %w", i+1, linkedResouceSchemas[i].TypeName, req.ActionType, err)
}
config, err := decodeDynamicValue(lr.Config, linkedResourceTy)
if err != nil {
return nil, fmt.Errorf("failed to decode config for linked resource #%d (%q) in action %q: %w", i+1, linkedResouceSchemas[i].TypeName, req.ActionType, err)
}
var priorIdentity cty.Value
if lr.PriorIdentity != nil && lr.PriorIdentity.IdentityData != nil {
priorIdentity, err = decodeDynamicValue(lr.PriorIdentity.IdentityData, linkedResourceIdentityTy)
if err != nil {
return nil, fmt.Errorf("failed to decode prior identity for linked resource #%d (%q) in action %q: %w", i+1, linkedResouceSchemas[i].TypeName, req.ActionType, err)
}
} else {
priorIdentity = cty.NullVal(linkedResourceIdentityTy)
}
inputLinkedResources = append(inputLinkedResources, providers.LinkedResourcePlanData{
PriorState: priorState,
Config: config,
PriorIdentity: priorIdentity,
})
}
planResp := p.provider.PlanAction(providers.PlanActionRequest{
ActionType: req.ActionType,
ProposedActionData: configVal,
LinkedResources: inputLinkedResources,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics)
if planResp.Diagnostics.HasErrors() {
return resp, nil
}
resp.Deferred = convert.DeferredToProto(planResp.Deferred)
linkedResources := make([]*tfplugin5.PlanAction_Response_LinkedResource, 0, len(planResp.LinkedResources))
for _, linked := range planResp.LinkedResources {
plannedState, err := encodeDynamicValue(linked.PlannedState, linked.PlannedState.Type())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
continue
}
plannedIdentity, err := encodeDynamicValue(linked.PlannedIdentity, linked.PlannedIdentity.Type())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
continue
}
linkedResources = append(linkedResources, &tfplugin5.PlanAction_Response_LinkedResource{
PlannedState: plannedState,
PlannedIdentity: &tfplugin5.ResourceIdentityData{
IdentityData: plannedIdentity,
},
})
}
resp.LinkedResources = linkedResources
return resp, nil
}
func (p *provider) InvokeAction(req *tfplugin5.InvokeAction_Request, server tfplugin5.Provider_InvokeActionServer) error {
actionSchema, ok := p.schema.Actions[req.ActionType]
if !ok {
return fmt.Errorf("action schema not found for action %q", req.ActionType)
}
ty := actionSchema.ConfigSchema.ImpliedType()
configVal, err := decodeDynamicValue(req.Config, ty)
if err != nil {
return err
}
linkedResourceData := make([]providers.LinkedResourceInvokeData, 0, len(req.LinkedResources))
linkedResourceSchemas := actionSchema.LinkedResources()
if len(linkedResourceSchemas) != len(req.LinkedResources) {
return fmt.Errorf("expected %d linked resources for action %q, got %d", len(linkedResourceSchemas), req.ActionType, len(req.LinkedResources))
}
for i, lr := range req.LinkedResources {
resourceSchema, ok := p.schema.ResourceTypes[linkedResourceSchemas[i].TypeName]
if !ok {
return fmt.Errorf("linked resource schema not found for type %q in action %q", linkedResourceSchemas[i].TypeName, req.ActionType)
}
linkedResourceTy := resourceSchema.Body.ImpliedType()
linkedResourceIdentityTy := resourceSchema.Identity.ImpliedType()
priorState, err := decodeDynamicValue(lr.PriorState, linkedResourceTy)
if err != nil {
return fmt.Errorf("failed to decode prior state for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
plannedState, err := decodeDynamicValue(lr.PlannedState, linkedResourceTy)
if err != nil {
return fmt.Errorf("failed to decode planned state for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
config, err := decodeDynamicValue(lr.Config, linkedResourceTy)
if err != nil {
return fmt.Errorf("failed to decode config for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
plannedIdentity := cty.NullVal(linkedResourceIdentityTy)
if lr.PlannedIdentity != nil && lr.PlannedIdentity.IdentityData != nil {
plannedIdentity, err = decodeDynamicValue(lr.PlannedIdentity.IdentityData, linkedResourceIdentityTy)
if err != nil {
return fmt.Errorf("failed to decode planned identity for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
}
linkedResourceData = append(linkedResourceData, providers.LinkedResourceInvokeData{
PriorState: priorState,
PlannedState: plannedState,
Config: config,
PlannedIdentity: plannedIdentity,
})
}
invokeResp := p.provider.InvokeAction(providers.InvokeActionRequest{
ActionType: req.ActionType,
PlannedActionData: configVal,
LinkedResources: linkedResourceData,
})
if invokeResp.Diagnostics.HasErrors() {
return invokeResp.Diagnostics.Err()
}
for invokeEvent := range invokeResp.Events {
switch invokeEvt := invokeEvent.(type) {
case providers.InvokeActionEvent_Progress:
server.Send(&tfplugin5.InvokeAction_Event{
Type: &tfplugin5.InvokeAction_Event_Progress_{
Progress: &tfplugin5.InvokeAction_Event_Progress{
Message: invokeEvt.Message,
},
},
})
case providers.InvokeActionEvent_Completed:
completed := &tfplugin5.InvokeAction_Event_Completed{
LinkedResources: []*tfplugin5.InvokeAction_Event_Completed_LinkedResource{},
}
completed.Diagnostics = convert.AppendProtoDiag(completed.Diagnostics, invokeEvt.Diagnostics)
for _, lr := range invokeEvt.LinkedResources {
newState, err := encodeDynamicValue(lr.NewState, lr.NewState.Type())
if err != nil {
return fmt.Errorf("failed to encode new state for linked resource: %w", err)
}
newIdentity, err := encodeDynamicValue(lr.NewIdentity, lr.NewIdentity.Type())
if err != nil {
return fmt.Errorf("failed to encode new identity for linked resource: %w", err)
}
completed.LinkedResources = append(completed.LinkedResources, &tfplugin5.InvokeAction_Event_Completed_LinkedResource{
NewState: newState,
NewIdentity: &tfplugin5.ResourceIdentityData{
IdentityData: newIdentity,
},
RequiresReplace: lr.RequiresReplace,
})
}
err := server.Send(&tfplugin5.InvokeAction_Event{
Type: &tfplugin5.InvokeAction_Event_Completed_{
Completed: completed,
},
})
if err != nil {
return fmt.Errorf("failed to send completed event: %w", err)
}
}
}
return nil
}
func (p *provider) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) {
resp := &tfplugin5.Stop_Response{}
err := p.provider.Stop()

@ -49,6 +49,7 @@ func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProvi
Functions: make(map[string]*tfplugin6.Function),
ListResourceSchemas: make(map[string]*tfplugin6.Schema),
StateStoreSchemas: make(map[string]*tfplugin6.Schema),
ActionSchemas: make(map[string]*tfplugin6.ActionSchema),
}
resp.Provider = &tfplugin6.Schema{
@ -102,6 +103,32 @@ func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProvi
return resp, nil
}
for typ, act := range p.schema.Actions {
newAct := tfplugin6.ActionSchema{
Schema: &tfplugin6.Schema{
Block: convert.ConfigSchemaToProto(act.ConfigSchema),
},
}
if act.Unlinked != nil {
newAct.Type = &tfplugin6.ActionSchema_Unlinked_{}
} else if act.Lifecycle != nil {
newAct.Type = &tfplugin6.ActionSchema_Lifecycle_{
Lifecycle: &tfplugin6.ActionSchema_Lifecycle{
Executes: convert.ExecutionOrderToProto(act.Lifecycle.Exectues),
LinkedResource: convert.LinkedResourceToProto(act.Lifecycle.LinkedResource),
},
}
} else if act.Linked != nil {
newAct.Type = &tfplugin6.ActionSchema_Linked_{
Linked: &tfplugin6.ActionSchema_Linked{
LinkedResources: convert.LinkedResourcesToProto(act.Linked.LinkedResources),
},
}
}
resp.ActionSchemas[typ] = &newAct
}
resp.ServerCapabilities = &tfplugin6.ServerCapabilities{
GetProviderSchemaOptional: p.schema.ServerCapabilities.GetProviderSchemaOptional,
PlanDestroy: p.schema.ServerCapabilities.PlanDestroy,
@ -899,6 +926,219 @@ func (p *provider6) DeleteState(ctx context.Context, req *tfplugin6.DeleteState_
panic("not implemented")
}
func (p *provider6) PlanAction(_ context.Context, req *tfplugin6.PlanAction_Request) (*tfplugin6.PlanAction_Response, error) {
resp := &tfplugin6.PlanAction_Response{}
actionSchema, ok := p.schema.Actions[req.ActionType]
if !ok {
return nil, fmt.Errorf("action schema not found for action %q", req.ActionType)
}
ty := actionSchema.ConfigSchema.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
linkedResouceSchemas := actionSchema.LinkedResources()
inputLinkedResources := make([]providers.LinkedResourcePlanData, 0, len(req.LinkedResources))
for i, lr := range req.LinkedResources {
resourceSchema, ok := p.schema.ResourceTypes[linkedResouceSchemas[i].TypeName]
if !ok {
return nil, fmt.Errorf("linked resource schema not found for type %q in action %q", linkedResouceSchemas[i].TypeName, req.ActionType)
}
linkedResourceTy := resourceSchema.Body.ImpliedType()
linkedResourceIdentityTy := resourceSchema.Identity.ImpliedType()
priorState, err := decodeDynamicValue6(lr.PriorState, linkedResourceTy)
if err != nil {
return nil, fmt.Errorf("failed to decode prior state for linked resource #%d (%q) in action %q: %w", i+1, linkedResouceSchemas[i].TypeName, req.ActionType, err)
}
config, err := decodeDynamicValue6(lr.Config, linkedResourceTy)
if err != nil {
return nil, fmt.Errorf("failed to decode config for linked resource #%d (%q) in action %q: %w", i+1, linkedResouceSchemas[i].TypeName, req.ActionType, err)
}
var priorIdentity cty.Value
if lr.PriorIdentity != nil && lr.PriorIdentity.IdentityData != nil {
priorIdentity, err = decodeDynamicValue6(lr.PriorIdentity.IdentityData, linkedResourceIdentityTy)
if err != nil {
return nil, fmt.Errorf("failed to decode prior identity for linked resource #%d (%q) in action %q: %w", i+1, linkedResouceSchemas[i].TypeName, req.ActionType, err)
}
} else {
priorIdentity = cty.NullVal(linkedResourceIdentityTy)
}
inputLinkedResources = append(inputLinkedResources, providers.LinkedResourcePlanData{
PriorState: priorState,
Config: config,
PriorIdentity: priorIdentity,
})
}
planResp := p.provider.PlanAction(providers.PlanActionRequest{
ActionType: req.ActionType,
ProposedActionData: configVal,
LinkedResources: inputLinkedResources,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics)
if planResp.Diagnostics.HasErrors() {
return resp, nil
}
resp.Deferred = convert.DeferredToProto(planResp.Deferred)
linkedResources := make([]*tfplugin6.PlanAction_Response_LinkedResource, 0, len(planResp.LinkedResources))
for _, linked := range planResp.LinkedResources {
plannedState, err := encodeDynamicValue6(linked.PlannedState, linked.PlannedState.Type())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
continue
}
plannedIdentity, err := encodeDynamicValue6(linked.PlannedIdentity, linked.PlannedIdentity.Type())
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
continue
}
linkedResources = append(linkedResources, &tfplugin6.PlanAction_Response_LinkedResource{
PlannedState: plannedState,
PlannedIdentity: &tfplugin6.ResourceIdentityData{
IdentityData: plannedIdentity,
},
})
}
resp.LinkedResources = linkedResources
return resp, nil
}
func (p *provider6) InvokeAction(req *tfplugin6.InvokeAction_Request, server tfplugin6.Provider_InvokeActionServer) error {
actionSchema, ok := p.schema.Actions[req.ActionType]
if !ok {
return fmt.Errorf("action schema not found for action %q", req.ActionType)
}
ty := actionSchema.ConfigSchema.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
return err
}
linkedResourceData := make([]providers.LinkedResourceInvokeData, 0, len(req.LinkedResources))
linkedResourceSchemas := actionSchema.LinkedResources()
if len(linkedResourceSchemas) != len(req.LinkedResources) {
return fmt.Errorf("expected %d linked resources for action %q, got %d", len(linkedResourceSchemas), req.ActionType, len(req.LinkedResources))
}
for i, lr := range req.LinkedResources {
resourceSchema, ok := p.schema.ResourceTypes[linkedResourceSchemas[i].TypeName]
if !ok {
return fmt.Errorf("linked resource schema not found for type %q in action %q", linkedResourceSchemas[i].TypeName, req.ActionType)
}
linkedResourceTy := resourceSchema.Body.ImpliedType()
linkedResourceIdentityTy := resourceSchema.Identity.ImpliedType()
priorState, err := decodeDynamicValue6(lr.PriorState, linkedResourceTy)
if err != nil {
return fmt.Errorf("failed to decode prior state for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
plannedState, err := decodeDynamicValue6(lr.PlannedState, linkedResourceTy)
if err != nil {
return fmt.Errorf("failed to decode planned state for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
config, err := decodeDynamicValue6(lr.Config, linkedResourceTy)
if err != nil {
return fmt.Errorf("failed to decode config for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
plannedIdentity := cty.NullVal(linkedResourceIdentityTy)
if lr.PlannedIdentity != nil && lr.PlannedIdentity.IdentityData != nil {
plannedIdentity, err = decodeDynamicValue6(lr.PlannedIdentity.IdentityData, linkedResourceIdentityTy)
if err != nil {
return fmt.Errorf("failed to decode planned identity for linked resource #%d (%q) in action %q: %w", i+1, linkedResourceSchemas[i].TypeName, req.ActionType, err)
}
}
linkedResourceData = append(linkedResourceData, providers.LinkedResourceInvokeData{
PriorState: priorState,
PlannedState: plannedState,
Config: config,
PlannedIdentity: plannedIdentity,
})
}
invokeResp := p.provider.InvokeAction(providers.InvokeActionRequest{
ActionType: req.ActionType,
PlannedActionData: configVal,
LinkedResources: linkedResourceData,
})
if invokeResp.Diagnostics.HasErrors() {
return invokeResp.Diagnostics.Err()
}
for invokeEvent := range invokeResp.Events {
switch invokeEvt := invokeEvent.(type) {
case providers.InvokeActionEvent_Progress:
server.Send(&tfplugin6.InvokeAction_Event{
Type: &tfplugin6.InvokeAction_Event_Progress_{
Progress: &tfplugin6.InvokeAction_Event_Progress{
Message: invokeEvt.Message,
},
},
})
case providers.InvokeActionEvent_Completed:
completed := &tfplugin6.InvokeAction_Event_Completed{
LinkedResources: []*tfplugin6.InvokeAction_Event_Completed_LinkedResource{},
}
completed.Diagnostics = convert.AppendProtoDiag(completed.Diagnostics, invokeEvt.Diagnostics)
for _, lr := range invokeEvt.LinkedResources {
newState, err := encodeDynamicValue6(lr.NewState, lr.NewState.Type())
if err != nil {
return fmt.Errorf("failed to encode new state for linked resource: %w", err)
}
newIdentity, err := encodeDynamicValue6(lr.NewIdentity, lr.NewIdentity.Type())
if err != nil {
return fmt.Errorf("failed to encode new identity for linked resource: %w", err)
}
completed.LinkedResources = append(completed.LinkedResources, &tfplugin6.InvokeAction_Event_Completed_LinkedResource{
NewState: newState,
NewIdentity: &tfplugin6.ResourceIdentityData{
IdentityData: newIdentity,
},
RequiresReplace: lr.RequiresReplace,
})
}
err := server.Send(&tfplugin6.InvokeAction_Event{
Type: &tfplugin6.InvokeAction_Event_Completed_{
Completed: completed,
},
})
if err != nil {
return fmt.Errorf("failed to send completed event: %w", err)
}
}
}
return nil
}
func (p *provider6) StopProvider(context.Context, *tfplugin6.StopProvider_Request) (*tfplugin6.StopProvider_Response, error) {
resp := &tfplugin6.StopProvider_Response{}
err := p.provider.Stop()

@ -32,3 +32,28 @@ func ProtoToDeferred(d *proto.Deferred) *providers.Deferred {
Reason: reason,
}
}
// DeferredToProto translates a providers.Deferred to a proto.Deferred.
func DeferredToProto(d *providers.Deferred) *proto.Deferred {
if d == nil {
return nil
}
var reason proto.Deferred_Reason
switch d.Reason {
case providers.DeferredReasonInvalid:
reason = proto.Deferred_UNKNOWN
case providers.DeferredReasonResourceConfigUnknown:
reason = proto.Deferred_RESOURCE_CONFIG_UNKNOWN
case providers.DeferredReasonProviderConfigUnknown:
reason = proto.Deferred_PROVIDER_CONFIG_UNKNOWN
case providers.DeferredReasonAbsentPrereq:
reason = proto.Deferred_ABSENT_PREREQ
default:
reason = proto.Deferred_UNKNOWN
}
return &proto.Deferred{
Reason: reason,
}
}

@ -105,6 +105,87 @@ func ProtoToProviderSchema(s *proto.Schema, id *proto.ResourceIdentitySchema) pr
return schema
}
func ProtoToExecutionOrder(s proto.ActionSchema_Lifecycle_ExecutionOrder) providers.ExecutionOrder {
switch s {
case proto.ActionSchema_Lifecycle_INVALID:
return providers.ExecutionOrderInvalid
case proto.ActionSchema_Lifecycle_BEFORE:
return providers.ExecutionOrderBefore
case proto.ActionSchema_Lifecycle_AFTER:
return providers.ExecutionOrderAfter
default:
panic("Unknown Execution Order, expected Invalid, Before, or After")
}
}
func ExecutionOrderToProto(s providers.ExecutionOrder) proto.ActionSchema_Lifecycle_ExecutionOrder {
switch s {
case providers.ExecutionOrderInvalid:
return proto.ActionSchema_Lifecycle_INVALID
case providers.ExecutionOrderBefore:
return proto.ActionSchema_Lifecycle_BEFORE
case providers.ExecutionOrderAfter:
return proto.ActionSchema_Lifecycle_AFTER
default:
panic("Unknown Execution Order, expected Invalid, Before, or After")
}
}
func ProtoToLinkedResource(lr *proto.ActionSchema_LinkedResource) providers.LinkedResourceSchema {
if lr == nil {
return providers.LinkedResourceSchema{}
}
return providers.LinkedResourceSchema{
TypeName: lr.TypeName,
}
}
func LinkedResourceToProto(lr providers.LinkedResourceSchema) *proto.ActionSchema_LinkedResource {
return &proto.ActionSchema_LinkedResource{
TypeName: lr.TypeName,
}
}
func ProtoToLinkedResources(lrs []*proto.ActionSchema_LinkedResource) []providers.LinkedResourceSchema {
linkedResources := make([]providers.LinkedResourceSchema, len(lrs))
for i, lr := range lrs {
linkedResources[i] = ProtoToLinkedResource(lr)
}
return linkedResources
}
func LinkedResourcesToProto(lrs []providers.LinkedResourceSchema) []*proto.ActionSchema_LinkedResource {
linkedResources := make([]*proto.ActionSchema_LinkedResource, len(lrs))
for i, lr := range lrs {
linkedResources[i] = LinkedResourceToProto(lr)
}
return linkedResources
}
func ProtoToActionSchema(s *proto.ActionSchema) providers.ActionSchema {
schema := providers.ActionSchema{
ConfigSchema: ProtoToConfigSchema(s.Schema.Block),
}
switch t := s.Type.(type) {
case *proto.ActionSchema_Unlinked_:
schema.Unlinked = &providers.UnlinkedAction{}
case *proto.ActionSchema_Lifecycle_:
schema.Lifecycle = &providers.LifecycleAction{
Exectues: ProtoToExecutionOrder(t.Lifecycle.Executes),
LinkedResource: ProtoToLinkedResource(t.Lifecycle.LinkedResource),
}
case *proto.ActionSchema_Linked_:
schema.Linked = &providers.LinkedAction{
LinkedResources: ProtoToLinkedResources(t.Linked.LinkedResources),
}
default:
panic("Unknown Action Type, expected schema to contain either Unlinked, Liefecycle, or Linked")
}
return schema
}
// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
// to a terraform *configschema.Block.
func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {

@ -104,6 +104,7 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
resp.DataSources = make(map[string]providers.Schema)
resp.EphemeralResourceTypes = make(map[string]providers.Schema)
resp.ListResourceTypes = make(map[string]providers.Schema)
resp.Actions = make(map[string]providers.ActionSchema)
// Some providers may generate quite large schemas, and the internal default
// grpc response size limit is 4MB. 64MB should cover most any use case, and
@ -191,6 +192,10 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
}
}
for name, action := range protoResp.ActionSchemas {
resp.Actions[name] = convert.ProtoToActionSchema(action)
}
if decls, err := convert.FunctionDeclsFromProto(protoResp.Functions); err == nil {
resp.Functions = decls
} else {
@ -1411,6 +1416,147 @@ func (p *GRPCProvider) DeleteState(r providers.DeleteStateRequest) providers.Del
panic("not implemented")
}
func (p *GRPCProvider) PlanAction(r providers.PlanActionRequest) (resp providers.PlanActionResponse) {
logger.Trace("GRPCProvider: PlanAction")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
actionSchema, ok := schema.Actions[r.ActionType]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown action %q", r.ActionType))
return resp
}
configMP, err := msgpack.Marshal(r.ProposedActionData, actionSchema.ConfigSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
linkedResources, err := linkedResourcePlanDataToProto(schema, actionSchema.LinkedResources(), r.LinkedResources)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
protoReq := &proto.PlanAction_Request{
ActionType: r.ActionType,
Config: &proto.DynamicValue{Msgpack: configMP},
LinkedResources: linkedResources,
ClientCapabilities: clientCapabilitiesToProto(r.ClientCapabilities),
}
protoResp, err := p.client.PlanAction(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
if resp.Diagnostics.HasErrors() {
return resp
}
resp.LinkedResources, err = protoToLinkedResourcePlans(schema, actionSchema.LinkedResources(), protoResp.LinkedResources)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
return resp
}
func (p *GRPCProvider) InvokeAction(r providers.InvokeActionRequest) (resp providers.InvokeActionResponse) {
logger.Trace("GRPCProvider: InvokeAction")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
actionSchema, ok := schema.Actions[r.ActionType]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown action %q", r.ActionType))
return resp
}
linkedResources, err := linkedResourceInvokeDataToProto(schema, actionSchema.LinkedResources(), r.LinkedResources)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
configMP, err := msgpack.Marshal(r.PlannedActionData, actionSchema.ConfigSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
protoReq := &proto.InvokeAction_Request{
ActionType: r.ActionType,
Config: &proto.DynamicValue{Msgpack: configMP},
LinkedResources: linkedResources,
}
protoClient, err := p.client.InvokeAction(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Events = func(yield func(providers.InvokeActionEvent) bool) {
logger.Trace("GRPCProvider: InvokeAction: streaming events")
for {
event, err := protoClient.Recv()
if err == io.EOF {
logger.Trace("GRPCProvider: InvokeAction: end of stream")
break
}
if err != nil {
// We handle this by returning a finished response with the error
// If the client errors we won't be receiving any more events.
yield(providers.InvokeActionEvent_Completed{
Diagnostics: grpcErr(err),
})
break
}
switch ev := event.Type.(type) {
case *proto.InvokeAction_Event_Progress_:
yield(providers.InvokeActionEvent_Progress{
Message: ev.Progress.Message,
})
case *proto.InvokeAction_Event_Completed_:
diags := convert.ProtoToDiagnostics(ev.Completed.Diagnostics)
linkedResources, err := protoToLinkedResourceResults(schema, actionSchema.LinkedResources(), ev.Completed.LinkedResources)
if err != nil {
diags = diags.Append(grpcErr(err))
}
yield(providers.InvokeActionEvent_Completed{
LinkedResources: linkedResources,
Diagnostics: diags,
})
default:
panic(fmt.Sprintf("unexpected event type %T in InvokeAction response", event.Type))
}
}
}
return resp
}
// closing the grpc connection is final, and terraform will call it at the end of every phase.
func (p *GRPCProvider) Close() error {
logger.Trace("GRPCProvider: Close")
@ -1457,3 +1603,174 @@ func clientCapabilitiesToProto(c providers.ClientCapabilities) *proto.ClientCapa
WriteOnlyAttributesAllowed: c.WriteOnlyAttributesAllowed,
}
}
func linkedResourcePlanDataToProto(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []providers.LinkedResourcePlanData) ([]*proto.PlanAction_Request_LinkedResource, error) {
protoLinkedResources := make([]*proto.PlanAction_Request_LinkedResource, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to encode and decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
priorStateMP, err := msgpack.Marshal(lr.PriorState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal prior state for linked resource %q: %w", linkedResourceType, err)
}
plannedStateMp, err := msgpack.Marshal(lr.PlannedState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal planned state for linked resource %q: %w", linkedResourceType, err)
}
configMp, err := msgpack.Marshal(lr.Config, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal config for linked resource %q: %w", linkedResourceType, err)
}
priorIdentityMp, err := msgpack.Marshal(lr.PriorIdentity, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal prior identity for linked resource %q: %w", linkedResourceType, err)
}
protoLinkedResources = append(protoLinkedResources, &proto.PlanAction_Request_LinkedResource{
PriorState: &proto.DynamicValue{Msgpack: priorStateMP},
PlannedState: &proto.DynamicValue{Msgpack: plannedStateMp},
Config: &proto.DynamicValue{Msgpack: configMp},
PriorIdentity: &proto.ResourceIdentityData{IdentityData: &proto.DynamicValue{Msgpack: priorIdentityMp}},
})
}
return protoLinkedResources, nil
}
func linkedResourceInvokeDataToProto(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []providers.LinkedResourceInvokeData) ([]*proto.InvokeAction_Request_LinkedResource, error) {
protoLinkedResources := make([]*proto.InvokeAction_Request_LinkedResource, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to encode and decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
priorStateMP, err := msgpack.Marshal(lr.PriorState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal prior state for linked resource %q: %w", linkedResourceType, err)
}
plannedStateMp, err := msgpack.Marshal(lr.PlannedState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal planned state for linked resource %q: %w", linkedResourceType, err)
}
configMp, err := msgpack.Marshal(lr.Config, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal config for linked resource %q: %w", linkedResourceType, err)
}
plannedIdentityMp, err := msgpack.Marshal(lr.PlannedIdentity, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal planned identity for linked resource %q: %w", linkedResourceType, err)
}
protoLinkedResources = append(protoLinkedResources, &proto.InvokeAction_Request_LinkedResource{
PriorState: &proto.DynamicValue{Msgpack: priorStateMP},
PlannedState: &proto.DynamicValue{Msgpack: plannedStateMp},
Config: &proto.DynamicValue{Msgpack: configMp},
PlannedIdentity: &proto.ResourceIdentityData{IdentityData: &proto.DynamicValue{Msgpack: plannedIdentityMp}},
})
}
return protoLinkedResources, nil
}
func protoToLinkedResourcePlans(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []*proto.PlanAction_Response_LinkedResource) ([]providers.LinkedResourcePlan, error) {
linkedResources := make([]providers.LinkedResourcePlan, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
plannedState, err := decodeDynamicValue(lr.PlannedState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode planned state for linked resource %q: %w", linkedResourceType, err)
}
plannedIdentity := cty.NullVal(resSchema.Identity.ImpliedType())
if lr.PlannedIdentity != nil && lr.PlannedIdentity.IdentityData != nil {
plannedIdentity, err = decodeDynamicValue(lr.PlannedIdentity.IdentityData, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode prior identity for linked resource %q: %w", linkedResourceType, err)
}
}
linkedResources = append(linkedResources, providers.LinkedResourcePlan{
PlannedState: plannedState,
PlannedIdentity: plannedIdentity,
})
}
return linkedResources, nil
}
func protoToLinkedResourceResults(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []*proto.InvokeAction_Event_Completed_LinkedResource) ([]providers.LinkedResourceResult, error) {
linkedResources := make([]providers.LinkedResourceResult, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
newState, err := decodeDynamicValue(lr.NewState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode planned state for linked resource %q: %w", linkedResourceType, err)
}
newIdentity := cty.NullVal(resSchema.Identity.ImpliedType())
if lr.NewIdentity != nil && lr.NewIdentity.IdentityData != nil {
newIdentity, err = decodeDynamicValue(lr.NewIdentity.IdentityData, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode prior identity for linked resource %q: %w", linkedResourceType, err)
}
}
linkedResources = append(linkedResources, providers.LinkedResourceResult{
NewState: newState,
NewIdentity: newIdentity,
RequiresReplace: lr.RequiresReplace,
})
}
return linkedResources, nil
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,50 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package mock_tfplugin5
import (
context "context"
"io"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
metadata "google.golang.org/grpc/metadata"
)
var _ proto.Provider_InvokeActionClient = (*MockInvokeProtoClient)(nil)
type MockInvokeProtoClient struct {
Events []*proto.InvokeAction_Event
}
func (m *MockInvokeProtoClient) Recv() (*proto.InvokeAction_Event, error) {
if len(m.Events) == 0 {
return nil, io.EOF
}
event := m.Events[0]
m.Events = m.Events[1:]
return event, nil
}
func (m *MockInvokeProtoClient) CloseSend() error {
return nil
}
func (m *MockInvokeProtoClient) Context() context.Context {
return context.TODO()
}
func (m *MockInvokeProtoClient) Header() (metadata.MD, error) {
return nil, nil
}
func (m *MockInvokeProtoClient) RecvMsg(k any) error {
return nil
}
func (m *MockInvokeProtoClient) SendMsg(k any) error {
return nil
}
func (m *MockInvokeProtoClient) Trailer() metadata.MD {
return nil
}

@ -32,3 +32,28 @@ func ProtoToDeferred(d *proto.Deferred) *providers.Deferred {
Reason: reason,
}
}
// DeferredToProto translates a providers.Deferred to a proto.Deferred.
func DeferredToProto(d *providers.Deferred) *proto.Deferred {
if d == nil {
return nil
}
var reason proto.Deferred_Reason
switch d.Reason {
case providers.DeferredReasonInvalid:
reason = proto.Deferred_UNKNOWN
case providers.DeferredReasonResourceConfigUnknown:
reason = proto.Deferred_RESOURCE_CONFIG_UNKNOWN
case providers.DeferredReasonProviderConfigUnknown:
reason = proto.Deferred_PROVIDER_CONFIG_UNKNOWN
case providers.DeferredReasonAbsentPrereq:
reason = proto.Deferred_ABSENT_PREREQ
default:
reason = proto.Deferred_UNKNOWN
}
return &proto.Deferred{
Reason: reason,
}
}

@ -111,6 +111,29 @@ func ProtoToProviderSchema(s *proto.Schema, id *proto.ResourceIdentitySchema) pr
return schema
}
func ProtoToActionSchema(s *proto.ActionSchema) providers.ActionSchema {
schema := providers.ActionSchema{
ConfigSchema: ProtoToConfigSchema(s.Schema.Block),
}
switch t := s.Type.(type) {
case *proto.ActionSchema_Unlinked_:
schema.Unlinked = &providers.UnlinkedAction{}
case *proto.ActionSchema_Lifecycle_:
schema.Lifecycle = &providers.LifecycleAction{
Exectues: ProtoToExecutionOrder(t.Lifecycle.Executes),
LinkedResource: ProtoToLinkedResource(t.Lifecycle.LinkedResource),
}
case *proto.ActionSchema_Linked_:
schema.Linked = &providers.LinkedAction{
LinkedResources: ProtoToLinkedResources(t.Linked.LinkedResources),
}
default:
panic("Unknown Action Type, expected schema to contain either Unlinked, Liefecycle, or Linked")
}
return schema
}
func ProtoToIdentitySchema(attributes []*proto.ResourceIdentitySchema_IdentityAttribute) *configschema.Object {
obj := &configschema.Object{
Attributes: make(map[string]*configschema.Attribute),
@ -363,3 +386,61 @@ func ResourceIdentitySchemaToProto(schema providers.IdentitySchema) *proto.Resou
IdentityAttributes: identityAttributes,
}
}
func ExecutionOrderToProto(s providers.ExecutionOrder) proto.ActionSchema_Lifecycle_ExecutionOrder {
switch s {
case providers.ExecutionOrderInvalid:
return proto.ActionSchema_Lifecycle_INVALID
case providers.ExecutionOrderBefore:
return proto.ActionSchema_Lifecycle_BEFORE
case providers.ExecutionOrderAfter:
return proto.ActionSchema_Lifecycle_AFTER
default:
panic("Unknown Execution Order, expected Invalid, Before, or After")
}
}
func ProtoToExecutionOrder(s proto.ActionSchema_Lifecycle_ExecutionOrder) providers.ExecutionOrder {
switch s {
case proto.ActionSchema_Lifecycle_INVALID:
return providers.ExecutionOrderInvalid
case proto.ActionSchema_Lifecycle_BEFORE:
return providers.ExecutionOrderBefore
case proto.ActionSchema_Lifecycle_AFTER:
return providers.ExecutionOrderAfter
default:
panic("Unknown Execution Order, expected Invalid, Before, or After")
}
}
func ProtoToLinkedResource(lr *proto.ActionSchema_LinkedResource) providers.LinkedResourceSchema {
if lr == nil {
return providers.LinkedResourceSchema{}
}
return providers.LinkedResourceSchema{
TypeName: lr.TypeName,
}
}
func LinkedResourceToProto(lr providers.LinkedResourceSchema) *proto.ActionSchema_LinkedResource {
return &proto.ActionSchema_LinkedResource{
TypeName: lr.TypeName,
}
}
func ProtoToLinkedResources(lrs []*proto.ActionSchema_LinkedResource) []providers.LinkedResourceSchema {
linkedResources := make([]providers.LinkedResourceSchema, len(lrs))
for i, lr := range lrs {
linkedResources[i] = ProtoToLinkedResource(lr)
}
return linkedResources
}
func LinkedResourcesToProto(lrs []providers.LinkedResourceSchema) []*proto.ActionSchema_LinkedResource {
linkedResources := make([]*proto.ActionSchema_LinkedResource, len(lrs))
for i, lr := range lrs {
linkedResources[i] = LinkedResourceToProto(lr)
}
return linkedResources
}

@ -105,6 +105,7 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
resp.EphemeralResourceTypes = make(map[string]providers.Schema)
resp.ListResourceTypes = make(map[string]providers.Schema)
resp.StateStores = make(map[string]providers.Schema)
resp.Actions = make(map[string]providers.ActionSchema)
// Some providers may generate quite large schemas, and the internal default
// grpc response size limit is 4MB. 64MB should cover most any use case, and
@ -203,6 +204,10 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
return resp
}
for name, action := range protoResp.ActionSchemas {
resp.Actions[name] = convert.ProtoToActionSchema(action)
}
if protoResp.ServerCapabilities != nil {
resp.ServerCapabilities.PlanDestroy = protoResp.ServerCapabilities.PlanDestroy
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
@ -1428,6 +1433,147 @@ func (p *GRPCProvider) Close() error {
return nil
}
func (p *GRPCProvider) PlanAction(r providers.PlanActionRequest) (resp providers.PlanActionResponse) {
logger.Trace("GRPCProvider: PlanAction")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
actionSchema, ok := schema.Actions[r.ActionType]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown action %q", r.ActionType))
return resp
}
configMP, err := msgpack.Marshal(r.ProposedActionData, actionSchema.ConfigSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
linkedResources, err := linkedResourcePlanDataToProto(schema, actionSchema.LinkedResources(), r.LinkedResources)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
protoReq := &proto6.PlanAction_Request{
ActionType: r.ActionType,
Config: &proto6.DynamicValue{Msgpack: configMP},
LinkedResources: linkedResources,
ClientCapabilities: clientCapabilitiesToProto(r.ClientCapabilities),
}
protoResp, err := p.client.PlanAction(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
if resp.Diagnostics.HasErrors() {
return resp
}
resp.LinkedResources, err = protoToLinkedResourcePlans(schema, actionSchema.LinkedResources(), protoResp.LinkedResources)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
return resp
}
func (p *GRPCProvider) InvokeAction(r providers.InvokeActionRequest) (resp providers.InvokeActionResponse) {
logger.Trace("GRPCProvider: InvokeAction")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
actionSchema, ok := schema.Actions[r.ActionType]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown action %q", r.ActionType))
return resp
}
linkedResources, err := linkedResourceInvokeDataToProto(schema, actionSchema.LinkedResources(), r.LinkedResources)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
configMP, err := msgpack.Marshal(r.PlannedActionData, actionSchema.ConfigSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
protoReq := &proto6.InvokeAction_Request{
ActionType: r.ActionType,
Config: &proto6.DynamicValue{Msgpack: configMP},
LinkedResources: linkedResources,
}
protoClient, err := p.client.InvokeAction(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Events = func(yield func(providers.InvokeActionEvent) bool) {
logger.Trace("GRPCProvider: InvokeAction: streaming events")
for {
event, err := protoClient.Recv()
if err == io.EOF {
logger.Trace("GRPCProvider: InvokeAction: end of stream")
break
}
if err != nil {
// We handle this by returning a finished response with the error
// If the client errors we won't be receiving any more events.
yield(providers.InvokeActionEvent_Completed{
Diagnostics: grpcErr(err),
})
break
}
switch ev := event.Type.(type) {
case *proto6.InvokeAction_Event_Progress_:
yield(providers.InvokeActionEvent_Progress{
Message: ev.Progress.Message,
})
case *proto6.InvokeAction_Event_Completed_:
diags := convert.ProtoToDiagnostics(ev.Completed.Diagnostics)
linkedResources, err := protoToLinkedResourceResults(schema, actionSchema.LinkedResources(), ev.Completed.LinkedResources)
if err != nil {
diags = diags.Append(grpcErr(err))
}
yield(providers.InvokeActionEvent_Completed{
LinkedResources: linkedResources,
Diagnostics: diags,
})
default:
panic(fmt.Sprintf("unexpected event type %T in InvokeAction response", event.Type))
}
}
}
return resp
}
// Decode a DynamicValue from either the JSON or MsgPack encoding.
func decodeDynamicValue(v *proto6.DynamicValue, ty cty.Type) (cty.Value, error) {
// always return a valid value
@ -1452,3 +1598,174 @@ func clientCapabilitiesToProto(c providers.ClientCapabilities) *proto6.ClientCap
WriteOnlyAttributesAllowed: c.WriteOnlyAttributesAllowed,
}
}
func linkedResourcePlanDataToProto(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []providers.LinkedResourcePlanData) ([]*proto6.PlanAction_Request_LinkedResource, error) {
protoLinkedResources := make([]*proto6.PlanAction_Request_LinkedResource, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to encode and decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
priorStateMP, err := msgpack.Marshal(lr.PriorState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal prior state for linked resource %q: %w", linkedResourceType, err)
}
plannedStateMp, err := msgpack.Marshal(lr.PlannedState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal planned state for linked resource %q: %w", linkedResourceType, err)
}
configMp, err := msgpack.Marshal(lr.Config, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal config for linked resource %q: %w", linkedResourceType, err)
}
priorIdentityMp, err := msgpack.Marshal(lr.PriorIdentity, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal prior identity for linked resource %q: %w", linkedResourceType, err)
}
protoLinkedResources = append(protoLinkedResources, &proto6.PlanAction_Request_LinkedResource{
PriorState: &proto6.DynamicValue{Msgpack: priorStateMP},
PlannedState: &proto6.DynamicValue{Msgpack: plannedStateMp},
Config: &proto6.DynamicValue{Msgpack: configMp},
PriorIdentity: &proto6.ResourceIdentityData{IdentityData: &proto6.DynamicValue{Msgpack: priorIdentityMp}},
})
}
return protoLinkedResources, nil
}
func linkedResourceInvokeDataToProto(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []providers.LinkedResourceInvokeData) ([]*proto6.InvokeAction_Request_LinkedResource, error) {
protoLinkedResources := make([]*proto6.InvokeAction_Request_LinkedResource, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to encode and decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
priorStateMP, err := msgpack.Marshal(lr.PriorState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal prior state for linked resource %q: %w", linkedResourceType, err)
}
plannedStateMp, err := msgpack.Marshal(lr.PlannedState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal planned state for linked resource %q: %w", linkedResourceType, err)
}
configMp, err := msgpack.Marshal(lr.Config, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal config for linked resource %q: %w", linkedResourceType, err)
}
plannedIdentityMp, err := msgpack.Marshal(lr.PlannedIdentity, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to marshal planned identity for linked resource %q: %w", linkedResourceType, err)
}
protoLinkedResources = append(protoLinkedResources, &proto6.InvokeAction_Request_LinkedResource{
PriorState: &proto6.DynamicValue{Msgpack: priorStateMP},
PlannedState: &proto6.DynamicValue{Msgpack: plannedStateMp},
Config: &proto6.DynamicValue{Msgpack: configMp},
PlannedIdentity: &proto6.ResourceIdentityData{IdentityData: &proto6.DynamicValue{Msgpack: plannedIdentityMp}},
})
}
return protoLinkedResources, nil
}
func protoToLinkedResourcePlans(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []*proto6.PlanAction_Response_LinkedResource) ([]providers.LinkedResourcePlan, error) {
linkedResources := make([]providers.LinkedResourcePlan, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
plannedState, err := decodeDynamicValue(lr.PlannedState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode planned state for linked resource %q: %w", linkedResourceType, err)
}
plannedIdentity := cty.NullVal(resSchema.Identity.ImpliedType())
if lr.PlannedIdentity != nil && lr.PlannedIdentity.IdentityData != nil {
plannedIdentity, err = decodeDynamicValue(lr.PlannedIdentity.IdentityData, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode prior identity for linked resource %q: %w", linkedResourceType, err)
}
}
linkedResources = append(linkedResources, providers.LinkedResourcePlan{
PlannedState: plannedState,
PlannedIdentity: plannedIdentity,
})
}
return linkedResources, nil
}
func protoToLinkedResourceResults(schema providers.GetProviderSchemaResponse, linkedResourceSchema []providers.LinkedResourceSchema, lrs []*proto6.InvokeAction_Event_Completed_LinkedResource) ([]providers.LinkedResourceResult, error) {
linkedResources := make([]providers.LinkedResourceResult, 0, len(lrs))
if len(lrs) != len(linkedResourceSchema) {
return nil, fmt.Errorf("mismatched number of linked resources: expected %d, got %d", len(linkedResourceSchema), len(lrs))
}
for i, lr := range lrs {
linkedResourceType := linkedResourceSchema[i].TypeName
// Currently we restrict linked resources to be within the same provider,
// therefore we can use the schema from the provider to decode the values
resSchema, ok := schema.ResourceTypes[linkedResourceType]
if !ok {
return nil, fmt.Errorf("unknown resource type %q for linked resource #%d", linkedResourceType, i)
}
newState, err := decodeDynamicValue(lr.NewState, resSchema.Body.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode planned state for linked resource %q: %w", linkedResourceType, err)
}
newIdentity := cty.NullVal(resSchema.Identity.ImpliedType())
if lr.NewIdentity != nil && lr.NewIdentity.IdentityData != nil {
newIdentity, err = decodeDynamicValue(lr.NewIdentity.IdentityData, resSchema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode prior identity for linked resource %q: %w", linkedResourceType, err)
}
}
linkedResources = append(linkedResources, providers.LinkedResourceResult{
NewState: newState,
NewIdentity: newIdentity,
RequiresReplace: lr.RequiresReplace,
})
}
return linkedResources, nil
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,50 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package mock_tfplugin6
import (
context "context"
"io"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
metadata "google.golang.org/grpc/metadata"
)
var _ proto.Provider_InvokeActionClient = (*MockInvokeProtoClient)(nil)
type MockInvokeProtoClient struct {
Events []*proto.InvokeAction_Event
}
func (m *MockInvokeProtoClient) Recv() (*proto.InvokeAction_Event, error) {
if len(m.Events) == 0 {
return nil, io.EOF
}
event := m.Events[0]
m.Events = m.Events[1:]
return event, nil
}
func (m *MockInvokeProtoClient) CloseSend() error {
return nil
}
func (m *MockInvokeProtoClient) Context() context.Context {
return context.TODO()
}
func (m *MockInvokeProtoClient) Header() (metadata.MD, error) {
return nil, nil
}
func (m *MockInvokeProtoClient) RecvMsg(k any) error {
return nil
}
func (m *MockInvokeProtoClient) SendMsg(k any) error {
return nil
}
func (m *MockInvokeProtoClient) Trailer() metadata.MD {
return nil
}

@ -72,6 +72,7 @@ func Provider() providers.Interface {
},
},
},
Actions: map[string]providers.ActionSchema{},
ServerCapabilities: providers.ServerCapabilities{
PlanDestroy: true,
GetProviderSchemaOptional: true,
@ -318,6 +319,18 @@ func (s simple) DeleteState(req providers.DeleteStateRequest) providers.DeleteSt
panic("not implemented")
}
func (s simple) PlanAction(providers.PlanActionRequest) providers.PlanActionResponse {
// Our schema doesn't include any actions, so it should be
// impossible to get here.
panic("PlanAction on provider that didn't declare any actions")
}
func (s simple) InvokeAction(providers.InvokeActionRequest) providers.InvokeActionResponse {
// Our schema doesn't include any actions, so it should be
// impossible to get here.
panic("InvokeAction on provider that didn't declare any actions")
}
func (s simple) Close() error {
return nil
}

@ -70,6 +70,7 @@ func Provider() providers.Interface {
},
},
},
Actions: map[string]providers.ActionSchema{},
ServerCapabilities: providers.ServerCapabilities{
PlanDestroy: true,
},
@ -280,6 +281,18 @@ func (s simple) DeleteState(req providers.DeleteStateRequest) providers.DeleteSt
panic("not implemented")
}
func (s simple) PlanAction(providers.PlanActionRequest) providers.PlanActionResponse {
// Our schema doesn't include any actions, so it should be
// impossible to get here.
panic("PlanAction on provider that didn't declare any actions")
}
func (s simple) InvokeAction(providers.InvokeActionRequest) providers.InvokeActionResponse {
// Our schema doesn't include any actions, so it should be
// impossible to get here.
panic("InvokeAction on provider that didn't declare any actions")
}
func (s simple) Close() error {
return nil
}

@ -433,6 +433,39 @@ func (m *Mock) DeleteState(req DeleteStateRequest) DeleteStateResponse {
return m.Provider.DeleteState(req)
}
func (m *Mock) PlanAction(request PlanActionRequest) PlanActionResponse {
plannedLinkedResources := make([]LinkedResourcePlan, 0, len(request.LinkedResources))
for i, linkedResource := range request.LinkedResources {
plannedLinkedResources[i] = LinkedResourcePlan{
PlannedState: linkedResource.PlannedState,
PlannedIdentity: linkedResource.PriorIdentity,
}
}
return PlanActionResponse{
LinkedResources: plannedLinkedResources,
Diagnostics: nil,
}
}
func (m *Mock) InvokeAction(request InvokeActionRequest) InvokeActionResponse {
linkedResources := make([]LinkedResourceResult, 0, len(request.LinkedResources))
for i, linkedResource := range request.LinkedResources {
linkedResources[i] = LinkedResourceResult{
NewState: linkedResource.PlannedState,
NewIdentity: linkedResource.PlannedIdentity,
}
}
return InvokeActionResponse{
Events: func(yield func(InvokeActionEvent) bool) {
yield(InvokeActionEvent_Completed{
LinkedResources: linkedResources,
})
},
Diagnostics: nil,
}
}
func (m *Mock) Close() error {
return m.Provider.Close()
}

@ -4,6 +4,8 @@
package providers
import (
"iter"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/configs/configschema"
@ -121,6 +123,14 @@ type Interface interface {
// DeleteState instructs a given state store to delete a specific state (i.e. a CE workspace)
DeleteState(DeleteStateRequest) DeleteStateResponse
// PlanAction plans an action to be invoked, providers might indicate potential drift and
// raise issues with the action configuration.
PlanAction(PlanActionRequest) PlanActionResponse
// InvokeAction invokes an action, providers return a stream of events that update terraform
// about the status of the action.
InvokeAction(InvokeActionRequest) InvokeActionResponse
// CancelAction cancels an action, triggering a graceful shutdown of the action.
// Close shuts down the plugin process if applicable.
Close() error
}
@ -158,6 +168,9 @@ type GetProviderSchemaResponse struct {
// StateStores maps the state store type name to that type's schema.
StateStores map[string]Schema
// Actions maps the name of the action to its schema.
Actions map[string]ActionSchema
// Diagnostics contains any warnings or errors from the method call.
Diagnostics tfdiags.Diagnostics
@ -184,6 +197,48 @@ type IdentitySchema struct {
Body *configschema.Object
}
type ExecutionOrder int
const (
ExecutionOrderInvalid ExecutionOrder = iota
ExecutionOrderBefore
ExecutionOrderAfter
)
type LinkedResourceSchema struct {
TypeName string
}
type UnlinkedAction struct{}
type LifecycleAction struct {
Exectues ExecutionOrder
LinkedResource LinkedResourceSchema
}
type LinkedAction struct {
LinkedResources []LinkedResourceSchema
}
type ActionSchema struct {
ConfigSchema *configschema.Block
// One of the following fields must be set, indicating the type of action.
Unlinked *UnlinkedAction
Lifecycle *LifecycleAction
Linked *LinkedAction
}
func (a ActionSchema) LinkedResources() []LinkedResourceSchema {
if a.Unlinked != nil {
return []LinkedResourceSchema{}
}
if a.Lifecycle != nil {
return []LinkedResourceSchema{a.Lifecycle.LinkedResource}
}
if a.Linked != nil {
return a.Linked.LinkedResources
}
panic("ActionSchema must have one of Unlinked, Lifecycle, or Linked set")
}
// Schema pairs a provider or resource schema with that schema's version.
// This is used to be able to upgrade the schema in UpgradeResourceState.
//
@ -812,3 +867,73 @@ type DeleteStateResponse struct {
// Diagnostics contains any warnings or errors from the method call.
Diagnostics tfdiags.Diagnostics
}
type LinkedResourcePlanData struct {
PriorState cty.Value
PlannedState cty.Value
Config cty.Value
PriorIdentity cty.Value
}
type LinkedResourcePlan struct {
PlannedState cty.Value
PlannedIdentity cty.Value
}
type LinkedResourceInvokeData struct {
PriorState cty.Value
PlannedState cty.Value
Config cty.Value
PlannedIdentity cty.Value
}
type LinkedResourceResult struct {
NewState cty.Value
NewIdentity cty.Value
RequiresReplace bool
}
type PlanActionRequest struct {
ActionType string
ProposedActionData cty.Value
LinkedResources []LinkedResourcePlanData
ClientCapabilities ClientCapabilities
}
type PlanActionResponse struct {
LinkedResources []LinkedResourcePlan
Deferred *Deferred
Diagnostics tfdiags.Diagnostics
}
type InvokeActionRequest struct {
ActionType string
LinkedResources []LinkedResourceInvokeData
PlannedActionData cty.Value
}
type InvokeActionResponse struct {
Events iter.Seq[InvokeActionEvent]
Diagnostics tfdiags.Diagnostics
}
type InvokeActionEvent interface {
isInvokeActionEvent()
}
// Completed Event
var _ InvokeActionEvent = &InvokeActionEvent_Completed{}
type InvokeActionEvent_Completed struct {
LinkedResources []LinkedResourceResult
Diagnostics tfdiags.Diagnostics
}
func (e InvokeActionEvent_Completed) isInvokeActionEvent() {}
// Progress Event
var _ InvokeActionEvent = &InvokeActionEvent_Progress{}
type InvokeActionEvent_Progress struct {
Message string
}
func (e InvokeActionEvent_Progress) isInvokeActionEvent() {}

@ -151,6 +151,16 @@ type MockProvider struct {
DeleteStateRequest providers.DeleteStateRequest
DeleteStateFn func(providers.DeleteStateRequest) providers.DeleteStateResponse
PlanActionCalled bool
PlanActionResponse providers.PlanActionResponse
PlanActionRequest providers.PlanActionRequest
PlanActionFn func(providers.PlanActionRequest) providers.PlanActionResponse
InvokeActionCalled bool
InvokeActionResponse providers.InvokeActionResponse
InvokeActionRequest providers.InvokeActionRequest
InvokeActionFn func(providers.InvokeActionRequest) providers.InvokeActionResponse
CloseCalled bool
CloseError error
}
@ -999,6 +1009,34 @@ func (p *MockProvider) DeleteState(r providers.DeleteStateRequest) (resp provide
return resp
}
func (p *MockProvider) PlanAction(r providers.PlanActionRequest) (resp providers.PlanActionResponse) {
p.Lock()
defer p.Unlock()
p.PlanActionCalled = true
p.PlanActionRequest = r
if p.PlanActionFn != nil {
return p.PlanActionFn(r)
}
return p.PlanActionResponse
}
func (p *MockProvider) InvokeAction(r providers.InvokeActionRequest) (resp providers.InvokeActionResponse) {
p.Lock()
defer p.Unlock()
p.InvokeActionCalled = true
p.InvokeActionRequest = r
if p.InvokeActionFn != nil {
return p.InvokeActionFn(r)
}
return p.InvokeActionResponse
}
func (p *MockProvider) Close() error {
defer p.beginWrite()()

@ -138,6 +138,14 @@ func (provider *mockProvider) DeleteState(req providers.DeleteStateRequest) prov
panic("not implemented in mock")
}
func (provider *mockProvider) PlanAction(providers.PlanActionRequest) providers.PlanActionResponse {
panic("not implemented in mock")
}
func (provider *mockProvider) InvokeAction(_ providers.InvokeActionRequest) providers.InvokeActionResponse {
panic("not implemented in mock")
}
func (provider *mockProvider) Close() error {
return nil // do nothing
}

@ -298,3 +298,31 @@ func (p *erroredProvider) DeleteState(providers.DeleteStateRequest) providers.De
Diagnostics: diags,
}
}
// PlanAction implements providers.Interface.
func (p *erroredProvider) PlanAction(providers.PlanActionRequest) providers.PlanActionResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Provider configuration is invalid",
"Cannot plan this action because its associated provider configuration is invalid.",
nil, // nil attribute path means the overall configuration block
))
return providers.PlanActionResponse{
Diagnostics: diags,
}
}
// InvokeAction implements providers.Interface.
func (p *erroredProvider) InvokeAction(_ providers.InvokeActionRequest) providers.InvokeActionResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Provider configuration is invalid",
"Cannot invoke this action because its associated provider configuration is invalid.",
nil, // nil attribute path means the overall configuration block
))
return providers.InvokeActionResponse{
Diagnostics: diags,
}
}

@ -316,6 +316,34 @@ func (o *offlineProvider) DeleteState(providers.DeleteStateRequest) providers.De
}
}
// PlanAction implements providers.Interface.
func (o *offlineProvider) PlanAction(request providers.PlanActionRequest) providers.PlanActionResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Called PlanAction on an unconfigured provider",
"Cannot plan this action because this provider is not configured. This is a bug in Terraform - please report it.",
nil, // nil attribute path means the overall configuration block
))
return providers.PlanActionResponse{
Diagnostics: diags,
}
}
// InvokeAction implements providers.Interface.
func (o *offlineProvider) InvokeAction(request providers.InvokeActionRequest) providers.InvokeActionResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Called InvokeAction on an unconfigured provider",
"Cannot invoke this action because this provider is not configured. This is a bug in Terraform - please report it.",
nil, // nil attribute path means the overall configuration block
))
return providers.InvokeActionResponse{
Diagnostics: diags,
}
}
func (o *offlineProvider) Close() error {
// pass the close call to the underlying unconfigured client
return o.unconfiguredClient.Close()

@ -364,6 +364,34 @@ func (u *unknownProvider) DeleteState(providers.DeleteStateRequest) providers.De
}
}
// PlanAction implements providers.Interface.
func (u *unknownProvider) PlanAction(request providers.PlanActionRequest) providers.PlanActionResponse {
return providers.PlanActionResponse{
Diagnostics: []tfdiags.Diagnostic{
tfdiags.AttributeValue(
tfdiags.Error,
"Provider configuration is unknown",
"Cannot plan this action because its associated provider configuration is unknown.",
nil, // nil attribute path means the overall configuration block
),
},
}
}
// InvokeAction implements providers.Interface.
func (u *unknownProvider) InvokeAction(request providers.InvokeActionRequest) providers.InvokeActionResponse {
return providers.InvokeActionResponse{
Diagnostics: []tfdiags.Diagnostic{
tfdiags.AttributeValue(
tfdiags.Error,
"Provider configuration is unknown",
"Cannot invoke this action because its associated provider configuration is unknown.",
nil, // nil attribute path means the overall configuration block
),
},
}
}
func (u *unknownProvider) Close() error {
// the underlying unconfiguredClient is managed elsewhere.
return nil

Loading…
Cancel
Save