-
Notifications
You must be signed in to change notification settings - Fork 132
Standalone caching client factory #1189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f1e859c
51822d4
9229764
373f50a
04b8fda
dac89c3
446d859
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,7 +72,7 @@ func (k ClientCacheKey) SameParent(other ClientCacheKey) (bool, error) { | |
| // | ||
| // See computeClientCacheKey for more details on how the client cache is derived | ||
| func ComputeClientCacheKeyFromClient(c Client) (ClientCacheKey, error) { | ||
| return computeClientCacheKey(c.GetVaultAuthObj(), c.GetVaultConnectionObj(), c.GetCredentialProvider().GetUID()) | ||
| return computeClientCacheKey(c.GetVaultAuthObj(), c.GetVaultConnectionObj(), c.GetCredentialProvider().GetUID(), false) | ||
| } | ||
|
|
||
| // ComputeClientCacheKeyFromObj for use in a ClientCache. It is derived from the configuration of obj. | ||
|
|
@@ -101,7 +101,7 @@ func ComputeClientCacheKeyFromObj(ctx context.Context, client ctrlclient.Client, | |
| return "", err | ||
| } | ||
|
|
||
| return computeClientCacheKey(authObj, connObj, provider.GetUID()) | ||
| return computeClientCacheKey(authObj, connObj, provider.GetUID(), false) | ||
| } | ||
|
|
||
| // ComputeClientCacheKeyFromMeta for use in a ClientCache. It is derived from the configuration of obj. | ||
|
|
@@ -134,7 +134,7 @@ func ComputeClientCacheKeyFromMeta(ctx context.Context, client ctrlclient.Client | |
| return "", err | ||
| } | ||
|
|
||
| return computeClientCacheKey(authObj, connObj, provider.GetUID()) | ||
| return computeClientCacheKey(authObj, connObj, provider.GetUID(), false) | ||
| } | ||
|
|
||
| // ComputeClientCacheKey for use in a ClientCache. It is derived by combining instances of | ||
|
|
@@ -153,38 +153,63 @@ func ComputeClientCacheKeyFromMeta(ctx context.Context, client ctrlclient.Client | |
| // allowed for Kubernetes resources, which is 63 characters. | ||
| // | ||
| // If the computed cache-key exceeds 63 characters, the limit imposed for Kubernetes resource names, | ||
| // or if any of the inputs do not coform in any way, and error will be returned. | ||
| func computeClientCacheKey(authObj *secretsv1beta1.VaultAuth, connObj *secretsv1beta1.VaultConnection, providerUID types.UID) (ClientCacheKey, error) { | ||
| // or if any of the inputs do not conform in any way, an error will be returned. | ||
| // | ||
| // Cache key generation is simpler when isStandalone is true (indicating a client without access to k8s resources): | ||
| // - Uses content-based hashes of authObj.Spec and connObj.Spec instead of UIDs | ||
| // - Generation is always 1 since objects aren't actual k8s resources | ||
| func computeClientCacheKey(authObj *secretsv1beta1.VaultAuth, connObj *secretsv1beta1.VaultConnection, providerUID types.UID, isStandalone bool) (ClientCacheKey, error) { | ||
| var errs error | ||
| method := authObj.Spec.Method | ||
| if method == "" { | ||
| errs = errors.Join(errs, fmt.Errorf("auth method is empty")) | ||
| } | ||
|
|
||
| // only used for duplicate UID detection, all values are ignored | ||
| seen := make(map[types.UID]int) | ||
| requireUIDLen := 36 | ||
| validateUID := func(name string, uid types.UID) { | ||
| if len(uid) != requireUIDLen { | ||
| errs = errors.Join(errs, fmt.Errorf("%w %d, must be %d", errorInvalidUIDLength, len(uid), requireUIDLen)) | ||
| var input string | ||
| if isStandalone { | ||
| // Standalone mode: use content-based hashes instead of K8s UIDs | ||
| if len(providerUID) == 0 { | ||
| errs = errors.Join(errs, fmt.Errorf("providerUID cannot be empty")) | ||
| } | ||
| if _, ok := seen[uid]; ok { | ||
| errs = errors.Join(errs, fmt.Errorf("%w %s", errorDuplicateUID, uid)) | ||
|
|
||
| if errs != nil { | ||
| return "", errs | ||
| } | ||
|
|
||
| authSpecHash := helpers.HashString(fmt.Sprintf("%+v", authObj.Spec)) | ||
| connSpecHash := helpers.HashString(fmt.Sprintf("%+v", connObj.Spec)) | ||
|
|
||
|
Comment on lines
+179
to
+181
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to validate the various Spec? Could the "repr" be empty or missing required values? |
||
| // Format: "authHash-1.connHash-1.providerUID" | ||
| // (generation is always 1 for standalone since we didn't fetch the auth and conn objects from a K8s resource) | ||
| input = fmt.Sprintf("%s-%d.%s-%d.%s", | ||
| authSpecHash, 1, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: is there a particular reason to interpolate the generation since it is always 1?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was just trying to match the format of the original client cache key, but I suppose it's not necessary! |
||
| connSpecHash, 1, providerUID) | ||
| } else { | ||
| // Normal VSO operation: use K8s resource UIDs with strict validation | ||
| seen := make(map[types.UID]int) | ||
| requireUIDLen := 36 | ||
| validateUID := func(name string, uid types.UID) { | ||
| if len(uid) != requireUIDLen { | ||
| errs = errors.Join(errs, fmt.Errorf("%w %d, must be %d", errorInvalidUIDLength, len(uid), requireUIDLen)) | ||
| } | ||
| if _, ok := seen[uid]; ok { | ||
| errs = errors.Join(errs, fmt.Errorf("%w %s", errorDuplicateUID, uid)) | ||
| } | ||
| seen[uid] = 1 | ||
| } | ||
| seen[uid] = 1 | ||
| } | ||
|
|
||
| validateUID("authUID", authObj.GetUID()) | ||
| validateUID("connUID", connObj.GetUID()) | ||
| validateUID("providerUID", providerUID) | ||
| validateUID("authUID", authObj.GetUID()) | ||
| validateUID("connUID", connObj.GetUID()) | ||
| validateUID("providerUID", providerUID) | ||
|
|
||
| if errs != nil { | ||
| return "", errs | ||
| } | ||
| if errs != nil { | ||
| return "", errs | ||
| } | ||
|
|
||
| input := fmt.Sprintf("%s-%d.%s-%d.%s", | ||
| authObj.GetUID(), authObj.GetGeneration(), | ||
| connObj.GetUID(), connObj.GetGeneration(), providerUID) | ||
| input = fmt.Sprintf("%s-%d.%s-%d.%s", | ||
| authObj.GetUID(), authObj.GetGeneration(), | ||
| connObj.GetUID(), connObj.GetGeneration(), providerUID) | ||
| } | ||
|
|
||
| key := strings.ToLower(method + "-" + helpers.HashString(input)) | ||
| if len(key) > 63 { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if you may have considered setting the UIDs from the Spec, before passing them into this function. That might obviate the need for the special casing here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, but got a bit scared off by the "read-only" description of the UID field (since traditionally the field is set by the API server).
I like your idea about making this whole thing a
standaloneClientFactoryinstead though; that may clean up all of this and also allow us to more definitively avoid breaking existing code.