Skip to content

refactor: extract registry from server.go#249

Merged
bennyz merged 1 commit into
centos-automotive-suite:mainfrom
bennyz:refactor-server-registry
Apr 27, 2026
Merged

refactor: extract registry from server.go#249
bennyz merged 1 commit into
centos-automotive-suite:mainfrom
bennyz:refactor-server-registry

Conversation

@bennyz

@bennyz bennyz commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

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

    • Registry token lifetime can now be configured in OperatorConfig (default: 4 hours), allowing customization of authentication token validity duration.
  • Refactor

    • Registry management functionality has been reorganized into dedicated modules with improved consistency across the system.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@bennyz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 7 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 50 minutes and 7 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 55686794-c4c7-4b04-9c55-1b6f023ca689

📥 Commits

Reviewing files that changed from the base of the PR and between cedd427 and 601ec35.

⛔ Files ignored due to path filters (1)
  • config/crd/bases/automotive.sdv.cloud.redhat.com_operatorconfigs.yaml is excluded by !config/crd/bases/**
📒 Files selected for processing (15)
  • api/v1alpha1/operatorconfig_types.go
  • internal/buildapi/client_tokens.go
  • internal/buildapi/container_builds.go
  • internal/buildapi/flash.go
  • internal/buildapi/flash_helpers.go
  • internal/buildapi/flash_test.go
  • internal/buildapi/progress.go
  • internal/buildapi/registry.go
  • internal/buildapi/registry_test.go
  • internal/buildapi/secrets.go
  • internal/buildapi/secrets_test.go
  • internal/buildapi/server.go
  • internal/buildapi/server_test.go
  • internal/buildapi/workspace.go
  • internal/common/labels/labels.go
📝 Walkthrough

Walkthrough

This PR introduces configurable registry token lifetime settings to the API, centralizes Kubernetes label/annotation constants into a dedicated package, and extracts registry orchestration logic into a new module that handles internal registry secret creation, token minting, and ImageStream lifecycle management with comprehensive tests.

Changes

Cohort / File(s) Summary
API Configuration
api/v1alpha1/operatorconfig_types.go
Added DefaultRegistryTokenLifetimeSeconds constant (4 hours) and RegistryTokenLifetimeSeconds field to OSBuildsConfig with getter method for configurable token lifetimes.
Centralized Label Constants
internal/common/labels/labels.go
New package centralizing Kubernetes label/annotation keys (automotive.sdv.cloud.redhat.com/*, app.kubernetes.io/*) and standard values ("true", "build-api", "operator", etc.) previously scattered across the codebase.
Registry Orchestration
internal/buildapi/registry.go, internal/buildapi/registry_test.go
New module implementing registry lifecycle: token lifetime resolution, internal-to-external URL translation, auto-detecting external registry routes, minting short-lived service account tokens, creating registry auth secrets, and managing ImageStream/ImageStreamTag resources. Comprehensive test suite covering all registry helpers.
Label Constant Migration
internal/buildapi/client_tokens.go, internal/buildapi/container_builds.go, internal/buildapi/flash.go, internal/buildapi/flash_helpers.go, internal/buildapi/flash_test.go, internal/buildapi/progress.go, internal/buildapi/server_test.go, internal/buildapi/workspace.go
Replaced hard-coded label/annotation key strings with centralized constants from internal/common/labels; updated annotation reads to use constant-based keys throughout token, build, flash, and workspace handling.
Server Refactoring
internal/buildapi/server.go
Moved registry orchestration functions to dedicated registry.go module; refactored build ownership and token/secret creation to use centralized label constants and dynamic token lifetime resolution; updated internal-registry secret and token minting call sites to pass resolved token lifetime.

Sequence Diagram(s)

sequenceDiagram
    participant Handler as Build Handler
    participant Resolver as Token Lifetime Resolver
    participant Config as OperatorConfig
    participant TokenMinter as Token Minter
    participant K8s as Kubernetes API
    participant K8sAuth as Service Account Auth

    Handler->>Resolver: resolveTokenLifetime(ctx, k8sClient, namespace)
    Resolver->>Config: Get OperatorConfig
    alt Config has RegistryTokenLifetimeSeconds
        Config-->>Resolver: Use configured value
    else Config missing/empty
        Config-->>Resolver: Use DefaultRegistryTokenLifetimeSeconds
    end
    Resolver-->>Handler: tokenLifetimeSeconds

    Handler->>K8s: createInternalRegistrySecret(..., tokenLifetimeSeconds)
    Handler->>TokenMinter: mintRegistryToken(ctx, c, namespace, tokenLifetimeSeconds)
    TokenMinter->>K8sAuth: Create short-lived token for build service account
    K8sAuth-->>TokenMinter: token + expiration
    TokenMinter-->>Handler: token, metav1.Time
    Handler->>K8s: Store dockerconfigjson secret with token
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • PR #81: Overlapping registry support implementation including mintRegistryToken, createInternalRegistrySecret, and registry URL translation logic.
  • PR #144: Also extends OSBuildsConfig API type with new configurable fields (trusted CA bundle) alongside registry token lifetime.
  • PR #239: Related API enhancements to OSBuildsConfig with configurable token/build lifetime semantics and corresponding getter methods.

Suggested reviewers

  • bkhizgiy

Poem

🐰 Hops of joy through label lands,
Constants gathered, well-fanned,
Registry tokens dance and play,
ImageStreams saved the build-api way!
No more magic strings astray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% 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
Title check ✅ Passed The title 'refactor: extract registry from server.go' accurately describes the main change—moving registry-related code from server.go to a new registry.go file, along with standardizing label/annotation constants.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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.

@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 (3)
internal/buildapi/registry_test.go (1)

116-131: Consider adding a failure-path test for deleteImageStreamTag.

deleteImageStream has both success and "does not exist" cases (Lines 91-114), but deleteImageStreamTag only covers the happy path. Adding the symmetric case protects against regressions if the Delete semantics change.

Suggested addition
+
+		It("returns error when ImageStreamTag does not exist", func() {
+			scheme := newRegistryTestScheme()
+			k8sClient := newRegistryTestClient(scheme)
+
+			err := deleteImageStreamTag(context.Background(), k8sClient, "ns", "missing-stream", "v1")
+			Expect(err).To(HaveOccurred())
+		})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/buildapi/registry_test.go` around lines 116 - 131, Add a new test
case beside the existing Describe("deleteImageStreamTag") spec that verifies
deleteImageStreamTag returns no error (or appropriate behavior) when the
ImageStreamTag does not exist: create a scheme without any existing
newUnstructuredImageStreamTag, construct k8sClient via newRegistryTestClient,
call deleteImageStreamTag(context.Background(), k8sClient, "ns", "my-stream",
"v1"), and assert the result is appropriate (e.g., no error) and that a
subsequent Get for the namespaced name "my-stream:v1" still returns a not-found
error; this mirrors the failure-path test pattern used for deleteImageStream.
internal/buildapi/registry.go (2)

232-261: Assumption worth documenting: single stream name across refs.

The loop overwrites streamName on each iteration, which is correct today because setupInternalRegistryBuild in server.go always pushes both the bootc container and the disk image to the same ImageStream (distinguished only by tag). If that invariant ever changes — e.g., hybrid paths begin using distinct streams — this function will silently miss cleaning up one of them. The existing doc comment covers the URL pattern; consider noting the single-stream assumption too.

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

In `@internal/buildapi/registry.go` around lines 232 - 261, Update the comment on
resolveImageStreamRefs to explicitly state it assumes both container push and
export OCI refs point to the same ImageStream (single stream name across refs)
because setupInternalRegistryBuild currently pushes both artifacts to the same
stream; also add a runtime guard in resolveImageStreamRefs that detects if
multiple different stream names appear across build.Spec.GetContainerPush() and
build.Spec.GetExportOCI() and logs a warning (or returns an error) so future
changes that use distinct streams are noticed; reference resolveImageStreamRefs,
setupInternalRegistryBuild, build.Spec.GetContainerPush, and
build.Spec.GetExportOCI when making the change.

55-60: Preserve underlying error for diagnosability.

When Get on the Route fails for reasons other than NotFound (RBAC denied, apiserver unreachable, CRD missing), the root cause is discarded and callers only see the generic guidance string. Consider wrapping the original error so operators can distinguish "route not configured" from "route lookup is failing".

Suggested change
 	if err := k8sClient.Get(ctx, types.NamespacedName{
 		Name:      "default-route",
 		Namespace: "openshift-image-registry",
 	}, route); err != nil {
-		return "", fmt.Errorf("cannot determine external registry route: set clusterRegistryRoute in OperatorConfig or expose default-route in openshift-image-registry")
+		return "", fmt.Errorf("cannot determine external registry route: set clusterRegistryRoute in OperatorConfig or expose default-route in openshift-image-registry: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/buildapi/registry.go` around lines 55 - 60, The current error
returned from k8sClient.Get (call to k8sClient.Get with types.NamespacedName for
"default-route" into variable route) discards the original error; change the
error handling to preserve and wrap the underlying error so callers can
distinguish "route not configured" from other failures (RBAC, apiserver
unreachable, CRD missing). Specifically, after k8sClient.Get(...) check for
apierrors.IsNotFound(err) to keep the existing guidance for a missing route
(mentioning OperatorConfig/clusterRegistryRoute), but for any other error return
a wrapped error that includes the original err (e.g., using fmt.Errorf("cannot
determine external registry route: %w", err) or errors.Wrap) so the root cause
is preserved in the returned error. Ensure references: k8sClient.Get, route,
OperatorConfig, clusterRegistryRoute.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/buildapi/registry_test.go`:
- Around line 116-131: Add a new test case beside the existing
Describe("deleteImageStreamTag") spec that verifies deleteImageStreamTag returns
no error (or appropriate behavior) when the ImageStreamTag does not exist:
create a scheme without any existing newUnstructuredImageStreamTag, construct
k8sClient via newRegistryTestClient, call
deleteImageStreamTag(context.Background(), k8sClient, "ns", "my-stream", "v1"),
and assert the result is appropriate (e.g., no error) and that a subsequent Get
for the namespaced name "my-stream:v1" still returns a not-found error; this
mirrors the failure-path test pattern used for deleteImageStream.

In `@internal/buildapi/registry.go`:
- Around line 232-261: Update the comment on resolveImageStreamRefs to
explicitly state it assumes both container push and export OCI refs point to the
same ImageStream (single stream name across refs) because
setupInternalRegistryBuild currently pushes both artifacts to the same stream;
also add a runtime guard in resolveImageStreamRefs that detects if multiple
different stream names appear across build.Spec.GetContainerPush() and
build.Spec.GetExportOCI() and logs a warning (or returns an error) so future
changes that use distinct streams are noticed; reference resolveImageStreamRefs,
setupInternalRegistryBuild, build.Spec.GetContainerPush, and
build.Spec.GetExportOCI when making the change.
- Around line 55-60: The current error returned from k8sClient.Get (call to
k8sClient.Get with types.NamespacedName for "default-route" into variable route)
discards the original error; change the error handling to preserve and wrap the
underlying error so callers can distinguish "route not configured" from other
failures (RBAC, apiserver unreachable, CRD missing). Specifically, after
k8sClient.Get(...) check for apierrors.IsNotFound(err) to keep the existing
guidance for a missing route (mentioning OperatorConfig/clusterRegistryRoute),
but for any other error return a wrapped error that includes the original err
(e.g., using fmt.Errorf("cannot determine external registry route: %w", err) or
errors.Wrap) so the root cause is preserved in the returned error. Ensure
references: k8sClient.Get, route, OperatorConfig, clusterRegistryRoute.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d543e9ee-afd3-4bcc-a2c4-270a363272c7

📥 Commits

Reviewing files that changed from the base of the PR and between 8dabcee and cedd427.

⛔ Files ignored due to path filters (1)
  • config/crd/bases/automotive.sdv.cloud.redhat.com_operatorconfigs.yaml is excluded by !config/crd/bases/**
📒 Files selected for processing (13)
  • api/v1alpha1/operatorconfig_types.go
  • internal/buildapi/client_tokens.go
  • internal/buildapi/container_builds.go
  • internal/buildapi/flash.go
  • internal/buildapi/flash_helpers.go
  • internal/buildapi/flash_test.go
  • internal/buildapi/progress.go
  • internal/buildapi/registry.go
  • internal/buildapi/registry_test.go
  • internal/buildapi/server.go
  • internal/buildapi/server_test.go
  • internal/buildapi/workspace.go
  • internal/common/labels/labels.go

@bennyz bennyz force-pushed the refactor-server-registry branch from cedd427 to 87a7674 Compare April 21, 2026 15:00
@bennyz bennyz requested a review from bkhizgiy April 23, 2026 06:41
@bennyz

bennyz commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

@ambient-code please review

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

Review: refactor: extract registry from server.go

Overall this is a clean refactoring. The separation of registry-related functionality into registry.go is well-motivated, and the labels package is a good step toward eliminating scattered string literals. The configurable RegistryTokenLifetimeSeconds is a useful addition. A few observations below.

What looks good

  • The labels package is well-structured with clear grouping (domain-specific keys, standard k8s keys, common values).
  • All createInternalRegistrySecret / mintRegistryToken call sites are updated consistently to pass through resolveTokenLifetime.
  • The getExternalRegistryRoute function now properly distinguishes NotFound from other errors when auto-detecting the Route (an improvement over the original behavior).
  • Test coverage for the new registry.go functions (ImageStream CRUD, setSecretOwnerRef, createFlashClientSecret, getExternalRegistryRoute) is solid.
  • DeepCopy does not need changes since RegistryTokenLifetimeSeconds is a value type -- correctly identified and left alone.

Suggestions

See inline comments.

Comment thread api/v1alpha1/operatorconfig_types.go
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit (optional): registry.go now also contains createFlashClientSecret and setSecretOwnerRef, which are not registry-specific. These are fine here for now since they were co-located in server.go before, but if this file keeps growing, you might consider a further split (e.g., secrets.go for generic secret helpers). Not blocking.

Comment thread internal/buildapi/registry.go
Comment thread internal/buildapi/registry.go
Comment thread internal/buildapi/registry.go
Comment thread internal/common/labels/labels.go
Comment thread internal/buildapi/registry_test.go
Comment thread internal/buildapi/progress.go
@bennyz bennyz force-pushed the refactor-server-registry branch from 87a7674 to deec981 Compare April 26, 2026 06:33
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Assisted-by: claude-opus-4.6
@bennyz bennyz force-pushed the refactor-server-registry branch from deec981 to 601ec35 Compare April 26, 2026 10:26
@bennyz bennyz merged commit 6cc971d into centos-automotive-suite:main Apr 27, 2026
4 checks passed
@bennyz bennyz deleted the refactor-server-registry branch April 27, 2026 08:27
@coderabbitai coderabbitai Bot mentioned this pull request Apr 27, 2026
10 tasks
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.

2 participants