diff --git a/ci-operator/config/openshift/must-gather-operator/openshift-must-gather-operator-master.yaml b/ci-operator/config/openshift/must-gather-operator/openshift-must-gather-operator-master.yaml index d25d2a3d11752..d885963a7c2ed 100644 --- a/ci-operator/config/openshift/must-gather-operator/openshift-must-gather-operator-master.yaml +++ b/ci-operator/config/openshift/must-gather-operator/openshift-must-gather-operator-master.yaml @@ -12,6 +12,32 @@ build_root: from_repository: true images: items: + - dockerfile_literal: |- + FROM registry.access.redhat.com/ubi9/go-toolset + USER 0 + RUN dnf install -y git make jq && \ + dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + dnf install -y gh && \ + dnf clean all + RUN dnf module reset -y nodejs && \ + dnf module enable -y nodejs:20 && \ + dnf install -y nodejs npm && \ + npm install -g @anthropic-ai/claude-code && \ + dnf clean all + WORKDIR /app + RUN git clone --depth 1 -b oape-review-handler https://github.com/neha037/oape-ai-e2e.git /tmp/oape && \ + cp -r /tmp/oape/scripts /app/scripts && \ + cp -r /tmp/oape/plugins /plugins && \ + mkdir -p /config && cp -r /tmp/oape/deploy/config/* /config/ && \ + rm -rf /tmp/oape + RUN go install golang.org/x/tools/cmd/goimports@v0.33.0 && \ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/v2.1.6/install.sh | sh -s -- -b /usr/local/bin v2.1.6 + RUN git config --global user.name "openshift-app-platform-shift-bot" && \ + git config --global user.email "267347085+openshift-app-platform-shift-bot@users.noreply.github.com" + RUN chmod -R g=u /opt/app-root/src + USER 1001 + to: review-handler-agent - dockerfile_path: Dockerfile.openshift from: base-rhel9 to: must-gather-operator @@ -28,7 +54,9 @@ operator: with: stable:must-gather promotion: to: - - name: "5.0" + - excluded_images: + - review-handler-agent + name: "5.0" namespace: ocp releases: initial: @@ -197,6 +225,99 @@ tests: requests: cpu: 100m workflow: optional-operators-ci-operator-sdk-gcp +- always_run: false + as: oape-review-handler + optional: true + steps: + test: + - as: review + commands: | + set -euo pipefail + + echo "[setup] Starting oape-review-handler for ${REPO_OWNER}/${REPO_NAME} PR#${PULL_NUMBER}" + + # --- Rehearsal detection --- + if [[ "${REPO_NAME}" == "release" && "${REPO_OWNER}" == "openshift" ]]; then + echo "[setup] Detected openshift/release context — switching to test target" + export REPO_OWNER="openshift" + export REPO_NAME="must-gather-operator" + TEST_PR=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/pulls?state=open&per_page=1" \ + | python3 -c "import sys,json; data=json.load(sys.stdin); print(data[0]['number'] if data else '')" 2>/dev/null || echo "") + if [[ -z "$TEST_PR" ]]; then + echo "[setup] No open PRs found — skipping" + exit 0 + fi + export PULL_NUMBER="$TEST_PR" + echo "[setup] Testing against ${REPO_OWNER}/${REPO_NAME}#${PULL_NUMBER}" + fi + + # --- GitHub auth: App token with GITHUB_TOKEN fallback --- + USE_APP_TOKEN="false" + if [[ -f /var/run/github-app/app-id && -f /var/run/github-app/private-key.pem ]]; then + echo "[auth] Attempting GitHub App token..." + APP_ID=$(cat /var/run/github-app/app-id) + PEM_PATH="/var/run/github-app/private-key.pem" + HEADER=$(printf '{"alg":"RS256","typ":"JWT"}' | openssl base64 -e -A | tr '+/' '-_' | tr -d '=') + NOW=$(date +%s); EXP=$((NOW + 300)) + PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$NOW" "$EXP" "$APP_ID" | openssl base64 -e -A | tr '+/' '-_' | tr -d '=') + SIGNATURE=$(printf '%s' "${HEADER}.${PAYLOAD}" | openssl dgst -sha256 -sign "$PEM_PATH" -binary | openssl base64 -e -A | tr '+/' '-_' | tr -d '=') + JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}" + INSTALL_RESPONSE=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer ${JWT}" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/installation") + HTTP_CODE=$(echo "$INSTALL_RESPONSE" | tail -1) + INSTALL_BODY=$(echo "$INSTALL_RESPONSE" | sed '$d') + if [[ "$HTTP_CODE" -eq 200 ]]; then + INST_ID=$(echo "$INSTALL_BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") + TOKEN_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST -H "Authorization: Bearer ${JWT}" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/app/installations/${INST_ID}/access_tokens") + T_CODE=$(echo "$TOKEN_RESPONSE" | tail -1) + T_BODY=$(echo "$TOKEN_RESPONSE" | sed '$d') + if [[ "$T_CODE" -eq 201 ]]; then + export GH_TOKEN=$(echo "$T_BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])") + USE_APP_TOKEN="true" + echo "[auth] GitHub App token generated successfully" + else + echo "[auth] WARN: App token creation failed (HTTP ${T_CODE}), falling back to GITHUB_TOKEN" + fi + else + echo "[auth] WARN: App not installed on ${REPO_OWNER}/${REPO_NAME} (HTTP ${HTTP_CODE}), falling back to GITHUB_TOKEN" + fi + else + echo "[auth] GitHub App credentials not mounted, using GITHUB_TOKEN" + fi + if [[ "$USE_APP_TOKEN" != "true" ]]; then + if [[ -z "${GH_TOKEN:-}" && -z "${GITHUB_TOKEN:-}" ]]; then + echo "[auth] ERROR: No GitHub token available" >&2 + exit 1 + fi + export GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN}}" + fi + + # --- GCP auth for Claude (Vertex AI) --- + export GOOGLE_APPLICATION_CREDENTIALS="/var/run/gcloud-adc/application_default_credentials.json" + export CLAUDE_CODE_USE_VERTEX="1" + export CLOUD_ML_REGION="global" + export ANTHROPIC_VERTEX_PROJECT_ID="itpc-gcp-hcm-pe-eng-claude" + + # --- Run review handler --- + export PR_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/pull/${PULL_NUMBER}" + export PLUGINS_DIR="/plugins/oape/skills" + + gh auth setup-git + /app/scripts/pr-agent/review-handler.sh --pr-url "$PR_URL" + credentials: + - mount_path: /var/run/gcloud-adc + name: oap-lts-claude-gcp-vertex-sa + namespace: test-credentials + - mount_path: /var/run/github-app + name: openshift-app-platform-shift-github-bot + namespace: test-credentials + from: review-handler-agent + resources: + requests: + cpu: "1" + memory: 500Mi + timeout: 1h0m0s zz_generated_metadata: branch: master org: openshift diff --git a/ci-operator/jobs/openshift/must-gather-operator/openshift-must-gather-operator-master-presubmits.yaml b/ci-operator/jobs/openshift/must-gather-operator/openshift-must-gather-operator-master-presubmits.yaml index c40b55040f47a..a75a48317aa1a 100644 --- a/ci-operator/jobs/openshift/must-gather-operator/openshift-must-gather-operator-master-presubmits.yaml +++ b/ci-operator/jobs/openshift/must-gather-operator/openshift-must-gather-operator-master-presubmits.yaml @@ -508,6 +508,89 @@ presubmits: secret: secretName: result-aggregator trigger: (?m)^/test( | .* )lint,?($|\s.*) + - agent: kubernetes + always_run: false + branches: + - ^master$ + - ^master- + cluster: build12 + context: ci/prow/oape-review-handler + decorate: true + decoration_config: + sparse_checkout_files: + - .ci-operator.yaml + - Dockerfile.openshift + labels: + ci.openshift.io/generator: prowgen + pj-rehearse.openshift.io/can-be-rehearsed: "true" + name: pull-ci-openshift-must-gather-operator-master-oape-review-handler + optional: true + rerun_command: /test oape-review-handler + 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=oape-review-handler + 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( | .* )oape-review-handler,?($|\s.*) - agent: kubernetes always_run: false branches: