-
Notifications
You must be signed in to change notification settings - Fork 25
Add opt-in cleanup policy for related-resource copies #181
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -11,3 +11,4 @@ | |
| .DS_Store | ||
| /docs/__pycache__ | ||
| .idea/ | ||
| temp/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -158,3 +158,48 @@ func (k objectKey) Annotations() labels.Set { | |
|
|
||
| return s | ||
| } | ||
|
|
||
| // relatedCopyLabels builds the provenance labels put on a destination copy of a related resource. | ||
| // They tie the copy to its owning primary object and the related resource identifier so that all | ||
| // copies of a given (primary, identifier) can be enumerated via relatedCopySelector. Names and | ||
| // namespaces are hashed because they can exceed the label value limit or contain invalid | ||
| // characters; the identifier is validated to be alphanumeric by the API, so it is used verbatim. | ||
| // The agent name (when set) is included so that each agent only ever prunes its own copies. | ||
| func relatedCopyLabels(primary ctrlruntimeclient.Object, identifier, agentName string) map[string]string { | ||
| set := map[string]string{ | ||
| relatedPrimaryNameHashLabel: crypto.Hash(primary.GetName()), | ||
| relatedIdentifierLabel: identifier, | ||
|
Member
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 ran this branch's
So an identifier the CRD accepts can make the copy's labels invalid, and the apply/create then fails — sync silently breaks for that related resource. (New failure surface specifically for |
||
| } | ||
|
|
||
| if namespace := primary.GetNamespace(); namespace != "" { | ||
| set[relatedPrimaryNamespaceHashLabel] = crypto.Hash(namespace) | ||
| } | ||
|
|
||
| if agentName != "" { | ||
| set[agentNameLabel] = agentName | ||
| } | ||
|
|
||
| return set | ||
| } | ||
|
|
||
| // relatedCopyAnnotations builds the human-facing provenance annotations (plaintext primary | ||
| // namespace/name) for a destination copy of a related resource. | ||
| func relatedCopyAnnotations(primary ctrlruntimeclient.Object) map[string]string { | ||
| set := map[string]string{ | ||
| relatedPrimaryNameAnnotation: primary.GetName(), | ||
| } | ||
|
|
||
| if namespace := primary.GetNamespace(); namespace != "" { | ||
| set[relatedPrimaryNamespaceAnnotation] = namespace | ||
| } | ||
|
|
||
| return set | ||
| } | ||
|
|
||
| // relatedCopySelector returns a label selector matching exactly the destination copies created for | ||
| // the given primary object and related resource identifier (scoped to the agent when set). Because | ||
| // it mirrors relatedCopyLabels, only objects the agent itself labelled are ever selected, so | ||
| // hand-created objects are never in scope for pruning. | ||
| func relatedCopySelector(primary ctrlruntimeclient.Object, identifier, agentName string) labels.Selector { | ||
| return labels.SelectorFromSet(relatedCopyLabels(primary, identifier, agentName)) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,14 @@ type objectSyncer struct { | |
| blockSourceDeletion bool | ||
| // whether or not to place sync-related metadata on the destination object | ||
| metadataOnDestination bool | ||
| // destLabels are additional labels stamped onto the destination object on every apply. | ||
| // Used to attach related-resource provenance (owning primary + identifier) to copies so | ||
| // that they can later be enumerated and pruned. These are applied additively and never | ||
| // clobber labels the copy already carries. | ||
| destLabels map[string]string | ||
| // destAnnotations are additional annotations stamped onto the destination object on every | ||
| // apply; the human-facing counterpart to destLabels. | ||
| destAnnotations map[string]string | ||
| // optional mutations for both directions of the sync | ||
| mutator mutation.Mutator | ||
| // stateStore is capable of remembering the state of a Kubernetes object | ||
|
|
@@ -420,6 +428,9 @@ func (s *objectSyncer) applyServerSide(ctx context.Context, log *zap.SugaredLogg | |
| s.labelWithAgent(desired) | ||
| } | ||
|
|
||
| // stamp any additional provenance labels/annotations (e.g. related-resource ownership) | ||
| s.ensureDestMetadata(desired) | ||
|
|
||
| // do not claim ownership of subresource content via the main resource | ||
| // apply; status is reconciled separately and "scale" is never written. | ||
| s.removeSubresources(desired) | ||
|
|
@@ -503,6 +514,9 @@ func (s *objectSyncer) ensureDestinationObject(ctx context.Context, log *zap.Sug | |
| s.labelWithAgent(destObj) | ||
| } | ||
|
|
||
| // stamp any additional provenance labels/annotations (e.g. related-resource ownership) | ||
| s.ensureDestMetadata(destObj) | ||
|
|
||
| // finally, we can create the destination object | ||
| objectLog := log.With("dest-object", newObjectKey(destObj, dest.clusterName, logicalcluster.None)) | ||
| objectLog.Debugw("Creating destination object…") | ||
|
|
@@ -551,6 +565,7 @@ func (s *objectSyncer) adoptExistingDestinationObject(ctx context.Context, log * | |
| ensureAnnotations(existingDestObj, sourceKey.Annotations()) | ||
|
|
||
| s.labelWithAgent(existingDestObj) | ||
| s.ensureDestMetadata(existingDestObj) | ||
|
|
||
| if err := dest.client.Update(ctx, existingDestObj); err != nil { | ||
| return fmt.Errorf("failed to upsert current destination object labels: %w", err) | ||
|
|
@@ -654,3 +669,16 @@ func (s *objectSyncer) labelWithAgent(obj *unstructured.Unstructured) { | |
| ensureLabels(obj, map[string]string{agentNameLabel: s.agentName}) | ||
| } | ||
| } | ||
|
|
||
| // ensureDestMetadata stamps the syncer's additional destination labels/annotations onto the given | ||
| // object. It is additive (it never removes labels the object already carries) and is used to | ||
| // attach related-resource provenance to destination copies. | ||
| func (s *objectSyncer) ensureDestMetadata(obj *unstructured.Unstructured) { | ||
|
Member
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.
|
||
| if len(s.destLabels) > 0 { | ||
| ensureLabels(obj, s.destLabels) | ||
| } | ||
|
|
||
| if len(s.destAnnotations) > 0 { | ||
| ensureAnnotations(obj, s.destAnnotations) | ||
| } | ||
| } | ||
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.
The label set is
{primary-name-hash, primary-namespace-hash, identifier, agent}, and the pruneListis typed only by projected Kind — nothing encodes the primary's GVK or the owning PublishedResource. Two PublishedResources that project copies to the same Kind, reuse the same identifier, and have primaries sharing name+namespace produce byte-identical labels, so one primary's prune selector matches the other's copies (verified: identical hashes, selector matches). Consider adding the PublishedResource name and/or primary GVK to the provenance labels.