@@ -607,12 +607,37 @@ func (s *GRPCProviderServer) ConfigureProvider(ctx context.Context, req *tfproto
607607 // request scoped contexts, however this is a large undertaking for very large providers.
608608 ctxHack := context .WithValue (ctx , StopContextKey , s .StopContext (context .Background ()))
609609
610+ // NOTE: This is a hack to pass the deferral_allowed field from the Terraform client to the
611+ // underlying (provider).Configure function, which cannot be changed because the function
612+ // signature is public. (╯°□°)╯︵ ┻━┻
613+ s .provider .deferralAllowed = configureDeferralAllowed (req .ClientCapabilities )
614+
610615 logging .HelperSchemaTrace (ctx , "Calling downstream" )
611616 diags := s .provider .Configure (ctxHack , config )
612617 logging .HelperSchemaTrace (ctx , "Called downstream" )
613618
614619 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , diags )
615620
621+ if s .provider .providerDeferred != nil {
622+ // Check if a deferred response was incorrectly set on the provider. This would cause an error during later RPCs.
623+ if ! s .provider .deferralAllowed {
624+ resp .Diagnostics = append (resp .Diagnostics , & tfprotov5.Diagnostic {
625+ Severity : tfprotov5 .DiagnosticSeverityError ,
626+ Summary : "Invalid Deferred Provider Response" ,
627+ Detail : "Provider configured a deferred response for all resources and data sources but the Terraform request " +
628+ "did not indicate support for deferred actions. This is an issue with the provider and should be reported to the provider developers." ,
629+ })
630+ } else {
631+ logging .HelperSchemaDebug (
632+ ctx ,
633+ "Provider has configured a deferred response, all associated resources and data sources will automatically return a deferred response." ,
634+ map [string ]interface {}{
635+ logging .KeyDeferredReason : s .provider .providerDeferred .Reason .String (),
636+ },
637+ )
638+ }
639+ }
640+
616641 return resp , nil
617642}
618643
@@ -632,6 +657,22 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
632657 }
633658 schemaBlock := s .getResourceSchemaBlock (req .TypeName )
634659
660+ if s .provider .providerDeferred != nil {
661+ logging .HelperSchemaDebug (
662+ ctx ,
663+ "Provider has deferred response configured, automatically returning deferred response." ,
664+ map [string ]interface {}{
665+ logging .KeyDeferredReason : s .provider .providerDeferred .Reason .String (),
666+ },
667+ )
668+
669+ resp .NewState = req .CurrentState
670+ resp .Deferred = & tfprotov5.Deferred {
671+ Reason : tfprotov5 .DeferredReason (s .provider .providerDeferred .Reason ),
672+ }
673+ return resp , nil
674+ }
675+
635676 stateVal , err := msgpack .Unmarshal (req .CurrentState .MsgPack , schemaBlock .ImpliedType ())
636677 if err != nil {
637678 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -731,6 +772,25 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
731772 resp .UnsafeToUseLegacyTypeSystem = true
732773 }
733774
775+ // Provider deferred response is present and the resource hasn't opted-in to CustomizeDiff being called, return early
776+ // with proposed new state as a best effort for PlannedState.
777+ if s .provider .providerDeferred != nil && ! res .ResourceBehavior .ProviderDeferred .EnablePlanModification {
778+ logging .HelperSchemaDebug (
779+ ctx ,
780+ "Provider has deferred response configured, automatically returning deferred response." ,
781+ map [string ]interface {}{
782+ logging .KeyDeferredReason : s .provider .providerDeferred .Reason .String (),
783+ },
784+ )
785+
786+ resp .PlannedState = req .ProposedNewState
787+ resp .PlannedPrivate = req .PriorPrivate
788+ resp .Deferred = & tfprotov5.Deferred {
789+ Reason : tfprotov5 .DeferredReason (s .provider .providerDeferred .Reason ),
790+ }
791+ return resp , nil
792+ }
793+
734794 priorStateVal , err := msgpack .Unmarshal (req .PriorState .MsgPack , schemaBlock .ImpliedType ())
735795 if err != nil {
736796 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -951,6 +1011,21 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
9511011 resp .RequiresReplace = append (resp .RequiresReplace , pathToAttributePath (p ))
9521012 }
9531013
1014+ // Provider deferred response is present, add the deferred response alongside the provider-modified plan
1015+ if s .provider .providerDeferred != nil {
1016+ logging .HelperSchemaDebug (
1017+ ctx ,
1018+ "Provider has deferred response configured, returning deferred response with modified plan." ,
1019+ map [string ]interface {}{
1020+ logging .KeyDeferredReason : s .provider .providerDeferred .Reason .String (),
1021+ },
1022+ )
1023+
1024+ resp .Deferred = & tfprotov5.Deferred {
1025+ Reason : tfprotov5 .DeferredReason (s .provider .providerDeferred .Reason ),
1026+ }
1027+ }
1028+
9541029 return resp , nil
9551030}
9561031
@@ -1145,6 +1220,48 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
11451220 Type : req .TypeName ,
11461221 }
11471222
1223+ if s .provider .providerDeferred != nil {
1224+ logging .HelperSchemaDebug (
1225+ ctx ,
1226+ "Provider has deferred response configured, automatically returning deferred response." ,
1227+ map [string ]interface {}{
1228+ logging .KeyDeferredReason : s .provider .providerDeferred .Reason .String (),
1229+ },
1230+ )
1231+
1232+ // The logic for ensuring the resource type is supported by this provider is inside of (provider).ImportState
1233+ // We need to check to ensure the resource type is supported before using the schema
1234+ _ , ok := s .provider .ResourcesMap [req .TypeName ]
1235+ if ! ok {
1236+ resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , fmt .Errorf ("unknown resource type: %s" , req .TypeName ))
1237+ return resp , nil
1238+ }
1239+
1240+ // Since we are automatically deferring, send back an unknown value for the imported object
1241+ schemaBlock := s .getResourceSchemaBlock (req .TypeName )
1242+ unknownVal := cty .UnknownVal (schemaBlock .ImpliedType ())
1243+ unknownStateMp , err := msgpack .Marshal (unknownVal , schemaBlock .ImpliedType ())
1244+ if err != nil {
1245+ resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
1246+ return resp , nil
1247+ }
1248+
1249+ resp .ImportedResources = []* tfprotov5.ImportedResource {
1250+ {
1251+ TypeName : req .TypeName ,
1252+ State : & tfprotov5.DynamicValue {
1253+ MsgPack : unknownStateMp ,
1254+ },
1255+ },
1256+ }
1257+
1258+ resp .Deferred = & tfprotov5.Deferred {
1259+ Reason : tfprotov5 .DeferredReason (s .provider .providerDeferred .Reason ),
1260+ }
1261+
1262+ return resp , nil
1263+ }
1264+
11481265 newInstanceStates , err := s .provider .ImportState (ctx , info , req .ID )
11491266 if err != nil {
11501267 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -1254,6 +1371,32 @@ func (s *GRPCProviderServer) ReadDataSource(ctx context.Context, req *tfprotov5.
12541371
12551372 schemaBlock := s .getDatasourceSchemaBlock (req .TypeName )
12561373
1374+ if s .provider .providerDeferred != nil {
1375+ logging .HelperSchemaDebug (
1376+ ctx ,
1377+ "Provider has deferred response configured, automatically returning deferred response." ,
1378+ map [string ]interface {}{
1379+ logging .KeyDeferredReason : s .provider .providerDeferred .Reason .String (),
1380+ },
1381+ )
1382+
1383+ // Send an unknown value for the data source
1384+ unknownVal := cty .UnknownVal (schemaBlock .ImpliedType ())
1385+ unknownStateMp , err := msgpack .Marshal (unknownVal , schemaBlock .ImpliedType ())
1386+ if err != nil {
1387+ resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
1388+ return resp , nil
1389+ }
1390+
1391+ resp .State = & tfprotov5.DynamicValue {
1392+ MsgPack : unknownStateMp ,
1393+ }
1394+ resp .Deferred = & tfprotov5.Deferred {
1395+ Reason : tfprotov5 .DeferredReason (s .provider .providerDeferred .Reason ),
1396+ }
1397+ return resp , nil
1398+ }
1399+
12571400 configVal , err := msgpack .Unmarshal (req .Config .MsgPack , schemaBlock .ImpliedType ())
12581401 if err != nil {
12591402 resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -1674,3 +1817,14 @@ func validateConfigNulls(ctx context.Context, v cty.Value, path cty.Path) []*tfp
16741817
16751818 return diags
16761819}
1820+
1821+ // Helper function that check a ConfigureProviderClientCapabilities struct to determine if a deferred response can be
1822+ // returned to the Terraform client. If no ConfigureProviderClientCapabilities have been passed from the client, then false
1823+ // is returned.
1824+ func configureDeferralAllowed (in * tfprotov5.ConfigureProviderClientCapabilities ) bool {
1825+ if in == nil {
1826+ return false
1827+ }
1828+
1829+ return in .DeferralAllowed
1830+ }
0 commit comments