blob: a93a34701e009137dd39ec65ebef431bf92f3caf [file]
#!/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."