Skip to content

feat(ctf): implement garbage collection via deletion in CTF index#2230

Closed
chrisbleyerSAP wants to merge 2 commits into
open-component-model:mainfrom
chrisbleyerSAP:ctf-gc-deletion-fix
Closed

feat(ctf): implement garbage collection via deletion in CTF index#2230
chrisbleyerSAP wants to merge 2 commits into
open-component-model:mainfrom
chrisbleyerSAP:ctf-gc-deletion-fix

Conversation

@chrisbleyerSAP

Copy link
Copy Markdown
Contributor

Replace tag-clearing with entry deletion when tags are moved to different digests. This prevents unbounded index growth and aligns CTF behavior with OCI registry garbage collection semantics.

When a tag is moved from one digest to another, the old entry is now completely removed instead of having its tag cleared. This provides automatic garbage collection without requiring external cleanup mechanisms.

Version aliasing (multiple tags per digest) and the untagged-first pattern (tagging artifacts after creation) continue to work as expected.

@chrisbleyerSAP chrisbleyerSAP requested a review from a team as a code owner April 9, 2026 11:11
@github-actions github-actions Bot added kind/feature new feature, enhancement, improvement, extension size/m Medium labels Apr 9, 2026
@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@chrisbleyerSAP has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 21 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 17 minutes and 21 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 760bb283-d8e7-48dc-b71c-a10860893f14

📥 Commits

Reviewing files that changed from the base of the PR and between 25a4012 and e3ad4f3.

📒 Files selected for processing (2)
  • bindings/go/ctf/index/v1/index.go
  • bindings/go/ctf/index/v1/index_test.go
📝 Walkthrough

Walkthrough

Rewrote Index.AddArtifact behavior: skip exact duplicates in a single pass, remove the first existing entry that shares repository+tag when adding a non-empty tag, attempt to retag an existing untagged entry matching repository+digest immediately, otherwise append the new artifact.

Changes

Cohort / File(s) Summary
Artifact Indexing Logic
bindings/go/ctf/index/v1/index.go
Reworked AddArtifact control flow: single-pass duplicate skip, delete existing repo+tag entry for non-empty tags (retag as removal), immediately retag first matching untagged entry by repo+digest, else append. Comment updated to reflect removal behavior.
Test Coverage & GC Behavior
bindings/go/ctf/index/v1/index_test.go
Updated retag-related tests to expect single-entry outcomes; split duplicate tests into tagged vs untagged cases; added TestAddArtifact_GarbageCollectionBehavior covering repeated latest updates, coexistence of multiple tags for same digest, and staged-release semantics; added fmt import for digest generation.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • fabianburth
  • jakobmoellerdev

Poem

🐰 I hop through indexes, neat and fleet,
I skip the copies, keep entries sweet,
I pull the old tag, give the new one space,
One tidy record wins the race! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(ctf): implement garbage collection via deletion in CTF index' accurately describes the main change—introducing garbage collection through entry deletion when tags move between digests.
Description check ✅ Passed The description is directly related to the changeset, explaining the replacement of tag-clearing with entry deletion, the benefits (preventing unbounded growth and OCI alignment), and noting that existing patterns continue to work.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@netlify

netlify Bot commented Apr 9, 2026

Copy link
Copy Markdown

Deploy Preview for ocm-website canceled.

Name Link
🔨 Latest commit e3ad4f3
🔍 Latest deploy log https://app.netlify.com/projects/ocm-website/deploys/69d79c50ebad1900085641e9

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
bindings/go/ctf/index/v1/index.go (1)

101-117: Consider removing stale pre-retag mutation logic in bindings/go/oci/ctf/store.go.

Now that AddArtifact owns retag resolution, the loop in addOrUpdateArtifactMetadataInIndex that mutates idx.GetArtifacts() (a clone) is misleading and has no effect. Simplifying that helper to delegate directly to idx.AddArtifact(meta) would reduce confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/go/ctf/index/v1/index.go` around lines 101 - 117, The loop in
addOrUpdateArtifactMetadataInIndex that mutates a cloned slice via
idx.GetArtifacts() is stale and redundant because AddArtifact now handles retag
resolution; remove the mutation/retag loop and replace the helper logic to
simply call idx.AddArtifact(meta) (or i.AddArtifact(a)) and return, ensuring all
retagging and de-duplication is delegated to the existing AddArtifact
implementation.
bindings/go/ctf/index/v1/index_test.go (1)

157-190: Optional: strengthen duplicate subtests with content assertions.

Both subtests currently assert only length. Adding checks for repository/tag/digest equality would better catch accidental mutation paths while still keeping the tests lightweight.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/go/ctf/index/v1/index_test.go` around lines 157 - 190, Add
assertions in the "tagged duplicate" and "untagged duplicate" subtests to verify
that the retained artifact's fields match the original ArtifactMetadata (use the
local variable a) to catch accidental mutations: after obtaining arts :=
idx.GetArtifacts(), assert arts[0].Repository == a.Repository, arts[0].Tag ==
a.Tag, and arts[0].Digest == a.Digest (and keep the existing length checks);
reference NewIndex, ArtifactMetadata, AddArtifact, and GetArtifacts to locate
the test code to update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bindings/go/ctf/index/v1/index_test.go`:
- Around line 157-190: Add assertions in the "tagged duplicate" and "untagged
duplicate" subtests to verify that the retained artifact's fields match the
original ArtifactMetadata (use the local variable a) to catch accidental
mutations: after obtaining arts := idx.GetArtifacts(), assert arts[0].Repository
== a.Repository, arts[0].Tag == a.Tag, and arts[0].Digest == a.Digest (and keep
the existing length checks); reference NewIndex, ArtifactMetadata, AddArtifact,
and GetArtifacts to locate the test code to update.

In `@bindings/go/ctf/index/v1/index.go`:
- Around line 101-117: The loop in addOrUpdateArtifactMetadataInIndex that
mutates a cloned slice via idx.GetArtifacts() is stale and redundant because
AddArtifact now handles retag resolution; remove the mutation/retag loop and
replace the helper logic to simply call idx.AddArtifact(meta) (or
i.AddArtifact(a)) and return, ensuring all retagging and de-duplication is
delegated to the existing AddArtifact implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a421ffd9-a0bc-48b0-afdb-2e73bcca3615

📥 Commits

Reviewing files that changed from the base of the PR and between 6cd576b and b0d85a9.

📒 Files selected for processing (2)
  • bindings/go/ctf/index/v1/index.go
  • bindings/go/ctf/index/v1/index_test.go

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
bindings/go/ctf/index/v1/index.go (1)

101-110: Consider simplifying addOrUpdateArtifactMetadataInIndex in store.go.

The helper function in bindings/go/oci/ctf/store.go:385-400 attempts to clear tags on a cloned artifacts array, but GetArtifacts() returns a snapshot via slices.Clone(), so these modifications never persist to the index. With AddArtifact now handling the "retag" scenario internally (removing old entries with same repo+tag but different digest), the helper's tag-clearing logic is dead code. Simplify it to call idx.AddArtifact(meta) directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/go/ctf/index/v1/index.go` around lines 101 - 110, The helper
addOrUpdateArtifactMetadataInIndex in bindings/go/oci/ctf/store.go is performing
dead work by mutating a cloned slice from GetArtifacts() to clear tags; because
GetArtifacts() returns a snapshot (slices.Clone()) and AddArtifact already
handles the "retag" case (removing prior repo+tag entries), remove the
tag-clearing loop and simply invoke idx.AddArtifact(meta) to update the index;
update references to the cloned artifacts slice accordingly and delete the
obsolete mutation logic so the function directly delegates to AddArtifact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bindings/go/ctf/index/v1/index.go`:
- Around line 101-110: The helper addOrUpdateArtifactMetadataInIndex in
bindings/go/oci/ctf/store.go is performing dead work by mutating a cloned slice
from GetArtifacts() to clear tags; because GetArtifacts() returns a snapshot
(slices.Clone()) and AddArtifact already handles the "retag" case (removing
prior repo+tag entries), remove the tag-clearing loop and simply invoke
idx.AddArtifact(meta) to update the index; update references to the cloned
artifacts slice accordingly and delete the obsolete mutation logic so the
function directly delegates to AddArtifact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8bc8b06b-3563-444c-9955-9fd9f7406bcb

📥 Commits

Reviewing files that changed from the base of the PR and between b0d85a9 and d6231b5.

📒 Files selected for processing (2)
  • bindings/go/ctf/index/v1/index.go
  • bindings/go/ctf/index/v1/index_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • bindings/go/ctf/index/v1/index_test.go

Replace tag-clearing with entry deletion when tags are moved to
different digests. This prevents unbounded index growth and aligns
CTF behavior with OCI registry garbage collection semantics.

When a tag is moved from one digest to another, the old entry is
now completely removed instead of having its tag cleared. This
provides automatic garbage collection without requiring external
cleanup mechanisms.

Version aliasing (multiple tags per digest) and the untagged-first
pattern (tagging artifacts after creation) continue to work as expected.

Signed-off-by: Christoph Bleyer <christoph.bleyer@sap.com>

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bindings/go/ctf/index/v1/index.go (1)

90-123: ⚠️ Potential issue | 🟡 Minor

Remove the dead tag-clearing loop in addOrUpdateArtifactMetadataInIndex.

The tag-clearing code at lines 390-396 in bindings/go/oci/ctf/store.go is ineffective: GetArtifacts() returns a clone of the slice, so modifications to arts[i].Tag affect only the clone, not the underlying index. The new AddArtifact() implementation already handles retagging correctly by explicitly removing entries with matching repo+tag but different digests (lines 97-105 of index.go), making the pre-clearing unnecessary. Remove lines 390-396 to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/go/ctf/index/v1/index.go` around lines 90 - 123, The tag-clearing
loop inside addOrUpdateArtifactMetadataInIndex should be removed because
GetArtifacts() returns a clone so setting arts[i].Tag only mutates the clone and
is a no-op; also index.AddArtifact already handles retagging by removing prior
repo+tag entries and retagging existing untagged entries. Edit
addOrUpdateArtifactMetadataInIndex to delete the dead loop that iterates over
arts and clears Tag (the code that assigns arts[i].Tag = ""), rely on calling
index.AddArtifact(…) instead, and ensure no other code paths expect that loop to
have run.
🧹 Nitpick comments (1)
bindings/go/ctf/index/v1/index.go (1)

111-118: MediaType is not preserved when retagging an untagged entry.

When an untagged entry is retagged (line 115), only the Tag field is updated. If the incoming ArtifactMetadata has a MediaType but the existing untagged entry does not (or vice versa), the incoming MediaType is silently discarded.

Consider whether MediaType should also be updated:

♻️ Optional fix to preserve MediaType
 		for idx, art := range i.Artifacts {
 			if art.Repository == a.Repository && art.Tag == "" && art.Digest == a.Digest {
 				i.Artifacts[idx].Tag = a.Tag
+				if a.MediaType != "" {
+					i.Artifacts[idx].MediaType = a.MediaType
+				}
 				return
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/go/ctf/index/v1/index.go` around lines 111 - 118, The loop in
Index.Artifacts that retags an existing untagged entry (in function/method
handling ArtifactMetadata where i.Artifacts[idx].Tag = a.Tag) currently only
updates Tag and thereby discards incoming MediaType; update that branch to also
preserve MediaType by setting i.Artifacts[idx].MediaType = a.MediaType (or if
you prefer not to overwrite existing non-empty values, set it only when
i.Artifacts[idx].MediaType == "" and a.MediaType != ""), ensuring the
ArtifactMetadata fields are consistent when retagging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@bindings/go/ctf/index/v1/index.go`:
- Around line 90-123: The tag-clearing loop inside
addOrUpdateArtifactMetadataInIndex should be removed because GetArtifacts()
returns a clone so setting arts[i].Tag only mutates the clone and is a no-op;
also index.AddArtifact already handles retagging by removing prior repo+tag
entries and retagging existing untagged entries. Edit
addOrUpdateArtifactMetadataInIndex to delete the dead loop that iterates over
arts and clears Tag (the code that assigns arts[i].Tag = ""), rely on calling
index.AddArtifact(…) instead, and ensure no other code paths expect that loop to
have run.

---

Nitpick comments:
In `@bindings/go/ctf/index/v1/index.go`:
- Around line 111-118: The loop in Index.Artifacts that retags an existing
untagged entry (in function/method handling ArtifactMetadata where
i.Artifacts[idx].Tag = a.Tag) currently only updates Tag and thereby discards
incoming MediaType; update that branch to also preserve MediaType by setting
i.Artifacts[idx].MediaType = a.MediaType (or if you prefer not to overwrite
existing non-empty values, set it only when i.Artifacts[idx].MediaType == "" and
a.MediaType != ""), ensuring the ArtifactMetadata fields are consistent when
retagging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fabeadec-153a-4d09-8fb3-3cabc3615b50

📥 Commits

Reviewing files that changed from the base of the PR and between d6231b5 and 25a4012.

📒 Files selected for processing (2)
  • bindings/go/ctf/index/v1/index.go
  • bindings/go/ctf/index/v1/index_test.go

@jakobmoellerdev

Copy link
Copy Markdown
Member

Closing as discussed

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

Labels

kind/feature new feature, enhancement, improvement, extension size/m Medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants