| #!/usr/bin/env bash | |
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| # Re-applies the token round-robin wiring to the gh-aw generated .lock.yml files, | |
| # which `gh aw compile` doesn't know about. Run after every compile: | |
| # | |
| # gh aw compile && bash .github/scripts/post-compile.sh | |
| # | |
| # Three edits per lock file (see .github/COPILOT_TOKENS.md for the design): | |
| # - point the agent execute step's COPILOT_GITHUB_TOKEN at the pick_copilot_token | |
| # job's output, falling back to the base secret | |
| # - point the agent job's "Redact secrets in logs" step at the same rotated token, | |
| # so a volunteer token is scrubbed from uploaded artifacts, not just the base one | |
| # - make the agent job depend on pick_copilot_token, and strip the self-reference | |
| # gh-aw sometimes adds to pick_copilot_token's own needs (that would be a cycle) | |
| # | |
| # Safe to re-run; a second run is a no-op. | |
| set -euo pipefail | |
| cd "$(git rev-parse --show-toplevel)" | |
| # Kept in an env var so perl doesn't try to interpolate the ${{ }} bits. | |
| export NEWVAL='${{ needs.pick_copilot_token.outputs.name != '"'"''"'"' && secrets[format('"'"'COPILOT_GITHUB_TOKEN_{0}'"'"', needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN }}' | |
| FILES=( | |
| ".github/workflows/weekly-repo-status.lock.yml" | |
| ".github/workflows/daily-issue-triage.lock.yml" | |
| ) | |
| fail() { echo "ERROR: $1" >&2; exit 1; } | |
| # Fixes up `needs:` for the agent and pick_copilot_token jobs only; gh-aw adds | |
| # pick_copilot_token to several other jobs' needs and those must stay as-is. | |
| # Reads stdin, writes stdout. gh-aw emits inline needs (needs: foo) for single | |
| # dependencies and block form for lists; inline is fine unless it needs editing, | |
| # in which case "INLINE_NEEDS:<job>" is printed to stderr and the caller bails. | |
| normalise_needs() { | |
| awk ' | |
| function isjob(l){ return (l ~ /^ [A-Za-z0-9_-]+:[ \t]*$/) } | |
| BEGIN { job=""; inneeds=0; agentpick=0 } | |
| { | |
| line=$0 | |
| if (isjob(line)) { | |
| if (inneeds && job=="agent" && !agentpick) print " - pick_copilot_token" | |
| inneeds=0; agentpick=0 | |
| name=line; sub(/^ /,"",name); sub(/:[ \t]*$/,"",name); job=name | |
| print line; next | |
| } | |
| if (line ~ /^ needs:[ \t]*[^ \t]/) { | |
| if (job=="agent" && line !~ /pick_copilot_token/) print "INLINE_NEEDS:" job > "/dev/stderr" | |
| if (job=="pick_copilot_token" && line ~ /pick_copilot_token/) print "INLINE_NEEDS:" job > "/dev/stderr" | |
| print line; next | |
| } | |
| if (line ~ /^ needs:[ \t]*$/) { inneeds=1; agentpick=0; print line; next } | |
| if (inneeds) { | |
| if (line ~ /^ - /) { | |
| item=line; sub(/^ - /,"",item); gsub(/[ \t\r]/,"",item) | |
| if (job=="pick_copilot_token" && item=="pick_copilot_token") next | |
| if (job=="agent" && item=="pick_copilot_token") agentpick=1 | |
| print line; next | |
| } else { | |
| if (job=="agent" && !agentpick) print " - pick_copilot_token" | |
| inneeds=0 | |
| print line; next | |
| } | |
| } | |
| print line | |
| } | |
| END { if (inneeds && job=="agent" && !agentpick) print " - pick_copilot_token" } | |
| ' | |
| } | |
| for f in "${FILES[@]}"; do | |
| if [ ! -f "$f" ]; then | |
| echo "WARN: $f not found, run 'gh aw compile' first? Skipping" >&2 | |
| continue | |
| fi | |
| # Repoint the agent execute step's token. The anchor is the GH_AW_PHASE: agent env | |
| # var further down the same env block; the detection job's block has | |
| # GH_AW_PHASE: detection so it doesn't match and keeps the base token. | |
| before=$(grep -cF 'COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) | |
| perl -0pi -e \ | |
| 's/^([ \t]*)COPILOT_GITHUB_TOKEN:[ \t]*\$\{\{[ \t]*secrets\.COPILOT_GITHUB_TOKEN[ \t]*\}\}[ \t]*\n(?=(?:[ \t]+[A-Z][A-Za-z0-9_]*:[^\n]*\n)*?[ \t]+GH_AW_PHASE:[ \t]*agent[ \t]*\n)/$1."COPILOT_GITHUB_TOKEN: ".$ENV{NEWVAL}."\n"/me' \ | |
| "$f" | |
| after=$(grep -cF 'COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) | |
| removed=$(( before - after )) | |
| if [ "$removed" -eq 1 ]; then token_edit="applied" | |
| elif [ "$removed" -eq 0 ] && grep -qE '^[ \t]+COPILOT_GITHUB_TOKEN: \$\{\{ needs\.pick_copilot_token' "$f"; then token_edit="already" | |
| else fail "$f: execute-step token line not patched as expected (removed=$removed), anchor drifted?" | |
| fi | |
| # Repoint the redact step's SECRET_COPILOT_GITHUB_TOKEN the same way; the line is | |
| # unique to the agent job's "Redact secrets in logs" step. | |
| before=$(grep -cF 'SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) | |
| if [ "$before" -gt 1 ]; then | |
| fail "$f: expected at most one redact-step SECRET_COPILOT_GITHUB_TOKEN line, found $before" | |
| fi | |
| perl -0pi -e \ | |
| 's/^([ \t]*)SECRET_COPILOT_GITHUB_TOKEN:[ \t]*\$\{\{[ \t]*secrets\.COPILOT_GITHUB_TOKEN[ \t]*\}\}[ \t]*$/$1."SECRET_COPILOT_GITHUB_TOKEN: ".$ENV{NEWVAL}/me' \ | |
| "$f" | |
| after=$(grep -cF 'SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) | |
| if [ "$before" -eq 1 ] && [ "$after" -eq 0 ]; then redact_edit="applied" | |
| elif [ "$before" -eq 0 ] && grep -qF 'SECRET_COPILOT_GITHUB_TOKEN: ${{ needs.pick_copilot_token.outputs.name' "$f"; then redact_edit="already" | |
| else fail "$f: redact-step SECRET_COPILOT_GITHUB_TOKEN line not patched (before=$before after=$after)" | |
| fi | |
| errf="$(mktemp)" | |
| normalise_needs < "$f" > "$f.tmp" 2>"$errf" | |
| if grep -q '^INLINE_NEEDS:' "$errf"; then | |
| rm -f "$f.tmp"; rm -f "$errf" | |
| fail "$f: agent/pick_copilot_token have inline 'needs:' that would need editing, update normalise_needs" | |
| fi | |
| rm -f "$errf" | |
| mv "$f.tmp" "$f" | |
| # sanity checks | |
| self_refs=$(awk ' | |
| /^ pick_copilot_token:[ \t]*$/{p=1;next} | |
| /^ [A-Za-z0-9_-]+:[ \t]*$/{p=0} | |
| p && /^ - pick_copilot_token[ \t]*$/{c++} | |
| END{print c+0}' "$f") | |
| [ "$self_refs" -eq 0 ] || fail "$f: pick_copilot_token still self-references (cycle)" | |
| awk '/^ agent:[ \t]*$/{a=1} /^ [A-Za-z0-9_-]+:[ \t]*$/ && !/agent/{if(a&&!seen)exit 3} a && /^ - pick_copilot_token/{seen=1} END{exit (seen?0:3)}' "$f" \ | |
| || fail "$f: agent job does not depend on pick_copilot_token" | |
| grep -qF 'Validate COPILOT_GITHUB_TOKEN secret' "$f" || echo "WARN: validate-secret step missing in $f" >&2 | |
| grep -qE '^ pick_copilot_token:$' "$f" || echo "WARN: pick_copilot_token job missing in $f, did compile include the .md jobs: block?" >&2 | |
| echo "$f: token-ref=$token_edit, redact-ref=$redact_edit, needs=normalised (self-refs=0, agent->pick ok)" | |
| done | |
| echo "Done." |