From a15896fc9d8cdb72553892e833426a80d0ecf6bb Mon Sep 17 00:00:00 2001 From: Marcus Burghardt Date: Wed, 13 May 2026 10:34:23 +0200 Subject: [PATCH 1/2] fix: replace peribolos --dump with dry-run for drift detection The drift detection workflow used peribolos --dump which internally calls GET /user (BotUser). This endpoint is incompatible with GitHub App installation tokens. Unlike the apply code path where --require-self=false bypasses BotUser, the dump code path calls it unconditionally (verified in kubernetes-sigs/prow dumpOrgConfig). Replace the YAML-diff approach with a dry-run approach: - Run peribolos without --confirm (same flags as apply workflow) - Filter log output for mutation function names (Edit*, Update*, Remove*, Create*, Delete*, Add*) - If any mutations would be applied, drift exists This is also more accurate than YAML diff because it uses peribolos own reconciliation logic, eliminating false positives from dump including fields not declared in peribolos.yaml. Additionally standardize workflow naming: - Rename files: peribolos-apply.yml, peribolos-validate.yml, peribolos-drift.yml - Rename display names: "Peribolos: Apply", "Peribolos: Validate", "Peribolos: Drift" Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: Marcus Burghardt --- ...pply_peribolos.yml => peribolos-apply.yml} | 2 +- ...rift_detection.yml => peribolos-drift.yml} | 71 +++++++++++++------ ...e-peribolos.yml => peribolos-validate.yml} | 2 +- 3 files changed, 53 insertions(+), 22 deletions(-) rename .github/workflows/{apply_peribolos.yml => peribolos-apply.yml} (99%) rename .github/workflows/{drift_detection.yml => peribolos-drift.yml} (62%) rename .github/workflows/{validate-peribolos.yml => peribolos-validate.yml} (96%) diff --git a/.github/workflows/apply_peribolos.yml b/.github/workflows/peribolos-apply.yml similarity index 99% rename from .github/workflows/apply_peribolos.yml rename to .github/workflows/peribolos-apply.yml index 62423c4..4f872e8 100644 --- a/.github/workflows/apply_peribolos.yml +++ b/.github/workflows/peribolos-apply.yml @@ -1,7 +1,7 @@ # Workflow to apply the Peribolos org configuration. # Runs on push to main, daily schedule, or manual dispatch. # Uses the complytime-bot GitHub App for authentication. -name: Apply Peribolos +name: "Peribolos: Apply" on: push: diff --git a/.github/workflows/drift_detection.yml b/.github/workflows/peribolos-drift.yml similarity index 62% rename from .github/workflows/drift_detection.yml rename to .github/workflows/peribolos-drift.yml index 487b390..d0e525e 100644 --- a/.github/workflows/drift_detection.yml +++ b/.github/workflows/peribolos-drift.yml @@ -1,6 +1,11 @@ # Workflow to detect drift between peribolos.yaml and actual GitHub org state. # Runs weekly on Monday mornings. Opens or updates a GitHub issue when drift is detected. -name: Drift Detection +# +# Approach: runs peribolos in dry-run mode (without --confirm) and checks if it +# would make any changes. If mutation log lines exist, drift is detected. +# This avoids `peribolos --dump` which calls GET /user (incompatible with +# installation tokens). +name: "Peribolos: Drift" on: schedule: @@ -49,32 +54,60 @@ jobs: private-key: ${{ secrets.COMPLYTIME_BOT_PRIVATE_KEY }} owner: complytime - - name: Dump current org state + - name: Run peribolos dry-run + id: drift env: APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | - set -o pipefail /tmp/peribolos \ --config-path /tmp/peribolos.yaml \ + --fix-org \ + --fix-org-members \ + --fix-teams \ + --fix-team-members \ + --fix-repos \ + --fix-team-repos \ + --min-admins 2 \ + --required-admins jflowers \ + --required-admins jpower432 \ + --required-admins marcusburghardt \ --require-self=false \ --github-token-path <(printf '%s' "$APP_TOKEN") \ - --dump complytime \ - --dump-full 2>/tmp/peribolos-dump-stderr.log | yq -P 'sort_keys(..)' > /tmp/org-actual.yaml - yq -P 'sort_keys(..)' /tmp/peribolos.yaml > /tmp/org-expected.yaml + 2>&1 > /tmp/peribolos-dryrun.log || true - - name: Compare org state - id: diff - run: | - if diff -u /tmp/org-expected.yaml /tmp/org-actual.yaml > /tmp/drift-diff.txt 2>&1; then + echo "--- Peribolos dry-run output ---" + jq -r '[.severity, .time, .msg] | join(" | ")' /tmp/peribolos-dryrun.log + + # Check for fatal errors (auth failure, config error, etc.) + if jq -e 'select(.severity == "fatal")' /tmp/peribolos-dryrun.log > /dev/null 2>&1; then + echo "::error::Peribolos dry-run failed. See output above." + exit 1 + fi + + # Extract mutation lines — these indicate drift + jq -r 'select(.msg | test("^(Edit|Update|Remove|Create|Delete|Add)")) | .msg' \ + /tmp/peribolos-dryrun.log > /tmp/drift-mutations.txt + + DRIFT_COUNT=$(wc -l < /tmp/drift-mutations.txt) + if [ "$DRIFT_COUNT" -eq 0 ]; then echo "drift=false" >> "$GITHUB_OUTPUT" echo "No drift detected." else echo "drift=true" >> "$GITHUB_OUTPUT" - echo "Drift detected between peribolos.yaml and actual org state." + echo "Drift detected: ${DRIFT_COUNT} change(s) would be applied:" + cat /tmp/drift-mutations.txt fi + - name: Upload dry-run log + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: peribolos-dryrun-log + path: /tmp/peribolos-dryrun.log + retention-days: 7 + - name: Check for existing drift issue - if: steps.diff.outputs.drift == 'true' + if: steps.drift.outputs.drift == 'true' id: existing-issue env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -88,7 +121,7 @@ jobs: echo "issue_number=${ISSUE_NUMBER}" >> "$GITHUB_OUTPUT" - name: Create or update drift issue - if: steps.diff.outputs.drift == 'true' + if: steps.drift.outputs.drift == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_NUMBER: ${{ steps.existing-issue.outputs.issue_number }} @@ -102,18 +135,16 @@ jobs: echo "**Date**: ${TIMESTAMP}" echo "**Workflow run**: ${WORKFLOW_URL}" echo "" - echo "The actual GitHub org state differs from what is declared in \`peribolos.yaml\`." - echo "This may indicate manual changes were made via the GitHub UI." + echo "The following changes would be applied by Peribolos to reconcile" + echo "the actual GitHub org state with \`peribolos.yaml\`:" echo "" - echo "### Diff" - echo "" - echo '```diff' - cat /tmp/drift-diff.txt + echo '```' + cat /tmp/drift-mutations.txt echo '```' echo "" echo "### Recommended Action" echo "" - echo "- Review the diff to determine if the changes are intentional" + echo "- Review the changes to determine if the drift is intentional" echo "- If unintentional: trigger a manual Peribolos apply via \`workflow_dispatch\`" echo "- If intentional: update \`peribolos.yaml\` to match the desired state" } > /tmp/issue-body.md diff --git a/.github/workflows/validate-peribolos.yml b/.github/workflows/peribolos-validate.yml similarity index 96% rename from .github/workflows/validate-peribolos.yml rename to .github/workflows/peribolos-validate.yml index bd2fff1..4bd6880 100644 --- a/.github/workflows/validate-peribolos.yml +++ b/.github/workflows/peribolos-validate.yml @@ -1,4 +1,4 @@ -name: Verify Peribolos +name: "Peribolos: Validate" on: push: From 929db91a1b9d45bf50f6cbe4bb4277bba208e258 Mon Sep 17 00:00:00 2001 From: Marcus Burghardt Date: Wed, 20 May 2026 16:56:23 +0200 Subject: [PATCH 2/2] fix(ci): fix token handling and redirect order in peribolos-drift Apply the same temp file fix for --github-token-path as in peribolos-apply (process substitution FIFO causes 401s on re-read). Fix shellcheck SC2069: reorder redirects so stderr is captured to the log file (> file 2>&1, not 2>&1 > file). Add --ignore-enterprise-teams to skip ent:* managed teams. Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: Marcus Burghardt --- .github/workflows/peribolos-drift.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/peribolos-drift.yml b/.github/workflows/peribolos-drift.yml index d0e525e..16104de 100644 --- a/.github/workflows/peribolos-drift.yml +++ b/.github/workflows/peribolos-drift.yml @@ -59,6 +59,14 @@ jobs: env: APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | + # Write the token to a secure temp file. Prow's SecretAgent + # re-reads --github-token-path periodically, so a process + # substitution FIFO (consumed on first read) causes 401s. + TOKEN_FILE=$(mktemp) + trap 'rm -f "$TOKEN_FILE"' EXIT + install -m 0600 /dev/null "$TOKEN_FILE" + printf '%s' "$APP_TOKEN" > "$TOKEN_FILE" + /tmp/peribolos \ --config-path /tmp/peribolos.yaml \ --fix-org \ @@ -72,8 +80,9 @@ jobs: --required-admins jpower432 \ --required-admins marcusburghardt \ --require-self=false \ - --github-token-path <(printf '%s' "$APP_TOKEN") \ - 2>&1 > /tmp/peribolos-dryrun.log || true + --ignore-enterprise-teams \ + --github-token-path "$TOKEN_FILE" \ + > /tmp/peribolos-dryrun.log 2>&1 || true echo "--- Peribolos dry-run output ---" jq -r '[.severity, .time, .msg] | join(" | ")' /tmp/peribolos-dryrun.log