@@ -27,6 +27,7 @@ import (
2727 "net/http"
2828 "net/url"
2929 "path"
30+ "reflect"
3031 "strconv"
3132 "strings"
3233 "time"
@@ -49,6 +50,7 @@ import (
4950 runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
5051 runtimemetrics "sigs.k8s.io/cluster-api/internal/runtime/metrics"
5152 runtimeregistry "sigs.k8s.io/cluster-api/internal/runtime/registry"
53+ "sigs.k8s.io/cluster-api/internal/util/cache"
5254 "sigs.k8s.io/cluster-api/util"
5355)
5456
@@ -96,7 +98,7 @@ type Client interface {
9698 CallAllExtensions (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject ) error
9799
98100 // CallExtension calls the ExtensionHandler with the given name.
99- CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject ) error
101+ CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject , opts ... CallExtensionOption ) error
100102}
101103
102104var _ Client = & client {}
@@ -276,6 +278,44 @@ func aggregateSuccessfulResponses(aggregatedResponse runtimehooksv1.ResponseObje
276278 aggregatedResponse .SetMessage (strings .Join (messages , ", " ))
277279}
278280
281+ // CallExtensionOption is the interface for configuration that modifies CallExtensionOptions for a CallExtension call.
282+ type CallExtensionOption interface {
283+ // ApplyToOptions applies this configuration to the given CallExtensionOptions.
284+ ApplyToOptions (* CallExtensionOptions )
285+ }
286+
287+ // CallExtensionCacheEntry is a cache entry for the cache that can be used with the CallExtension call via
288+ // the WithCaching option.
289+ type CallExtensionCacheEntry struct {
290+ CacheKey string
291+ Response runtimehooksv1.ResponseObject
292+ }
293+
294+ // Key returns the cache key of a CallExtensionCacheEntry.
295+ func (c CallExtensionCacheEntry ) Key () string {
296+ return c .CacheKey
297+ }
298+
299+ // WithCaching enables caching for the CallExtension call.
300+ type WithCaching struct {
301+ Cache cache.Cache [CallExtensionCacheEntry ]
302+ CacheKeyFunc func (* runtimeregistry.ExtensionRegistration , runtimehooksv1.RequestObject ) string
303+ }
304+
305+ // ApplyToOptions applies WithCaching to the given CallExtensionOptions.
306+ func (w WithCaching ) ApplyToOptions (in * CallExtensionOptions ) {
307+ in .WithCaching = true
308+ in .Cache = w .Cache
309+ in .CacheKeyFunc = w .CacheKeyFunc
310+ }
311+
312+ // CallExtensionOptions contains the options for the CallExtension call.
313+ type CallExtensionOptions struct {
314+ WithCaching bool
315+ Cache cache.Cache [CallExtensionCacheEntry ]
316+ CacheKeyFunc func (* runtimeregistry.ExtensionRegistration , runtimehooksv1.RequestObject ) string
317+ }
318+
279319// CallExtension makes the call to the extension with the given name.
280320// The response object passed will be updated with the response of the call.
281321// An error is returned if the extension is not compatible with the hook.
@@ -288,7 +328,13 @@ func aggregateSuccessfulResponses(aggregatedResponse runtimehooksv1.ResponseObje
288328// Nb. FailurePolicy does not affect the following kinds of errors:
289329// - Internal errors. Examples: hooks is incompatible with ExtensionHandler, ExtensionHandler information is missing.
290330// - Error when ExtensionHandler returns a response with `Status` set to `Failure`.
291- func (c * client ) CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject ) error {
331+ func (c * client ) CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject , opts ... CallExtensionOption ) error {
332+ // Calculate the options.
333+ options := & CallExtensionOptions {}
334+ for _ , opt := range opts {
335+ opt .ApplyToOptions (options )
336+ }
337+
292338 log := ctrl .LoggerFrom (ctx ).WithValues ("extensionHandler" , name , "hook" , runtimecatalog .HookName (hook ))
293339 ctx = ctrl .LoggerInto (ctx , log )
294340 hookGVH , err := c .catalog .GroupVersionHook (hook )
@@ -331,15 +377,31 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
331377 // Prepare the request by merging the settings in the registration with the settings in the request.
332378 request = cloneAndAddSettings (request , registration .Settings )
333379
334- opts := & httpCallOptions {
380+ var cacheKey string
381+ if options .WithCaching {
382+ // Return a cached response if response is cached.
383+ cacheKey = options .CacheKeyFunc (registration , request )
384+ if cacheEntry , ok := options .Cache .Has (cacheKey ); ok {
385+ // Set response to cacheEntry.Response.
386+ outVal := reflect .ValueOf (response )
387+ cacheVal := reflect .ValueOf (cacheEntry .Response )
388+ if ! cacheVal .Type ().AssignableTo (outVal .Type ()) {
389+ return fmt .Errorf ("failed to call extension handler %q: cached response of type %s instead of type %s" , name , cacheVal .Type (), outVal .Type ())
390+ }
391+ reflect .Indirect (outVal ).Set (reflect .Indirect (cacheVal ))
392+ return nil
393+ }
394+ }
395+
396+ httpOpts := & httpCallOptions {
335397 catalog : c .catalog ,
336398 config : registration .ClientConfig ,
337399 registrationGVH : registration .GroupVersionHook ,
338400 hookGVH : hookGVH ,
339401 name : strings .TrimSuffix (registration .Name , "." + registration .ExtensionConfigName ),
340402 timeout : timeoutDuration ,
341403 }
342- err = httpCall (ctx , request , response , opts )
404+ err = httpCall (ctx , request , response , httpOpts )
343405 if err != nil {
344406 // If the error is errCallingExtensionHandler then apply failure policy to calculate
345407 // the effective result of the operation.
@@ -368,6 +430,14 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
368430 log .V (4 ).Info ("Extension handler returned success response" )
369431 }
370432
433+ if options .WithCaching {
434+ // Add response to the cache.
435+ options .Cache .Add (CallExtensionCacheEntry {
436+ CacheKey : cacheKey ,
437+ Response : response ,
438+ })
439+ }
440+
371441 // Received a successful response from the extension handler. The `response` object
372442 // has been populated with the result. Return no error.
373443 return nil
0 commit comments