diff --git a/.asf.yaml b/.asf.yaml index cb957e57fd95..1b922d6e9d84 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -57,6 +57,7 @@ github: - jmsperu - GaOrtiga - bhouse-nexthop + - Dogface2k rulesets: - name: "Default Branch Protection" diff --git a/.github/COPILOT_TOKENS.md b/.github/COPILOT_TOKENS.md new file mode 100644 index 000000000000..7bf5993a8627 --- /dev/null +++ b/.github/COPILOT_TOKENS.md @@ -0,0 +1,90 @@ + + +# Contributing a Copilot token for the agentic workflows + +This repo runs scheduled [GitHub Agentic Workflows](https://github.github.com/gh-aw/) (the +`*.lock.yml` files compiled from `*.md` in `.github/workflows/`) that drive the GitHub Copilot +CLI. Each run needs a GitHub token from an account with an active Copilot license. So that no +single person's Copilot quota gets burned through, runs rotate day by day across a pool of +volunteer tokens. + +If you have a Copilot license and want to help share the load, add your token to the pool. + +## What kind of token + +- A fine-grained personal access token. Classic PATs don't work with the Copilot CLI. +- Resource owner: your own personal account. +- Permission: Account permissions > "Copilot Requests" > Read. That's the only permission it + needs, no repo access. +- Your account must have an active Copilot seat. + +Create it at . Give it a sensible +expiration; when it lapses the health check (below) will flag it and you can re-add it. + +## How to add it + +1. Pick a short alias for yourself, e.g. `t1`, `t2`, `vol3`. The alias shows up in workflow logs, + so keep it non-identifying if you prefer. +2. Add your token as a repository secret named `COPILOT_GITHUB_TOKEN_` + (e.g. `COPILOT_GITHUB_TOKEN_t1`). Repo admins do this via + *Settings > Secrets and variables > Actions > New repository secret*, or: + ``` + gh secret set COPILOT_GITHUB_TOKEN_t1 --body "github_pat_xxx" + ``` +3. Ask a repo admin to register the alias by appending it to the repository variable + `GH_AW_COPILOT_TOKEN_NAMES`, which is a JSON array: + ``` + gh variable set GH_AW_COPILOT_TOKEN_NAMES --body '["t1","t2","t3"]' + ``` + The workflows can't enumerate secrets, so this variable is the source of truth for the pool. + A token isn't used until its alias is listed there. + +## How rotation works (for maintainers) + +Each agent workflow (`weekly-repo-status`, `daily-issue-triage`) defines a `pick_copilot_token` +job in its `.md` source. The job has to run outside the agent job because strict mode forbids +reading secrets there. It picks today's alias by day-of-year mod N, checks the token is live +(`GET /user` returns 200, otherwise it moves on to the next candidate) and outputs the chosen +alias. The token value itself never crosses jobs. The two workflows use different +`ROTATION_SLOT`s, which start them half the pool apart so they don't land on the same +volunteer on the same day (with at least two tokens in the pool). + +The agent job resolves the secret itself via +`secrets[format('COPILOT_GITHUB_TOKEN_{0}', needs.pick_copilot_token.outputs.name)]` and falls +back to the base `COPILOT_GITHUB_TOKEN` when the pick job outputs an empty name. Keep the base +secret set to one reliable token. + +`gh aw compile` doesn't know about this wiring, so after editing the `.md` sources run: + +``` +gh aw compile && bash .github/scripts/post-compile.sh +``` + +See the header of `.github/scripts/post-compile.sh` for what it patches. + +To check the pool, trigger the "Copilot token health" workflow +(`.github/workflows/copilot-token-health.yml`) from the Actions tab. It prints an HTTP status +code per alias and nothing else, so no account identities end up in logs. Note it can't tell +when a token is live but has used up its monthly Copilot requests. + +## Removing a token + +Delete the `COPILOT_GITHUB_TOKEN_` secret and remove `` from +`GH_AW_COPILOT_TOKEN_NAMES`. diff --git a/.github/scripts/post-compile.sh b/.github/scripts/post-compile.sh new file mode 100755 index 000000000000..a93a34701e00 --- /dev/null +++ b/.github/scripts/post-compile.sh @@ -0,0 +1,148 @@ +#!/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:" 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." diff --git a/.github/workflows/copilot-token-health.yml b/.github/workflows/copilot-token-health.yml new file mode 100644 index 000000000000..f421dfbc8375 --- /dev/null +++ b/.github/workflows/copilot-token-health.yml @@ -0,0 +1,64 @@ +# 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. + +# Manual health check for the pool of volunteer Copilot tokens (see .github/COPILOT_TOKENS.md). +# Trigger it from the Actions tab to find dead tokens in GH_AW_COPILOT_TOKEN_NAMES so they can be +# pruned. Only HTTP status codes are printed, never account logins. A 200 just means the token is +# live; there is no endpoint to check whether its monthly Copilot requests are used up. +name: Copilot token health + +on: + workflow_dispatch: {} + +permissions: {} + +jobs: + resolve: + runs-on: ubuntu-latest + outputs: + names: ${{ steps.list.outputs.names }} + steps: + - id: list + env: + NAMES: ${{ vars.GH_AW_COPILOT_TOKEN_NAMES || '[]' }} + run: echo "names=$NAMES" >> "$GITHUB_OUTPUT" + + check: + needs: resolve + if: ${{ needs.resolve.outputs.names != '[]' && needs.resolve.outputs.names != '' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + name: ${{ fromJson(needs.resolve.outputs.names) }} + steps: + - name: Check token liveness + env: + TOKEN: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', matrix.name)] }} + run: | + set -euo pipefail + if [ -z "${TOKEN:-}" ]; then + echo "::error::no secret COPILOT_GITHUB_TOKEN_${{ matrix.name }} found for registered alias '${{ matrix.name }}'" + exit 1 + fi + code=$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $TOKEN" https://api.github.com/user || echo 000) + echo "token '${{ matrix.name }}': HTTP $code" + if [ "$code" != "200" ]; then + echo "::error::token '${{ matrix.name }}' is not live (HTTP $code), consider removing it from GH_AW_COPILOT_TOKEN_NAMES" + exit 1 + fi diff --git a/.github/workflows/daily-issue-triage.lock.yml b/.github/workflows/daily-issue-triage.lock.yml index bd07aeefd811..bc9716b7b1fa 100644 --- a/.github/workflows/daily-issue-triage.lock.yml +++ b/.github/workflows/daily-issue-triage.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"919fb17c7928e5e96d9c0a2854670a42f9c5f6cfc2059b46009bb3c23640d0ca","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"44bf96d0f69a352a086fae0139fbae37562f8568a3095458e0bb0d80eaf73a98","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-5"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55","digest":"sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55","digest":"sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55","digest":"sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19","digest":"sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -35,12 +35,12 @@ # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 @@ -90,7 +90,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -106,7 +106,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_MODEL: "claude-sonnet-5" GH_AW_INFO_VERSION: "1.0.52" GH_AW_INFO_AGENT_VERSION: "1.0.52" GH_AW_INFO_CLI_VERSION: "v0.76.1" @@ -135,7 +135,7 @@ jobs: env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false sparse-checkout: | @@ -194,20 +194,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + cat << 'GH_AW_PROMPT_7885f18f67e7c010_EOF' - GH_AW_PROMPT_7c51e8f15cc7af75_EOF + GH_AW_PROMPT_7885f18f67e7c010_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + cat << 'GH_AW_PROMPT_7885f18f67e7c010_EOF' Tools: add_comment(max:10), add_labels(max:10), missing_tool, missing_data, noop - GH_AW_PROMPT_7c51e8f15cc7af75_EOF + GH_AW_PROMPT_7885f18f67e7c010_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + cat << 'GH_AW_PROMPT_7885f18f67e7c010_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -236,12 +236,12 @@ jobs: {{/if}} - GH_AW_PROMPT_7c51e8f15cc7af75_EOF + GH_AW_PROMPT_7885f18f67e7c010_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + cat << 'GH_AW_PROMPT_7885f18f67e7c010_EOF' {{#runtime-import .github/workflows/daily-issue-triage.md}} - GH_AW_PROMPT_7c51e8f15cc7af75_EOF + GH_AW_PROMPT_7885f18f67e7c010_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -319,7 +319,9 @@ jobs: retention-days: 1 agent: - needs: activation + needs: + - activation + - pick_copilot_token runs-on: ubuntu-latest permissions: read-all concurrency: @@ -349,7 +351,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -371,7 +373,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Create gh-aw temp directory @@ -447,9 +449,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_09fd9551c3cd7278_EOF' - {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_09fd9551c3cd7278_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_12891f19d2c5a9d1_EOF' + {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_12891f19d2c5a9d1_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -661,7 +663,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_37cac1d5ee0c175c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f4f9bbb59e3fefdc_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -705,7 +707,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_37cac1d5ee0c175c_EOF + GH_AW_MCP_CONFIG_f4f9bbb59e3fefdc_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -752,8 +754,8 @@ jobs: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_GITHUB_TOKEN: ${{ needs.pick_copilot_token.outputs.name != '' && secrets[format('COPILOT_GITHUB_TOKEN_{0}', needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-sonnet-5 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -815,7 +817,7 @@ jobs: await main(); env: GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_COPILOT_GITHUB_TOKEN: ${{ needs.pick_copilot_token.outputs.name != '' && secrets[format('COPILOT_GITHUB_TOKEN_{0}', needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN }} SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -939,6 +941,7 @@ jobs: - activation - agent - detection + - pick_copilot_token - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || @@ -961,7 +964,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1030,7 +1033,8 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_MISSING_TOOL_CREATE_ISSUE: "false" + GH_AW_MISSING_TOOL_TITLE_PREFIX: "[missing tool]" GH_AW_WORKFLOW_NAME: "Daily Issue Triage" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" @@ -1046,7 +1050,8 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "false" + GH_AW_REPORT_INCOMPLETE_TITLE_PREFIX: "[incomplete]" GH_AW_WORKFLOW_NAME: "Daily Issue Triage" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" @@ -1083,7 +1088,7 @@ jobs: GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" - GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_FAILURE_REPORT_AS_ISSUE: "false" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "60" @@ -1112,7 +1117,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1141,7 +1146,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false # --- Threat Detection --- @@ -1242,7 +1247,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_MODEL: claude-sonnet-5 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_VERSION: v0.76.1 @@ -1299,6 +1304,79 @@ jobs: } } + pick_copilot_token: + needs: activation + runs-on: ubuntu-latest + outputs: + name: ${{ steps.pick.outputs.name }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Compute candidate names by date + id: names + run: | + set -euo pipefail + NAMES=() + if [ -n "${NAMES_JSON:-}" ]; then + mapfile -t NAMES < <(printf '%s' "$NAMES_JSON" | jq -r '.[]') + fi + N=${#NAMES[@]} + K=3 # today's pick plus 2 fallbacks in case it's dead + if [ "$N" -eq 0 ]; then + for o in $(seq 0 $((K-1))); do echo "name_$o=" >> "$GITHUB_OUTPUT"; done + echo "GH_AW_COPILOT_TOKEN_NAMES is empty -> agent will use base COPILOT_GITHUB_TOKEN" + exit 0 + fi + DOY=$(date -u +%-j) + # slot 1 starts half the pool away from slot 0 so the two workflows + # pick different tokens whenever the pool has at least 2 + START=$(( (DOY - 1 + ROTATION_SLOT * ((N + 1) / 2)) % N )) + for o in $(seq 0 $((K-1))); do + i=$(( (START + o) % N )) + echo "name_$o=${NAMES[$i]}" >> "$GITHUB_OUTPUT" + done + env: + NAMES_JSON: ${{ vars.GH_AW_COPILOT_TOKEN_NAMES }} + ROTATION_SLOT: "1" + - name: Pick first live token name + id: pick + run: | + set -euo pipefail + live() { + [ -n "$1" ] && [ "$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $1" https://api.github.com/user || echo 000)" = "200" ] + } + for pair in "$NAME_0|$CAND_0" "$NAME_1|$CAND_1" "$NAME_2|$CAND_2"; do + nm="${pair%%|*}"; tok="${pair#*|}" + if [ -z "$tok" ]; then continue; fi + echo "::add-mask::$tok" + if live "$tok"; then + echo "name=$nm" >> "$GITHUB_OUTPUT" + echo "Selected rotated token '$nm'" + exit 0 + fi + done + # empty name makes the agent job fall back to the base COPILOT_GITHUB_TOKEN secret + [ -n "$BASE" ] && echo "::add-mask::$BASE" + echo "name=" >> "$GITHUB_OUTPUT" + if live "$BASE"; then echo "Falling back to base COPILOT_GITHUB_TOKEN"; else + echo "WARNING: no live Copilot token (rotated or base)" >&2; fi + env: + BASE: ${{ secrets.COPILOT_GITHUB_TOKEN }} + CAND_0: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_0)] }} + CAND_1: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_1)] }} + CAND_2: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_2)] }} + NAME_0: ${{ steps.names.outputs.name_0 }} + NAME_1: ${{ steps.names.outputs.name_1 }} + NAME_2: ${{ steps.names.outputs.name_2 }} + safe_outputs: needs: - activation @@ -1318,7 +1396,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_MODEL: "claude-sonnet-5" GH_AW_ENGINE_VERSION: "1.0.52" GH_AW_WORKFLOW_ID: "daily-issue-triage" GH_AW_WORKFLOW_NAME: "Daily Issue Triage" @@ -1336,7 +1414,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1381,7 +1459,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-issue-triage.md b/.github/workflows/daily-issue-triage.md index 719dca1f3c63..c3bf7b485335 100644 --- a/.github/workflows/daily-issue-triage.md +++ b/.github/workflows/daily-issue-triage.md @@ -14,7 +14,91 @@ permissions: read-all network: defaults +# claude-sonnet-5: frontier agentic model at promotional $2/$10 per 1M tokens — +# better and cheaper than the claude-sonnet-4.6 engine default this workflow +# previously fell back to. Triage needs real judgment (duplicates, severity). +engine: + id: copilot + model: claude-sonnet-5 + +# Rotates the Copilot token across volunteer PATs, see .github/COPILOT_TOKENS.md. +# Strict mode forbids reading secrets in the agent job, so this job picks today's +# token and outputs its alias only; the agent job resolves the secret itself. +# ROTATION_SLOT 1 staggers this workflow half the pool away from +# weekly-repo-status so the two pick different tokens (pool of 2 or more). +# After `gh aw compile`, run `bash .github/scripts/post-compile.sh` to re-wire the +# agent job to this output. +jobs: + pick_copilot_token: + runs-on: ubuntu-latest + outputs: + name: ${{ steps.pick.outputs.name }} + steps: + - name: Compute candidate names by date + id: names + env: + NAMES_JSON: "${{ vars.GH_AW_COPILOT_TOKEN_NAMES }}" + ROTATION_SLOT: "1" + run: | + set -euo pipefail + NAMES=() + if [ -n "${NAMES_JSON:-}" ]; then + mapfile -t NAMES < <(printf '%s' "$NAMES_JSON" | jq -r '.[]') + fi + N=${#NAMES[@]} + K=3 # today's pick plus 2 fallbacks in case it's dead + if [ "$N" -eq 0 ]; then + for o in $(seq 0 $((K-1))); do echo "name_$o=" >> "$GITHUB_OUTPUT"; done + echo "GH_AW_COPILOT_TOKEN_NAMES is empty -> agent will use base COPILOT_GITHUB_TOKEN" + exit 0 + fi + DOY=$(date -u +%-j) + # slot 1 starts half the pool away from slot 0 so the two workflows + # pick different tokens whenever the pool has at least 2 + START=$(( (DOY - 1 + ROTATION_SLOT * ((N + 1) / 2)) % N )) + for o in $(seq 0 $((K-1))); do + i=$(( (START + o) % N )) + echo "name_$o=${NAMES[$i]}" >> "$GITHUB_OUTPUT" + done + - name: Pick first live token name + id: pick + env: + NAME_0: "${{ steps.names.outputs.name_0 }}" + NAME_1: "${{ steps.names.outputs.name_1 }}" + NAME_2: "${{ steps.names.outputs.name_2 }}" + CAND_0: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_0)] }}" + CAND_1: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_1)] }}" + CAND_2: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_2)] }}" + BASE: "${{ secrets.COPILOT_GITHUB_TOKEN }}" + run: | + set -euo pipefail + live() { + [ -n "$1" ] && [ "$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $1" https://api.github.com/user || echo 000)" = "200" ] + } + for pair in "$NAME_0|$CAND_0" "$NAME_1|$CAND_1" "$NAME_2|$CAND_2"; do + nm="${pair%%|*}"; tok="${pair#*|}" + if [ -z "$tok" ]; then continue; fi + echo "::add-mask::$tok" + if live "$tok"; then + echo "name=$nm" >> "$GITHUB_OUTPUT" + echo "Selected rotated token '$nm'" + exit 0 + fi + done + # empty name makes the agent job fall back to the base COPILOT_GITHUB_TOKEN secret + [ -n "$BASE" ] && echo "::add-mask::$BASE" + echo "name=" >> "$GITHUB_OUTPUT" + if live "$BASE"; then echo "Falling back to base COPILOT_GITHUB_TOKEN"; else + echo "WARNING: no live Copilot token (rotated or base)" >&2; fi + safe-outputs: + # Don't open tracking issues when the agentic run itself fails or is unhealthy + report-failure-as-issue: false + missing-tool: + create-issue: false + report-incomplete: + create-issue: false add-labels: target: "*" max: 10 diff --git a/.github/workflows/daily-repo-status.md b/.github/workflows/daily-repo-status.md deleted file mode 100644 index 49b553940b8b..000000000000 --- a/.github/workflows/daily-repo-status.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -description: | - This workflow creates daily repo status reports. It gathers recent repository - activity (issues, PRs, discussions, releases, code changes) and generates - engaging GitHub issues with productivity insights, community highlights, - and project recommendations. - -on: - schedule: daily - workflow_dispatch: - -permissions: - contents: read - issues: read - pull-requests: read - -network: defaults - -engine: - id: copilot - model: claude-haiku-4.5 - -tools: - github: - # If in a public repo, setting `lockdown: false` allows - # reading issues, pull requests and comments from 3rd-parties - # If in a private repo this has no particular effect. - lockdown: false - min-integrity: none # This workflow is allowed to examine and comment on any issues - -safe-outputs: - mentions: false - allowed-github-references: [] - create-issue: - title-prefix: "[repo-status] " - labels: [report, daily-status] - close-older-issues: true -source: githubnext/agentics/workflows/repo-status.md@main ---- - -# Repo Status - -Create an upbeat daily status report for the repo as a GitHub issue. - -## What to include - -- Recent repository activity (issues, PRs, discussions, releases, code changes) -- Progress tracking, goal reminders and highlights -- Project status and recommendations -- Actionable next steps for maintainers - -## Style - -- Be positive, encouraging, and helpful 🌟 -- Use emojis moderately for engagement -- Keep it concise - adjust length based on actual activity - -## Process - -1. Gather recent activity from the repository -2. Study the repository, its issues and its pull requests -3. Create a new GitHub issue with your findings and insights diff --git a/.github/workflows/sonar-check.yml b/.github/workflows/sonar-check.yml index fbb3cb9f540d..7f2a1bdb2935 100644 --- a/.github/workflows/sonar-check.yml +++ b/.github/workflows/sonar-check.yml @@ -80,12 +80,14 @@ jobs: const emojiMap = { A: '🟢', B: '🟡', C: '🟠', D: '🔴', F: '⛔' }; const emoji = emojiMap[grade] ?? '❓'; const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const marker = ''; const branchRow = branchPct !== 'N/A' ? `| Branch coverage | **${branchPct}%** |` : ''; const body = [ + marker, `## ${emoji} Test Coverage Grade: \`${grade}\` — ${label}`, '', '| Metric | Value |', @@ -106,10 +108,26 @@ jobs: `> [View full Actions run](${runUrl})`, ].filter(l => l !== undefined).join('\n'); - await github.rest.issues.createComment({ + const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - body: body, }); - console.log('Posted coverage grade comment'); + const existing = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body: body, + }); + console.log('Updated existing coverage grade comment'); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body, + }); + console.log('Posted coverage grade comment'); + } diff --git a/.github/workflows/daily-repo-status.lock.yml b/.github/workflows/weekly-repo-status.lock.yml similarity index 90% rename from .github/workflows/daily-repo-status.lock.yml rename to .github/workflows/weekly-repo-status.lock.yml index 0992d3b67de0..63198d8602dd 100644 --- a/.github/workflows/daily-repo-status.lock.yml +++ b/.github/workflows/weekly-repo-status.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"bcecce6f1d9f8df2b3eca9eb7bb1fdbac13c396c240a2dc802a96546f435b969","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot","agent_model":"claude-haiku-4.5"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b5de248d6646003240bf108bb14834943aea8fd06334c62d6c08e396400ab2dc","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-luna"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55","digest":"sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55","digest":"sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55","digest":"sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19","digest":"sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -22,7 +22,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# This workflow creates daily repo status reports. It gathers recent repository +# This workflow creates weekly repo status reports. It gathers recent repository # activity (issues, PRs, discussions, releases, code changes) and generates # engaging GitHub issues with productivity insights, community highlights, # and project recommendations. @@ -36,12 +36,12 @@ # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 @@ -51,11 +51,10 @@ # - ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 # - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 -name: "Repo Status" +name: "Weekly Repo Status" on: schedule: - - cron: "11 19 * * *" - # Friendly format: daily (scattered) + - cron: "0 12 * * 0" workflow_dispatch: inputs: aw_context: @@ -69,7 +68,7 @@ permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}" -run-name: "Repo Status" +run-name: "Weekly Repo Status" jobs: activation: @@ -91,13 +90,13 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_SETUP_WORKFLOW_NAME: "Weekly Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-repo-status.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.52" GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_BODY_MODIFIED: "false" @@ -107,11 +106,11 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: "claude-haiku-4.5" + GH_AW_INFO_MODEL: "gpt-5.6-luna" GH_AW_INFO_VERSION: "1.0.52" GH_AW_INFO_AGENT_VERSION: "1.0.52" GH_AW_INFO_CLI_VERSION: "v0.76.1" - GH_AW_INFO_WORKFLOW_NAME: "Repo Status" + GH_AW_INFO_WORKFLOW_NAME: "Weekly Repo Status" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" @@ -136,7 +135,7 @@ jobs: env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false sparse-checkout: | @@ -161,7 +160,7 @@ jobs: id: check-lock-file uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_WORKFLOW_FILE: "daily-repo-status.lock.yml" + GH_AW_WORKFLOW_FILE: "weekly-repo-status.lock.yml" GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | @@ -195,20 +194,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + cat << 'GH_AW_PROMPT_4dd73a0378be2614_EOF' - GH_AW_PROMPT_eeb322738661ed58_EOF + GH_AW_PROMPT_4dd73a0378be2614_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + cat << 'GH_AW_PROMPT_4dd73a0378be2614_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_eeb322738661ed58_EOF + GH_AW_PROMPT_4dd73a0378be2614_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + cat << 'GH_AW_PROMPT_4dd73a0378be2614_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -237,12 +236,12 @@ jobs: {{/if}} - GH_AW_PROMPT_eeb322738661ed58_EOF + GH_AW_PROMPT_4dd73a0378be2614_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + cat << 'GH_AW_PROMPT_4dd73a0378be2614_EOF' - {{#runtime-import .github/workflows/daily-repo-status.md}} - GH_AW_PROMPT_eeb322738661ed58_EOF + {{#runtime-import .github/workflows/weekly-repo-status.md}} + GH_AW_PROMPT_4dd73a0378be2614_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -319,7 +318,9 @@ jobs: retention-days: 1 agent: - needs: activation + needs: + - activation + - pick_copilot_token runs-on: ubuntu-latest permissions: contents: read @@ -333,7 +334,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - GH_AW_WORKFLOW_ID_SANITIZED: dailyrepostatus + GH_AW_WORKFLOW_ID_SANITIZED: weeklyrepostatus outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} @@ -352,15 +353,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_SETUP_WORKFLOW_NAME: "Weekly Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-repo-status.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.52" GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_BODY_MODIFIED: "false" @@ -374,7 +375,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Create gh-aw temp directory @@ -450,15 +451,15 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_51571b44da85874d_EOF' - {"create_issue":{"close_older_issues":true,"labels":["report","daily-status"],"max":1,"title_prefix":"[repo-status] "},"create_report_incomplete_issue":{},"mentions":{"enabled":false},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_51571b44da85874d_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_47d10a8743973435_EOF' + {"create_issue":{"close_older_issues":true,"labels":["report","weekly-status"],"max":1,"title_prefix":"[repo-status] "},"mentions":{"enabled":false},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_47d10a8743973435_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[repo-status] \". Labels [\"report\" \"daily-status\"] will be automatically added." + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[repo-status] \". Labels [\"report\" \"weekly-status\"] will be automatically added." }, "repo_params": {}, "dynamic_tools": [] @@ -658,7 +659,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_4b3a7789a6eea081_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_3ad3d264c5e0c6f9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -702,7 +703,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_4b3a7789a6eea081_EOF + GH_AW_MCP_CONFIG_3ad3d264c5e0c6f9_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -749,8 +750,8 @@ jobs: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: claude-haiku-4.5 + COPILOT_GITHUB_TOKEN: ${{ needs.pick_copilot_token.outputs.name != '' && secrets[format('COPILOT_GITHUB_TOKEN_{0}', needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: gpt-5.6-luna GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -812,7 +813,7 @@ jobs: await main(); env: GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_COPILOT_GITHUB_TOKEN: ${{ needs.pick_copilot_token.outputs.name != '' && secrets[format('COPILOT_GITHUB_TOKEN_{0}', needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN }} SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -937,6 +938,7 @@ jobs: - activation - agent - detection + - pick_copilot_token - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || @@ -946,7 +948,7 @@ jobs: contents: read issues: write concurrency: - group: "gh-aw-conclusion-daily-repo-status" + group: "gh-aw-conclusion-weekly-repo-status" cancel-in-progress: false queue: max outputs: @@ -957,15 +959,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_SETUP_WORKFLOW_NAME: "Weekly Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-repo-status.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.52" GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_BODY_MODIFIED: "false" @@ -990,7 +992,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_NAME: "Weekly Repo Status" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} @@ -1008,7 +1010,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_NAME: "Weekly Repo Status" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} @@ -1026,8 +1028,9 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_MISSING_TOOL_CREATE_ISSUE: "false" + GH_AW_MISSING_TOOL_TITLE_PREFIX: "[missing tool]" + GH_AW_WORKFLOW_NAME: "Weekly Repo Status" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" with: @@ -1042,8 +1045,9 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "false" + GH_AW_REPORT_INCOMPLETE_TITLE_PREFIX: "[incomplete]" + GH_AW_WORKFLOW_NAME: "Weekly Repo Status" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" with: @@ -1059,12 +1063,12 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_NAME: "Weekly Repo Status" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_WORKFLOW_ID: "daily-repo-status" + GH_AW_WORKFLOW_ID: "weekly-repo-status" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} @@ -1079,7 +1083,7 @@ jobs: GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" - GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_FAILURE_REPORT_AS_ISSUE: "false" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" @@ -1108,15 +1112,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_SETUP_WORKFLOW_NAME: "Weekly Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-repo-status.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.52" GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_BODY_MODIFIED: "false" @@ -1137,7 +1141,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false # --- Threat Detection --- @@ -1185,8 +1189,8 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - WORKFLOW_NAME: "Repo Status" - WORKFLOW_DESCRIPTION: "This workflow creates daily repo status reports. It gathers recent repository\nactivity (issues, PRs, discussions, releases, code changes) and generates\nengaging GitHub issues with productivity insights, community highlights,\nand project recommendations." + WORKFLOW_NAME: "Weekly Repo Status" + WORKFLOW_DESCRIPTION: "This workflow creates weekly repo status reports. It gathers recent repository\nactivity (issues, PRs, discussions, releases, code changes) and generates\nengaging GitHub issues with productivity insights, community highlights,\nand project recommendations." HAS_PATCH: ${{ needs.agent.outputs.has_patch }} with: script: | @@ -1238,7 +1242,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: claude-haiku-4.5 + COPILOT_MODEL: gpt-5.6-luna GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_VERSION: v0.76.1 @@ -1295,6 +1299,79 @@ jobs: } } + pick_copilot_token: + needs: activation + runs-on: ubuntu-latest + outputs: + name: ${{ steps.pick.outputs.name }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Compute candidate names by date + id: names + run: | + set -euo pipefail + NAMES=() + if [ -n "${NAMES_JSON:-}" ]; then + mapfile -t NAMES < <(printf '%s' "$NAMES_JSON" | jq -r '.[]') + fi + N=${#NAMES[@]} + K=3 # today's pick plus 2 fallbacks in case it's dead + if [ "$N" -eq 0 ]; then + for o in $(seq 0 $((K-1))); do echo "name_$o=" >> "$GITHUB_OUTPUT"; done + echo "GH_AW_COPILOT_TOKEN_NAMES is empty -> agent will use base COPILOT_GITHUB_TOKEN" + exit 0 + fi + DOY=$(date -u +%-j) + # slot 1 starts half the pool away from slot 0 so the two workflows + # pick different tokens whenever the pool has at least 2 + START=$(( (DOY - 1 + ROTATION_SLOT * ((N + 1) / 2)) % N )) + for o in $(seq 0 $((K-1))); do + i=$(( (START + o) % N )) + echo "name_$o=${NAMES[$i]}" >> "$GITHUB_OUTPUT" + done + env: + NAMES_JSON: ${{ vars.GH_AW_COPILOT_TOKEN_NAMES }} + ROTATION_SLOT: "0" + - name: Pick first live token name + id: pick + run: | + set -euo pipefail + live() { + [ -n "$1" ] && [ "$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $1" https://api.github.com/user || echo 000)" = "200" ] + } + for pair in "$NAME_0|$CAND_0" "$NAME_1|$CAND_1" "$NAME_2|$CAND_2"; do + nm="${pair%%|*}"; tok="${pair#*|}" + if [ -z "$tok" ]; then continue; fi + echo "::add-mask::$tok" + if live "$tok"; then + echo "name=$nm" >> "$GITHUB_OUTPUT" + echo "Selected rotated token '$nm'" + exit 0 + fi + done + # empty name makes the agent job fall back to the base COPILOT_GITHUB_TOKEN secret + [ -n "$BASE" ] && echo "::add-mask::$BASE" + echo "name=" >> "$GITHUB_OUTPUT" + if live "$BASE"; then echo "Falling back to base COPILOT_GITHUB_TOKEN"; else + echo "WARNING: no live Copilot token (rotated or base)" >&2; fi + env: + BASE: ${{ secrets.COPILOT_GITHUB_TOKEN }} + CAND_0: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_0)] }} + CAND_1: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_1)] }} + CAND_2: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_2)] }} + NAME_0: ${{ steps.names.outputs.name_0 }} + NAME_1: ${{ steps.names.outputs.name_1 }} + NAME_2: ${{ steps.names.outputs.name_2 }} + safe_outputs: needs: - activation @@ -1307,15 +1384,15 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-repo-status" + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/weekly-repo-status" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "claude-haiku-4.5" + GH_AW_ENGINE_MODEL: "gpt-5.6-luna" GH_AW_ENGINE_VERSION: "1.0.52" - GH_AW_WORKFLOW_ID: "daily-repo-status" - GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_ID: "weekly-repo-status" + GH_AW_WORKFLOW_NAME: "Weekly Repo Status" GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" outputs: @@ -1330,15 +1407,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_SETUP_WORKFLOW_NAME: "Weekly Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-repo-status.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.52" GH_AW_INFO_AWF_VERSION: "v0.25.55" GH_AW_INFO_BODY_MODIFIED: "false" @@ -1375,7 +1452,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"labels\":[\"report\",\"daily-status\"],\"max\":1,\"title_prefix\":\"[repo-status] \"},\"create_report_incomplete_issue\":{},\"mentions\":{\"enabled\":false},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"labels\":[\"report\",\"weekly-status\"],\"max\":1,\"title_prefix\":\"[repo-status] \"},\"mentions\":{\"enabled\":false},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/weekly-repo-status.md b/.github/workflows/weekly-repo-status.md new file mode 100644 index 000000000000..b469b59e9a71 --- /dev/null +++ b/.github/workflows/weekly-repo-status.md @@ -0,0 +1,141 @@ +--- +description: | + This workflow creates weekly repo status reports. It gathers recent repository + activity (issues, PRs, discussions, releases, code changes) and generates + engaging GitHub issues with productivity insights, community highlights, + and project recommendations. + +on: + # 12:00 UTC every Sunday. Fixed cron (not the "weekly" fuzzy shorthand) + # because that scatters the run time to spread load. + schedule: "0 12 * * 0" + workflow_dispatch: + +permissions: + contents: read + issues: read + pull-requests: read + +network: defaults + +# gpt-5.6-luna: cheapest lightweight model ($0.20/$1.20 per 1M tokens) — plenty +# for this formulaic weekly report (see GitHub Copilot models-and-pricing docs) +engine: + id: copilot + model: gpt-5.6-luna + +# Rotates the Copilot token across volunteer PATs, see .github/COPILOT_TOKENS.md. +# Strict mode forbids reading secrets in the agent job, so this job picks today's +# token and outputs its alias only; the agent job resolves the secret itself. +# After `gh aw compile`, run `bash .github/scripts/post-compile.sh` to re-wire the +# agent job to this output. +jobs: + pick_copilot_token: + runs-on: ubuntu-latest + outputs: + name: ${{ steps.pick.outputs.name }} + steps: + - name: Compute candidate names by date + id: names + env: + NAMES_JSON: "${{ vars.GH_AW_COPILOT_TOKEN_NAMES }}" + ROTATION_SLOT: "0" + run: | + set -euo pipefail + NAMES=() + if [ -n "${NAMES_JSON:-}" ]; then + mapfile -t NAMES < <(printf '%s' "$NAMES_JSON" | jq -r '.[]') + fi + N=${#NAMES[@]} + K=3 # today's pick plus 2 fallbacks in case it's dead + if [ "$N" -eq 0 ]; then + for o in $(seq 0 $((K-1))); do echo "name_$o=" >> "$GITHUB_OUTPUT"; done + echo "GH_AW_COPILOT_TOKEN_NAMES is empty -> agent will use base COPILOT_GITHUB_TOKEN" + exit 0 + fi + DOY=$(date -u +%-j) + # slot 1 starts half the pool away from slot 0 so the two workflows + # pick different tokens whenever the pool has at least 2 + START=$(( (DOY - 1 + ROTATION_SLOT * ((N + 1) / 2)) % N )) + for o in $(seq 0 $((K-1))); do + i=$(( (START + o) % N )) + echo "name_$o=${NAMES[$i]}" >> "$GITHUB_OUTPUT" + done + - name: Pick first live token name + id: pick + env: + NAME_0: "${{ steps.names.outputs.name_0 }}" + NAME_1: "${{ steps.names.outputs.name_1 }}" + NAME_2: "${{ steps.names.outputs.name_2 }}" + CAND_0: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_0)] }}" + CAND_1: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_1)] }}" + CAND_2: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', steps.names.outputs.name_2)] }}" + BASE: "${{ secrets.COPILOT_GITHUB_TOKEN }}" + run: | + set -euo pipefail + live() { + [ -n "$1" ] && [ "$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $1" https://api.github.com/user || echo 000)" = "200" ] + } + for pair in "$NAME_0|$CAND_0" "$NAME_1|$CAND_1" "$NAME_2|$CAND_2"; do + nm="${pair%%|*}"; tok="${pair#*|}" + if [ -z "$tok" ]; then continue; fi + echo "::add-mask::$tok" + if live "$tok"; then + echo "name=$nm" >> "$GITHUB_OUTPUT" + echo "Selected rotated token '$nm'" + exit 0 + fi + done + # empty name makes the agent job fall back to the base COPILOT_GITHUB_TOKEN secret + [ -n "$BASE" ] && echo "::add-mask::$BASE" + echo "name=" >> "$GITHUB_OUTPUT" + if live "$BASE"; then echo "Falling back to base COPILOT_GITHUB_TOKEN"; else + echo "WARNING: no live Copilot token (rotated or base)" >&2; fi + +tools: + github: + # If in a public repo, setting `lockdown: false` allows + # reading issues, pull requests and comments from 3rd-parties + # If in a private repo this has no particular effect. + lockdown: false + min-integrity: none # This workflow is allowed to examine and comment on any issues + +safe-outputs: + mentions: false + allowed-github-references: [] + # Don't open tracking issues when the agentic run itself fails or is unhealthy + report-failure-as-issue: false + missing-tool: + create-issue: false + report-incomplete: + create-issue: false + create-issue: + title-prefix: "[repo-status] " + labels: [report, weekly-status] + close-older-issues: true +source: githubnext/agentics/workflows/repo-status.md@main +--- + +# Weekly Repo Status + +Create an upbeat weekly status report for the repo as a GitHub issue. + +## What to include + +- Recent repository activity (issues, PRs, discussions, releases, code changes) +- Progress tracking, goal reminders and highlights +- Project status and recommendations +- Actionable next steps for maintainers + +## Style + +- Be positive, encouraging, and helpful 🌟 +- Use emojis moderately for engagement +- Keep it concise - adjust length based on actual activity + +## Process + +1. Gather recent activity from the repository +2. Study the repository, its issues and its pull requests +3. Create a new GitHub issue with your findings and insights diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91537e25267e..a7b564b0f373 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,26 +26,26 @@ repos: - id: identity - id: check-hooks-apply - repo: https://github.com/thlorenz/doctoc.git - rev: v2.2.0 + rev: v2.3.0 hooks: - id: doctoc name: Add TOC for Markdown files files: ^CONTRIBUTING\.md$|^INSTALL\.md$|^README\.md$ - repo: https://github.com/oxipng/oxipng - rev: v9.1.5 + rev: v10.1.0 hooks: - id: oxipng name: run oxipng description: optimize PNG images with lossless compression args: ['-o', '4', '--strip', 'safe', '--alpha'] - repo: https://github.com/gitleaks/gitleaks - rev: v8.27.2 + rev: v8.30.0 hooks: - id: gitleaks name: run gitleaks description: detect hardcoded secrets - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.5 + rev: v1.5.6 hooks: - id: chmod name: set file permissions @@ -172,12 +172,12 @@ repos: name: run codespell description: Check spelling with codespell - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.3.0 hooks: - id: flake8 args: [--config, .github/linters/.flake8] - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.45.0 + rev: v0.48.0 hooks: - id: markdownlint name: run markdownlint @@ -186,7 +186,7 @@ repos: types: [markdown] files: \.md$ - repo: https://github.com/adrienverge/yamllint - rev: v1.37.1 + rev: v1.38.0 hooks: - id: yamllint name: run yamllint diff --git a/README.md b/README.md index 852674ab7b1f..53516503aac4 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ [![Simulator CI](https://github.com/apache/cloudstack/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/ci.yml) [![UI Build](https://github.com/apache/cloudstack/actions/workflows/ui.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/ui.yml) +[![Good First Issues](https://img.shields.io/badge/good%20first%20issues-94D5DA)](https://github.com/apache/cloudstack/issues?q=is%3Aissue%20state%3Aopen%20label%3Agood-first-issue) +[![CloudStack Documentation Status](https://app.readthedocs.org/projects/cloudstack-documentation/badge/?version=latest)](https://app.readthedocs.org/projects/cloudstack-documentation/builds/) +[![CloudStack Website Status](https://img.shields.io/website?url=https%3A%2F%2Fcloudstack.apache.org%2F +)](https://cloudstack.apache.org/) + [![Apache CloudStack](tools/logo/apache_cloudstack.png)](https://cloudstack.apache.org/) @@ -210,7 +215,7 @@ The following provides more details on the included cryptographic software: ## Star History -[![Apache CloudStack Star History](https://api.star-history.com/svg?repos=apache/cloudstack&type=Date)](https://www.star-history.com/#apache/cloudstack&Date) +[![Apache CloudStack Star History](https://star-history.dera.page/svg?repos=apache/cloudstack&type=Date)](https://star-history.dera.page/#apache/cloudstack&Date) ## Contributors diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index f4198819dd16..2fad96ec1da2 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -2025,6 +2025,17 @@ private Pair recreateVolume(VolumeVO vol, VirtualMachinePro volume = volFactory.getVolume(newVol.getId(), destPool); + // CLVM: pin templateless volume creation to the deploy host + StoragePoolVO poolVO = _storagePoolDao.findById(destPool.getId()); + if (poolVO != null && ClvmPoolManager.isClvmPoolType(poolVO.getPoolType())) { + Long hostId = vm.getVirtualMachine().getHostId(); + if (hostId != null) { + volume.setDestinationHostId(hostId); + clvmPoolManager.setClvmLockHostId(volume.getId(), hostId); + logger.info("CLVM pool detected during volume creation without a template. Setting lock host {} for volume {} " + + "to route creation to correct host", hostId, volume.getUuid()); + } + } future = volService.createVolumeAsync(volume, destPool); } else { final VirtualMachineTemplate template = _entityMgr.findById(VirtualMachineTemplate.class, templateId); diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/network/README.md b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/network/README.md index 9101a591587e..56197e5fc8cd 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/network/README.md +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/network/README.md @@ -256,7 +256,7 @@ For all standard network / VPC commands, CloudStack now executes the script as: ``` | Field | Value | -|---|---| +| --- | --- | | `physical-network-extension-details` | Physical-network extension metadata registered on the physical network, enriched with `physicalnetworkname`. | | `network-extension-details` | Additional network or VPC details stored in CloudStack and forwarded as a JSON object. | | `payload` | Command-specific JSON object for the command being executed. | @@ -277,7 +277,7 @@ The following names appear repeatedly inside the nested `payload` object. ### Network-level fields (added by `addNetworkToPayload`) | Field | Description | -|---|---| +| --- | --- | | `network_id` | CloudStack numeric network ID. | | `vlan` | Guest VLAN tag (for example `100`). Extracted from the broadcast URI. May be empty for flat networks. | | `zone_id` | CloudStack zone ID. | @@ -292,7 +292,7 @@ The following names appear repeatedly inside the nested `payload` object. ### NIC-level fields (added by `addNicToPayload`) | Field | Description | -|---|---| +| --- | --- | | `nic_id` | CloudStack numeric NIC ID. | | `nic_uuid` | NIC UUID — matches `external_ids:iface-id` written by the KVM agent for OVN port binding. | | `mac` | VM NIC MAC address. | @@ -308,7 +308,7 @@ The following names appear repeatedly inside the nested `payload` object. ### Public-IP fields (added by `addPublicIpToPayload`) | Field | Description | -|---|---| +| --- | --- | | `public_ip` | A public IP address. | | `public_vlan` | VLAN tag of the public IP segment. | | `public_gateway` | Gateway of the public IP segment. | @@ -319,7 +319,7 @@ The following names appear repeatedly inside the nested `payload` object. ### DNS / extension-IP fields | Field | Description | -|---|---| +| --- | --- | | `extension_ip` | The IP the extension device uses on the guest side. Equals the gateway when SourceNat/Gateway is provided; otherwise it is a dedicated IP from the guest subnet. | | `dns` | Comma-separated DNS server list. | | `domain` | Network domain suffix. | @@ -357,7 +357,7 @@ passed back to later `ensure-network-device` calls as `payload.current_details`. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. Omitted for VPC-level calls. | | `vlan` | Guest VLAN. Present only for network-level calls. | | `zone_id` | CloudStack zone ID. | @@ -389,7 +389,7 @@ configure the gateway. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `gateway` | Guest network gateway. | @@ -411,7 +411,7 @@ URI — without them the KVM agent (`OvsVifDriver`) will not set `external_ids:iface-id` on the OVS tap port and OVN port-binding will fail: | Output key | Required value | Description | -|---|---|---| +| --- | --- | --- | | `network.broadcast_domain_type` | `"Lswitch"` (OVN) or appropriate type | Sets `BroadcastDomainType` on the network record. | | `network.broadcast_uri` | e.g. `"ovn://cs-net-"` | Sets the broadcast URI used by the hypervisor agent. | @@ -440,7 +440,7 @@ return. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `vpc_id` | Present for VPC tier networks. | @@ -471,7 +471,7 @@ can set up the VPC-level SNAT rule at this stage. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `vpc_id` | VPC ID. | | `vpc_cidr` | VPC supernet CIDR. | | `public_ip` | Source-NAT IP, when already allocated. | @@ -492,7 +492,7 @@ The `extension.details` blob is removed from CloudStack after a successful retur **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `vpc_id` | VPC ID. | --- @@ -507,7 +507,7 @@ The `extension.details` blob is removed from CloudStack after a successful retur **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `vpc_id` | VPC ID. | | `vpc_cidr` | VPC supernet CIDR. | | `public_ip` | New source-NAT IP. | @@ -531,7 +531,7 @@ network (source NAT, static NAT, PF, LB allocation). **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN. | | `public_ip` | The public IP being assigned or released. | @@ -556,7 +556,7 @@ and a VM private IP. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `public_ip` | Public IP. | @@ -578,7 +578,7 @@ and a VM private IP. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `public_ip` | Public IP. | @@ -606,7 +606,7 @@ rules for the network, so a full rebuild is always safe. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `gateway` | Guest network gateway. | @@ -645,7 +645,7 @@ rules for the network, so a full rebuild is always safe. ``` | Field | Description | -|---|---| +| --- | --- | | `default_egress_allow` | `true` = permissive egress by default (explicit rules are deny rules); `false` = restrictive (explicit rules are allow rules). | | `cidr` | Guest network CIDR. | | `rules[].type` | `"ingress"` or `"egress"`. | @@ -670,7 +670,7 @@ Rules are applied in ascending `number` order. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `gateway` | Guest network gateway. | @@ -702,7 +702,7 @@ Rules are applied in ascending `number` order. ``` | Field | Description | -|---|---| +| --- | --- | | `number` | Rule priority (lower number = higher priority). | | `action` | `"allow"` or `"deny"`. | | `trafficType` | `"ingress"` or `"egress"`. | @@ -730,7 +730,7 @@ those belonging to DHCP/DNS-enabled offerings. **`prepare-nic` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `mac` | VM NIC MAC address. | @@ -749,7 +749,7 @@ those belonging to DHCP/DNS-enabled offerings. **`release-nic` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `mac` | VM NIC MAC address. | @@ -779,7 +779,7 @@ network whose DHCP service is provided by this extension. **`add-dhcp-entry` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `mac` | VM NIC MAC address, for example `02:00:00:00:00:01`. | | `ip` | VM assigned IP. | @@ -796,7 +796,7 @@ network whose DHCP service is provided by this extension. **`remove-dhcp-entry` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `mac` | VM NIC MAC address. | | `ip` | VM assigned IP. | @@ -816,7 +816,7 @@ without tying it to a specific VM. **`config-dhcp-subnet` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `gateway` | Guest network gateway. | | `cidr` | Guest network CIDR. | @@ -830,7 +830,7 @@ without tying it to a specific VM. **`remove-dhcp-subnet` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `extension_ip` | Extension IP. | | `vpc_id` | Present for VPC tier networks. | @@ -845,7 +845,7 @@ without tying it to a specific VM. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `nic_id` | CloudStack NIC ID. | | `options` | Compact JSON string such as `{"15":"example.com","119":"search.example.com"}`. | @@ -862,7 +862,7 @@ provided by this extension. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `ip` | VM IP. | | `hostname` | VM hostname. | @@ -879,7 +879,7 @@ provided by this extension. **`config-dns-subnet` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `gateway` | Guest network gateway. | | `cidr` | Guest network CIDR. | @@ -893,7 +893,7 @@ provided by this extension. **`remove-dns-subnet` payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `extension_ip` | Extension IP. | | `vpc_id` | Present for VPC tier networks. | @@ -911,7 +911,7 @@ meta-data/*, password) for the VM so the metadata HTTP server can serve it. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `ip` | VM IP. | | `gateway` | Gateway of the VM NIC on this network. | @@ -947,7 +947,7 @@ Your metadata HTTP server should serve each entry at: **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `ip` | VM IP. | | `gateway` | Gateway of the VM NIC. | @@ -966,7 +966,7 @@ Your metadata HTTP server should serve each entry at: **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `ip` | VM IP. | | `gateway` | Gateway of the VM NIC. | @@ -985,7 +985,7 @@ Your metadata HTTP server should serve each entry at: **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `ip` | VM IP. | | `gateway` | Gateway of the VM NIC. | @@ -1006,7 +1006,7 @@ which host they run on (cloud-init `availability-zone` / host detection). **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `ip` | VM IP. | | `gateway` | Gateway of the VM NIC. | @@ -1029,7 +1029,7 @@ virtual server → backend pool mappings. **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `vlan` | Guest VLAN tag. | | `lb_rules` | JSON array of LB rules shown below. | @@ -1057,7 +1057,7 @@ virtual server → backend pool mappings. ``` | Field | Description | -|---|---| +| --- | --- | | `revoke` | `true` → delete this rule; `false` → create/update. | | `backends[].revoked` | `true` → this backend has been removed from the rule. | | `algorithm` | `roundrobin`, `leastconn`, or `source`. | @@ -1077,7 +1077,7 @@ calls). **Payload fields (`payload` object):** | Field | Description | -|---|---| +| --- | --- | | `network_id` | Network ID. | | `gateway` | Guest network gateway. | | `cidr` | Guest network CIDR. | @@ -1133,7 +1133,7 @@ including `physical-network-extension-details` and `network-extension-details`. **Top-level payload keys (network-level):** | Key | Description | -|---|---| +| --- | --- | | `network_id` | The CloudStack network ID. | | `vpc_id` | Present when the network belongs to a VPC. | | `action` | The action name passed by the operator. | @@ -1144,7 +1144,7 @@ including `physical-network-extension-details` and `network-extension-details`. **Top-level payload keys (VPC-level):** | Key | Description | -|---|---| +| --- | --- | | `vpc_id` | The CloudStack VPC ID. | | `action` | The action name passed by the operator. | | `action-params` | JSON object with arbitrary key/value parameters. | @@ -1160,7 +1160,7 @@ Hook scripts should parse the payload file directly. ## Service-to-Command Mapping | CloudStack Network Service | Commands triggered | -|---|---| +| --- | --- | | **SourceNat / Gateway** | `assign-ip`, `release-ip` | | **StaticNat** | `add-static-nat`, `delete-static-nat` | | **PortForwarding** | `add-port-forward`, `delete-port-forward` | @@ -1300,7 +1300,7 @@ included in every command payload. ## Exit Codes | Exit code | Meaning | -|---|---| +| --- | --- | | `0` | Success. | | Any non-zero | Failure. CloudStack logs the exit code and script output, and treats the operation as failed. | @@ -1308,7 +1308,7 @@ For SSH-proxy scripts you may use sub-codes for diagnostics (they are logged but not interpreted differently by CloudStack): | Suggested code | Suggested meaning | -|---|---| +| --- | --- | | `1` | Usage / configuration error. | | `2` | SSH connection / authentication failure. | | `3` | Remote script returned non-zero. | diff --git a/plugins/storage/volume/ontap/README.md b/plugins/storage/volume/ontap/README.md index e7e066aafb55..059bf99e09d8 100644 --- a/plugins/storage/volume/ontap/README.md +++ b/plugins/storage/volume/ontap/README.md @@ -36,15 +36,15 @@ The NetApp ONTAP Storage Plugin provides integration between Apache CloudStack a ### Component Structure -| Package | Description | -|---------|-------------------------------------------------------| -| `driver` | Primary datastore driver implementation | +| Package | Description | +| --- | --- | +| `driver` | Primary datastore driver implementation | | `feign` | REST API clients and data models for ONTAP operations | -| `lifecycle` | Storage pool lifecycle management | -| `listener` | Host connection event handlers | -| `provider` | Main provider and strategy factory | -| `service` | ONTAP Storage strategy implementations (NAS/SAN) | -| `utils` | Constants and helper utilities | +| `lifecycle` | Storage pool lifecycle management | +| `listener` | Host connection event handlers | +| `provider` | Main provider and strategy factory | +| `service` | ONTAP Storage strategy implementations (NAS/SAN) | +| `utils` | Constants and helper utilities | ## Requirements @@ -74,7 +74,7 @@ ONTAP requires a minimum volume size of **1.56 GB** (1,677,721,600 bytes). The p When creating an ONTAP primary storage pool, provide the following details in the URL field (semicolon-separated key=value pairs): | Parameter | Required | Description | -|-----------|----------|-------------| +| --- | --- | --- | | `username` | Yes | ONTAP cluster admin username | | `password` | Yes | ONTAP cluster admin password | | `svmName` | Yes | Storage Virtual Machine name | @@ -90,7 +90,7 @@ username=admin;password=secretpass;svmName=svm1;protocol=ISCSI;managementLIF=192 ## Port Configuration | Protocol | Default Port | -|----------|--------------| +| --- | --- | | NFS | 2049 | | iSCSI | 3260 | | ONTAP Management API | 443 (HTTPS) | diff --git a/tools/build/build_asf.sh b/tools/build/build_asf.sh index 44d41472b05a..bc43d3b5b1d2 100755 --- a/tools/build/build_asf.sh +++ b/tools/build/build_asf.sh @@ -103,6 +103,9 @@ perl -pi -e "s/-SNAPSHOT//" tools/marvin/marvin/deployAndRun.py perl -pi -e "s/-SNAPSHOT//" tools/docker/Dockerfile perl -pi -e "s/-SNAPSHOT//" tools/docker/Dockerfile.marvin perl -pi -e "s/-SNAPSHOT//" tools/docker/Dockerfile.centos6 +perl -pi -e "s/-SNAPSHOT//" tools/docker/Dockerfile.s390x +perl -pi -e "s/-SNAPSHOT//" plugins/hypervisors/ovm/pom.xml +perl -pi -e "s/-SNAPSHOT//" plugins/hypervisors/ovm3/pom.xml case "$currentversion" in *-SNAPSHOT*) diff --git a/ui/src/config/section/infra/phynetworks.js b/ui/src/config/section/infra/phynetworks.js index 9b7a0a83f7f3..0863eff6ec0b 100644 --- a/ui/src/config/section/infra/phynetworks.js +++ b/ui/src/config/section/infra/phynetworks.js @@ -94,7 +94,7 @@ export default { icon: 'edit-outlined', label: 'label.update.physical.network', dataView: true, - args: ['vlan', 'tags', 'externaldetails'] + args: ['vlan', 'tags'] }, { api: 'addTrafficType',