Skip to content

[KFLUXINFRA-3003] Add DR sanity pipeline with staging-only Triggers and Kueue LocalQueue#13186

Open
vsokolen-redhat wants to merge 1 commit into
redhat-appstudio:mainfrom
vsokolen-redhat:dr-sanity-pipeline-v2
Open

[KFLUXINFRA-3003] Add DR sanity pipeline with staging-only Triggers and Kueue LocalQueue#13186
vsokolen-redhat wants to merge 1 commit into
redhat-appstudio:mainfrom
vsokolen-redhat:dr-sanity-pipeline-v2

Conversation

@vsokolen-redhat

@vsokolen-redhat vsokolen-redhat commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Structure

CI clusters (development/): Pipeline + 6 Tasks + dr-sanity-runner RBAC + Namespace + LocalQueue

Staging clusters (staging/base/ on top): CronJob (daily 02:00 UTC) + EventListener + TriggerBinding + TriggerTemplate + NetworkPolicy + cron-trigger RBAC

Note

~130 stale sleep-pipeline-run PipelineRuns on stone-stg-rh01 should be cleaned up before or after this merges to prevent them from running when the LocalQueue becomes active.

Risk Assessment

Jira: KFLUXINFRA-3003

@openshift-ci

openshift-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: vsokolen-redhat

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 requested review from eisraeli and enkeefe00 July 23, 2026 16:30
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Kustomize Render Diff

Comparing febdaf5b014dba6931

Component Environment Changes
components/disaster-recovery/development development +706 -134
components/disaster-recovery/staging/stone-stage-p01 staging +880 -23
components/disaster-recovery/staging/stone-stg-rh01 staging +880 -23

Total: 3 components, +2466 -180 lines

📋 Full diff available in the workflow summary and as a downloadable artifact.

@qodo-for-redhat-appstudio

Copy link
Copy Markdown

PR Summary by Qodo

Add DR sanity Tekton pipeline; stage-only Triggers and Kueue LocalQueue

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Replace the placeholder sleep Pipeline with an end-to-end DR sanity workflow
 (build→backup→restore→verify).
• Scope Tekton Triggers/CronJob resources to staging only to avoid CI ArgoCD sync failures.
• Add Kueue LocalQueue wiring so staging PipelineRuns don’t remain PipelineRunPending.
Diagram

graph TD
  A["Staging CronJob"] --> B["Tekton EventListener"] --> C["TriggerTemplate"] --> D["PipelineRun"] --> E["Kueue LocalQueue"] --> F["dr-sanity-pipeline"]
  F --> G["Konflux APIs (Component/Release/Snapshot)"]
  F --> H["Velero (Backup/Restore)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep Triggers in development and patch them out for CI clusters
  • ➕ Single overlay location for all DR resources
  • ➕ Avoids duplicating staging-only manifests
  • ➖ Higher risk of accidentally reintroducing CRDs CI clusters don’t have (sync failures)
  • ➖ More complex/fragile Kustomize conditionals across many clusters
2. Replace Triggers-based scheduling with a direct CronJob-created PipelineRun
  • ➕ Removes dependency on Tekton Triggers CRDs entirely
  • ➕ Simpler operational surface area (CronJob only)
  • ➖ Reimplements parts of Triggers behavior (authn, templating)
  • ➖ May diverge from existing Tekton Triggers conventions already used elsewhere
3. Scope runner permissions tighter (namespace Roles + minimal ClusterRole)
  • ➕ Reduces blast radius of finalizer patching/namespace delete/networkpolicy operations
  • ➕ Easier to reason about and audit
  • ➖ Some actions are inherently cluster-scoped (namespace delete, cross-namespace NetworkPolicy/Velero ops) so ClusterRole likely still needed
  • ➖ More time to iterate to find the minimal permission set

Recommendation: The staging-only Triggers overlay plus Kueue LocalQueue is the most reliable fix for the concrete failures described (missing Triggers CRDs in CI; PipelineRuns stuck pending without a LocalQueue). The main follow-up worth considering is tightening the dr-sanity-runner ClusterRole once the pipeline stabilizes, but structurally the current split (dev = pipeline logic; staging = scheduling/Triggers) is appropriate.

Files changed (11) +808 / -32 · 1 not counted

Enhancement (1) +675 / -6
pipeline.yamlReplace sleep pipeline with full DR sanity tasks and pipeline wiring +675/-6

Replace sleep pipeline with full DR sanity tasks and pipeline wiring

• Adds multiple Tekton Tasks (preflight, baseline run, backup, disaster+restore, post-restore verification, cleanup) and replaces the old sleep-pipeline with dr-sanity-pipeline. The pipeline performs Konflux build/integration/release flows, triggers Velero backups/restores, simulates failure via NetworkPolicy, and validates post-restore behavior with cleanup in finally.

components/disaster-recovery/development/pipeline.yaml

Other (10) +133 / -26
kustomization.yamlStop deploying Triggers/CronJob in development; add LocalQueue/Namespace +2/-6

Stop deploying Triggers/CronJob in development; add LocalQueue/Namespace

• Removes Triggers/CronJob and related staging-only resources from the development overlay. Adds LocalQueue and Namespace resources so CI/dev clusters only apply the pipeline, tasks, and runner RBAC.

components/disaster-recovery/development/kustomization.yaml

localqueue.yamlAdd Kueue LocalQueue for Tekton PipelineRuns +6/-0

Add Kueue LocalQueue for Tekton PipelineRuns

• Introduces a LocalQueue named pipelines-queue pointing at cluster-pipeline-queue. This enables PipelineRun admission on clusters where Kueue gating is enforced.

components/disaster-recovery/development/localqueue.yaml

rbac.yamlCreate dr-sanity-runner ServiceAccount + ClusterRole for DR workflow operations +64/-15

Create dr-sanity-runner ServiceAccount + ClusterRole for DR workflow operations

• Replaces the prior Triggers-focused SA/RBAC with a pipeline runner identity. Grants permissions needed for Velero backup/restore polling, Konflux CR interactions (components/releases/snapshots), Tekton run polling/finalizer patching, namespace deletion, and temporary NetworkPolicy management.

components/disaster-recovery/development/rbac.yaml

cronjob.yamlAdjust DR trigger schedule and add runtime deadlines +3/-3

Adjust DR trigger schedule and add runtime deadlines

• Changes the schedule to daily at 02:00 UTC and adds deadlines (startingDeadlineSeconds and activeDeadlineSeconds) to prevent runaway or stale scheduled executions. Keeps concurrencyPolicy=Forbid and remains suspended by default via patching.

components/disaster-recovery/staging/base/cronjob.yaml

event_listener.yamlStage-only EventListener manifest cleanup +0/-1

Stage-only EventListener manifest cleanup

• Removes example/comment header while retaining the EventListener resource in the staging overlay. Keeps Triggers resources out of CI/development clusters where CRDs are not present.

components/disaster-recovery/staging/base/event_listener.yaml

kustomization.yamlStage-only overlay for Triggers/Cron scheduling resources +9/-0

Stage-only overlay for Triggers/Cron scheduling resources

• Sets the namespace and explicitly layers staging-only CronJob, EventListener, TriggerTemplate/Binding, Triggers RBAC, and NetworkPolicy on top of the shared development resources. Ensures CI clusters do not attempt to sync Triggers CRDs.

components/disaster-recovery/staging/base/kustomization.yaml

networkpolicy_allow_el_from_ns.yamlAllow EventListener ingress from selected namespaces (staging overlay) not counted

Allow EventListener ingress from selected namespaces (staging overlay)

• Carries the NetworkPolicy used to allow access to the EventListener from permitted namespaces in staging. This file is now applied only in staging where the EventListener exists.

components/disaster-recovery/staging/base/networkpolicy_allow_el_from_ns.yaml

rbac-triggers.yamlAdd staging-only Triggers EventListener ServiceAccount and bindings +31/-0

Add staging-only Triggers EventListener ServiceAccount and bindings

• Introduces cron-trigger ServiceAccount and associated RoleBinding/ClusterRoleBinding for Tekton Triggers EventListener operation. Keeps Triggers RBAC isolated to staging clusters where Triggers is installed.

components/disaster-recovery/staging/base/rbac-triggers.yaml

triggerbinding.yamlStage-only TriggerBinding manifest cleanup +0/-1

Stage-only TriggerBinding manifest cleanup

• Removes example/comment header while retaining the TriggerBinding resource in staging. Continues to support CronJob→EventListener→TriggerTemplate invocation flow.

components/disaster-recovery/staging/base/triggerbinding.yaml

triggertemplate.yamlCreate PipelineRun template for dr-sanity-pipeline using runner SA +18/-0

Create PipelineRun template for dr-sanity-pipeline using runner SA

• Adds a TriggerTemplate that generates dr-sanity-pipeline PipelineRuns (tekton.dev/v1) with a 3h pipeline timeout. Configures taskRunTemplate.serviceAccountName=dr-sanity-runner so the PipelineRun executes with the DR runner permissions.

components/disaster-recovery/staging/base/triggertemplate.yaml

@qodo-for-redhat-appstudio

qodo-for-redhat-appstudio Bot commented Jul 23, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
⚠️ Tickets: not configured — ticket URL found in PR but could not be fetched — check ticket provider credentials
✅ Compliance rules (platform): 3 rules

Grey Divider


Action required

1. Snapshot unchecked post-restore 🐞 Bug ≡ Correctness
Description
dr-post-restore-verify creates a Release using $SNAPSHOT without verifying the snapshot lookup
returned a name, so Release creation can fail (or be invalid) when no Snapshot exists.
Code

components/disaster-recovery/development/pipeline.yaml[R518-533]

+      echo "Finding post-restore Snapshot..."
+      SNAPSHOT=$(oc get snapshots.appstudio.redhat.com -n "$NS" \
+        -l "appstudio.openshift.io/application=$APP" \
+        --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
+      echo "Creating post-restore Release..."
+      RELEASE_NAME="dr-sanity-post-restore-release-$(date +%Y%m%d%H%M%S)"
+      cat <<RELEASE_EOF | oc apply -f -
+      apiVersion: appstudio.redhat.com/v1alpha1
+      kind: Release
+      metadata:
+        name: $RELEASE_NAME
+        namespace: $NS
+      spec:
+        releasePlan: $RP
+        snapshot: $SNAPSHOT
+      RELEASE_EOF
Relevance

⭐⭐⭐ High

Fail-fast on missing lookup values is commonly accepted; baseline task already validates SNAPSHOT
similarly.

PR-#10815
PR-#13074

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Post-restore verification uses SNAPSHOT directly in the Release manifest without checking it is
non-empty, while baseline-run explicitly fails when no snapshot is found.

components/disaster-recovery/development/pipeline.yaml[157-164]
components/disaster-recovery/development/pipeline.yaml[518-533]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In `dr-post-restore-verify`, the script queries for the latest Snapshot and then immediately interpolates it into a Release manifest without checking for an empty result.

### Issue Context
`dr-baseline-run` already contains an explicit `if [ -z "$SNAPSHOT" ]; then exit 1` guard; the post-restore path should be consistent to avoid attempting to create a Release with an empty snapshot reference.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[518-533]

### Suggested fix
Add an emptiness check after computing `SNAPSHOT` (mirroring `dr-baseline-run`), and `exit 1` with a clear error message if no Snapshot is found.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Timeout polls never fail 🐞 Bug ☼ Reliability
Description
Several polling loops in the DR Tasks break on success but never exit non-zero when the loop limit
is exhausted, so the Task can continue and print “PASSED” even when
build/integration/release/backup/restore never completed.
Code

components/disaster-recovery/development/pipeline.yaml[R120-155]

+      echo "Waiting for build PipelineRun $BUILD_PR to complete..."
+      for i in $(seq 1 120); do
+        STATUS=$(oc get pipelinerun "$BUILD_PR" -n "$NS" \
+          -o jsonpath='{.status.conditions[0].reason}' 2>/dev/null || echo "Pending")
+        if [ "$STATUS" = "Completed" ] || [ "$STATUS" = "Succeeded" ]; then
+          echo "OK: Build completed"
+          break
+        elif [ "$STATUS" = "Failed" ]; then
+          echo "FAIL: Build PipelineRun $BUILD_PR failed"
+          exit 1
+        fi
+        echo "  Build status: $STATUS (attempt $i/120)"
+        sleep 15
+      done
+
+      echo "Waiting for integration test PipelineRun..."
+      for i in $(seq 1 60); do
+        INT_PR=$(oc get pipelineruns -n "$NS" \
+          -l "appstudio.openshift.io/application=$APP,test.appstudio.openshift.io/scenario" \
+          --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}' 2>/dev/null || echo "")
+        if [ -n "$INT_PR" ]; then
+          INT_STATUS=$(oc get pipelinerun "$INT_PR" -n "$NS" \
+            -o jsonpath='{.status.conditions[0].reason}' 2>/dev/null || echo "Pending")
+          if [ "$INT_STATUS" = "Completed" ] || [ "$INT_STATUS" = "Succeeded" ]; then
+            echo "OK: Integration test completed ($INT_PR)"
+            break
+          elif [ "$INT_STATUS" = "Failed" ]; then
+            echo "FAIL: Integration test PipelineRun $INT_PR failed"
+            exit 1
+          fi
+          echo "  Integration status: $INT_STATUS (attempt $i/60)"
+        else
+          echo "  Waiting for integration PipelineRun to appear (attempt $i/60)"
+        fi
+        sleep 15
+      done
Relevance

⭐⭐⭐ High

Repo reviews favor making automation fail deterministically on timeouts instead of silently
continuing.

PR-#12769
PR-#12880

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
In each cited section, the code breaks on success or exits on explicit failure phases but proceeds
after the for loop without checking whether success was reached, enabling silent timeouts to be
treated as success.

components/disaster-recovery/development/pipeline.yaml[120-156]
components/disaster-recovery/development/pipeline.yaml[258-277]
components/disaster-recovery/development/pipeline.yaml[383-409]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Multiple bounded polling loops (build completion, integration completion, release completion, backup completion, restore completion) have no post-loop “timed out” failure check. If the awaited condition never becomes true and never enters an explicit failure phase, the loop ends and the script proceeds as if it succeeded, causing false-positive DR results and potentially operating on incomplete state.

## Issue Context
Example patterns:
- `dr-baseline-run`: waits for build/integration/release but doesn’t fail if any of those never reach success before the loop ends.
- `dr-backup`: writes `backup-name` and prints `Backup PASSED` even if backup phase never becomes `Completed`.
- `dr-disaster-and-restore`: prints `PASSED` even if restore never becomes `Completed` before the loop ends.

## Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[120-156]
- components/disaster-recovery/development/pipeline.yaml[258-277]
- components/disaster-recovery/development/pipeline.yaml[383-409]

## Suggested fix
For each polling loop:
- Track a `success=false` flag; set it to true on success and `break`.
- After the loop, if `success != true`, print a clear timeout error and `exit 1`.
- For the disaster-and-restore task, ensure NetworkPolicy cleanup occurs on timeout paths as well (and rely on `finally` only as a last resort).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. LocalQueue API mismatch 🐞 Bug ≡ Correctness
Description
The DR LocalQueue is defined as kueue.x-k8s.io/v1beta1, but the repo’s development Kueue CRD test
resource serves only v1beta2, so this manifest can fail to apply/sync on dev/staging clusters that
match that CRD versioning.
Code

components/disaster-recovery/development/localqueue.yaml[R1-2]

+apiVersion: kueue.x-k8s.io/v1beta1
+kind: LocalQueue
Relevance

⭐⭐⭐ High

Likely real Kueue API-version drift; repo has history of correcting Kueue/LocalQueue config
inconsistencies.

PR-#11040
PR-#12640

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The DR LocalQueue is v1beta1, while the development LocalQueue CRD test resource serves only v1beta2
and the development Kueue config uses v1beta2, indicating an API version mismatch for
development/staging installs.

components/disaster-recovery/development/localqueue.yaml[1-6]
components/policies/development/kueue/queue-config/.chainsaw-test/resources/localqueue-crd.yaml[13-16]
components/kueue/development/queue-config/cluster-queue.yaml[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`components/disaster-recovery/development/localqueue.yaml` uses `apiVersion: kueue.x-k8s.io/v1beta1`, but the repo’s development Kueue CRD test manifest for LocalQueue only serves `v1beta2`. If the dev/staging clusters follow that versioning, ArgoCD sync/apply will fail with “no matches for kind LocalQueue in version …/v1beta1”.

## Issue Context
Development Kueue config in this repo uses `kueue.x-k8s.io/v1beta2`, and the dev chainsaw LocalQueue CRD serves only `v1beta2`.

## Fix Focus Areas
- components/disaster-recovery/development/localqueue.yaml[1-6]
- components/policies/development/kueue/queue-config/.chainsaw-test/resources/localqueue-crd.yaml[13-16]
- components/kueue/development/queue-config/cluster-queue.yaml[1-10]

## Suggested fix
- Change `apiVersion` in `localqueue.yaml` to `kueue.x-k8s.io/v1beta2` (or, if you truly must support both, split by overlay so dev/staging use v1beta2 and only environments that serve v1beta1 apply v1beta1).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Arbitrary BSL selection 🐞 Bug ☼ Reliability ⭐ New
Description
dr-preflight and dr-backup select the BackupStorageLocation via .items[0], which is an
implicit selection policy; on clusters with multiple BSLs this can validate one BSL but create
backups against a different (possibly unavailable/unintended) BSL.
Code

components/disaster-recovery/development/pipeline.yaml[R33-36]

+      echo "Checking BackupStorageLocation..."
+      BSL_PHASE=$(oc get backupstoragelocations -n openshift-adp -o jsonpath='{.items[0].status.phase}' 2>/dev/null || echo "Unknown")
+      if [ "$BSL_PHASE" != "Available" ]; then
+        echo "FAIL: BSL not Available (phase=$BSL_PHASE)"
Relevance

⭐⭐⭐ High

Team has accepted fixes avoiding implicit ordering/selection; explicit BSL selection prevents
multi-BSL reliability bugs.

PR-#11344

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Preflight checks BSL phase using .items[0] and the backup Task also sets storageLocation from
.items[0].metadata.name, instead of using an explicit storageLocation name (a pattern used
elsewhere in-repo).

components/disaster-recovery/development/pipeline.yaml[33-36]
components/disaster-recovery/development/pipeline.yaml[228-255]
components/backup/base/member/schedules/backup-tenants-schedule.yaml[41-43]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The pipeline selects BackupStorageLocation using the first returned list item (`.items[0]`). That’s an implicit policy and can point to the wrong BSL when multiple locations exist.

## Issue Context
Other Velero-related manifests in this repo use an explicit `storageLocation` name, which avoids ambiguity.

## Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[33-38]
- components/disaster-recovery/development/pipeline.yaml[228-255]

## Suggested fix approach
- Add a pipeline/task param like `VELERO_BSL_NAME` (with a sensible default for the target cluster) and:
 - In preflight: check phase for that specific BSL by name.
 - In backup: set `spec.storageLocation` to that same explicit name.
- Alternatively: select the BSL by filtering for `.status.phase==Available` and then ensure the *same chosen name* is used for both the preflight check and the Backup creation (don’t compute it twice independently).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. PipelineRun correlation race 🐞 Bug ≡ Correctness ⭐ New
Description
dr-baseline-run (and dr-post-restore-verify) identifies the build/release PipelineRun by
watching the *total* PipelineRun count and then selecting the newest PipelineRun in the namespace,
so concurrent unrelated PipelineRuns can be misidentified and the DR check can wait on/validate the
wrong run.
Code

components/disaster-recovery/development/pipeline.yaml[R97-110]

+      # Record PipelineRun count before triggering
+      BEFORE_COUNT=$(oc get pipelineruns -n "$NS" --no-headers 2>/dev/null | wc -l)
+
+      echo "Triggering build on $COMP..."
+      oc annotate component/"$COMP" -n "$NS" \
+        build.appstudio.openshift.io/request=trigger-pac-build --overwrite
+
+      echo "Waiting for new build PipelineRun..."
+      for i in $(seq 1 120); do
+        CURRENT_COUNT=$(oc get pipelineruns -n "$NS" --no-headers 2>/dev/null | wc -l)
+        if [ "$CURRENT_COUNT" -gt "$BEFORE_COUNT" ]; then
+          BUILD_PR=$(oc get pipelineruns -n "$NS" \
+            --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
+          echo "Found build PipelineRun: $BUILD_PR"
Relevance

⭐⭐ Medium

Concurrency mis-correlation risk is plausible, but no close precedent requiring label-based
PipelineRun correlation in scripts.

PR-#11933
PR-#11344

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Tasks use namespace-wide PipelineRun count and then pick the newest PipelineRun by
creationTimestamp, without any label/field selector tying the chosen PipelineRun to the
build/release they just triggered.

components/disaster-recovery/development/pipeline.yaml[97-110]
components/disaster-recovery/development/pipeline.yaml[181-190]
components/disaster-recovery/development/pipeline.yaml[458-473]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The pipeline currently “guesses” the PipelineRun it should wait for by (a) counting all PipelineRuns in a namespace and (b) selecting the newest one. This can associate the DR sanity pipeline with an unrelated PipelineRun created concurrently, producing incorrect pass/fail results.

## Issue Context
This pattern appears in both the baseline run path and the post-restore verification path, and similarly for the release PipelineRun in the managed namespace.

## Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[97-114]
- components/disaster-recovery/development/pipeline.yaml[181-200]
- components/disaster-recovery/development/pipeline.yaml[458-477]

## Suggested fix approach
- Correlate by a deterministic selector instead of “newest PipelineRun”, e.g.:
 - Prefer label/field filtering that uniquely identifies the intended PipelineRun (if there is a known label for component/application/scenario/release), OR
 - Capture the set of PipelineRun names *before* triggering, then after triggering compute the new names and apply additional checks (age window, expected labels/params/ownerReferences) before choosing, OR
 - Avoid the indirect “annotate component -> controller creates PipelineRun” correlation by creating a PipelineRun/Run object directly (if acceptable for this DR test).
- Apply the same correlation approach for release PipelineRuns in `$MANAGED_NS` (don’t just pick the newest PipelineRun in that namespace).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Velero replica check brittle 🐞 Bug ≡ Correctness
Description
dr-preflight exits unless the Velero Deployment has exactly readyReplicas==1, so a healthy
Velero deployment scaled to 2+ replicas will incorrectly fail the DR pipeline.
Code

components/disaster-recovery/development/pipeline.yaml[R25-30]

+      echo "Checking Velero deployment..."
+      READY=$(oc get deploy velero -n openshift-adp -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
+      if [ "$READY" != "1" ]; then
+        echo "FAIL: Velero deployment not ready (readyReplicas=$READY)"
+        exit 1
+      fi
Relevance

⭐⭐⭐ High

Repo often accepts resilience fixes; hard-coding readyReplicas==1 is brittle for healthy scaled
deployments.

PR-#11344
PR-#10815

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The preflight step hard-fails on any value other than "1" for .status.readyReplicas.

components/disaster-recovery/development/pipeline.yaml[25-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`dr-preflight` treats Velero as ready only when `.status.readyReplicas` equals the string `"1"`. This is brittle: a healthy Velero Deployment can legitimately run with more than one replica.

### Issue Context
This check is part of the new DR sanity Tekton Task `dr-preflight` and blocks the entire pipeline.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[25-30]

### Suggested fix
Update the check to accept `readyReplicas >= 1` (or compare `readyReplicas` against the Deployment's desired replica count, e.g. `.spec.replicas`, and require them to match).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
7. Runner RBAC too broad ✓ Resolved 🐞 Bug ⛨ Security
Description
dr-sanity-runner is bound via ClusterRoleBinding to a ClusterRole that can delete arbitrary
namespaces and create/delete NetworkPolicies cluster-wide, creating a very large blast radius if the
ServiceAccount is misused or the Pipeline parameters are accidentally pointed at the wrong
namespace.
Code

components/disaster-recovery/development/rbac.yaml[R56-80]

+# Namespace operations
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "delete"]
+# NetworkPolicies — create/delete in image-controller namespace
+- apiGroups: ["networking.k8s.io"]
+  resources: ["networkpolicies"]
+  verbs: ["create", "delete", "get", "patch"]
+# Pods and events for failure diagnostics
+- apiGroups: [""]
+  resources: ["pods", "events", "pods/log"]
+  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
-  name: triggers-cron-eventlistener-binding-cr
+  name: dr-sanity-runner
subjects:
- kind: ServiceAccount
-  name: cron-trigger
-  namespace: default
+  name: dr-sanity-runner
+  namespace: konflux-disaster-recovery
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
-  name: tekton-triggers-eventlistener-clusterroles
+  name: dr-sanity-runner
Relevance

⭐⭐⭐ High

Least-privilege RBAC tightening is historically accepted; broad cluster-wide wildcard access was
previously flagged and fixed.

PR-#13013

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The ClusterRole explicitly allows deleting cluster-scoped namespaces and creating/deleting
networkpolicies, and is bound cluster-wide via ClusterRoleBinding to the runner ServiceAccount;
the Pipeline then performs namespace deletion and NetworkPolicy creation, so accidental or malicious
use would have cluster-wide impact.

components/disaster-recovery/development/rbac.yaml[9-80]
components/disaster-recovery/development/pipeline.yaml[301-406]
PR-#13013

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `dr-sanity-runner` ServiceAccount is granted a cluster-wide ClusterRoleBinding with highly destructive permissions (notably `namespaces: delete` and `networkpolicies: create/delete`). This means any workload using that SA token can delete any namespace, not just the intended DR test namespace, and can mutate NetworkPolicies outside konflux-disaster-recovery.

## Issue Context
The Pipeline does need cross-namespace access (e.g., `openshift-adp` for Velero and `image-controller` for the disaster simulation), but the current binding grants these powers across *all* namespaces without guardrails.

## Fix Focus Areas
- components/disaster-recovery/development/rbac.yaml[9-80]
- components/disaster-recovery/development/pipeline.yaml[301-406]

## Suggested fix
Choose one (or combine):
1) Split RBAC by namespace:
  - Use namespace-scoped `Role`+`RoleBinding` in `openshift-adp` (Backups/Restores/BSLs) and `image-controller` (NetworkPolicies).
  - Keep a minimal ClusterRole only where truly required.
2) Restrict namespace deletion:
  - If the target tenant namespace is fixed, use `resourceNames: ["releng-test-product-tenant"]` for `namespaces` delete.
  - Alternatively, remove the tenant namespace as a free-form parameter and hardcode/validate against an allowlist.
3) Drop unused permissions (pods/events/pods/log) if not needed for this pipeline’s implemented diagnostics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

8. Cleanup result misleading value 🐞 Bug ⚙ Maintainability
Description
dr-cleanup documents its test-result as "PASSED or FAILED" but always writes COMPLETED, making
the published result inconsistent with its declared meaning.
Code

components/disaster-recovery/development/pipeline.yaml[R566-600]

+  results:
+  - name: test-result
+    description: Overall test result (PASSED or FAILED)
+  steps:
+  - name: cleanup
    image: quay.io/konflux-ci/tools@sha256:b846eb68ab06f4c61c987bd1053e0e2dda13e18ecd5a75c945a18f7d78743bd8
    command: ["bash", "-c"]
    args:
-      - 'sleep 5'
+    - |
+      echo "=== DR Cleanup ==="
+      NS="$(params.TENANT_NAMESPACE)"
+
+      # Remove NetworkPolicy if it was left behind
+      oc delete networkpolicy dr-block-image-controller -n image-controller --ignore-not-found 2>/dev/null || true
+
+      # Delete test Release CRs
+      for REL in $(oc get releases.appstudio.redhat.com -n "$NS" -o name 2>/dev/null | grep "dr-sanity"); do
+        oc delete "$REL" -n "$NS" --ignore-not-found 2>/dev/null || true
+        echo "Deleted $REL"
+      done
+
+      # Delete test Backup CRs
+      for BACKUP in $(oc get backups.velero.io -n openshift-adp -o name 2>/dev/null | grep "dr-sanity"); do
+        oc delete "$BACKUP" -n openshift-adp --ignore-not-found 2>/dev/null || true
+        echo "Deleted $BACKUP"
+      done
+
+      # Delete test Restore CRs
+      for RESTORE in $(oc get restores.velero.io -n openshift-adp -o name 2>/dev/null | grep "dr-sanity"); do
+        oc delete "$RESTORE" -n openshift-adp --ignore-not-found 2>/dev/null || true
+        echo "Deleted $RESTORE"
+      done
+
+      printf "COMPLETED" > "$(results.test-result.path)"
+      echo "=== Cleanup Done ==="
Relevance

⭐⭐⭐ High

Team fixes misleading docs/help text to match actual emitted values; result description/value
mismatch likely to be corrected.

PR-#11933

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Task result description and the value written to the result file disagree in the same task
definition.

components/disaster-recovery/development/pipeline.yaml[566-600]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The cleanup task's `test-result` result is described as `PASSED or FAILED`, but the implementation always writes `COMPLETED`.

### Issue Context
Even if the result is not currently consumed, it becomes a footgun for future use and is misleading during debugging.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[566-600]

### Suggested fix
Either:
- Change the result description to match the actual value (`COMPLETED`), or
- Write `PASSED`/`FAILED` based on an explicit input/param that indicates pipeline outcome (or remove the result entirely if it is not intended to be consumed).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 1e11373

Results up to commit 6baf24c ⚖️ Balanced


🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. LocalQueue API mismatch 🐞 Bug ≡ Correctness
Description
The DR LocalQueue is defined as kueue.x-k8s.io/v1beta1, but the repo’s development Kueue CRD test
resource serves only v1beta2, so this manifest can fail to apply/sync on dev/staging clusters that
match that CRD versioning.
Code

components/disaster-recovery/development/localqueue.yaml[R1-2]

+apiVersion: kueue.x-k8s.io/v1beta1
+kind: LocalQueue
Relevance

⭐⭐⭐ High

Likely real Kueue API-version drift; repo has history of correcting Kueue/LocalQueue config
inconsistencies.

PR-#11040
PR-#12640

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The DR LocalQueue is v1beta1, while the development LocalQueue CRD test resource serves only v1beta2
and the development Kueue config uses v1beta2, indicating an API version mismatch for
development/staging installs.

components/disaster-recovery/development/localqueue.yaml[1-6]
components/policies/development/kueue/queue-config/.chainsaw-test/resources/localqueue-crd.yaml[13-16]
components/kueue/development/queue-config/cluster-queue.yaml[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`components/disaster-recovery/development/localqueue.yaml` uses `apiVersion: kueue.x-k8s.io/v1beta1`, but the repo’s development Kueue CRD test manifest for LocalQueue only serves `v1beta2`. If the dev/staging clusters follow that versioning, ArgoCD sync/apply will fail with “no matches for kind LocalQueue in version …/v1beta1”.

## Issue Context
Development Kueue config in this repo uses `kueue.x-k8s.io/v1beta2`, and the dev chainsaw LocalQueue CRD serves only `v1beta2`.

## Fix Focus Areas
- components/disaster-recovery/development/localqueue.yaml[1-6]
- components/policies/development/kueue/queue-config/.chainsaw-test/resources/localqueue-crd.yaml[13-16]
- components/kueue/development/queue-config/cluster-queue.yaml[1-10]

## Suggested fix
- Change `apiVersion` in `localqueue.yaml` to `kueue.x-k8s.io/v1beta2` (or, if you truly must support both, split by overlay so dev/staging use v1beta2 and only environments that serve v1beta1 apply v1beta1).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Timeout polls never fail 🐞 Bug ☼ Reliability
Description
Several polling loops in the DR Tasks break on success but never exit non-zero when the loop limit
is exhausted, so the Task can continue and print “PASSED” even when
build/integration/release/backup/restore never completed.
Code

components/disaster-recovery/development/pipeline.yaml[R120-155]

+      echo "Waiting for build PipelineRun $BUILD_PR to complete..."
+      for i in $(seq 1 120); do
+        STATUS=$(oc get pipelinerun "$BUILD_PR" -n "$NS" \
+          -o jsonpath='{.status.conditions[0].reason}' 2>/dev/null || echo "Pending")
+        if [ "$STATUS" = "Completed" ] || [ "$STATUS" = "Succeeded" ]; then
+          echo "OK: Build completed"
+          break
+        elif [ "$STATUS" = "Failed" ]; then
+          echo "FAIL: Build PipelineRun $BUILD_PR failed"
+          exit 1
+        fi
+        echo "  Build status: $STATUS (attempt $i/120)"
+        sleep 15
+      done
+
+      echo "Waiting for integration test PipelineRun..."
+      for i in $(seq 1 60); do
+        INT_PR=$(oc get pipelineruns -n "$NS" \
+          -l "appstudio.openshift.io/application=$APP,test.appstudio.openshift.io/scenario" \
+          --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}' 2>/dev/null || echo "")
+        if [ -n "$INT_PR" ]; then
+          INT_STATUS=$(oc get pipelinerun "$INT_PR" -n "$NS" \
+            -o jsonpath='{.status.conditions[0].reason}' 2>/dev/null || echo "Pending")
+          if [ "$INT_STATUS" = "Completed" ] || [ "$INT_STATUS" = "Succeeded" ]; then
+            echo "OK: Integration test completed ($INT_PR)"
+            break
+          elif [ "$INT_STATUS" = "Failed" ]; then
+            echo "FAIL: Integration test PipelineRun $INT_PR failed"
+            exit 1
+          fi
+          echo "  Integration status: $INT_STATUS (attempt $i/60)"
+        else
+          echo "  Waiting for integration PipelineRun to appear (attempt $i/60)"
+        fi
+        sleep 15
+      done
Relevance

⭐⭐⭐ High

Repo reviews favor making automation fail deterministically on timeouts instead of silently
continuing.

PR-#12769
PR-#12880

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
In each cited section, the code breaks on success or exits on explicit failure phases but proceeds
after the for loop without checking whether success was reached, enabling silent timeouts to be
treated as success.

components/disaster-recovery/development/pipeline.yaml[120-156]
components/disaster-recovery/development/pipeline.yaml[258-277]
components/disaster-recovery/development/pipeline.yaml[383-409]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Multiple bounded polling loops (build completion, integration completion, release completion, backup completion, restore completion) have no post-loop “timed out” failure check. If the awaited condition never becomes true and never enters an explicit failure phase, the loop ends and the script proceeds as if it succeeded, causing false-positive DR results and potentially operating on incomplete state.

## Issue Context
Example patterns:
- `dr-baseline-run`: waits for build/integration/release but doesn’t fail if any of those never reach success before the loop ends.
- `dr-backup`: writes `backup-name` and prints `Backup PASSED` even if backup phase never becomes `Completed`.
- `dr-disaster-and-restore`: prints `PASSED` even if restore never becomes `Completed` before the loop ends.

## Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[120-156]
- components/disaster-recovery/development/pipeline.yaml[258-277]
- components/disaster-recovery/development/pipeline.yaml[383-409]

## Suggested fix
For each polling loop:
- Track a `success=false` flag; set it to true on success and `break`.
- After the loop, if `success != true`, print a clear timeout error and `exit 1`.
- For the disaster-and-restore task, ensure NetworkPolicy cleanup occurs on timeout paths as well (and rely on `finally` only as a last resort).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
3. Runner RBAC too broad 🐞 Bug ⛨ Security
Description
dr-sanity-runner is bound via ClusterRoleBinding to a ClusterRole that can delete arbitrary
namespaces and create/delete NetworkPolicies cluster-wide, creating a very large blast radius if the
ServiceAccount is misused or the Pipeline parameters are accidentally pointed at the wrong
namespace.
Code

components/disaster-recovery/development/rbac.yaml[R56-80]

+# Namespace operations
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "delete"]
+# NetworkPolicies — create/delete in image-controller namespace
+- apiGroups: ["networking.k8s.io"]
+  resources: ["networkpolicies"]
+  verbs: ["create", "delete", "get", "patch"]
+# Pods and events for failure diagnostics
+- apiGroups: [""]
+  resources: ["pods", "events", "pods/log"]
+  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
-  name: triggers-cron-eventlistener-binding-cr
+  name: dr-sanity-runner
subjects:
- kind: ServiceAccount
-  name: cron-trigger
-  namespace: default
+  name: dr-sanity-runner
+  namespace: konflux-disaster-recovery
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
-  name: tekton-triggers-eventlistener-clusterroles
+  name: dr-sanity-runner
Relevance

⭐⭐⭐ High

Least-privilege RBAC tightening is historically accepted; broad cluster-wide wildcard access was
previously flagged and fixed.

PR-#13013

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The ClusterRole explicitly allows deleting cluster-scoped namespaces and creating/deleting
networkpolicies, and is bound cluster-wide via ClusterRoleBinding to the runner ServiceAccount;
the Pipeline then performs namespace deletion and NetworkPolicy creation, so accidental or malicious
use would have cluster-wide impact.

components/disaster-recovery/development/rbac.yaml[9-80]
components/disaster-recovery/development/pipeline.yaml[301-406]
PR-#13013

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `dr-sanity-runner` ServiceAccount is granted a cluster-wide ClusterRoleBinding with highly destructive permissions (notably `namespaces: delete` and `networkpolicies: create/delete`). This means any workload using that SA token can delete any namespace, not just the intended DR test namespace, and can mutate NetworkPolicies outside konflux-disaster-recovery.

## Issue Context
The Pipeline does need cross-namespace access (e.g., `openshift-adp` for Velero and `image-controller` for the disaster simulation), but the current binding grants these powers across *all* namespaces without guardrails.

## Fix Focus Areas
- components/disaster-recovery/development/rbac.yaml[9-80]
- components/disaster-recovery/development/pipeline.yaml[301-406]

## Suggested fix
Choose one (or combine):
1) Split RBAC by namespace:
  - Use namespace-scoped `Role`+`RoleBinding` in `openshift-adp` (Backups/Restores/BSLs) and `image-controller` (NetworkPolicies).
  - Keep a minimal ClusterRole only where truly required.
2) Restrict namespace deletion:
  - If the target tenant namespace is fixed, use `resourceNames: ["releng-test-product-tenant"]` for `namespaces` delete.
  - Alternatively, remove the tenant namespace as a free-form parameter and hardcode/validate against an allowlist.
3) Drop unused permissions (pods/events/pods/log) if not needed for this pipeline’s implemented diagnostics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 0bc47df ⚖️ Balanced


🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Snapshot unchecked post-restore 🐞 Bug ≡ Correctness
Description
dr-post-restore-verify creates a Release using $SNAPSHOT without verifying the snapshot lookup
returned a name, so Release creation can fail (or be invalid) when no Snapshot exists.
Code

components/disaster-recovery/development/pipeline.yaml[R518-533]

+      echo "Finding post-restore Snapshot..."
+      SNAPSHOT=$(oc get snapshots.appstudio.redhat.com -n "$NS" \
+        -l "appstudio.openshift.io/application=$APP" \
+        --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
+      echo "Creating post-restore Release..."
+      RELEASE_NAME="dr-sanity-post-restore-release-$(date +%Y%m%d%H%M%S)"
+      cat <<RELEASE_EOF | oc apply -f -
+      apiVersion: appstudio.redhat.com/v1alpha1
+      kind: Release
+      metadata:
+        name: $RELEASE_NAME
+        namespace: $NS
+      spec:
+        releasePlan: $RP
+        snapshot: $SNAPSHOT
+      RELEASE_EOF
Relevance

⭐⭐⭐ High

Fail-fast on missing lookup values is commonly accepted; baseline task already validates SNAPSHOT
similarly.

PR-#10815
PR-#13074

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Post-restore verification uses SNAPSHOT directly in the Release manifest without checking it is
non-empty, while baseline-run explicitly fails when no snapshot is found.

components/disaster-recovery/development/pipeline.yaml[157-164]
components/disaster-recovery/development/pipeline.yaml[518-533]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In `dr-post-restore-verify`, the script queries for the latest Snapshot and then immediately interpolates it into a Release manifest without checking for an empty result.

### Issue Context
`dr-baseline-run` already contains an explicit `if [ -z "$SNAPSHOT" ]; then exit 1` guard; the post-restore path should be consistent to avoid attempting to create a Release with an empty snapshot reference.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[518-533]

### Suggested fix
Add an emptiness check after computing `SNAPSHOT` (mirroring `dr-baseline-run`), and `exit 1` with a clear error message if no Snapshot is found.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Velero replica check brittle 🐞 Bug ≡ Correctness
Description
dr-preflight exits unless the Velero Deployment has exactly readyReplicas==1, so a healthy
Velero deployment scaled to 2+ replicas will incorrectly fail the DR pipeline.
Code

components/disaster-recovery/development/pipeline.yaml[R25-30]

+      echo "Checking Velero deployment..."
+      READY=$(oc get deploy velero -n openshift-adp -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
+      if [ "$READY" != "1" ]; then
+        echo "FAIL: Velero deployment not ready (readyReplicas=$READY)"
+        exit 1
+      fi
Relevance

⭐⭐⭐ High

Repo often accepts resilience fixes; hard-coding readyReplicas==1 is brittle for healthy scaled
deployments.

PR-#11344
PR-#10815

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The preflight step hard-fails on any value other than "1" for .status.readyReplicas.

components/disaster-recovery/development/pipeline.yaml[25-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`dr-preflight` treats Velero as ready only when `.status.readyReplicas` equals the string `"1"`. This is brittle: a healthy Velero Deployment can legitimately run with more than one replica.

### Issue Context
This check is part of the new DR sanity Tekton Task `dr-preflight` and blocks the entire pipeline.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[25-30]

### Suggested fix
Update the check to accept `readyReplicas >= 1` (or compare `readyReplicas` against the Deployment's desired replica count, e.g. `.spec.replicas`, and require them to match).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational
3. Cleanup result misleading value 🐞 Bug ⚙ Maintainability
Description
dr-cleanup documents its test-result as "PASSED or FAILED" but always writes COMPLETED, making
the published result inconsistent with its declared meaning.
Code

components/disaster-recovery/development/pipeline.yaml[R566-600]

+  results:
+  - name: test-result
+    description: Overall test result (PASSED or FAILED)
+  steps:
+  - name: cleanup
    image: quay.io/konflux-ci/tools@sha256:b846eb68ab06f4c61c987bd1053e0e2dda13e18ecd5a75c945a18f7d78743bd8
    command: ["bash", "-c"]
    args:
-      - 'sleep 5'
+    - |
+      echo "=== DR Cleanup ==="
+      NS="$(params.TENANT_NAMESPACE)"
+
+      # Remove NetworkPolicy if it was left behind
+      oc delete networkpolicy dr-block-image-controller -n image-controller --ignore-not-found 2>/dev/null || true
+
+      # Delete test Release CRs
+      for REL in $(oc get releases.appstudio.redhat.com -n "$NS" -o name 2>/dev/null | grep "dr-sanity"); do
+        oc delete "$REL" -n "$NS" --ignore-not-found 2>/dev/null || true
+        echo "Deleted $REL"
+      done
+
+      # Delete test Backup CRs
+      for BACKUP in $(oc get backups.velero.io -n openshift-adp -o name 2>/dev/null | grep "dr-sanity"); do
+        oc delete "$BACKUP" -n openshift-adp --ignore-not-found 2>/dev/null || true
+        echo "Deleted $BACKUP"
+      done
+
+      # Delete test Restore CRs
+      for RESTORE in $(oc get restores.velero.io -n openshift-adp -o name 2>/dev/null | grep "dr-sanity"); do
+        oc delete "$RESTORE" -n openshift-adp --ignore-not-found 2>/dev/null || true
+        echo "Deleted $RESTORE"
+      done
+
+      printf "COMPLETED" > "$(results.test-result.path)"
+      echo "=== Cleanup Done ==="
Relevance

⭐⭐⭐ High

Team fixes misleading docs/help text to match actual emitted values; result description/value
mismatch likely to be corrected.

PR-#11933

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Task result description and the value written to the result file disagree in the same task
definition.

components/disaster-recovery/development/pipeline.yaml[566-600]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The cleanup task's `test-result` result is described as `PASSED or FAILED`, but the implementation always writes `COMPLETED`.

### Issue Context
Even if the result is not currently consumed, it becomes a footgun for future use and is misleading during debugging.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[566-600]

### Suggested fix
Either:
- Change the result description to match the actual value (`COMPLETED`), or
- Write `PASSED`/`FAILED` based on an explicit input/param that indicates pipeline outcome (or remove the result entirely if it is not intended to be consumed).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment on lines +1 to +2
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Localqueue api mismatch 🐞 Bug ≡ Correctness

The DR LocalQueue is defined as kueue.x-k8s.io/v1beta1, but the repo’s development Kueue CRD test
resource serves only v1beta2, so this manifest can fail to apply/sync on dev/staging clusters that
match that CRD versioning.
Agent Prompt
## Issue description
`components/disaster-recovery/development/localqueue.yaml` uses `apiVersion: kueue.x-k8s.io/v1beta1`, but the repo’s development Kueue CRD test manifest for LocalQueue only serves `v1beta2`. If the dev/staging clusters follow that versioning, ArgoCD sync/apply will fail with “no matches for kind LocalQueue in version …/v1beta1”.

## Issue Context
Development Kueue config in this repo uses `kueue.x-k8s.io/v1beta2`, and the dev chainsaw LocalQueue CRD serves only `v1beta2`.

## Fix Focus Areas
- components/disaster-recovery/development/localqueue.yaml[1-6]
- components/policies/development/kueue/queue-config/.chainsaw-test/resources/localqueue-crd.yaml[13-16]
- components/kueue/development/queue-config/cluster-queue.yaml[1-10]

## Suggested fix
- Change `apiVersion` in `localqueue.yaml` to `kueue.x-k8s.io/v1beta2` (or, if you truly must support both, split by overlay so dev/staging use v1beta2 and only environments that serve v1beta1 apply v1beta1).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +120 to +155
echo "Waiting for build PipelineRun $BUILD_PR to complete..."
for i in $(seq 1 120); do
STATUS=$(oc get pipelinerun "$BUILD_PR" -n "$NS" \
-o jsonpath='{.status.conditions[0].reason}' 2>/dev/null || echo "Pending")
if [ "$STATUS" = "Completed" ] || [ "$STATUS" = "Succeeded" ]; then
echo "OK: Build completed"
break
elif [ "$STATUS" = "Failed" ]; then
echo "FAIL: Build PipelineRun $BUILD_PR failed"
exit 1
fi
echo " Build status: $STATUS (attempt $i/120)"
sleep 15
done

echo "Waiting for integration test PipelineRun..."
for i in $(seq 1 60); do
INT_PR=$(oc get pipelineruns -n "$NS" \
-l "appstudio.openshift.io/application=$APP,test.appstudio.openshift.io/scenario" \
--sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}' 2>/dev/null || echo "")
if [ -n "$INT_PR" ]; then
INT_STATUS=$(oc get pipelinerun "$INT_PR" -n "$NS" \
-o jsonpath='{.status.conditions[0].reason}' 2>/dev/null || echo "Pending")
if [ "$INT_STATUS" = "Completed" ] || [ "$INT_STATUS" = "Succeeded" ]; then
echo "OK: Integration test completed ($INT_PR)"
break
elif [ "$INT_STATUS" = "Failed" ]; then
echo "FAIL: Integration test PipelineRun $INT_PR failed"
exit 1
fi
echo " Integration status: $INT_STATUS (attempt $i/60)"
else
echo " Waiting for integration PipelineRun to appear (attempt $i/60)"
fi
sleep 15
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Timeout polls never fail 🐞 Bug ☼ Reliability

Several polling loops in the DR Tasks break on success but never exit non-zero when the loop limit
is exhausted, so the Task can continue and print “PASSED” even when
build/integration/release/backup/restore never completed.
Agent Prompt
## Issue description
Multiple bounded polling loops (build completion, integration completion, release completion, backup completion, restore completion) have no post-loop “timed out” failure check. If the awaited condition never becomes true and never enters an explicit failure phase, the loop ends and the script proceeds as if it succeeded, causing false-positive DR results and potentially operating on incomplete state.

## Issue Context
Example patterns:
- `dr-baseline-run`: waits for build/integration/release but doesn’t fail if any of those never reach success before the loop ends.
- `dr-backup`: writes `backup-name` and prints `Backup PASSED` even if backup phase never becomes `Completed`.
- `dr-disaster-and-restore`: prints `PASSED` even if restore never becomes `Completed` before the loop ends.

## Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[120-156]
- components/disaster-recovery/development/pipeline.yaml[258-277]
- components/disaster-recovery/development/pipeline.yaml[383-409]

## Suggested fix
For each polling loop:
- Track a `success=false` flag; set it to true on success and `break`.
- After the loop, if `success != true`, print a clear timeout error and `exit 1`.
- For the disaster-and-restore task, ensure NetworkPolicy cleanup occurs on timeout paths as well (and rely on `finally` only as a last resort).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread components/disaster-recovery/development/rbac.yaml Outdated
@konflux-ci-qe-bot

konflux-ci-qe-bot commented Jul 23, 2026

Copy link
Copy Markdown

🤖 Pipeline Failure Analysis

Category: Infrastructure

The pipeline failed because critical ArgoCD applications, including backup (OADP) and Kueue components, timed out during deployment and reconciliation due to missing resources and a degraded redhat-oadp-operator subscription indicating OpenShift Operator Lifecycle Manager (OLM) issues.

📋 Technical Details

Immediate Cause

The pipeline failed due to an ARGOCD_SYNC_TIMEOUT, indicating that multiple ArgoCD applications responsible for deploying backup and Kueue components did not synchronize successfully within the 45-minute limit.

Contributing Factors

The failure to sync was primarily caused by critical resources for these applications, such as ConfigMaps, Secrets, Jobs, and Deployments, remaining in a 'Missing' state. This underlying issue stemmed from a degraded redhat-oadp-operator subscription, which reported "CatalogSourcesUnhealthy" and "ResolutionFailed," indicating problems with the OpenShift Operator Lifecycle Manager (OLM) that prevented the operator and its associated resources from being properly provisioned.

Impact

The inability to deploy and reconcile essential backup and Kueue components rendered the disaster recovery setup incomplete, directly blocking the successful execution of the appstudio-konflux-disaster-recovery step and causing the overall pipeline to fail.

🔍 Evidence

appstudio-konflux-disaster-recovery/redhat-appstudio-konflux-disaster-recovery

Category: infrastructure
Root Cause: The cluster failed to deploy and reconcile critical ArgoCD applications, including backup and Kueue components. This was due to issues with the OpenShift Operator Lifecycle Manager (OLM), as evidenced by the redhat-oadp-operator subscription being degraded with "CatalogSourcesUnhealthy" and "ResolutionFailed", leading to core resources for multiple applications remaining in a 'Missing' state.

Logs:

artifacts/step-name/build-log.txt line 1603
[2026-07-24 12:58:50] [ERROR] TIMEOUT: Applications failed to sync within 45 minutes
artifacts/step-name/build-log.txt line 1613
  Health Status: Degraded
artifacts/step-name/build-log.txt line 1615
  Unhealthy Resources:
    - ConfigMap/image-repository-resource-modifier-config: Missing - no message
    - Secret/minio-storage-configuration: Missing - no message
    - Job/create-velero-bucket: Missing - no message
    - Tenant/oadp-minio-storage: Missing - no message
    - DataProtectionApplication/velero-aws: Missing - no message
    - Subscription/redhat-oadp-operator: Degraded - 1: CatalogSourcesUnhealthy | False
2: ResolutionFailed | True
artifacts/step-name/build-log.txt line 1626
  Health Status: Degraded
artifacts/step-name/build-log.txt line 1628
  Unhealthy Resources:
    - ConfigMap/tekton-kueue-config: Missing - no message
    - Namespace/tekton-kueue: Missing - no message
    - Secret/metrics-reader: Missing - no message
    - Service/tekton-kueue-controller-manager-metrics-service: Missing - no message
    - Service/tekton-kueue-webhook-service: Missing - no message
    - ServiceAccount/metrics-reader: Missing - no message
    - ServiceAccount/tekton-kueue-controller-manager: Missing - no message
    - ServiceAccount/tekton-kueue-webhook: Missing - no message
    - MutatingWebhookConfiguration/tekton-kueue-mutating-webhook-configuration: Missing - no message
    - Deployment/tekton-kueue-controller-manager: Missing - no message
artifacts/step-name/build-log.txt line 1640
  Operation Message: waiting for completion of hook batch/Job/kueue-crd-check
artifacts/step-name/build-log.txt line 1650
[2026-07-24 12:59:05] [ERROR] Failure Reason: ARGOCD_SYNC_TIMEOUT: 4 apps failed to sync

Analysis powered by prow-failure-analysis | Build: 2080601483404906496

@vsokolen-redhat
vsokolen-redhat force-pushed the dr-sanity-pipeline-v2 branch from 6baf24c to 0bc47df Compare July 24, 2026 07:21
Comment on lines +25 to +30
echo "Checking Velero deployment..."
READY=$(oc get deploy velero -n openshift-adp -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
if [ "$READY" != "1" ]; then
echo "FAIL: Velero deployment not ready (readyReplicas=$READY)"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Velero replica check brittle 🐞 Bug ≡ Correctness

dr-preflight exits unless the Velero Deployment has exactly readyReplicas==1, so a healthy
Velero deployment scaled to 2+ replicas will incorrectly fail the DR pipeline.
Agent Prompt
### Issue description
`dr-preflight` treats Velero as ready only when `.status.readyReplicas` equals the string `"1"`. This is brittle: a healthy Velero Deployment can legitimately run with more than one replica.

### Issue Context
This check is part of the new DR sanity Tekton Task `dr-preflight` and blocks the entire pipeline.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[25-30]

### Suggested fix
Update the check to accept `readyReplicas >= 1` (or compare `readyReplicas` against the Deployment's desired replica count, e.g. `.spec.replicas`, and require them to match).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +518 to +533
echo "Finding post-restore Snapshot..."
SNAPSHOT=$(oc get snapshots.appstudio.redhat.com -n "$NS" \
-l "appstudio.openshift.io/application=$APP" \
--sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
echo "Creating post-restore Release..."
RELEASE_NAME="dr-sanity-post-restore-release-$(date +%Y%m%d%H%M%S)"
cat <<RELEASE_EOF | oc apply -f -
apiVersion: appstudio.redhat.com/v1alpha1
kind: Release
metadata:
name: $RELEASE_NAME
namespace: $NS
spec:
releasePlan: $RP
snapshot: $SNAPSHOT
RELEASE_EOF

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Snapshot unchecked post-restore 🐞 Bug ≡ Correctness

dr-post-restore-verify creates a Release using $SNAPSHOT without verifying the snapshot lookup
returned a name, so Release creation can fail (or be invalid) when no Snapshot exists.
Agent Prompt
### Issue description
In `dr-post-restore-verify`, the script queries for the latest Snapshot and then immediately interpolates it into a Release manifest without checking for an empty result.

### Issue Context
`dr-baseline-run` already contains an explicit `if [ -z "$SNAPSHOT" ]; then exit 1` guard; the post-restore path should be consistent to avoid attempting to create a Release with an empty snapshot reference.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[518-533]

### Suggested fix
Add an emptiness check after computing `SNAPSHOT` (mirroring `dr-baseline-run`), and `exit 1` with a clear error message if no Snapshot is found.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +566 to +600
results:
- name: test-result
description: Overall test result (PASSED or FAILED)
steps:
- name: cleanup
image: quay.io/konflux-ci/tools@sha256:b846eb68ab06f4c61c987bd1053e0e2dda13e18ecd5a75c945a18f7d78743bd8
command: ["bash", "-c"]
args:
- 'sleep 5'
- |
echo "=== DR Cleanup ==="
NS="$(params.TENANT_NAMESPACE)"

# Remove NetworkPolicy if it was left behind
oc delete networkpolicy dr-block-image-controller -n image-controller --ignore-not-found 2>/dev/null || true

# Delete test Release CRs
for REL in $(oc get releases.appstudio.redhat.com -n "$NS" -o name 2>/dev/null | grep "dr-sanity"); do
oc delete "$REL" -n "$NS" --ignore-not-found 2>/dev/null || true
echo "Deleted $REL"
done

# Delete test Backup CRs
for BACKUP in $(oc get backups.velero.io -n openshift-adp -o name 2>/dev/null | grep "dr-sanity"); do
oc delete "$BACKUP" -n openshift-adp --ignore-not-found 2>/dev/null || true
echo "Deleted $BACKUP"
done

# Delete test Restore CRs
for RESTORE in $(oc get restores.velero.io -n openshift-adp -o name 2>/dev/null | grep "dr-sanity"); do
oc delete "$RESTORE" -n openshift-adp --ignore-not-found 2>/dev/null || true
echo "Deleted $RESTORE"
done

printf "COMPLETED" > "$(results.test-result.path)"
echo "=== Cleanup Done ==="

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

3. Cleanup result misleading value 🐞 Bug ⚙ Maintainability

dr-cleanup documents its test-result as "PASSED or FAILED" but always writes COMPLETED, making
the published result inconsistent with its declared meaning.
Agent Prompt
### Issue description
The cleanup task's `test-result` result is described as `PASSED or FAILED`, but the implementation always writes `COMPLETED`.

### Issue Context
Even if the result is not currently consumed, it becomes a footgun for future use and is misleading during debugging.

### Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[566-600]

### Suggested fix
Either:
- Change the result description to match the actual value (`COMPLETED`), or
- Write `PASSED`/`FAILED` based on an explicit input/param that indicates pipeline outcome (or remove the result entirely if it is not intended to be consumed).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-for-redhat-appstudio

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0bc47df

…RBAC, and LocalQueue

Replace the sleep pipeline with a full DR sanity test pipeline (6 Tasks:
preflight, baseline-run, backup, disaster-and-restore, post-restore-verify,
cleanup).

Cluster-scoped and CRD-dependent resources are placed in staging/base/ only:
- Tekton Triggers (CronJob, EventListener, TriggerBinding, TriggerTemplate)
- ClusterRole/ClusterRoleBinding for dr-sanity-runner
- Kueue LocalQueue
- NetworkPolicy and cron-trigger RBAC

CI ephemeral clusters do not have Tekton Triggers CRDs and the ArgoCD app
is namespace-scoped, so cluster-scoped resources break sync (see revert
redhat-appstudio#13183 of original redhat-appstudio#13074).

CI clusters (development/): Namespace + ServiceAccount + Pipeline + Tasks.
Staging clusters (staging/base/): adds CronJob + Triggers + RBAC + LocalQueue.
@vsokolen-redhat
vsokolen-redhat force-pushed the dr-sanity-pipeline-v2 branch from 0bc47df to 1e11373 Compare July 24, 2026 10:30
Comment on lines +97 to +110
# Record PipelineRun count before triggering
BEFORE_COUNT=$(oc get pipelineruns -n "$NS" --no-headers 2>/dev/null | wc -l)

echo "Triggering build on $COMP..."
oc annotate component/"$COMP" -n "$NS" \
build.appstudio.openshift.io/request=trigger-pac-build --overwrite

echo "Waiting for new build PipelineRun..."
for i in $(seq 1 120); do
CURRENT_COUNT=$(oc get pipelineruns -n "$NS" --no-headers 2>/dev/null | wc -l)
if [ "$CURRENT_COUNT" -gt "$BEFORE_COUNT" ]; then
BUILD_PR=$(oc get pipelineruns -n "$NS" \
--sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
echo "Found build PipelineRun: $BUILD_PR"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Pipelinerun correlation race 🐞 Bug ≡ Correctness

dr-baseline-run (and dr-post-restore-verify) identifies the build/release PipelineRun by
watching the *total* PipelineRun count and then selecting the newest PipelineRun in the namespace,
so concurrent unrelated PipelineRuns can be misidentified and the DR check can wait on/validate the
wrong run.
Agent Prompt
## Issue description
The pipeline currently “guesses” the PipelineRun it should wait for by (a) counting all PipelineRuns in a namespace and (b) selecting the newest one. This can associate the DR sanity pipeline with an unrelated PipelineRun created concurrently, producing incorrect pass/fail results.

## Issue Context
This pattern appears in both the baseline run path and the post-restore verification path, and similarly for the release PipelineRun in the managed namespace.

## Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[97-114]
- components/disaster-recovery/development/pipeline.yaml[181-200]
- components/disaster-recovery/development/pipeline.yaml[458-477]

## Suggested fix approach
- Correlate by a deterministic selector instead of “newest PipelineRun”, e.g.:
  - Prefer label/field filtering that uniquely identifies the intended PipelineRun (if there is a known label for component/application/scenario/release), OR
  - Capture the set of PipelineRun names *before* triggering, then after triggering compute the new names and apply additional checks (age window, expected labels/params/ownerReferences) before choosing, OR
  - Avoid the indirect “annotate component -> controller creates PipelineRun” correlation by creating a PipelineRun/Run object directly (if acceptable for this DR test).
- Apply the same correlation approach for release PipelineRuns in `$MANAGED_NS` (don’t just pick the newest PipelineRun in that namespace).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +33 to +36
echo "Checking BackupStorageLocation..."
BSL_PHASE=$(oc get backupstoragelocations -n openshift-adp -o jsonpath='{.items[0].status.phase}' 2>/dev/null || echo "Unknown")
if [ "$BSL_PHASE" != "Available" ]; then
echo "FAIL: BSL not Available (phase=$BSL_PHASE)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Arbitrary bsl selection 🐞 Bug ☼ Reliability

dr-preflight and dr-backup select the BackupStorageLocation via .items[0], which is an
implicit selection policy; on clusters with multiple BSLs this can validate one BSL but create
backups against a different (possibly unavailable/unintended) BSL.
Agent Prompt
## Issue description
The pipeline selects BackupStorageLocation using the first returned list item (`.items[0]`). That’s an implicit policy and can point to the wrong BSL when multiple locations exist.

## Issue Context
Other Velero-related manifests in this repo use an explicit `storageLocation` name, which avoids ambiguity.

## Fix Focus Areas
- components/disaster-recovery/development/pipeline.yaml[33-38]
- components/disaster-recovery/development/pipeline.yaml[228-255]

## Suggested fix approach
- Add a pipeline/task param like `VELERO_BSL_NAME` (with a sensible default for the target cluster) and:
  - In preflight: check phase for that specific BSL by name.
  - In backup: set `spec.storageLocation` to that same explicit name.
- Alternatively: select the BSL by filtering for `.status.phase==Available` and then ensure the *same chosen name* is used for both the preflight check and the Backup creation (don’t compute it twice independently).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-for-redhat-appstudio

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1e11373

@openshift-ci

openshift-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

@vsokolen-redhat: 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/appstudio-e2e-tests 1e11373 link true /test appstudio-e2e-tests
ci/prow/appstudio-konflux-disaster-recovery 1e11373 link false /test appstudio-konflux-disaster-recovery

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.

@vsokolen-redhat

Copy link
Copy Markdown
Contributor Author

/retest

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants