@@ -607,12 +607,37 @@ func (s *GRPCProviderServer) ConfigureProvider(ctx context.Context, req *tfproto
607
607
// request scoped contexts, however this is a large undertaking for very large providers.
608
608
ctxHack := context .WithValue (ctx , StopContextKey , s .StopContext (context .Background ()))
609
609
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
+
610
615
logging .HelperSchemaTrace (ctx , "Calling downstream" )
611
616
diags := s .provider .Configure (ctxHack , config )
612
617
logging .HelperSchemaTrace (ctx , "Called downstream" )
613
618
614
619
resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , diags )
615
620
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
+
616
641
return resp , nil
617
642
}
618
643
@@ -632,6 +657,22 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re
632
657
}
633
658
schemaBlock := s .getResourceSchemaBlock (req .TypeName )
634
659
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
+
635
676
stateVal , err := msgpack .Unmarshal (req .CurrentState .MsgPack , schemaBlock .ImpliedType ())
636
677
if err != nil {
637
678
resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -731,6 +772,25 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
731
772
resp .UnsafeToUseLegacyTypeSystem = true
732
773
}
733
774
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
+
734
794
priorStateVal , err := msgpack .Unmarshal (req .PriorState .MsgPack , schemaBlock .ImpliedType ())
735
795
if err != nil {
736
796
resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -951,6 +1011,21 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot
951
1011
resp .RequiresReplace = append (resp .RequiresReplace , pathToAttributePath (p ))
952
1012
}
953
1013
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
+
954
1029
return resp , nil
955
1030
}
956
1031
@@ -1145,6 +1220,48 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
1145
1220
Type : req .TypeName ,
1146
1221
}
1147
1222
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
+
1148
1265
newInstanceStates , err := s .provider .ImportState (ctx , info , req .ID )
1149
1266
if err != nil {
1150
1267
resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -1254,6 +1371,32 @@ func (s *GRPCProviderServer) ReadDataSource(ctx context.Context, req *tfprotov5.
1254
1371
1255
1372
schemaBlock := s .getDatasourceSchemaBlock (req .TypeName )
1256
1373
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
+
1257
1400
configVal , err := msgpack .Unmarshal (req .Config .MsgPack , schemaBlock .ImpliedType ())
1258
1401
if err != nil {
1259
1402
resp .Diagnostics = convert .AppendProtoDiag (ctx , resp .Diagnostics , err )
@@ -1674,3 +1817,14 @@ func validateConfigNulls(ctx context.Context, v cty.Value, path cty.Path) []*tfp
1674
1817
1675
1818
return diags
1676
1819
}
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