-
Notifications
You must be signed in to change notification settings - Fork 184
fix(tenantresource): ensure original map is not modified in prepareAdditionalMetadata #1572
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
fix(tenantresource): ensure original map is not modified in prepareAdditionalMetadata #1572
Conversation
8c6a07d
to
ff20f02
Compare
@yehlo the idea behind prepareAdditionalMetadata is that we either return empty map or the original one if it's defined in the spec. We are not doing any modification at this point, so making copy of the map is unnecessary. Do you see any misbehaviour with this approach? |
Hi @Svarrogh1337 If the original map is returned instead of a copy it will be updated through the reconciliation leading to the This leads to the original map being mutated here: As the map is a pointer to the actual resource received here https://github.com/projectcapsule/capsule/blob/v0.10.0/controllers/resources/global.go#L51-L61 the map within k8s will be mutated aswell. This leads to the following misbehaviour: I create a apiVersion: capsule.clastix.io/v1beta2
kind: GlobalTenantResource
metadata:
name: my-replication
spec:
pruningOnDelete: true
resources:
- additionalMetadata:
annotations:
foo: "bar"
labels:
foo: "bar"
namespacedItems:
- apiVersion: v1
kind: Configmap
namespace: my-conf
selector:
matchLabels:
do-replicate: "true"
resyncPeriod: 60s
tenantSelector:
matchLabels:
receive-replicated: "true" While reconciling all tenants matching the While this map will then be added as metadata on resource creation, which results in the expected behaviour creating a configmap like this: apiVersion: v1
kind: LimitRange
metadata:
annotations:
foo: "bar"
capsule.clastix.io/tenant: my-tenant # mutated default tenant annotation
creationTimestamp: "2025-08-08T06:37:28Z"
labels:
foo: "bar"
capsule.clastix.io/resources: "0" # mutated default tenant label
capsule.clastix.io/tenant: my-tenant # mutated default tenant label
name: my-conf
namespace: my-tenant-namespace
data:
foo: "bar" It will also result in the original apiVersion: capsule.clastix.io/v1beta2
kind: GlobalTenantResource
metadata:
name: my-replication
spec:
pruningOnDelete: true
resources:
- additionalMetadata:
annotations:
foo: "bar"
capsule.clastix.io/tenant: my-tenant # added due to the map being a pointer
labels:
foo: "bar"
capsule.clastix.io/resources: "0" # added due to the map being a pointer
capsule.clastix.io/tenant: my-tenant # added due to the map being a pointer
namespacedItems:
- apiVersion: v1
kind: Configmap
namespace: my-conf
selector:
matchLabels:
do-replicate: "true"
resyncPeriod: 60s
tenantSelector:
matchLabels:
receive-replicated: "true" While this does not impact the replicated resource it results in diffs within GitOps processes resulting in endless reapplies. Note Please note that this only happens if Hope this helps clarifying what I tried to solve with this PR. |
c493375
to
c9a5a30
Compare
@Svarrogh1337 why do we need that function in the first place? As @yehlo the map given as args is always a reference, so any modification to it behaives like you know it from pointers. Cant we just drop the function and directly loop or even deepycopy in the metadata check? It really doesnt add much value to abstract this to a function. Probably even simpler is if we just pass it directly to createOrUpdate and check for nil in there. Blablabla. Just the above. |
I was thinking of dropping it in favour of DeepCopy indeed. @yehlo wdyt? |
@Svarrogh1337 sure I don't mind though maps don't support the I can import the Removing the if spec.AdditionalMetadata != nil {
if spec.AdditionalMetadata.Annotations != nil {
objAnnotations = maps.Clone(spec.AdditionalMetadata.Annotations)
} else {
objAnnotations = make(map[string]string)
}
if spec.AdditionalMetadata.Labels != nil {
objLabels = maps.Clone(spec.AdditionalMetadata.Labels)
} else {
objLabels = make(map[string]string)
}
} I'd recommend keeping the method to keep it DRY and either stick to looping over keys (as seen in this PR) or use I might aswell just be overthinking. Let me know what you'd prefer and I'll update accordingly. |
6c66d92
to
ac33d22
Compare
…ad of returning reference Signed-off-by: Joshua Leuenberger <[email protected]>
…psule#1569) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> chore(tenantresource): make linter happy Signed-off-by: Joshua Leuenberger <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…jectcapsule#1573) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…psule#1575) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ac33d22
to
620c489
Compare
Alright I now left it as is and fixed the commit message to not include camelCase. |
Good, i will add e2e tests for this in my refactor branch. Thanks for the contribution! 🚀 |
Saw that the original map was returned when
additionalMetadata
was specified leading to the controller adding the labels and annotations as specified here: https://github.com/projectcapsule/capsule/blob/v0.10.0/controllers/resources/processor.go#L133 to theglobaltenantresource.spec.resources.additionalMetadata
.Leading to the
tenantresource.spec
looking like this:I made sure to just create a new map and return this instead.