diff --git a/.github/workflows/branch-protection-check.yml b/.github/workflows/branch-protection-check.yml deleted file mode 100644 index a6e5a298..00000000 --- a/.github/workflows/branch-protection-check.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Branch Protection Check - -on: - schedule: - - cron: '0 9 * * 1' # Every Monday at 9 AM UTC - workflow_dispatch: - -permissions: - contents: read - -jobs: - verify-config: - name: Verify Dependabot and auto-merge setup - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - persist-credentials: false - - - name: Validate Dependabot and workflow configuration - run: | - set -euo pipefail - pip install --quiet pyyaml - python3 <<'PY' - import sys - from pathlib import Path - - import yaml - - def fail(msg: str) -> None: - print(f"❌ {msg}") - sys.exit(1) - - dependabot_path = Path(".github/dependabot.yml") - if not dependabot_path.is_file(): - fail("Dependabot configuration missing (.github/dependabot.yml)") - - def load_workflow(path: Path) -> dict: - with path.open() as f: - data = yaml.safe_load(f) - if not isinstance(data, dict): - fail(f"{path.name} must be a YAML mapping") - return data - - with dependabot_path.open() as f: - cfg = yaml.safe_load(f) - if not isinstance(cfg, dict): - fail("dependabot.yml must be a YAML mapping") - if cfg.get("version") != 2: - fail("dependabot.yml: version must be 2") - updates = cfg.get("updates") - if not isinstance(updates, list) or not updates: - fail("dependabot.yml: updates must be a non-empty list") - for i, entry in enumerate(updates): - if not isinstance(entry, dict): - fail(f"dependabot.yml: updates[{i}] must be a mapping") - if not entry.get("package-ecosystem"): - fail(f"dependabot.yml: updates[{i}] missing package-ecosystem") - if "directory" not in entry: - fail(f"dependabot.yml: updates[{i}] missing directory") - - print("✅ dependabot.yml structure is valid") - for entry in updates: - print(f" - {entry.get('package-ecosystem')} ({entry.get('directory')})") - - workflow_path = Path(".github/workflows/dependabot-auto-merge.yml") - if not workflow_path.is_file(): - fail("dependabot-auto-merge workflow missing") - - wf = load_workflow(workflow_path) - # PyYAML parses bare `on:` as boolean True, not the string "on" - on = wf.get("on") or wf.get(True) - if not isinstance(on, dict) or "pull_request_target" not in on: - fail("dependabot-auto-merge.yml must use pull_request_target trigger") - jobs = wf.get("jobs") - if not isinstance(jobs, dict) or "auto-merge" not in jobs: - fail("dependabot-auto-merge.yml must define jobs.auto-merge") - job = jobs["auto-merge"] - if not isinstance(job, dict): - fail("jobs.auto-merge must be a mapping") - steps = job.get("steps") - if not isinstance(steps, list) or not steps: - fail("jobs.auto-merge must define steps") - uses = [ - s.get("uses", "") - for s in steps - if isinstance(s, dict) - ] - if not any("dependabot/fetch-metadata" in u for u in uses): - fail("jobs.auto-merge must include dependabot/fetch-metadata") - - print("✅ dependabot-auto-merge.yml structure is valid") - print("") - print("ℹ️ dependabot-auto-merge.yml enables merge via GraphQL") - print(" enablePullRequestAutoMerge; the PR merges only after existing") - print(" required status checks pass (e.g. ci/prow/*). No extra CI job.") - PY diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml deleted file mode 100644 index 212cf545..00000000 --- a/.github/workflows/dependabot-auto-merge.yml +++ /dev/null @@ -1,234 +0,0 @@ -name: Dependabot Auto-Merge - -on: - pull_request_target: - types: [opened, synchronize, reopened, ready_for_review] - -permissions: - contents: write - pull-requests: write - checks: read - actions: read - -jobs: - auto-merge: - runs-on: ubuntu-latest - if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository_owner == 'openshift' - steps: - - name: Fetch Dependabot Metadata - id: metadata - uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Enable Auto-Merge for Safe Updates - id: enable-auto-merge - if: | - steps.metadata.outputs.update-type == 'version-update:semver-patch' || - steps.metadata.outputs.update-type == 'version-update:semver-minor' - env: - UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }} - DEPENDENCY_NAMES: ${{ steps.metadata.outputs.dependency-names }} - PREVIOUS_VERSION: ${{ steps.metadata.outputs.previous-version }} - NEW_VERSION: ${{ steps.metadata.outputs.new-version }} - REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - set -euo pipefail - GH_TOKEN="${{ secrets.GITHUB_TOKEN }}" - export GH_TOKEN - - comment_count() { - local marker="$1" - local http_code - http_code=$(curl -sS -w "%{http_code}" -o /tmp/comments-list.json \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - "https://api.github.com/repos/${REPOSITORY}/issues/${PR_NUMBER}/comments") - if [[ "$http_code" != "200" ]]; then - echo "::warning::Could not list PR comments (HTTP ${http_code})" >&2 - echo "1" - return - fi - jq --arg m "$marker" '[.[] | select(.body | contains($m))] | length' /tmp/comments-list.json - } - - post_issue_comment() { - local body="$1" - local http_code - http_code=$(curl -sS -w "%{http_code}" -o /tmp/comment-response.json \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - "https://api.github.com/repos/${REPOSITORY}/issues/${PR_NUMBER}/comments" \ - -d "$(jq -n --arg body "$body" '{body: $body}')") - if [[ ! "$http_code" =~ ^2 ]]; then - echo "❌ Failed to post PR comment. HTTP status: ${http_code}" - cat /tmp/comment-response.json - echo "::warning::PR comment could not be posted" - return 1 - fi - } - - graphql_auto_merge_ok() { - local http_code="$1" - [[ "$http_code" == "200" ]] || return 1 - jq -e '(.errors // []) | length == 0' /tmp/response.json >/dev/null 2>&1 || return 1 - jq -e '.data.enablePullRequestAutoMerge.pullRequest != null' /tmp/response.json >/dev/null 2>&1 - } - - graphql_error_summary() { - jq -c '(.errors // []) | if length > 0 then . else .data end' /tmp/response.json 2>/dev/null || cat /tmp/response.json - } - - echo "Enabling auto-merge for ${UPDATE_TYPE} update" - echo "Dependency: ${DEPENDENCY_NAMES}" - - pr_http_code=$(curl -sS -w "%{http_code}" -o /tmp/pr-response.json \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - "https://api.github.com/repos/${REPOSITORY}/pulls/${PR_NUMBER}") - - if [[ "$pr_http_code" != "200" ]]; then - echo "❌ Failed to fetch PR metadata. HTTP status: ${pr_http_code}" - cat /tmp/pr-response.json - echo "auto_merge_enabled=false" >> "$GITHUB_OUTPUT" - exit 1 - fi - - PR_NODE_ID=$(jq -r '.node_id' /tmp/pr-response.json) - if [[ -z "$PR_NODE_ID" || "$PR_NODE_ID" == "null" ]]; then - echo "❌ Failed to parse PR node ID from response" - cat /tmp/pr-response.json - echo "auto_merge_enabled=false" >> "$GITHUB_OUTPUT" - exit 1 - fi - - http_code=$(curl -sS -w "%{http_code}" -o /tmp/response.json \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - "https://api.github.com/graphql" \ - -d "{\"query\":\"mutation { enablePullRequestAutoMerge(input: { pullRequestId: \\\"$PR_NODE_ID\\\", mergeMethod: SQUASH }) { pullRequest { autoMergeRequest { enabledAt } } } }\"}") - - if graphql_auto_merge_ok "$http_code"; then - echo "✅ Auto-merge enabled successfully via GraphQL" - cat /tmp/response.json - echo "auto_merge_enabled=true" >> "$GITHUB_OUTPUT" - else - api_detail=$(graphql_error_summary) - echo "❌ Failed to enable auto-merge. HTTP status: ${http_code}" - echo "Response body:" - cat /tmp/response.json - echo "auto_merge_enabled=false" >> "$GITHUB_OUTPUT" - echo "::warning::Could not enable auto-merge. PR may need manual review." - if [[ "$(comment_count 'Dependabot Auto-Merge Status')" -eq 0 ]]; then - failure_body=$(jq -rn \ - --arg ut "$UPDATE_TYPE" \ - --arg deps "$DEPENDENCY_NAMES" \ - --arg prev "$PREVIOUS_VERSION" \ - --arg new "$NEW_VERSION" \ - --arg api "$api_detail" \ - '@text "🤖 **Dependabot Auto-Merge Status** - - This PR meets the criteria for auto-merge but could not be automatically merged. - - **Details:** - - Update type: \($ut) - - Dependencies: \($deps) - - Previous version: \($prev) - - New version: \($new) - - API response: `\($api)` - - Please review and merge manually if appropriate."') - post_issue_comment "$failure_body" || true - else - echo "Auto-merge status comment already posted; skipping duplicate" - fi - fi - - - name: Comment on Major Version Updates - if: steps.metadata.outputs.update-type == 'version-update:semver-major' - env: - DEPENDENCY_NAMES: ${{ steps.metadata.outputs.dependency-names }} - PREVIOUS_VERSION: ${{ steps.metadata.outputs.previous-version }} - NEW_VERSION: ${{ steps.metadata.outputs.new-version }} - REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - set -euo pipefail - GH_TOKEN="${{ secrets.GITHUB_TOKEN }}" - export GH_TOKEN - - comments_http=$(curl -sS -w "%{http_code}" -o /tmp/comments-list.json \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - "https://api.github.com/repos/${REPOSITORY}/issues/${PR_NUMBER}/comments") - if [[ "$comments_http" != "200" ]]; then - echo "::warning::Could not list PR comments (HTTP ${comments_http})" >&2 - exit 0 - fi - existing=$(jq '[.[] | select(.body | contains("Major Version Update Detected"))] | length' /tmp/comments-list.json) - - if [[ "$existing" -gt 0 ]]; then - echo "Major-version notice already posted; skipping duplicate comment" - exit 0 - fi - - major_body=$(jq -rn \ - --arg deps "$DEPENDENCY_NAMES" \ - --arg prev "$PREVIOUS_VERSION" \ - --arg new "$NEW_VERSION" \ - '@text "🚨 **Major Version Update Detected** 🚨 - - This PR contains a major version update that requires manual review: - - **Dependency:** \($deps) - - **Previous version:** \($prev) - - **New version:** \($new) - - Please review the changelog and breaking changes before merging. - - Auto-merge has been **disabled** for this PR."') - - http_code=$(curl -sS -w "%{http_code}" -o /tmp/comment-response.json \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - "https://api.github.com/repos/${REPOSITORY}/issues/${PR_NUMBER}/comments" \ - -d "$(jq -n --arg body "$major_body" '{body: $body}')") - - if [[ ! "$http_code" =~ ^2 ]]; then - echo "❌ Failed to post major-version comment. HTTP status: ${http_code}" - cat /tmp/comment-response.json - echo "::warning::Major-version comment could not be posted" - fi - - - name: Log Auto-Merge Decision - if: always() && steps.metadata.outcome == 'success' - env: - UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }} - DEPENDENCY_NAMES: ${{ steps.metadata.outputs.dependency-names }} - PR_NUMBER: ${{ github.event.pull_request.number }} - AUTO_MERGE_ENABLED: ${{ steps.enable-auto-merge.outputs.auto_merge_enabled }} - run: | - echo "Auto-merge decision for PR #${PR_NUMBER}:" - echo "- Update type: ${UPDATE_TYPE}" - echo "- Dependency: ${DEPENDENCY_NAMES}" - - case "${UPDATE_TYPE}" in - version-update:semver-patch|version-update:semver-minor) - if [[ "${AUTO_MERGE_ENABLED}" == "true" ]]; then - echo "✅ Auto-merge ENABLED (GraphQL mutation succeeded)" - elif [[ "${AUTO_MERGE_ENABLED}" == "false" ]]; then - echo "❌ Auto-merge NOT enabled (GraphQL mutation failed — see enable step logs)" - else - echo "⚠️ Auto-merge enable step did not complete (check workflow logs)" - fi - ;; - version-update:semver-major) - echo "❌ Auto-merge DISABLED: Major version update" - ;; - *) - echo "❌ Auto-merge DISABLED: Update type not eligible for auto-merge (${UPDATE_TYPE})" - ;; - esac diff --git a/.tekton/rbac-permissions-operator-pko-pull-request.yaml b/.tekton/rbac-permissions-operator-pko-pull-request.yaml index 14e527d7..751bc953 100644 --- a/.tekton/rbac-permissions-operator-pko-pull-request.yaml +++ b/.tekton/rbac-permissions-operator-pko-pull-request.yaml @@ -30,6 +30,11 @@ spec: # skip-preflight-cert-check: Required for internal registry with self-signed certs in CI - name: skip-preflight-cert-check value: "true" + # skip-roxctl-scan: ACS fails on FROM-scratch PKO images (YAML only, no OS). + # Do not use skip-checks here — that also drops clamav/SAST/RPM scans and + # Enterprise Contract fails. Param is in openshift/boilerplate#866. + - name: skip-roxctl-scan + value: "true" - name: image-expires-after value: 3d taskRunTemplate: @@ -43,8 +48,9 @@ spec: params: - name: url value: https://github.com/openshift/boilerplate + # Pin until https://github.com/openshift/boilerplate/pull/866 merges, then revert to master. - name: revision - value: master + value: 9fe5a4235d443a9e50352252ef415ee1bfe00bb8 - name: pathInRepo value: pipelines/docker-build-oci-ta/pipeline.yaml status: {} diff --git a/.tekton/rbac-permissions-operator-pko-push.yaml b/.tekton/rbac-permissions-operator-pko-push.yaml index 0bd00f60..bb59161a 100644 --- a/.tekton/rbac-permissions-operator-pko-push.yaml +++ b/.tekton/rbac-permissions-operator-pko-push.yaml @@ -30,6 +30,11 @@ spec: # skip-preflight-cert-check: Required for internal registry with self-signed certs in CI - name: skip-preflight-cert-check value: "true" + # skip-roxctl-scan: ACS fails on FROM-scratch PKO images (YAML only, no OS). + # Do not use skip-checks here — that also drops clamav/SAST/RPM scans and + # Enterprise Contract fails. Param is in openshift/boilerplate#866. + - name: skip-roxctl-scan + value: "true" taskRunTemplate: serviceAccountName: build-pipeline-rbac-permissions-operator-pko workspaces: @@ -41,8 +46,9 @@ spec: params: - name: url value: https://github.com/openshift/boilerplate + # Pin until https://github.com/openshift/boilerplate/pull/866 merges, then revert to master. - name: revision - value: master + value: 9fe5a4235d443a9e50352252ef415ee1bfe00bb8 - name: pathInRepo value: pipelines/docker-build-oci-ta/pipeline.yaml status: {}