diff --git a/.github/actions/apply-repo-settings/README.md b/.github/actions/apply-repo-settings/README.md index a934187..a8626ba 100644 --- a/.github/actions/apply-repo-settings/README.md +++ b/.github/actions/apply-repo-settings/README.md @@ -10,15 +10,16 @@ The upstream app is a Probot webhook server — it's designed to listen for `pus ## Inputs -| Input | Required | Default | Description | -| --------------- | -------- | ---------------------- | ------------------------------------------------------------------ | -| `app-id` | yes | — | GitHub App ID. Needs `Administration: write` and `Contents: read`. | -| `private-key` | yes | — | The App's PEM private key. | -| `owner` | no | current owner | Target repo owner. | -| `repo` | no | current repo | Target repo name. | -| `settings-file` | no | `.github/settings.yml` | Path to the YAML to apply. | -| `dry-run` | no | `false` | Print what would change without applying. | -| `sections` | no | `repository,rulesets` | Comma-separated section names to apply. | +| Input | Required | Default | Description | +| --------------- | -------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `token` | yes | — | GitHub token with `Administration: write` on the target repo (typically from `checkout-as-app` or `github-app-auth`). | +| `owner` | no | current owner | Target repo owner. | +| `repo` | no | current repo | Target repo name. | +| `settings-file` | no | `.github/settings.yml` | Path to the YAML to apply. | +| `dry-run` | no | `false` | Print what would change without applying. | +| `sections` | no | `repository,rulesets` | Comma-separated section names to apply. | + +The action does **not** do any templating or placeholder substitution on `settings-file` — it applies the YAML as-is. If you need env-var-based substitution (e.g. resolving a GitHub App ID into `bypass_actors[].actor_id`), render the file upstream (e.g. with `envsubst`) before invoking this action. ## Outputs @@ -37,7 +38,7 @@ Source: [`pages/index.html`](../../../pages/index.html). Deployed by [`.github/w After creating the app: 1. Install it on every repo you want to manage. -2. Add `APPLY_REPO_SETTINGS_APP_ID` and `APPLY_REPO_SETTINGS_PRIVATE_KEY` as secrets. +2. Add `APPLY_REPO_SETTINGS_APP_ID` and `APPLY_REPO_SETTINGS_PRIVATE_KEY` as secrets (consumed by `checkout-as-app` / `github-app-auth` — this action only takes the resulting token). ## Example workflow @@ -65,11 +66,15 @@ jobs: apply: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: nsheaps/github-actions/.github/actions/apply-repo-settings@main + - name: Checkout as GitHub App + id: checkout + uses: nsheaps/github-actions/.github/actions/checkout-as-app@main with: app-id: ${{ secrets.APPLY_REPO_SETTINGS_APP_ID }} private-key: ${{ secrets.APPLY_REPO_SETTINGS_PRIVATE_KEY }} + - uses: nsheaps/github-actions/.github/actions/apply-repo-settings@main + with: + token: ${{ steps.checkout.outputs.token }} dry-run: ${{ inputs.dry-run || false }} ``` diff --git a/.github/actions/apply-repo-settings/action.sh b/.github/actions/apply-repo-settings/action.sh index 06a7011..ee7a285 100755 --- a/.github/actions/apply-repo-settings/action.sh +++ b/.github/actions/apply-repo-settings/action.sh @@ -17,7 +17,6 @@ set -euo pipefail : "${SETTINGS_FILE:=.github/settings.yml}" : "${DRY_RUN:=false}" : "${SECTIONS:=repository,rulesets}" -: "${APP_ID:=}" if [[ ! -f "$SETTINGS_FILE" ]]; then echo "::error file=$SETTINGS_FILE::settings file not found" @@ -130,19 +129,6 @@ apply_rulesets() { name="$(yq -r ".rulesets[$i].name" "$SETTINGS_FILE")" body="$(yq -o=json ".rulesets[$i]" "$SETTINGS_FILE")" - # Substitute actor_id -1 placeholder for Integration bypass actors with the real App ID. - # actor_id must be the GitHub App ID (integer), not the installation ID. - # Gracefully skip Integration bypass actors if APP_ID is not configured. - if [[ -n "$APP_ID" ]]; then - body="$(echo "$body" | jq \ - --argjson app_id "$APP_ID" \ - '.bypass_actors //= [] | .bypass_actors |= map(if .actor_type == "Integration" and .actor_id == -1 then .actor_id = $app_id else . end)')" - else - body="$(echo "$body" | jq \ - 'if .bypass_actors then .bypass_actors |= map(select(not (.actor_type == "Integration" and .actor_id == -1))) else . end')" - info "APP_ID not set — skipping placeholder Integration bypass actors for: $name" - fi - existing_id="$(echo "$existing" | jq -r --arg n "$name" '.[] | select(.name == $n) | .id // empty')" if [[ -z "$existing_id" ]]; then diff --git a/.github/actions/apply-repo-settings/action.yml b/.github/actions/apply-repo-settings/action.yml index 5cd4a21..1abe130 100644 --- a/.github/actions/apply-repo-settings/action.yml +++ b/.github/actions/apply-repo-settings/action.yml @@ -7,19 +7,9 @@ branding: inputs: token: - description: 'Pre-generated GitHub App installation token. When provided, app-id/private-key are not used for authentication. app-id is still required for substituting the actor_id: -1 placeholder in Integration bypass actors.' - required: false - default: '' - - app-id: - description: 'GitHub App ID. Required. Used to substitute the actor_id: -1 placeholder in Integration bypass actors. Also used for authentication when token is not provided. See docs/setup.html in this action directory for an HTML helper that builds the app via the GitHub manifest flow.' + description: 'GitHub token with administration:write on the target repo (typically a GitHub App installation token from checkout-as-app or github-app-auth).' required: true - private-key: - description: 'GitHub App private key (PEM). Required when token is not provided.' - required: false - default: '' - owner: description: 'Target repo owner. Defaults to the current repo owner.' required: false @@ -53,32 +43,52 @@ outputs: runs: using: 'composite' steps: - - name: Authenticate as GitHub App - id: app-token - if: ${{ inputs.token == '' }} - uses: nsheaps/github-actions/.github/actions/github-app-auth@603052841cd49b9fdc99bc32979ff9c45c9b669e # 2026-05-22 - with: - app-id: ${{ inputs.app-id }} - private-key: ${{ inputs.private-key }} - owner: ${{ inputs.owner }} - skip-checkout: 'true' - - - name: Ensure yq is available + - name: Ensure mikefarah/yq is available shell: bash run: | - if ! command -v yq >/dev/null 2>&1; then - echo "Installing yq..." - sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + set -euo pipefail + # The action needs mikefarah's Go yq (supports `-o=json` for + # YAML→JSON conversion). Some runners pre-install kislyuk's Python + # yq — a `jq` wrapper that does NOT understand `-o=json` and exits + # with cryptic "Unknown option" errors. Detect, and if the resolved + # `yq` is the wrong one, install mikefarah's to /usr/local/bin/yq + # and prepend that path for subsequent steps so it shadows the + # pre-installed wrapper. + install_mikefarah() { + echo "Installing mikefarah/yq to /usr/local/bin/yq..." + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 sudo chmod +x /usr/local/bin/yq + } + yq_is_mikefarah() { + # mikefarah's --version line contains the github.com/mikefarah URL. + "$1" --version 2>&1 | grep -q 'mikefarah' + } + if command -v yq >/dev/null 2>&1 && yq_is_mikefarah "$(command -v yq)"; then + echo "mikefarah/yq already on PATH: $(command -v yq)" + else + if command -v yq >/dev/null 2>&1; then + echo "Wrong yq detected at $(command -v yq) ($(yq --version 2>&1)); shadowing with mikefarah's." + fi + install_mikefarah + echo "/usr/local/bin" >> "$GITHUB_PATH" + fi + # Sanity check: must be mikefarah's yq when action.sh runs in the next step. + # We can't rely on $GITHUB_PATH having taken effect in THIS step, so + # validate by absolute path if we just installed, else use what's on PATH. + check_bin=$(command -v yq) + if [[ -x /usr/local/bin/yq ]] && yq_is_mikefarah /usr/local/bin/yq; then + check_bin=/usr/local/bin/yq fi - yq --version + echo "Verifying $check_bin..." + "$check_bin" --version + yq_is_mikefarah "$check_bin" || { echo "::error::yq sanity check failed: not mikefarah"; exit 1; } - name: Apply settings id: apply shell: bash env: - GH_TOKEN: ${{ inputs.token || steps.app-token.outputs.token }} - APP_ID: ${{ inputs.app-id }} + GH_TOKEN: ${{ inputs.token }} OWNER: ${{ inputs.owner }} REPO: ${{ inputs.repo }} SETTINGS_FILE: ${{ inputs.settings-file }}