Skip to content

feat: improve catalog discovery UX for scheduled builds - #430

Merged
bennyz merged 3 commits into
centos-automotive-suite:mainfrom
bennyz:catalog-ux
Jul 27, 2026
Merged

feat: improve catalog discovery UX for scheduled builds#430
bennyz merged 3 commits into
centos-automotive-suite:mainfrom
bennyz:catalog-ux

Conversation

@bennyz

@bennyz bennyz commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Propagate schedule name labels from ImageBuild to CatalogImage,
add sort/latest API params, and improve CLI table output with
schedule names, AGE column, and smart TAGS column hiding.

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • CI/CD improvement
  • Refactoring

Testing

  • Unit tests pass (make test)
  • Linter passes (make lint)
  • Manifests are up to date (make manifests generate)
  • Tested on OpenShift cluster (if applicable)

Summary by CodeRabbit

  • New Features

    • Added --latest and --sort options for catalog listing, with latest-per-schedule filtering and newest-first default behavior.
    • Improved catalog tables and details: show schedule names, human-readable AGE, optional TAGS column, and friendly sizes; table rows now better reflect available metadata.
    • Updated catalog create/get/remove/verify/publish requests to work without a namespace concept.
    • Catalog publishing now upserts by registry URL (updates existing entries instead of creating duplicates).
  • Tests

    • Expanded unit and end-to-end coverage for sorting, latest filtering, formatting, schedule labeling, and publish update/create behavior.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@bennyz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cd0c4186-1c3b-49af-9945-cfa38eb24092

📥 Commits

Reviewing files that changed from the base of the PR and between 245c361 and c4d294c.

📒 Files selected for processing (15)
  • cmd/caib/catalog/add.go
  • cmd/caib/catalog/catalog.go
  • cmd/caib/catalog/catalog_test.go
  • cmd/caib/catalog/get.go
  • cmd/caib/catalog/list.go
  • cmd/caib/catalog/publish.go
  • cmd/caib/catalog/remove.go
  • cmd/caib/catalog/verify.go
  • internal/buildapi/catalog/handlers.go
  • internal/buildapi/catalog/handlers_test.go
  • internal/buildapi/catalog/models.go
  • internal/controller/catalogimage/catalogimage_controller.go
  • internal/controller/catalogimage/publisher.go
  • internal/controller/catalogimage/publisher_test.go
  • test/e2e/catalog_discovery_test.go
📝 Walkthrough

Walkthrough

Catalog publishing now upserts by registry URL and propagates schedule metadata. Catalog listing adds sorting and latest filtering, while CLI tables and detail views show schedule names, ages, tags, formatted sizes, and expanded metadata.

Changes

Catalog lifecycle and discovery

Layer / File(s) Summary
Publisher upsert and schedule labels
internal/controller/catalogimage/*
Publishing updates matching registry entries or creates new ones, while scheduled builds add schedule labels and shared boolean label values.
Schedule-aware catalog API
internal/buildapi/catalog/*
API responses expose schedule names, and listing supports creation/name sorting plus latest-per-group filtering with default-namespace resource access.
Catalog CLI output and formatting
cmd/caib/catalog/*
Catalog commands remove namespace request handling and render sorting, latest filtering, schedule names, ages, tags, metadata, formatted targets, and human-readable sizes.
Catalog discovery validation
test/e2e/catalog_discovery_test.go, cmd/caib/catalog/catalog_test.go, internal/buildapi/catalog/handlers_test.go, internal/controller/catalogimage/publisher_test.go
Unit and end-to-end tests cover formatting, publishing behavior, sorting, latest filtering, schedule propagation, and catalog presentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ImageBuild
  participant Publisher
  participant CatalogImage
  ImageBuild->>Publisher: provide schedule metadata
  Publisher->>CatalogImage: find by registry URL
  Publisher->>CatalogImage: update existing or create new
Loading
sequenceDiagram
  participant CLI
  participant CatalogAPI
  participant CatalogImages
  CLI->>CatalogAPI: request sort/latest options
  CatalogAPI->>CatalogImages: sort and deduplicate results
  CatalogAPI-->>CLI: return catalog responses
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: catalog discovery UX improvements for scheduled builds.
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.
✨ 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.

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
test/e2e/catalog_discovery_test.go (1)

52-87: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

applyCatalogImage emits an empty labels: block if labels is nil.

All current callers pass labels, but the helper produces invalid YAML (labels: followed by spec:) otherwise. Emit the labels: key only when the map is non-empty, mirroring the tags handling.

🤖 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 `@test/e2e/catalog_discovery_test.go` around lines 52 - 87, Update the
applyCatalogImage helper so labelYAML includes the labels: key only when labels
is non-empty, matching the existing tagsYAML conditional behavior. Ensure
CatalogImage manifests generated without labels remain valid YAML while
preserving the current formatting when labels are provided.
cmd/caib/catalog/list.go (1)

64-64: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Validate --sort client-side.

Any value is forwarded, and the API silently falls through to unsorted output for unrecognized values, so --sort=nmae fails quietly. Reject anything other than created/name before issuing the request.

♻️ Proposed fix
 	if listSort != "" {
+		if listSort != "created" && listSort != "name" {
+			return fmt.Errorf("invalid --sort value %q (supported: created, name)", listSort)
+		}
 		params.Set("sort", listSort)
 	}

Also applies to: 147-149

🤖 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 `@cmd/caib/catalog/list.go` at line 64, Validate the listSort value in the list
command before issuing the API request, accepting only "created" and "name" and
rejecting all other values with an error. Apply this validation to the flag
defined by cmd.Flags().StringVar and the request flow covering the related
lines, ensuring invalid input never reaches the API.
internal/controller/catalogimage/publisher_test.go (1)

66-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test asserts on its own fixture, not on PublishFromImageBuild.

Nothing here invokes production code beyond GetContainerPush(); the schedule-name assertion just re-reads the label the test set. Use newFakePublisher() and call PublishFromImageBuild so the propagation into the CatalogImage label is actually covered.

♻️ Sketch
-	registryURL := ib.Spec.GetContainerPush()
-	if registryURL == "" {
-		t.Fatal("expected container push URL")
-	}
-
-	scheduleName := ib.Labels[automotivev1alpha1.LabelScheduledImageBuildName]
-	if scheduleName != "nightly-autosd-qemu" {
-		t.Errorf("expected schedule name from ImageBuild label, got %q", scheduleName)
-	}
+	pub := newFakePublisher()
+	res, err := pub.PublishFromImageBuild(context.Background(), ib, "", nil, nil, PublishSourceScheduled)
+	if err != nil {
+		t.Fatalf("PublishFromImageBuild() error: %v", err)
+	}
+	if got := res.CatalogImage.Labels[automotivev1alpha1.LabelScheduledImageBuildName]; got != "nightly-autosd-qemu" {
+		t.Errorf("expected schedule label propagated, got %q", got)
+	}

Note PublishFromImageBuild sets VerifyAccessibility: true, so the fake publisher needs a stub RegistryClient rather than nil to avoid a nil dereference in verifyAndExtractMetadata.

🤖 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 `@internal/controller/catalogimage/publisher_test.go` around lines 66 - 93,
Rewrite TestPublishFromImageBuild_PropagatesScheduleName to use newFakePublisher
and invoke PublishFromImageBuild with the ImageBuild fixture, rather than
reading the fixture’s label directly. Configure the fake publisher with a stub
RegistryClient because PublishFromImageBuild enables VerifyAccessibility and
verifyAndExtractMetadata must not dereference nil; assert the resulting
CatalogImage label contains the expected schedule name.
🤖 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.

Inline comments:
In `@cmd/caib/catalog/list.go`:
- Around line 239-250: Update the catalog list rendering loop around displayName
so NAME always uses img.Name, and add a separate SCHEDULE column populated from
img.ScheduleName. Preserve the existing target and age values, and ensure
scheduled and unscheduled rows remain distinguishable without replacing the
resource identifier.

In `@internal/buildapi/catalog/handlers.go`:
- Around line 140-168: The catalog image handler must apply sorting and
latest-per-group filtering to the complete result set, not a server-paginated
subset. Update the List call and surrounding flow in the handler so client-side
Limit is omitted when sorting or params.Latest is active, then truncate to the
requested limit after the sort and latestGroupKey deduplication and omit the
Continue token; preserve normal pagination when neither is active. Also validate
unrecognized params.Sort values and return HTTP 400 instead of silently
returning unsorted results.

In `@internal/controller/catalogimage/publisher.go`:
- Around line 250-277: Update updateCatalogImage to clear derived labels
LabelBootc, LabelTarget, and LabelScheduledImageBuildName before conditionally
setting them from the current PublishOptions, so removed metadata does not
persist. Also refresh catalogImage.Status.SourceImageBuild from
opts.SourceImageBuildName during Publish after the catalog update succeeds,
ensuring the status change is persisted.

In `@test/e2e/catalog_discovery_test.go`:
- Around line 328-349: The catalog sorting test should verify ordering between
the two fixtures rather than assuming aName is first globally. In the “should
sort alphabetically with --sort name” test, locate both aName and zName in
result.Items and assert that aName’s index is less than zName’s index, while
preserving the existing minimum-item and command-success checks.

---

Nitpick comments:
In `@cmd/caib/catalog/list.go`:
- Line 64: Validate the listSort value in the list command before issuing the
API request, accepting only "created" and "name" and rejecting all other values
with an error. Apply this validation to the flag defined by
cmd.Flags().StringVar and the request flow covering the related lines, ensuring
invalid input never reaches the API.

In `@internal/controller/catalogimage/publisher_test.go`:
- Around line 66-93: Rewrite TestPublishFromImageBuild_PropagatesScheduleName to
use newFakePublisher and invoke PublishFromImageBuild with the ImageBuild
fixture, rather than reading the fixture’s label directly. Configure the fake
publisher with a stub RegistryClient because PublishFromImageBuild enables
VerifyAccessibility and verifyAndExtractMetadata must not dereference nil;
assert the resulting CatalogImage label contains the expected schedule name.

In `@test/e2e/catalog_discovery_test.go`:
- Around line 52-87: Update the applyCatalogImage helper so labelYAML includes
the labels: key only when labels is non-empty, matching the existing tagsYAML
conditional behavior. Ensure CatalogImage manifests generated without labels
remain valid YAML while preserving the current formatting when labels are
provided.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8baf0401-9882-4ec9-b095-61965eab8b34

📥 Commits

Reviewing files that changed from the base of the PR and between 00c23fb and eaed7e6.

📒 Files selected for processing (10)
  • cmd/caib/catalog/catalog_test.go
  • cmd/caib/catalog/get.go
  • cmd/caib/catalog/list.go
  • internal/buildapi/catalog/handlers.go
  • internal/buildapi/catalog/handlers_test.go
  • internal/buildapi/catalog/models.go
  • internal/controller/catalogimage/catalogimage_controller.go
  • internal/controller/catalogimage/publisher.go
  • internal/controller/catalogimage/publisher_test.go
  • test/e2e/catalog_discovery_test.go

Comment thread cmd/caib/catalog/list.go
Comment thread internal/buildapi/catalog/handlers.go Outdated
Comment thread internal/controller/catalogimage/publisher.go
Comment thread test/e2e/catalog_discovery_test.go
@bennyz
bennyz added this pull request to the merge queue Jul 27, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 27, 2026
@bennyz
bennyz added this pull request to the merge queue Jul 27, 2026
@bennyz
bennyz removed this pull request from the merge queue due to a manual request Jul 27, 2026

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
internal/buildapi/catalog/handlers.go (1)

97-133: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

needsPostProcessing omits phase/tags filters, so limit is applied before filtering.

When only phase or tags is set (no sort/latest), client.Limit(...) truncates the raw list before postFilterAndSort filters by phase/tags (Lines 369-388). This can return an incomplete page (or empty page) even though more matching items exist — the same class of bug previously flagged for sort/latest, now surfacing for phase/tags.

🐛 Proposed fix
-	needsPostProcessing := params.Sort != "" || params.Latest
+	needsPostProcessing := params.Sort != "" || params.Latest || params.Phase != "" || params.Tags != ""
🤖 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 `@internal/buildapi/catalog/handlers.go` around lines 97 - 133, Update
needsPostProcessing in the catalog list handler to include phase and tags
filters, so client.Limit and continuation options are bypassed whenever
postFilterAndSort must filter by those parameters. Preserve the existing
post-filtering and response limit behavior for all such requests.
🤖 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.

Inline comments:
In `@internal/buildapi/catalog/handlers.go`:
- Around line 421-431: Update latestGroupKey so images with empty architecture,
distro, and target labels do not share the same deduplication key. Return a
unique per-image key for this all-empty metadata case, while preserving
schedule-based grouping and the existing distro/arch/target grouping for images
with metadata.
- Around line 390-416: Update the sorting and deduplication flow around sortBy
and params.Latest so latest grouping always selects the item with the greatest
CreationTimestamp, independent of the requested display sort. Perform latest
selection in creation-time order, then re-apply the requested sort (including
sortByName) to the filtered items before returning them.

---

Outside diff comments:
In `@internal/buildapi/catalog/handlers.go`:
- Around line 97-133: Update needsPostProcessing in the catalog list handler to
include phase and tags filters, so client.Limit and continuation options are
bypassed whenever postFilterAndSort must filter by those parameters. Preserve
the existing post-filtering and response limit behavior for all such requests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4957be33-dedd-4dfa-9a8e-093f998065f0

📥 Commits

Reviewing files that changed from the base of the PR and between eaed7e6 and 245c361.

📒 Files selected for processing (15)
  • cmd/caib/catalog/add.go
  • cmd/caib/catalog/catalog.go
  • cmd/caib/catalog/catalog_test.go
  • cmd/caib/catalog/get.go
  • cmd/caib/catalog/list.go
  • cmd/caib/catalog/publish.go
  • cmd/caib/catalog/remove.go
  • cmd/caib/catalog/verify.go
  • internal/buildapi/catalog/handlers.go
  • internal/buildapi/catalog/handlers_test.go
  • internal/buildapi/catalog/models.go
  • internal/controller/catalogimage/catalogimage_controller.go
  • internal/controller/catalogimage/publisher.go
  • internal/controller/catalogimage/publisher_test.go
  • test/e2e/catalog_discovery_test.go
💤 Files with no reviewable changes (1)
  • cmd/caib/catalog/catalog.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • cmd/caib/catalog/get.go
  • internal/controller/catalogimage/catalogimage_controller.go
  • internal/buildapi/catalog/handlers_test.go
  • test/e2e/catalog_discovery_test.go
  • internal/controller/catalogimage/publisher_test.go
  • internal/controller/catalogimage/publisher.go

Comment thread internal/buildapi/catalog/handlers.go Outdated
Comment thread internal/buildapi/catalog/handlers.go
bennyz added 3 commits July 27, 2026 20:32
Propagate schedule name labels from ImageBuild to CatalogImage,
add sort/latest API params, and improve CLI table output with
schedule names, AGE column, and smart TAGS column hiding.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Scheduled builds push to a fixed tag, so successive builds produce
CatalogImages with the same registry URL. The old check-and-reject
pattern caused all but the first publish to fail silently.

Replace checkDuplicates() with a find-or-create pattern: look up an
existing CatalogImage by registry URL field index, update its metadata
if found, or create a new one otherwise.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Assisted-by: claude-opus-4.6
The catalog CLI defaulted to namespace "default" when --namespace wasn't
provided, causing lookups to fail when CatalogImages live in the operator
namespace. Since users have no direct OpenShift access, namespace
selection is unnecessary — the server already resolves its own namespace
from the pod's service account.

Remove --namespace/-n and --all-namespaces flags from all catalog
subcommands. Strip namespace query params from CLI requests and namespace
fields from API models. Server handlers now always use h.defaultNamespace
instead of accepting client-provided namespace overrides.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Assisted-by: claude-opus-4.6
@bennyz
bennyz merged commit 43dbc95 into centos-automotive-suite:main Jul 27, 2026
4 of 5 checks passed
@bennyz
bennyz deleted the catalog-ux branch July 27, 2026 17:33
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