diff --git a/ci-operator/config/openshift/installer/openshift-installer-main.yaml b/ci-operator/config/openshift/installer/openshift-installer-main.yaml index 163a9fc1ea484..193fc94588535 100644 --- a/ci-operator/config/openshift/installer/openshift-installer-main.yaml +++ b/ci-operator/config/openshift/installer/openshift-installer-main.yaml @@ -1612,6 +1612,16 @@ tests: env: JIRA_AGENT_MAX_ISSUES: "1" workflow: installer-jira-agent +- as: periodic-review-agent + cron: '@yearly' + steps: + workflow: installer-review-agent +- always_run: false + as: address-review-comments + optional: true + skip_if_only_changed: .* + steps: + workflow: installer-review-agent-trigger zz_generated_metadata: branch: main org: openshift diff --git a/ci-operator/jobs/openshift/installer/openshift-installer-main-periodics.yaml b/ci-operator/jobs/openshift/installer/openshift-installer-main-periodics.yaml index 6b8e1cb285070..5d5805c7784b5 100644 --- a/ci-operator/jobs/openshift/installer/openshift-installer-main-periodics.yaml +++ b/ci-operator/jobs/openshift/installer/openshift-installer-main-periodics.yaml @@ -90,3 +90,94 @@ periodics: - name: result-aggregator secret: secretName: result-aggregator +- agent: kubernetes + cluster: build01 + cron: '@yearly' + decorate: true + decoration_config: + sparse_checkout_files: + - .ci-operator.yaml + - images/baremetal/Dockerfile.ci + - images/installer/Dockerfile.ci + - images/installer/Dockerfile.upi.ci + - images/libvirt/Dockerfile.ci + - images/openstack/Dockerfile.ci + extra_refs: + - base_ref: main + org: openshift + repo: installer + sparse_checkout_files: + - .ci-operator.yaml + - images/baremetal/Dockerfile.ci + - images/installer/Dockerfile.ci + - images/installer/Dockerfile.upi.ci + - images/libvirt/Dockerfile.ci + - images/openstack/Dockerfile.ci + labels: + ci.openshift.io/generator: prowgen + pj-rehearse.openshift.io/can-be-rehearsed: "true" + name: periodic-ci-openshift-installer-main-periodic-review-agent + spec: + containers: + - args: + - --gcs-upload-secret=/secrets/gcs/service-account.json + - --image-import-pull-secret=/etc/pull-secret/.dockerconfigjson + - --lease-server-credentials-file=/etc/boskos/credentials + - --report-credentials-file=/etc/report/credentials + - --secret-dir=/secrets/ci-pull-credentials + - --target=periodic-review-agent + command: + - ci-operator + env: + - name: HTTP_SERVER_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + image: quay-proxy.ci.openshift.org/openshift/ci:ci_ci-operator_latest + imagePullPolicy: Always + name: "" + ports: + - containerPort: 8080 + name: http + resources: + requests: + cpu: 10m + volumeMounts: + - mountPath: /etc/boskos + name: boskos + readOnly: true + - mountPath: /secrets/ci-pull-credentials + name: ci-pull-credentials + readOnly: true + - mountPath: /secrets/gcs + name: gcs-credentials + readOnly: true + - mountPath: /secrets/manifest-tool + name: manifest-tool-local-pusher + readOnly: true + - mountPath: /etc/pull-secret + name: pull-secret + readOnly: true + - mountPath: /etc/report + name: result-aggregator + readOnly: true + serviceAccountName: ci-operator + volumes: + - name: boskos + secret: + items: + - key: credentials + path: credentials + secretName: boskos-credentials + - name: ci-pull-credentials + secret: + secretName: ci-pull-credentials + - name: manifest-tool-local-pusher + secret: + secretName: manifest-tool-local-pusher + - name: pull-secret + secret: + secretName: registry-pull-credentials + - name: result-aggregator + secret: + secretName: result-aggregator diff --git a/ci-operator/jobs/openshift/installer/openshift-installer-main-presubmits.yaml b/ci-operator/jobs/openshift/installer/openshift-installer-main-presubmits.yaml index 7dbeef16c87ff..509852fd2ec50 100644 --- a/ci-operator/jobs/openshift/installer/openshift-installer-main-presubmits.yaml +++ b/ci-operator/jobs/openshift/installer/openshift-installer-main-presubmits.yaml @@ -1,5 +1,93 @@ presubmits: openshift/installer: + - agent: kubernetes + always_run: false + branches: + - ^main$ + - ^main- + cluster: build01 + context: ci/prow/address-review-comments + decorate: true + decoration_config: + sparse_checkout_files: + - .ci-operator.yaml + - images/baremetal/Dockerfile.ci + - images/installer/Dockerfile.ci + - images/installer/Dockerfile.upi.ci + - images/libvirt/Dockerfile.ci + - images/openstack/Dockerfile.ci + labels: + ci.openshift.io/generator: prowgen + pj-rehearse.openshift.io/can-be-rehearsed: "true" + name: pull-ci-openshift-installer-main-address-review-comments + optional: true + rerun_command: /test address-review-comments + skip_if_only_changed: .* + spec: + containers: + - args: + - --gcs-upload-secret=/secrets/gcs/service-account.json + - --image-import-pull-secret=/etc/pull-secret/.dockerconfigjson + - --lease-server-credentials-file=/etc/boskos/credentials + - --report-credentials-file=/etc/report/credentials + - --secret-dir=/secrets/ci-pull-credentials + - --target=address-review-comments + command: + - ci-operator + env: + - name: HTTP_SERVER_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + image: quay-proxy.ci.openshift.org/openshift/ci:ci_ci-operator_latest + imagePullPolicy: Always + name: "" + ports: + - containerPort: 8080 + name: http + resources: + requests: + cpu: 10m + volumeMounts: + - mountPath: /etc/boskos + name: boskos + readOnly: true + - mountPath: /secrets/ci-pull-credentials + name: ci-pull-credentials + readOnly: true + - mountPath: /secrets/gcs + name: gcs-credentials + readOnly: true + - mountPath: /secrets/manifest-tool + name: manifest-tool-local-pusher + readOnly: true + - mountPath: /etc/pull-secret + name: pull-secret + readOnly: true + - mountPath: /etc/report + name: result-aggregator + readOnly: true + serviceAccountName: ci-operator + volumes: + - name: boskos + secret: + items: + - key: credentials + path: credentials + secretName: boskos-credentials + - name: ci-pull-credentials + secret: + secretName: ci-pull-credentials + - name: manifest-tool-local-pusher + secret: + secretName: manifest-tool-local-pusher + - name: pull-secret + secret: + secretName: registry-pull-credentials + - name: result-aggregator + secret: + secretName: result-aggregator + trigger: (?m)^/test( | .* )address-review-comments,?($|\s.*) - agent: kubernetes always_run: true branches: diff --git a/ci-operator/step-registry/hypershift/review-agent/README.md b/ci-operator/step-registry/hypershift/review-agent/README.md index 488a462c83aae..ae8447336b533 100644 --- a/ci-operator/step-registry/hypershift/review-agent/README.md +++ b/ci-operator/step-registry/hypershift/review-agent/README.md @@ -12,6 +12,9 @@ configuration: | `REVIEW_AGENT_FORK_REPO` | `https://github.com/hypershift-community/hypershift` | | `REVIEW_AGENT_UPSTREAM_REPO` | `openshift/hypershift` | +This uses App mode (the default). For PAT mode, set `REVIEW_AGENT_AUTH_MODE="pat"` and +`REVIEW_AGENT_FORK_ORG` instead of `REVIEW_AGENT_FORK_REPO` — the fork URL is auto-derived. + Credentials use the `hypershift-team-claude-prow` secret (configured in the generic step refs). ## Jobs diff --git a/ci-operator/step-registry/hypershift/review-agent/hypershift-review-agent-workflow.yaml b/ci-operator/step-registry/hypershift/review-agent/hypershift-review-agent-workflow.yaml index 9512436b7f6d3..0f08d3844a28c 100644 --- a/ci-operator/step-registry/hypershift/review-agent/hypershift-review-agent-workflow.yaml +++ b/ci-operator/step-registry/hypershift/review-agent/hypershift-review-agent-workflow.yaml @@ -9,7 +9,8 @@ workflow: post: - ref: review-agent-report env: - REVIEW_AGENT_FORK_REPO: "https://github.com/hypershift-community/hypershift" + REVIEW_AGENT_AUTH_MODE: "pat" + REVIEW_AGENT_FORK_ORG: "jira-solve-bot" REVIEW_AGENT_UPSTREAM_REPO: "openshift/hypershift" documentation: |- HyperShift-specific wrapper for the generic review-agent workflow. diff --git a/ci-operator/step-registry/installer/review-agent/OWNERS b/ci-operator/step-registry/installer/review-agent/OWNERS new file mode 100644 index 0000000000000..08ccacb339494 --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/OWNERS @@ -0,0 +1,10 @@ +approvers: +- bryan-cox +- csrwng +- celebdor +- enxebre +reviewers: +- bryan-cox +- csrwng +- celebdor +- enxebre diff --git a/ci-operator/step-registry/installer/review-agent/installer-review-agent-workflow.metadata.json b/ci-operator/step-registry/installer/review-agent/installer-review-agent-workflow.metadata.json new file mode 100644 index 0000000000000..dcb0090bfebff --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/installer-review-agent-workflow.metadata.json @@ -0,0 +1,17 @@ +{ + "path": "installer/review-agent/installer-review-agent-workflow.yaml", + "owners": { + "approvers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre" + ], + "reviewers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/installer/review-agent/installer-review-agent-workflow.yaml b/ci-operator/step-registry/installer/review-agent/installer-review-agent-workflow.yaml new file mode 100644 index 0000000000000..a1c6bc69e567a --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/installer-review-agent-workflow.yaml @@ -0,0 +1,19 @@ +workflow: + as: installer-review-agent + steps: + pre: + - ref: review-agent-setup + test: + - ref: review-agent-process + post: + - ref: review-agent-report + env: + REVIEW_AGENT_AUTH_MODE: "pat" + REVIEW_AGENT_FORK_ORG: "jira-solve-bot" + REVIEW_AGENT_UPSTREAM_REPO: "openshift/installer" + documentation: |- + Installer-specific wrapper for the generic review-agent workflow. + Uses PAT auth mode — auto-forks openshift/installer into jira-solve-bot + for push operations. + + Credentials: Uses hypershift-team-claude-prow (shared secret with gh-pat). diff --git a/ci-operator/step-registry/installer/review-agent/trigger/OWNERS b/ci-operator/step-registry/installer/review-agent/trigger/OWNERS new file mode 100644 index 0000000000000..08ccacb339494 --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/trigger/OWNERS @@ -0,0 +1,10 @@ +approvers: +- bryan-cox +- csrwng +- celebdor +- enxebre +reviewers: +- bryan-cox +- csrwng +- celebdor +- enxebre diff --git a/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-commands.sh b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-commands.sh new file mode 100644 index 0000000000000..90d5b4d92d03e --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-commands.sh @@ -0,0 +1,110 @@ +#!/bin/bash +set -euo pipefail + +echo "=== Installer Review Agent Trigger ===" + +PR_NUMBER="${PULL_NUMBER:-}" +if [ -z "$PR_NUMBER" ]; then + echo "ERROR: PULL_NUMBER not set. This step must run as a presubmit." + exit 1 +fi +echo "Triggering review agent for PR #$PR_NUMBER" + +UPSTREAM_REPO="${REVIEW_AGENT_UPSTREAM_REPO:-openshift/installer}" +CREDS_DIR="/var/run/claude-code-service-account" +TOKEN_FILE="${CREDS_DIR}/gangway-token" +PAT_FILE="${CREDS_DIR}/${REVIEW_AGENT_PAT_KEY:-gh-pat}" + +if [ ! -f "$TOKEN_FILE" ]; then + echo "ERROR: Gangway token not found at ${TOKEN_FILE}" + exit 1 +fi + +POST_DATA=$(jq -n --arg pr "$PR_NUMBER" \ + '{job_execution_type: "1", pod_spec_options: {envs: {MULTISTAGE_PARAM_OVERRIDE_REVIEW_AGENT_TARGET_PR: $pr}}}') + +echo "Triggering periodic job: ${PERIODIC_JOB_NAME}" + +MAX_RETRIES=10 +RETRY_INTERVAL=10 +JOB_ID="" + +for ((i=1; i<=MAX_RETRIES; i++)); do + set +x + RESPONSE=$(curl -s -X POST -d "${POST_DATA}" \ + -H "Authorization: Bearer $(cat "${TOKEN_FILE}")" \ + "${GANGWAY_API}/v1/executions/${PERIODIC_JOB_NAME}" \ + -w "\n%{http_code}") + set -x + HTTP_STATUS=$(echo "$RESPONSE" | tail -1) + JSON_BODY=$(echo "$RESPONSE" | sed '$d') + + if [ "$HTTP_STATUS" -eq 200 ]; then + JOB_ID=$(echo "$JSON_BODY" | jq -r '.id') + echo "Job triggered successfully. Job ID: ${JOB_ID}" + break + else + echo "[$i/$MAX_RETRIES] Gangway API returned HTTP $HTTP_STATUS. Retrying in ${RETRY_INTERVAL}s..." + sleep "$RETRY_INTERVAL" + fi +done + +if [ -z "$JOB_ID" ] || [ "$JOB_ID" = "null" ]; then + echo "ERROR: Failed to trigger periodic job after $MAX_RETRIES retries" + exit 1 +fi + +# Poll gangway for the Prow job URL (up to ~60s) +sleep 10 + +set +x +JOB_URL="" +for ((i=1; i<=10; i++)); do + STATUS_RESPONSE=$(curl -s -X GET \ + -H "Authorization: Bearer $(cat "${TOKEN_FILE}")" \ + "${GANGWAY_API}/v1/executions/${JOB_ID}" \ + -w "\n%{http_code}") + STATUS_HTTP=$(echo "$STATUS_RESPONSE" | tail -1) + STATUS_BODY=$(echo "$STATUS_RESPONSE" | sed '$d') + + if [ "$STATUS_HTTP" -eq 200 ]; then + JOB_URL=$(echo "$STATUS_BODY" | jq -r '.job_url // empty') + if [ -n "$JOB_URL" ]; then + echo "Job URL: ${JOB_URL}" + break + fi + fi + echo "[$i/10] Waiting for Prow job URL..." + sleep 5 +done +set -x + +# Post a comment on the PR using the PAT +if [ -f "$PAT_FILE" ]; then + echo "Posting PR comment..." + [[ $- == *x* ]] && _was_tracing=true || _was_tracing=false + set +x + + GITHUB_TOKEN_PAT=$(cat "$PAT_FILE") + + if [ -n "$JOB_URL" ]; then + COMMENT_BODY="Review agent triggered. [View job](${JOB_URL})" + else + COMMENT_BODY="Review agent triggered (Gangway execution ID: \`${JOB_ID}\`). The Prow job has not started yet — check the [job history](https://prow.ci.openshift.org/job-history/gs/test-platform-results/logs/${PERIODIC_JOB_NAME}) for the run once it begins." + fi + + curl --fail --silent --show-error -X POST \ + -H "Authorization: token ${GITHUB_TOKEN_PAT}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${UPSTREAM_REPO}/issues/${PR_NUMBER}/comments" \ + -d "$(jq -n --arg body "$COMMENT_BODY" '{body: $body}')" > /dev/null + + $_was_tracing && set -x || true + echo "Comment posted on PR #$PR_NUMBER" +else + echo "WARNING: PAT not found at ${PAT_FILE}, skipping PR comment" +fi + +echo "=== Trigger Complete ===" +echo "Job ID: ${JOB_ID}" +echo "Job URL: ${JOB_URL:-pending}" diff --git a/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-ref.metadata.json b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-ref.metadata.json new file mode 100644 index 0000000000000..5cfaad27e86b3 --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-ref.metadata.json @@ -0,0 +1,17 @@ +{ + "path": "installer/review-agent/trigger/installer-review-agent-trigger-ref.yaml", + "owners": { + "approvers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre" + ], + "reviewers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-ref.yaml b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-ref.yaml new file mode 100644 index 0000000000000..826fd902e54ae --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-ref.yaml @@ -0,0 +1,33 @@ +ref: + as: installer-review-agent-trigger + from: claude-ai-helpers + commands: installer-review-agent-trigger-commands.sh + env: + - name: GANGWAY_API + default: "https://gangway-ci.apps.ci.l2s4.p1.openshiftapps.com" + documentation: |- + Gangway API endpoint for triggering periodic jobs. + - name: PERIODIC_JOB_NAME + default: "periodic-ci-openshift-installer-main-periodic-review-agent" + documentation: |- + Name of the periodic job to trigger via gangway. + - name: REVIEW_AGENT_UPSTREAM_REPO + default: "openshift/installer" + documentation: |- + Upstream repo for posting PR comments. + - name: REVIEW_AGENT_PAT_KEY + default: "gh-pat" + documentation: |- + Key name in the credential secret for the GitHub PAT. + resources: + requests: + cpu: 100m + memory: 256Mi + credentials: + - namespace: test-credentials + name: hypershift-team-claude-prow + mount_path: /var/run/claude-code-service-account + documentation: |- + Trigger step for the Installer Review Agent. + Uses PAT auth to post a PR comment. Triggers the periodic review-agent + job via gangway instead of running inline (avoids presubmit abort on push). diff --git a/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-workflow.metadata.json b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-workflow.metadata.json new file mode 100644 index 0000000000000..f13d55a1885f9 --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-workflow.metadata.json @@ -0,0 +1,17 @@ +{ + "path": "installer/review-agent/trigger/installer-review-agent-trigger-workflow.yaml", + "owners": { + "approvers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre" + ], + "reviewers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-workflow.yaml b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-workflow.yaml new file mode 100644 index 0000000000000..c2f3f7256bf9b --- /dev/null +++ b/ci-operator/step-registry/installer/review-agent/trigger/installer-review-agent-trigger-workflow.yaml @@ -0,0 +1,9 @@ +workflow: + as: installer-review-agent-trigger + steps: + test: + - ref: installer-review-agent-trigger + documentation: |- + Lightweight workflow that triggers the installer review-agent periodic + job via gangway instead of running the agent inline. This avoids the + presubmit aborting when the agent pushes to the PR branch. diff --git a/ci-operator/step-registry/review-agent/ONBOARDING.md b/ci-operator/step-registry/review-agent/ONBOARDING.md index 3573303739413..b8c1878dfaa7c 100644 --- a/ci-operator/step-registry/review-agent/ONBOARDING.md +++ b/ci-operator/step-registry/review-agent/ONBOARDING.md @@ -4,9 +4,24 @@ This guide walks you through onboarding your team to the generic review-agent wo ## Prerequisites -### 1. GitHub App +### Authentication -You need a GitHub App installed on both your fork and upstream repos: +The review-agent supports two authentication modes: + +#### PAT Mode (Works out of the box) + +A GitHub classic PAT (`gh-pat`) is already configured in the shared `hypershift-team-claude-prow` secret. Teams using this secret just need to set two env vars in their workflow — no additional credential setup required: + +```yaml +REVIEW_AGENT_AUTH_MODE: "pat" +REVIEW_AGENT_FORK_ORG: "jira-solve-bot" +``` + +The PAT automatically handles fork creation, branch push, and PR operations against any public `openshift/` repo. The fork is created on demand in the `jira-solve-bot` account if it doesn't already exist. + +#### GitHub App Mode (Default) + +If you prefer per-repo GitHub App installations, this is the default when `REVIEW_AGENT_AUTH_MODE` is not set. Requires a GitHub App installed on both your fork and upstream repos: | Key | Description | |---|---| @@ -19,9 +34,18 @@ The app needs these permissions: - **Fork repo**: Contents (read/write), Pull requests (read/write) - **Upstream repo**: Pull requests (read), Issues (read) -### 2. Vault Secret +### Vault Secret -Create a secret in the `test-credentials` namespace with these keys: +The shared `hypershift-team-claude-prow` secret already contains both PAT and App credentials. If your team uses a different secret, create one in the `test-credentials` namespace with these keys: + +**PAT mode:** + +| Key | Description | +|---|---| +| `claude-prow` | GCP service account JSON for Vertex AI authentication | +| `gh-pat` | GitHub classic PAT with `public_repo` and `read:org` scopes | + +**App mode:** | Key | Description | |---|---| @@ -33,7 +57,7 @@ Create a secret in the `test-credentials` namespace with these keys: Mount path: `/var/run/claude-code-service-account` -### 3. Vertex AI Access +### Vertex AI Access The GCP service account needs access to the `itpc-gcp-hybrid-pe-eng-claude` project (or your configured project) for Claude API access via Vertex AI. @@ -43,7 +67,31 @@ The GCP service account needs access to the `itpc-gcp-hybrid-pe-eng-claude` proj Create a thin workflow YAML under your team's step-registry directory that sets the required env vars and delegates to the generic steps. -Example for `ci-operator/step-registry/myteam/review-agent/myteam-review-agent-workflow.yaml`: +**PAT mode** (recommended): + +```yaml +# ci-operator/step-registry/myteam/review-agent/myteam-review-agent-workflow.yaml +workflow: + as: myteam-review-agent + steps: + pre: + - ref: jira-agent-github-app-auth + - ref: review-agent-setup + test: + - ref: review-agent-process + post: + - ref: review-agent-report + env: + REVIEW_AGENT_AUTH_MODE: "pat" + REVIEW_AGENT_FORK_ORG: "jira-solve-bot" + REVIEW_AGENT_UPSTREAM_REPO: "openshift/myrepo" + documentation: |- + MyTeam-specific wrapper for the generic review-agent workflow. + Uses PAT auth mode — the agent auto-forks the upstream repo + into the bot user's account if needed. +``` + +**App mode:** ```yaml workflow: @@ -124,9 +172,17 @@ This generates the Prow job configs and validates the step registry. | Variable | Example | Purpose | |---|---|---| -| `REVIEW_AGENT_FORK_REPO` | `https://github.com/hypershift-community/hypershift` | Fork repo URL to clone and push to | | `REVIEW_AGENT_UPSTREAM_REPO` | `openshift/hypershift` | Upstream `owner/repo` for `gh pr` operations | +### Auth mode configuration + +| Variable | Default | Purpose | +|---|---|---| +| `REVIEW_AGENT_AUTH_MODE` | `app` | `"app"` (GitHub App) or `"pat"` (classic PAT) | +| `REVIEW_AGENT_FORK_ORG` | (none) | GitHub user/org to fork into (PAT mode only) | +| `REVIEW_AGENT_PAT_KEY` | `gh-pat` | Key name in secret for the PAT (PAT mode only) | +| `REVIEW_AGENT_FORK_REPO` | (none) | Fork repo URL (required in App mode, auto-derived in PAT mode) | + ### Optional (override in CI config) | Variable | Default | Purpose | @@ -140,6 +196,7 @@ This generates the Prow job configs and validates the step registry. | Value | Derived From | Example | |---|---|---| | Clone directory | `basename $REVIEW_AGENT_FORK_REPO` | `/tmp/hypershift` | +| Fork repo (PAT mode) | `REVIEW_AGENT_FORK_ORG` + upstream repo name | `jira-solve-bot/hypershift` | | System prompt context | Both env vars | "PR in openshift/hypershift from hypershift fork" | | Telemetry `repo` field | `REVIEW_AGENT_UPSTREAM_REPO` | `openshift/hypershift` | | Report PR links | `REVIEW_AGENT_UPSTREAM_REPO` | `github.com/openshift/hypershift/pull/123` | @@ -150,6 +207,8 @@ For on-demand Gangway invocation (triggering the periodic job from a PR comment ## Example: Complete Workflow for `openshift/example-operator` +**PAT mode:** + ```yaml # ci-operator/step-registry/example-operator/review-agent/example-operator-review-agent-workflow.yaml workflow: @@ -163,10 +222,12 @@ workflow: post: - ref: review-agent-report env: - REVIEW_AGENT_FORK_REPO: "https://github.com/example-org/example-operator" + REVIEW_AGENT_AUTH_MODE: "pat" + REVIEW_AGENT_FORK_ORG: "jira-solve-bot" REVIEW_AGENT_UPSTREAM_REPO: "openshift/example-operator" documentation: |- Example Operator wrapper for the generic review-agent workflow. + Uses PAT mode for auto-fork and PR operations. ``` ```yaml diff --git a/ci-operator/step-registry/review-agent/README.md b/ci-operator/step-registry/review-agent/README.md index 617545939577f..a8eb7e5ef764f 100644 --- a/ci-operator/step-registry/review-agent/README.md +++ b/ci-operator/step-registry/review-agent/README.md @@ -12,14 +12,27 @@ This workflow processes a single PR per invocation: ## Architecture -Teams onboard by creating a thin wrapper workflow that sets two environment variables: +Teams onboard by creating a thin wrapper workflow. Two authentication modes are supported: -| Variable | Purpose | -|---|---| -| `REVIEW_AGENT_FORK_REPO` | Fork repo URL to clone and push to | -| `REVIEW_AGENT_UPSTREAM_REPO` | Upstream `owner/repo` for `gh pr` operations | +### App mode (default) -All other values (clone dir, git remote URL, system prompt, PR links, report footer, telemetry repo field) are derived from these two. +| Variable | Required | Purpose | +|---|---|---| +| `REVIEW_AGENT_FORK_REPO` | Yes | Fork repo URL to clone and push to | +| `REVIEW_AGENT_UPSTREAM_REPO` | Yes | Upstream `owner/repo` for `gh pr` operations | + +### PAT mode + +| Variable | Required | Purpose | +|---|---|---| +| `REVIEW_AGENT_AUTH_MODE` | Yes | Set to `"pat"` | +| `REVIEW_AGENT_FORK_ORG` | Yes | GitHub org/user to fork into (fork auto-created if missing) | +| `REVIEW_AGENT_UPSTREAM_REPO` | Yes | Upstream `owner/repo` for `gh pr` operations | +| `REVIEW_AGENT_PAT_KEY` | No | Key name in secret for the PAT (default: `gh-pat`) | + +In PAT mode, `REVIEW_AGENT_FORK_REPO` is auto-derived from `REVIEW_AGENT_FORK_ORG` and the upstream repo name. + +All other values (clone dir, git remote URL, system prompt, PR links, report footer, telemetry repo field) are derived from these variables. Teams with a different credential secret create thin ref YAML wrappers pointing to the generic commands scripts with their own `credentials:` block. See [ONBOARDING.md](ONBOARDING.md). diff --git a/ci-operator/step-registry/review-agent/process/review-agent-process-commands.sh b/ci-operator/step-registry/review-agent/process/review-agent-process-commands.sh index 07aa45bc9fee8..338330382af36 100644 --- a/ci-operator/step-registry/review-agent/process/review-agent-process-commands.sh +++ b/ci-operator/step-registry/review-agent/process/review-agent-process-commands.sh @@ -3,9 +3,18 @@ set -euo pipefail echo "=== Review Agent Process ===" +# Auth mode: "app" (GitHub App, default) or "pat" (classic PAT) +REVIEW_AGENT_AUTH_MODE="${REVIEW_AGENT_AUTH_MODE:-app}" +REVIEW_AGENT_PAT_KEY="${REVIEW_AGENT_PAT_KEY:-gh-pat}" +REVIEW_AGENT_FORK_ORG="${REVIEW_AGENT_FORK_ORG:-}" + # Validate required env vars -if [[ -z "${REVIEW_AGENT_FORK_REPO:-}" ]]; then - echo "ERROR: REVIEW_AGENT_FORK_REPO is required (e.g. https://github.com/hypershift-community/hypershift)" +if [[ "$REVIEW_AGENT_AUTH_MODE" == "app" ]] && [[ -z "${REVIEW_AGENT_FORK_REPO:-}" ]]; then + echo "ERROR: REVIEW_AGENT_FORK_REPO is required in App auth mode (e.g. https://github.com/hypershift-community/hypershift)" + exit 1 +fi +if [[ "$REVIEW_AGENT_AUTH_MODE" == "pat" ]] && [[ -z "$REVIEW_AGENT_FORK_ORG" ]]; then + echo "ERROR: REVIEW_AGENT_FORK_ORG is required in PAT auth mode" exit 1 fi if [[ -z "${REVIEW_AGENT_UPSTREAM_REPO:-}" ]]; then @@ -13,6 +22,13 @@ if [[ -z "${REVIEW_AGENT_UPSTREAM_REPO:-}" ]]; then exit 1 fi +# In PAT mode, derive fork repo from FORK_ORG + upstream repo name if not set +if [[ "$REVIEW_AGENT_AUTH_MODE" == "pat" ]] && [[ -z "${REVIEW_AGENT_FORK_REPO:-}" ]]; then + REVIEW_AGENT_FORK_REPO="https://github.com/${REVIEW_AGENT_FORK_ORG}/${REVIEW_AGENT_UPSTREAM_REPO#*/}" +fi + +echo "Configuration: AUTH_MODE=$REVIEW_AGENT_AUTH_MODE" + # Derive clone directory from fork repo URL CLONE_DIR="/tmp/$(basename "$REVIEW_AGENT_FORK_REPO")" @@ -66,62 +82,121 @@ git config user.email "ci-bot@redhat.com" # Add upstream remote for PR operations git remote add upstream "https://github.com/${REVIEW_AGENT_UPSTREAM_REPO}.git" -# Source the github-app-auth library (written by jira-agent-github-app-auth pre step) -echo "Loading GitHub App auth library..." -if [ ! -f "${SHARED_DIR}/github-app-auth.sh" ]; then - echo "ERROR: github-app-auth.sh not found in SHARED_DIR." - echo "Ensure jira-agent-github-app-auth runs as a pre step." - exit 1 -fi -# shellcheck source=/dev/null -source "${SHARED_DIR}/github-app-auth.sh" - GITHUB_APP_CREDS_DIR="/var/run/claude-code-service-account" -INSTALLATION_ID_FORK_FILE="${GITHUB_APP_CREDS_DIR}/installation-id" -INSTALLATION_ID_UPSTREAM_FILE="${GITHUB_APP_CREDS_DIR}/o-h-installation-id" - -if [ ! -f "$INSTALLATION_ID_FORK_FILE" ] || [ ! -f "$INSTALLATION_ID_UPSTREAM_FILE" ]; then - echo "GitHub App credentials not yet available in ${GITHUB_APP_CREDS_DIR}" - echo "Available files:" - ls -la "${GITHUB_APP_CREDS_DIR}/" || echo "Directory does not exist" - echo "" - echo "Waiting for Vault secretsync to complete. The following keys are required:" - echo " - app-id" - echo " - installation-id (for fork)" - echo " - o-h-installation-id (for upstream)" - echo " - private-key" - echo "" - echo "ERROR: Required credentials are missing. Re-run once secrets are synced." - exit 1 -fi -INSTALLATION_ID_FORK=$(cat "$INSTALLATION_ID_FORK_FILE") -INSTALLATION_ID_UPSTREAM=$(cat "$INSTALLATION_ID_UPSTREAM_FILE") - -# Generate token for fork - for pushing branches -echo "Generating GitHub App token for fork..." -GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK") -if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then - echo "ERROR: Failed to generate GitHub App token for fork" - exit 1 -fi -echo "Fork token generated successfully" +if [[ "$REVIEW_AGENT_AUTH_MODE" == "pat" ]]; then + # PAT mode: single token for push + PR operations + echo "Loading GitHub PAT credentials..." + PAT_FILE="${GITHUB_APP_CREDS_DIR}/${REVIEW_AGENT_PAT_KEY}" + if [ ! -f "$PAT_FILE" ]; then + echo "ERROR: PAT file not found: $PAT_FILE" + ls -la "${GITHUB_APP_CREDS_DIR}/" || echo "Directory does not exist" + exit 1 + fi + [[ $- == *x* ]] && _was_tracing=true || _was_tracing=false + set +x + GITHUB_TOKEN_PAT=$(cat "$PAT_FILE") + if [ -z "$GITHUB_TOKEN_PAT" ]; then + echo "ERROR: PAT file is empty: $PAT_FILE" + $_was_tracing && set -x || true + exit 1 + fi + git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_PAT}; }; f" + export GITHUB_TOKEN="$GITHUB_TOKEN_PAT" + echo "PAT configured for git and GitHub CLI" + $_was_tracing && set -x || true + + # Ensure the fork exists (auto-fork if needed) + FORK_REPO_NAME="${REVIEW_AGENT_UPSTREAM_REPO#*/}" + echo "Checking if fork ${REVIEW_AGENT_FORK_ORG}/${FORK_REPO_NAME} exists..." + FORK_HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REVIEW_AGENT_FORK_ORG}/${FORK_REPO_NAME}") + if [ "$FORK_HTTP_CODE" != "200" ]; then + echo "Fork not found (HTTP ${FORK_HTTP_CODE}). Creating fork of ${REVIEW_AGENT_UPSTREAM_REPO}..." + FORK_RESPONSE=$(curl -s -X POST \ + --connect-timeout 10 --max-time 30 \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REVIEW_AGENT_UPSTREAM_REPO}/forks" \ + -d '{"default_branch_only":true}') + FORK_FULL_NAME=$(echo "$FORK_RESPONSE" | jq -r '.full_name // empty' 2>/dev/null) + if [ -z "$FORK_FULL_NAME" ]; then + echo "ERROR: Failed to create fork. API response:" + echo "$FORK_RESPONSE" | head -20 + exit 1 + fi + echo "Fork creation initiated: ${FORK_FULL_NAME}" + # Poll until ready + WAITED=0 + while [ $WAITED -lt 120 ]; do + FORK_HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REVIEW_AGENT_FORK_ORG}/${FORK_REPO_NAME}") + [ "$FORK_HTTP_CODE" = "200" ] && break + echo "Waiting for fork to be ready... (${WAITED}s/120s)" + sleep 10 + WAITED=$((WAITED + 10)) + done + if [ "$FORK_HTTP_CODE" != "200" ]; then + echo "ERROR: Fork not ready after 120s" + exit 1 + fi + echo "Fork ${REVIEW_AGENT_FORK_ORG}/${FORK_REPO_NAME} is ready" + else + echo "Fork ${REVIEW_AGENT_FORK_ORG}/${FORK_REPO_NAME} already exists" + fi +else + # App mode: separate fork/upstream tokens + echo "Loading GitHub App auth library..." + if [ ! -f "${SHARED_DIR}/github-app-auth.sh" ]; then + echo "ERROR: github-app-auth.sh not found in SHARED_DIR." + echo "Ensure jira-agent-github-app-auth runs as a pre step." + exit 1 + fi + # shellcheck source=/dev/null + source "${SHARED_DIR}/github-app-auth.sh" + + INSTALLATION_ID_FORK_FILE="${GITHUB_APP_CREDS_DIR}/installation-id" + INSTALLATION_ID_UPSTREAM_FILE="${GITHUB_APP_CREDS_DIR}/o-h-installation-id" + + if [ ! -f "$INSTALLATION_ID_FORK_FILE" ] || [ ! -f "$INSTALLATION_ID_UPSTREAM_FILE" ]; then + echo "GitHub App credentials not yet available in ${GITHUB_APP_CREDS_DIR}" + echo "Available files:" + ls -la "${GITHUB_APP_CREDS_DIR}/" || echo "Directory does not exist" + echo "ERROR: Required credentials are missing. Re-run once secrets are synced." + exit 1 + fi -# Generate token for upstream - for reading PRs and comments -echo "Generating GitHub App token for upstream..." -GITHUB_TOKEN_UPSTREAM=$(generate_github_token "$INSTALLATION_ID_UPSTREAM") -if [ -z "$GITHUB_TOKEN_UPSTREAM" ] || [ "$GITHUB_TOKEN_UPSTREAM" = "null" ]; then - echo "ERROR: Failed to generate GitHub App token for upstream" - exit 1 -fi -echo "Upstream token generated successfully" + INSTALLATION_ID_FORK=$(cat "$INSTALLATION_ID_FORK_FILE") + INSTALLATION_ID_UPSTREAM=$(cat "$INSTALLATION_ID_UPSTREAM_FILE") -# Configure git to use the fork token for push operations via credential helper -git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_FORK}; }; f" + echo "Generating GitHub App token for fork..." + GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK") + if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then + echo "ERROR: Failed to generate GitHub App token for fork" + exit 1 + fi + echo "Fork token generated successfully" -# Export upstream token as GITHUB_TOKEN for gh CLI (used for PR operations) -export GITHUB_TOKEN="$GITHUB_TOKEN_UPSTREAM" -echo "GitHub App tokens configured successfully" + echo "Generating GitHub App token for upstream..." + GITHUB_TOKEN_UPSTREAM=$(generate_github_token "$INSTALLATION_ID_UPSTREAM") + if [ -z "$GITHUB_TOKEN_UPSTREAM" ] || [ "$GITHUB_TOKEN_UPSTREAM" = "null" ]; then + echo "ERROR: Failed to generate GitHub App token for upstream" + exit 1 + fi + echo "Upstream token generated successfully" + + # Disable tracing due to token handling + [[ $- == *x* ]] && _was_tracing=true || _was_tracing=false + set +x + git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_FORK}; }; f" + export GITHUB_TOKEN="$GITHUB_TOKEN_UPSTREAM" + echo "GitHub App tokens configured successfully" + $_was_tracing && set -x || true +fi # TODO: Stronger sandboxing (container-level isolation, ai-guardian, PreToolUse hooks) # tracked in https://redhat.atlassian.net/browse/CNTRLPLANE-3750 diff --git a/ci-operator/step-registry/review-agent/process/review-agent-process-ref.yaml b/ci-operator/step-registry/review-agent/process/review-agent-process-ref.yaml index cf3955c1967d6..24ca799fd1ea6 100644 --- a/ci-operator/step-registry/review-agent/process/review-agent-process-ref.yaml +++ b/ci-operator/step-registry/review-agent/process/review-agent-process-ref.yaml @@ -19,16 +19,37 @@ ref: default: "/var/run/claude-code-service-account/claude-prow" documentation: |- Path to the Google Cloud service account JSON key file for Vertex AI authentication. + - name: REVIEW_AGENT_AUTH_MODE + default: "app" + documentation: |- + Authentication mode: "app" (GitHub App, default) or "pat" (classic PAT). + In "pat" mode, a single classic PAT handles fork creation, push, and PR + operations against any public repo. Requires REVIEW_AGENT_FORK_ORG. + - name: REVIEW_AGENT_FORK_ORG + default: "" + documentation: |- + GitHub org or user account to fork repos into (PAT mode only). + Example: "jira-solve-bot" + - name: REVIEW_AGENT_PAT_KEY + default: "gh-pat" + documentation: |- + Key name in the credential secret for the GitHub classic PAT (PAT mode only). - name: REVIEW_AGENT_FORK_REPO default: "" documentation: |- Fork repo URL to clone and push to (e.g. https://github.com/hypershift-community/hypershift). - Set by the team's wrapper workflow. + Required in App mode. In PAT mode, auto-derived from REVIEW_AGENT_FORK_ORG + and the upstream repo name. - name: REVIEW_AGENT_UPSTREAM_REPO default: "" documentation: |- Upstream owner/repo for gh CLI PR operations (e.g. openshift/hypershift). - Set by the team's wrapper workflow. + Required in both App and PAT modes. Set by the team's wrapper workflow. + - name: REVIEW_AGENT_TARGET_PR + default: "" + documentation: |- + Explicit PR number to process. When set, overrides PULL_NUMBER from the + presubmit context. Useful for periodic jobs triggered via gangway. - name: MULTISTAGE_PARAM_OVERRIDE_REVIEW_AGENT_TARGET_PR default: "" documentation: |- @@ -57,5 +78,10 @@ ref: review comments on a single PR. Designed for presubmit use: PULL_NUMBER is provided by the Prow presubmit context. - Requires REVIEW_AGENT_FORK_REPO and REVIEW_AGENT_UPSTREAM_REPO - to be set by the wrapper workflow. + App mode (default): requires REVIEW_AGENT_FORK_REPO and + REVIEW_AGENT_UPSTREAM_REPO to be set by the wrapper workflow. + + PAT mode: requires REVIEW_AGENT_AUTH_MODE="pat", + REVIEW_AGENT_FORK_ORG, and REVIEW_AGENT_UPSTREAM_REPO. + REVIEW_AGENT_FORK_REPO is auto-derived from FORK_ORG and the + upstream repo name. diff --git a/ci-operator/step-registry/review-agent/review-agent-workflow.yaml b/ci-operator/step-registry/review-agent/review-agent-workflow.yaml index 4fa3329d30fda..6babfba3bf3a2 100644 --- a/ci-operator/step-registry/review-agent/review-agent-workflow.yaml +++ b/ci-operator/step-registry/review-agent/review-agent-workflow.yaml @@ -17,9 +17,14 @@ workflow: 2. Test: Addresses review comments using /openshift-developer:address-review-pr 3. Post: Generates HTML report with token usage, cost estimates, and action badges - Teams onboard by creating a thin wrapper workflow that sets two env vars: - - REVIEW_AGENT_FORK_REPO: Fork repo URL to clone and push to - - REVIEW_AGENT_UPSTREAM_REPO: Upstream owner/repo for gh CLI operations + Teams onboard by creating a thin wrapper workflow. Two auth modes: + + App mode (default): set REVIEW_AGENT_FORK_REPO and REVIEW_AGENT_UPSTREAM_REPO. + Requires a GitHub App installed on both fork and upstream repos. + + PAT mode: set REVIEW_AGENT_AUTH_MODE="pat", REVIEW_AGENT_FORK_ORG, and + REVIEW_AGENT_UPSTREAM_REPO. The fork repo is auto-derived from the org and + upstream repo name. Optionally set REVIEW_AGENT_PAT_KEY (default: "gh-pat"). Teams with a different credential secret create thin ref YAML wrappers pointing to the generic commands scripts. See ONBOARDING.md.