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