|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 3 | +# or more contributor license agreements. See the NOTICE file |
| 4 | +# distributed with this work for additional information |
| 5 | +# regarding copyright ownership. The ASF licenses this file |
| 6 | +# to you under the Apache License, Version 2.0 (the |
| 7 | +# "License"); you may not use this file except in compliance |
| 8 | +# with the License. You may obtain a copy of the License at |
| 9 | +# |
| 10 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +# |
| 12 | +# Unless required by applicable law or agreed to in writing, |
| 13 | +# software distributed under the License is distributed on an |
| 14 | +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | +# KIND, either express or implied. See the License for the |
| 16 | +# specific language governing permissions and limitations |
| 17 | +# under the License. |
| 18 | + |
| 19 | +# Re-applies the token round-robin wiring to the gh-aw generated .lock.yml files, |
| 20 | +# which `gh aw compile` doesn't know about. Run after every compile: |
| 21 | +# |
| 22 | +# gh aw compile && bash .github/scripts/post-compile.sh |
| 23 | +# |
| 24 | +# Three edits per lock file (see .github/COPILOT_TOKENS.md for the design): |
| 25 | +# - point the agent execute step's COPILOT_GITHUB_TOKEN at the pick_copilot_token |
| 26 | +# job's output, falling back to the base secret |
| 27 | +# - point the agent job's "Redact secrets in logs" step at the same rotated token, |
| 28 | +# so a volunteer token is scrubbed from uploaded artifacts, not just the base one |
| 29 | +# - make the agent job depend on pick_copilot_token, and strip the self-reference |
| 30 | +# gh-aw sometimes adds to pick_copilot_token's own needs (that would be a cycle) |
| 31 | +# |
| 32 | +# Safe to re-run; a second run is a no-op. |
| 33 | + |
| 34 | +set -euo pipefail |
| 35 | + |
| 36 | +cd "$(git rev-parse --show-toplevel)" |
| 37 | + |
| 38 | +# Kept in an env var so perl doesn't try to interpolate the ${{ }} bits. |
| 39 | +export NEWVAL='${{ needs.pick_copilot_token.outputs.name != '"'"''"'"' && secrets[format('"'"'COPILOT_GITHUB_TOKEN_{0}'"'"', needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN }}' |
| 40 | + |
| 41 | +FILES=( |
| 42 | + ".github/workflows/daily-repo-status.lock.yml" |
| 43 | + ".github/workflows/daily-issue-triage.lock.yml" |
| 44 | +) |
| 45 | + |
| 46 | +fail() { echo "ERROR: $1" >&2; exit 1; } |
| 47 | + |
| 48 | +# Fixes up `needs:` for the agent and pick_copilot_token jobs only; gh-aw adds |
| 49 | +# pick_copilot_token to several other jobs' needs and those must stay as-is. |
| 50 | +# Reads stdin, writes stdout. gh-aw emits inline needs (needs: foo) for single |
| 51 | +# dependencies and block form for lists; inline is fine unless it needs editing, |
| 52 | +# in which case "INLINE_NEEDS:<job>" is printed to stderr and the caller bails. |
| 53 | +normalise_needs() { |
| 54 | + awk ' |
| 55 | + function isjob(l){ return (l ~ /^ [A-Za-z0-9_-]+:[ \t]*$/) } |
| 56 | + BEGIN { job=""; inneeds=0; agentpick=0 } |
| 57 | + { |
| 58 | + line=$0 |
| 59 | + if (isjob(line)) { |
| 60 | + if (inneeds && job=="agent" && !agentpick) print " - pick_copilot_token" |
| 61 | + inneeds=0; agentpick=0 |
| 62 | + name=line; sub(/^ /,"",name); sub(/:[ \t]*$/,"",name); job=name |
| 63 | + print line; next |
| 64 | + } |
| 65 | + if (line ~ /^ needs:[ \t]*[^ \t]/) { |
| 66 | + if (job=="agent" && line !~ /pick_copilot_token/) print "INLINE_NEEDS:" job > "/dev/stderr" |
| 67 | + if (job=="pick_copilot_token" && line ~ /pick_copilot_token/) print "INLINE_NEEDS:" job > "/dev/stderr" |
| 68 | + print line; next |
| 69 | + } |
| 70 | + if (line ~ /^ needs:[ \t]*$/) { inneeds=1; agentpick=0; print line; next } |
| 71 | + if (inneeds) { |
| 72 | + if (line ~ /^ - /) { |
| 73 | + item=line; sub(/^ - /,"",item); gsub(/[ \t\r]/,"",item) |
| 74 | + if (job=="pick_copilot_token" && item=="pick_copilot_token") next |
| 75 | + if (job=="agent" && item=="pick_copilot_token") agentpick=1 |
| 76 | + print line; next |
| 77 | + } else { |
| 78 | + if (job=="agent" && !agentpick) print " - pick_copilot_token" |
| 79 | + inneeds=0 |
| 80 | + print line; next |
| 81 | + } |
| 82 | + } |
| 83 | + print line |
| 84 | + } |
| 85 | + END { if (inneeds && job=="agent" && !agentpick) print " - pick_copilot_token" } |
| 86 | + ' |
| 87 | +} |
| 88 | + |
| 89 | +for f in "${FILES[@]}"; do |
| 90 | + if [ ! -f "$f" ]; then |
| 91 | + echo "WARN: $f not found, run 'gh aw compile' first? Skipping" >&2 |
| 92 | + continue |
| 93 | + fi |
| 94 | + |
| 95 | + # Repoint the agent execute step's token. The anchor is the GH_AW_PHASE: agent env |
| 96 | + # var further down the same env block; the detection job's block has |
| 97 | + # GH_AW_PHASE: detection so it doesn't match and keeps the base token. |
| 98 | + before=$(grep -cF 'COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) |
| 99 | + perl -0pi -e \ |
| 100 | + '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' \ |
| 101 | + "$f" |
| 102 | + after=$(grep -cF 'COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) |
| 103 | + removed=$(( before - after )) |
| 104 | + if [ "$removed" -eq 1 ]; then token_edit="applied" |
| 105 | + elif [ "$removed" -eq 0 ] && grep -qE '^[ \t]+COPILOT_GITHUB_TOKEN: \$\{\{ needs\.pick_copilot_token' "$f"; then token_edit="already" |
| 106 | + else fail "$f: execute-step token line not patched as expected (removed=$removed), anchor drifted?" |
| 107 | + fi |
| 108 | + |
| 109 | + # Repoint the redact step's SECRET_COPILOT_GITHUB_TOKEN the same way; the line is |
| 110 | + # unique to the agent job's "Redact secrets in logs" step. |
| 111 | + before=$(grep -cF 'SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) |
| 112 | + if [ "$before" -gt 1 ]; then |
| 113 | + fail "$f: expected at most one redact-step SECRET_COPILOT_GITHUB_TOKEN line, found $before" |
| 114 | + fi |
| 115 | + perl -0pi -e \ |
| 116 | + 's/^([ \t]*)SECRET_COPILOT_GITHUB_TOKEN:[ \t]*\$\{\{[ \t]*secrets\.COPILOT_GITHUB_TOKEN[ \t]*\}\}[ \t]*$/$1."SECRET_COPILOT_GITHUB_TOKEN: ".$ENV{NEWVAL}/me' \ |
| 117 | + "$f" |
| 118 | + after=$(grep -cF 'SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}' "$f" || true) |
| 119 | + if [ "$before" -eq 1 ] && [ "$after" -eq 0 ]; then redact_edit="applied" |
| 120 | + elif [ "$before" -eq 0 ] && grep -qF 'SECRET_COPILOT_GITHUB_TOKEN: ${{ needs.pick_copilot_token.outputs.name' "$f"; then redact_edit="already" |
| 121 | + else fail "$f: redact-step SECRET_COPILOT_GITHUB_TOKEN line not patched (before=$before after=$after)" |
| 122 | + fi |
| 123 | + |
| 124 | + errf="$(mktemp)" |
| 125 | + normalise_needs < "$f" > "$f.tmp" 2>"$errf" |
| 126 | + if grep -q '^INLINE_NEEDS:' "$errf"; then |
| 127 | + rm -f "$f.tmp"; rm -f "$errf" |
| 128 | + fail "$f: agent/pick_copilot_token have inline 'needs:' that would need editing, update normalise_needs" |
| 129 | + fi |
| 130 | + rm -f "$errf" |
| 131 | + mv "$f.tmp" "$f" |
| 132 | + |
| 133 | + # sanity checks |
| 134 | + self_refs=$(awk ' |
| 135 | + /^ pick_copilot_token:[ \t]*$/{p=1;next} |
| 136 | + /^ [A-Za-z0-9_-]+:[ \t]*$/{p=0} |
| 137 | + p && /^ - pick_copilot_token[ \t]*$/{c++} |
| 138 | + END{print c+0}' "$f") |
| 139 | + [ "$self_refs" -eq 0 ] || fail "$f: pick_copilot_token still self-references (cycle)" |
| 140 | + 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" \ |
| 141 | + || fail "$f: agent job does not depend on pick_copilot_token" |
| 142 | + grep -qF 'Validate COPILOT_GITHUB_TOKEN secret' "$f" || echo "WARN: validate-secret step missing in $f" >&2 |
| 143 | + grep -qE '^ pick_copilot_token:$' "$f" || echo "WARN: pick_copilot_token job missing in $f, did compile include the .md jobs: block?" >&2 |
| 144 | + |
| 145 | + echo "$f: token-ref=$token_edit, redact-ref=$redact_edit, needs=normalised (self-refs=0, agent->pick ok)" |
| 146 | +done |
| 147 | + |
| 148 | +echo "Done." |
0 commit comments