Skip to content

Ephemeral Cluster Controller: Terminate ci-operator gracefully#5126

Open
danilo-gemoli wants to merge 1 commit intoopenshift:mainfrom
danilo-gemoli:feat/ephemeralcluster/ci-operator-graceful-termination
Open

Ephemeral Cluster Controller: Terminate ci-operator gracefully#5126
danilo-gemoli wants to merge 1 commit intoopenshift:mainfrom
danilo-gemoli:feat/ephemeralcluster/ci-operator-graceful-termination

Conversation

@danilo-gemoli
Copy link
Copy Markdown
Contributor

@danilo-gemoli danilo-gemoli commented Apr 23, 2026

Up until now, when the EphemeralCluster object is gone, the ProwJob running ci-operator is simply aborted.
This might lead to an unpleasant situation in which ci-operator is getting killed in the middle of provisioning an ephemeral-cluster, without giving it the chance to clean up the resources that have been created in the cloud so far.

With the new approach, the controller lets ci-operator finish the provisioning procedure and then triggers the deprovision right away, without waiting for the Konflux's test to complete.

In this way we let ci-operator to gracefully terminate its job.

In order to ease the review, I will provide a real use case:

  1. The EphemeralCluster object in app.ci gets delete. This might result as a consequence of the Konflux's pipeling being canceled, as an example.
  2. The prowjobreconciler kicks in and get the ProwJob object tied to the EphemeralCluster. We don't know what is the status of this ProwJob, it might be running, pending, etc.
  3. The prowjobreconciler looks for the ci-op-xxx NS that should have been created by the ci-operator instance run by the ProwJob
    1. If the NS exists, the reconciler create the test-done-signal secret in order to inform ci-operator to start the cluster deprovisioning procedures as soon as possible.
    2. If the NS doesn't exist, the reconciler simply aborts the ProwJob, as it currently does.

Summary by CodeRabbit

  • Bug Fixes

    • Graceful handling when a referenced ephemeral cluster is missing: either triggers deprovisioning via the test-namespace signal or cleanly aborts the job.
  • Improvements

    • Better multi-cluster behavior: reconciler selects the correct build target for a job and updates job completion timing consistently.
  • Tests

    • Added tests covering graceful terminate scenarios, secret creation for deprovisioning, and unknown-cluster error handling.

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: automatic mode

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Walkthrough

Reconciler split control-plane and per-cluster clients (masterClient + buildClients). When an EphemeralCluster is missing, reconciler lists ci-operator namespaces by job label and creates (or preserves) a deprovisioning signal secret in the test namespace instead of immediately aborting the ProwJob. Tests and imports updated to metav1/apierrors.

Changes

Cohort / File(s) Summary
Graceful Termination Logic
pkg/controller/ephemeralcluster/prowjobreconciler.go
Adds masterClient vs buildClients separation; on missing EphemeralCluster calls gracefullyTerminateClusterProvisioning, lists namespaces by steps.LabelJobID, and ensures EphemeralClusterTestDoneSignalSecretName exists in the ci-operator namespace. Updates error/type imports (apierrors, corev1, metav1).
Reconciler Standardization & API
pkg/controller/ephemeralcluster/reconciler.go
Replaces kerrors with apierrors and v1 meta/time usage with metav1; removes instance method r.buildClientFor in favor of package-level buildClientFor(pj, r.buildClients); wiring now passes buildClients into manager registration.
ProwJob Reconciler Tests
pkg/controller/ephemeralcluster/prowjobreconciler_test.go
Introduces per-cluster fake buildClients factory, seeds ci-operator namespace(s) for graceful-terminate tests, asserts creation/preservation of the deprovisioning secret in the build client tied to ProwJob.Spec.Cluster, and adds negative case for missing build client. Switches tests to corev1/metav1.
Reconciler Tests Type Fixes
pkg/controller/ephemeralcluster/reconciler_test.go
Replaced all v1 meta/time/type usages with metav1 equivalents (ObjectMeta, NewTime, timestamps) and corrected an expected error message typo.

Sequence Diagram(s)

sequenceDiagram
    participant Reconciler as Reconciler (PJ)
    participant Master as MasterClient
    participant NSAPI as Namespace API
    participant Build as BuildClient
    participant SecretAPI as Secret API
    participant Prov as Provisioning

    Reconciler->>Master: Get EphemeralCluster(ref)
    Master-->>Reconciler: NotFound
    Reconciler->>NSAPI: List namespaces by steps.LabelJobID
    NSAPI-->>Reconciler: ci-operator namespace (or none)
    alt namespace found
        Reconciler->>Build: buildClientFor(PJ.Spec.Cluster)
        Build-->>Reconciler: client or error
        Reconciler->>SecretAPI: Get Secret(EphemeralClusterTestDoneSignalSecretName)
        SecretAPI-->>Reconciler: NotFound / Exists
        alt secret missing
            Reconciler->>SecretAPI: Create Secret (test-done signal)
            SecretAPI-->>Reconciler: Created
            SecretAPI->>Prov: Provisioning sees secret -> deprovision
        else secret exists
            Reconciler->>Reconciler: Leave secret in place
        end
    else namespace not found
        Reconciler->>Master: Abort/Update ProwJob (set CompletionTime)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 11 | ❌ 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 (11 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Ephemeral Cluster Controller: Terminate ci-operator gracefully' directly addresses the main objective of the pull request—enabling graceful termination of ci-operator when an EphemeralCluster is removed.
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.
Stable And Deterministic Test Names ✅ Passed Test files use standard Go testing with table-driven tests, not Ginkgo framework. No dynamic test names present.
Test Structure And Quality ✅ Passed Test code demonstrates solid quality with table-driven approach, meaningful assertion messages, fake Kubernetes clients, and consistent patterns aligning with repository conventions.
Microshift Test Compatibility ✅ Passed Test files use standard Go unit tests with func Test*(t *testing.T) patterns and only standard Kubernetes APIs compatible with MicroShift.
Single Node Openshift (Sno) Test Compatibility ✅ Passed Modified test files are standard Go unit tests using testing package, not Ginkgo e2e tests, so custom check does not apply.
Topology-Aware Scheduling Compatibility ✅ Passed Pull request modifies only controller reconciliation logic without adding topology-aware scheduling constraints.
Ote Binary Stdout Contract ✅ Passed Pull request contains no OTE binary code or stdout contract violations. Modified files are standard Kubernetes controller-runtime Go code with proper test structure.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The pull request contains only standard Go unit tests using the testing package with fake Kubernetes clients, not Ginkgo e2e tests.
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

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

@openshift-ci openshift-ci Bot requested review from deepsm007 and psalajova April 23, 2026 08:11
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 23, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: danilo-gemoli

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 23, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
pkg/controller/ephemeralcluster/reconciler.go (1)

599-617: ⚠️ Potential issue | 🔴 Critical

EC deletion still takes the old hard-abort path.

Line 617 still aborts any non-terminal ProwJob immediately. Because the finalizer keeps the EphemeralCluster around until this path finishes, pkg/controller/ephemeralcluster/prowjobreconciler.go never sees the EC as missing during normal deletion, so the new graceful-termination flow is bypassed and ci-operator can still be killed mid-provisioning. Please route this branch through the same “signal deprovision if the ci-operator namespace exists, otherwise abort” logic instead of unconditionally calling abortProwJob.

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

In `@pkg/controller/ephemeralcluster/reconciler.go` around lines 599 - 617, The
current branch unconditionally calls r.abortProwJob(&pj, AbortProwJobDeleteEC)
for any non-terminal prowv1.ProwJob state, which forces the hard-abort path;
instead, replicate the logic used in
pkg/controller/ephemeralcluster/prowjobreconciler.go: check whether the
ci-operator namespace for this EphemeralCluster exists (the same existence check
used in prowjobreconciler), and if the namespace exists signal
deprovision/graceful-termination (the deprovision signaling path), otherwise
proceed to call r.abortProwJob to hard-abort; update the switch/default branch
in reconciler.go to route through that conditional rather than unconditionally
calling r.abortProwJob (keep removeFinalizer and the
log.WithField("prowjob_name", pj.Name) unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/controller/ephemeralcluster/prowjobreconciler.go`:
- Around line 128-145: The secret creation has a TOCTOU race: keep the
Get/Create flow idempotent by treating AlreadyExists as success or by using a
create-or-update helper; specifically, in prowjobreconciler.go where you call
buildClient.Get(...) and then buildClient.Create(...) for
api.EphemeralClusterTestDoneSignalSecretName, catch
apierrors.IsAlreadyExists(err) from Create and treat it as non-fatal (log.Debug
and continue) or replace the Create with controllerutil.CreateOrUpdate (or
client.Patch/Update) to ensure idempotency; apply the same change in
notifyTestComplete in reconciler.go so both places accept AlreadyExists as
success or use the same create-or-update helper.

---

Outside diff comments:
In `@pkg/controller/ephemeralcluster/reconciler.go`:
- Around line 599-617: The current branch unconditionally calls
r.abortProwJob(&pj, AbortProwJobDeleteEC) for any non-terminal prowv1.ProwJob
state, which forces the hard-abort path; instead, replicate the logic used in
pkg/controller/ephemeralcluster/prowjobreconciler.go: check whether the
ci-operator namespace for this EphemeralCluster exists (the same existence check
used in prowjobreconciler), and if the namespace exists signal
deprovision/graceful-termination (the deprovision signaling path), otherwise
proceed to call r.abortProwJob to hard-abort; update the switch/default branch
in reconciler.go to route through that conditional rather than unconditionally
calling r.abortProwJob (keep removeFinalizer and the
log.WithField("prowjob_name", pj.Name) unchanged).
🪄 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: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 57a3c51d-90ef-45d0-a65e-77b0ba61ba1f

📥 Commits

Reviewing files that changed from the base of the PR and between e1c7f20 and 1766c0c.

📒 Files selected for processing (4)
  • pkg/controller/ephemeralcluster/prowjobreconciler.go
  • pkg/controller/ephemeralcluster/prowjobreconciler_test.go
  • pkg/controller/ephemeralcluster/reconciler.go
  • pkg/controller/ephemeralcluster/reconciler_test.go

Comment on lines +128 to 145
createSecret := false
if err := buildClient.Get(ctx, types.NamespacedName{Name: api.EphemeralClusterTestDoneSignalSecretName, Namespace: ns}, &corev1.Secret{}); err != nil {
if apierrors.IsNotFound(err) {
createSecret = true
} else {
log.WithError(err).Warn("Failed to fetch the secret")
return reconcile.Result{}, err
}
}

log = log.WithField("secret", api.EphemeralClusterTestDoneSignalSecretName)
if createSecret {
if err := buildClient.Create(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
Name: api.EphemeralClusterTestDoneSignalSecretName,
Namespace: ns,
}}); err != nil {
log.WithError(err).Warn("Failed to create the secret")
return reconcile.Result{}, err
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 23, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the signal-secret creation idempotent.

This Get/Create sequence has a TOCTOU window: if test-done-signal is created after Line 129 but before Line 140, Create returns AlreadyExists and this reconcile fails even though the desired state is already satisfied. Please treat apierrors.IsAlreadyExists(err) as success, or switch to a create-or-update helper. The same fix should be shared with notifyTestComplete in pkg/controller/ephemeralcluster/reconciler.go.

Possible fix
  if createSecret {
  	if err := buildClient.Create(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
  		Name:      api.EphemeralClusterTestDoneSignalSecretName,
  		Namespace: ns,
  	}}); err != nil {
- 		log.WithError(err).Warn("Failed to create the secret")
- 		return reconcile.Result{}, err
+ 		if !apierrors.IsAlreadyExists(err) {
+ 			log.WithError(err).Warn("Failed to create the secret")
+ 			return reconcile.Result{}, err
+ 		}
  	}
  	log.Info("Secret created")
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
createSecret := false
if err := buildClient.Get(ctx, types.NamespacedName{Name: api.EphemeralClusterTestDoneSignalSecretName, Namespace: ns}, &corev1.Secret{}); err != nil {
if apierrors.IsNotFound(err) {
createSecret = true
} else {
log.WithError(err).Warn("Failed to fetch the secret")
return reconcile.Result{}, err
}
}
log = log.WithField("secret", api.EphemeralClusterTestDoneSignalSecretName)
if createSecret {
if err := buildClient.Create(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
Name: api.EphemeralClusterTestDoneSignalSecretName,
Namespace: ns,
}}); err != nil {
log.WithError(err).Warn("Failed to create the secret")
return reconcile.Result{}, err
createSecret := false
if err := buildClient.Get(ctx, types.NamespacedName{Name: api.EphemeralClusterTestDoneSignalSecretName, Namespace: ns}, &corev1.Secret{}); err != nil {
if apierrors.IsNotFound(err) {
createSecret = true
} else {
log.WithError(err).Warn("Failed to fetch the secret")
return reconcile.Result{}, err
}
}
log = log.WithField("secret", api.EphemeralClusterTestDoneSignalSecretName)
if createSecret {
if err := buildClient.Create(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
Name: api.EphemeralClusterTestDoneSignalSecretName,
Namespace: ns,
}}); err != nil {
if !apierrors.IsAlreadyExists(err) {
log.WithError(err).Warn("Failed to create the secret")
return reconcile.Result{}, err
}
}
log.Info("Secret created")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controller/ephemeralcluster/prowjobreconciler.go` around lines 128 - 145,
The secret creation has a TOCTOU race: keep the Get/Create flow idempotent by
treating AlreadyExists as success or by using a create-or-update helper;
specifically, in prowjobreconciler.go where you call buildClient.Get(...) and
then buildClient.Create(...) for api.EphemeralClusterTestDoneSignalSecretName,
catch apierrors.IsAlreadyExists(err) from Create and treat it as non-fatal
(log.Debug and continue) or replace the Create with
controllerutil.CreateOrUpdate (or client.Patch/Update) to ensure idempotency;
apply the same change in notifyTestComplete in reconciler.go so both places
accept AlreadyExists as success or use the same create-or-update helper.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This should never happen in practice because the function gracefullyTerminateClusterProvisioning is executed only when the EphemeralCluster object doesn't exist anymore, therefore there is no way for the reconciler in pkg/controller/ephemeralcluster/reconciler.go to create the test-done-signal.

In notifyTestComplete requests are handled sequentially, therefore there is no way to interleave Get/Create sequence.

Am I missing anything?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@danilo-gemoli, your analysis is correct — you're not missing anything.

The two conditions together do eliminate the race in practice:

  1. Mutual exclusion via EphemeralCluster existencegracefullyTerminateClusterProvisioning is only reached when the EC object is already gone. Since notifyTestComplete in reconciler.go is driven by the EC's lifecycle, there is no live EC for it to reconcile, so it cannot concurrently create test-done-signal for the same cluster.

  2. MaxConcurrentReconciles: 1 — the ProwJob reconciler is single-threaded, so two concurrent runs of gracefullyTerminateClusterProvisioning for the same ProwJob are impossible, ruling out the only remaining interleaver.

My concern was based on a general controller-runtime pattern where these invariants don't hold; in this specific design they do. Happy to drop the suggestion.

@danilo-gemoli danilo-gemoli force-pushed the feat/ephemeralcluster/ci-operator-graceful-termination branch from 1766c0c to 38eecaa Compare April 23, 2026 08:23
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/controller/ephemeralcluster/prowjobreconciler_test.go (1)

303-318: Make secret assertions deterministic and diagnostics clearer.

This block currently lists all secrets and assumes len == 1, which is brittle and produces misleading errors ("Too many secrets" also when zero). It also doesn’t guard for a missing cluster client and uses "unexpected prowjob" while comparing a secret.

Proposed refactor
 if tc.wantSecret != nil {
-	gotSecrets := &corev1.SecretList{}
-	client := clients[tc.pj.Spec.Cluster]
-	if err := client.List(context.TODO(), gotSecrets); err != nil {
-		t.Errorf("get secrets: %s", err)
-	}
-
-	if len(gotSecrets.Items) == 1 {
-		gotSecret := gotSecrets.Items[0]
-		ignoreFields := cmpopts.IgnoreFields(corev1.Secret{}, "ResourceVersion")
-		if diff := cmp.Diff(tc.wantSecret, &gotSecret, ignoreFields); diff != "" {
-			t.Errorf("unexpected prowjob: %s", diff)
-		}
-	} else {
-		t.Errorf("Too many secrets")
-	}
+	buildClient, ok := clients[tc.pj.Spec.Cluster]
+	if !ok {
+		t.Fatalf("missing build client for cluster %q", tc.pj.Spec.Cluster)
+	}
+
+	gotSecret := &corev1.Secret{}
+	key := types.NamespacedName{
+		Name:      tc.wantSecret.Name,
+		Namespace: tc.wantSecret.Namespace,
+	}
+	if err := buildClient.Get(context.TODO(), key, gotSecret); err != nil {
+		t.Fatalf("get secret %s/%s: %v", key.Namespace, key.Name, err)
+	}
+
+	ignoreFields := cmpopts.IgnoreFields(corev1.Secret{}, "ResourceVersion")
+	if diff := cmp.Diff(tc.wantSecret, gotSecret, ignoreFields); diff != "" {
+		t.Errorf("unexpected secret: %s", diff)
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controller/ephemeralcluster/prowjobreconciler_test.go` around lines 303 -
318, The secret assertion is brittle: locate the test block using gotSecrets,
corev1.SecretList, clients and tc.pj.Spec.Cluster and make it deterministic by
first verifying the cluster client exists (clients[tc.pj.Spec.Cluster]) and
returning a clear error if missing, then fetch the specific secret instead of
assuming a single list entry—either call client.Get for
tc.wantSecret.Name/Namespace or List with a field/label selector matching
tc.wantSecret, and handle zero vs multiple matches with distinct messages ("no
secret found: <name>" vs "multiple secrets found: <name>"). Finally use cmp.Diff
to compare tc.wantSecret to the retrieved secret and update the error text to
reference "secret" (not "prowjob") and include the diff in the error message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/controller/ephemeralcluster/prowjobreconciler_test.go`:
- Around line 303-318: The secret assertion is brittle: locate the test block
using gotSecrets, corev1.SecretList, clients and tc.pj.Spec.Cluster and make it
deterministic by first verifying the cluster client exists
(clients[tc.pj.Spec.Cluster]) and returning a clear error if missing, then fetch
the specific secret instead of assuming a single list entry—either call
client.Get for tc.wantSecret.Name/Namespace or List with a field/label selector
matching tc.wantSecret, and handle zero vs multiple matches with distinct
messages ("no secret found: <name>" vs "multiple secrets found: <name>").
Finally use cmp.Diff to compare tc.wantSecret to the retrieved secret and update
the error text to reference "secret" (not "prowjob") and include the diff in the
error message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b8c4c26-b252-4a85-82c3-3d56c13b32a2

📥 Commits

Reviewing files that changed from the base of the PR and between 1766c0c and 38eecaa.

📒 Files selected for processing (4)
  • pkg/controller/ephemeralcluster/prowjobreconciler.go
  • pkg/controller/ephemeralcluster/prowjobreconciler_test.go
  • pkg/controller/ephemeralcluster/reconciler.go
  • pkg/controller/ephemeralcluster/reconciler_test.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/controller/ephemeralcluster/prowjobreconciler.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/controller/ephemeralcluster/reconciler.go
  • pkg/controller/ephemeralcluster/reconciler_test.go

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 23, 2026

@danilo-gemoli: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/breaking-changes 38eecaa link false /test breaking-changes
ci/prow/format 38eecaa link true /test format

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 23, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 23, 2026

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant