Skip to content

GEP-33 addition : update garbage collection for images#35

Open
anton-paulovich wants to merge 7 commits into
masterfrom
feat/gep-33-garbage-collection
Open

GEP-33 addition : update garbage collection for images#35
anton-paulovich wants to merge 7 commits into
masterfrom
feat/gep-33-garbage-collection

Conversation

@anton-paulovich

@anton-paulovich anton-paulovich commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

The Garbage Collector has been upgraded to understand both legacy OCI tags and the new GEP-33 "Clean Versions". It now operates in three smart phases to keep the CloudProfile clean

  1. The Shield (Protection Mapping)
    Before deleting anything, the GC looks at all running Shoots to see what images they are using.

    • If a Shoot uses a legacy tag (e.g., 2254.0.0-metal-sci), that tag is protected.

    • [NEW] If a Shoot uses a clean version (e.g., 2254.0.0), the GC automatically acts as a shield. It digs into the providerConfig, finds all the long, raw OCI tags (Capability Flavors) backing that clean version, and protects all of them

  2. The Sweep (Standard Cleanup)
    The GC looks at the Keppel registry and identifies all tags that are older than the configured maxAge

    • If an old tag is not protected by the "Shield" in step 1, it is safely deleted from both the legacy spec.machineImages and the new providerConfig.capabilityFlavors arrays.
  3. The Deep Clean (Cascade Deletion)
    Previously, "Clean Version" entries (like the "folder" for 2254.0.0) would live in the CloudProfile forever, even after all of their actual OCI tags were deleted, causing infinite clutter.

    • [NEW] The GC now performs a cascade deletion. If it deletes an old OCI tag, it checks if the parent "Clean Version" has any other tags left. If the array of Capability Flavors drops to zero, the GC automatically deletes the empty Clean Version entirely from the CloudProfile

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved garbage collection for OCI-backed machine image versions to always attempt required version cleanup, while safely ignoring invalid versions.
    • Enhanced capability-flavor–aware cascade deletion so clean machine image versions are only removed when no capability flavors remain.
    • Strengthened protection of OCI tags referenced via Shoot and capability flavors to prevent unintended deletions.
  • Tests

    • Added additional registry and OCI listing test scaffolding.
    • Expanded garbage-collection test coverage for capability-flavor preservation and cascading deletion scenarios.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a86f5dda-8945-4946-b3da-ef14f23d09ee

📥 Commits

Reviewing files that changed from the base of the PR and between 0da9349 and 1e68b38.

📒 Files selected for processing (2)
  • controllers/managedcloudprofile_controller.go
  • controllers/managedcloudprofile_controller_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • controllers/managedcloudprofile_controller.go

📝 Walkthrough

Walkthrough

The ManagedCloudProfile controller's garbage collection is extended to handle capability-flavor lifecycle. deleteVersions now unconditionally runs, tracks clean versions retaining flavors, removes stale flavor entries from provider config when their backing OCI tags are GC'd, and cascade-deletes clean version entries only when all flavors are gone. getReferencedVersions additionally protects raw OCI tags backing Shoot-referenced clean versions. Four new test cases validate these behaviors.

Changes

GC Capability-Flavor Preservation and Cascade Deletion

Layer / File(s) Summary
Unconditional deleteVersions invocation and capability-flavor cascade deletion
controllers/managedcloudprofile_controller.go
Removes the conditional guard on len(versionsToDelete) so deleteVersions always runs. Introduces cleanVersionsWithFlavors map to track which clean versions retain flavors after deletions. Removes stale capability-flavor entries from provider config when their backing OCI tags are scheduled for deletion, deletes provider-config clean entries only when all flavors are removed, and cascade-deletes CloudProfile spec versions for fully dereferenced clean entries while legacy flat entries are deleted based on direct tag match.
Expand referenced set to protect raw OCI tags via capabilityFlavors
controllers/managedcloudprofile_controller.go
getReferencedVersions reloads the ProviderConfig and for each Shoot-referenced clean version adds raw OCI tag versions from capabilityFlavors to the referenced set, preventing garbage collection from deleting dependent OCI tags.
Test infrastructure and GC capability-flavor reconciliation cases
controllers/managedcloudprofile_controller_test.go
Adds emptyOCISource/emptyFactory test doubles for zero-version scenarios and fakeRegistryClientWithTags with preset OCI timestamps. Adds four GC reconciliation tests: raw OCI tag protection via Shoot reference, partial flavor deletion, full cascade deletion when all flavors are GC'd, and stale clean-version cleanup when provider config has zero flavors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • cobaltcore-dev/cloud-profile-sync#5: Introduces the GC workflow and reference-version filtering in managedcloudprofile_controller that this PR directly extends with capability-flavor and OCI-tag cascade handling.
  • cobaltcore-dev/cloud-profile-sync#8: Modifies the same reconcileGarbageCollection area in managedcloudprofile_controller.go, altering GC configuration and early-exit behavior that interacts with the version/flavor preservation logic introduced here.
  • cobaltcore-dev/cloud-profile-sync#17: Modifies getReferencedVersions to refactor how the referenced version set is computed, which overlaps with this PR's extension adding OCI clean-to-raw tag protection.

Suggested reviewers

  • defo89
  • fwiesel
  • adziauho

Poem

🐇 Hop, hop through the OCI tags so bright,
A clean version shielded when Shoots hold tight.
Old flavors pruned when their raw tags depart,
But cascade-delete waits for the very last part.
No orphaned image left in the night! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: updating garbage collection for images with GEP-33, which aligns with the PR's core objective of supporting both legacy and new clean-version image management.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gep-33-garbage-collection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
controllers/managedcloudprofile_controller.go (1)

212-216: 💤 Low value

Inconsistent map access may delete untracked provider config entries.

At lines 254-255, the single-value map access !cleanVersionsWithFlavors[mv.Version] returns true (delete) for entries not in the map. However, at lines 275-276, the code uses two-value access (hasRemainingFlavors, isCleanVersion := cleanVersionsWithFlavors[mv.Version]) which safely handles missing keys.

If a provider config entry has Image == "" but empty CapabilityFlavors (a malformed but possible state), it would be deleted here even though it wasn't explicitly tracked. For consistency and defensive coding, use the same two-value pattern:

🛡️ Suggested fix for consistent map access
-			// Clean version entry — delete if all flavors were removed.
-			return !cleanVersionsWithFlavors[mv.Version]
+			// Clean version entry — delete only if tracked and all flavors were removed.
+			hasRemainingFlavors, isCleanVersion := cleanVersionsWithFlavors[mv.Version]
+			return isCleanVersion && !hasRemainingFlavors

Also applies to: 226-256

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/managedcloudprofile_controller.go` around lines 212 - 216, The
cleanVersionsWithFlavors map uses inconsistent access patterns that can cause
unintended deletions. Replace the single-value map access pattern with the
two-value pattern throughout the code. Specifically, where the code currently
checks !cleanVersionsWithFlavors[mv.Version] (which treats missing keys as
false), change it to use the two-value assignment pattern like
hasRemainingFlavors, isCleanVersion := cleanVersionsWithFlavors[mv.Version] to
explicitly distinguish between keys that were never tracked in the map versus
keys that exist with a false value. This defensive approach ensures that
provider config entries which were never explicitly tracked are not accidentally
deleted due to malformed state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@controllers/managedcloudprofile_controller.go`:
- Around line 212-216: The cleanVersionsWithFlavors map uses inconsistent access
patterns that can cause unintended deletions. Replace the single-value map
access pattern with the two-value pattern throughout the code. Specifically,
where the code currently checks !cleanVersionsWithFlavors[mv.Version] (which
treats missing keys as false), change it to use the two-value assignment pattern
like hasRemainingFlavors, isCleanVersion := cleanVersionsWithFlavors[mv.Version]
to explicitly distinguish between keys that were never tracked in the map versus
keys that exist with a false value. This defensive approach ensures that
provider config entries which were never explicitly tracked are not accidentally
deleted due to malformed state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 520c50d4-6b87-478a-8233-333700588385

📥 Commits

Reviewing files that changed from the base of the PR and between b8ea370 and 0da9349.

📒 Files selected for processing (2)
  • controllers/managedcloudprofile_controller.go
  • controllers/managedcloudprofile_controller_test.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Upgrades the ManagedCloudProfile garbage collection logic to understand GEP-33 “clean versions” and their backing capability flavor tags, ensuring referenced images are protected and enabling cascade-deletion of empty clean-version entries.

Changes:

  • Extend GC reference detection to protect raw OCI tags backing clean versions referenced by Shoots.
  • Update deletion logic to delete old capability flavors and cascade-delete clean-version entries when their flavors are gone.
  • Add new reconciler tests covering protection, partial flavor cleanup, and cascade deletion.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
go.mod Updates the Go version directive to include a patch version.
controllers/managedcloudprofile_controller.go Implements clean-version shielding via capability flavors and cascade deletion during GC.
controllers/managedcloudprofile_controller_test.go Adds tests for preserving referenced flavors and cascading clean-version deletions.
.typos.toml Adds an ignore-regex for a specific identifier to avoid typos false positives.
.golangci.yaml Relaxes the go.mod go directive pattern to allow patch versions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread controllers/managedcloudprofile_controller.go
Comment thread .typos.toml
Comment thread go.mod
@github-actions

Copy link
Copy Markdown

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/cobaltcore-dev/cloud-profile-sync/controllers 65.56% (+7.02%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/cobaltcore-dev/cloud-profile-sync/controllers/managedcloudprofile_controller.go 65.56% (+7.02%) 241 (+36) 158 (+38) 83 (-2) 👍

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/cobaltcore-dev/cloud-profile-sync/controllers/managedcloudprofile_controller_test.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

controllers/managedcloudprofile_controller.go:285

  • deleteVersions() always performs a Update() call even when versionsToDelete is empty and there are no stale/empty clean-version entries to cascade-delete. Since reconcileGarbageCollection() now calls this method unconditionally, this can create unnecessary CloudProfile writes (resourceVersion churn) every GC run.
	if err := r.Update(ctx, &cp); err != nil {
		return err
	}
	return nil

Comment thread .typos.toml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants