Repository navigation
Update Agent Versions #3861
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update Agent Versions | |
| on: | |
| schedule: | |
| # Run hourly at minute 0 | |
| - cron: "0 * * * *" | |
| workflow_dispatch: | |
| inputs: | |
| apply: | |
| description: "Apply updates and commit to main" | |
| required: false | |
| default: false | |
| type: boolean | |
| agents: | |
| description: "Comma-separated agent IDs (leave empty for all)" | |
| required: false | |
| default: "" | |
| type: string | |
| permissions: | |
| contents: read | |
| jobs: | |
| check-versions: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| has_updates: ${{ steps.check.outputs.has_updates }} | |
| updates_json: ${{ steps.check.outputs.updates_json }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.14" | |
| - name: Check for version updates | |
| id: check | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| INPUT_AGENTS: ${{ inputs.agents }} | |
| run: | | |
| set +e | |
| ARGS=(--json) | |
| if [ -n "$INPUT_AGENTS" ]; then | |
| if [[ ! "$INPUT_AGENTS" =~ ^[a-z0-9-]+(,[a-z0-9-]+)*$ ]]; then | |
| echo "Invalid agents input: $INPUT_AGENTS" >&2 | |
| exit 1 | |
| fi | |
| ARGS+=(--agents "$INPUT_AGENTS") | |
| fi | |
| OUTPUT=$(python .github/workflows/update_versions.py "${ARGS[@]}") | |
| EXIT_CODE=$? | |
| echo "$OUTPUT" | |
| # Save JSON output | |
| UPDATES_DELIMITER="$(uuidgen)" | |
| { | |
| echo "updates_json<<$UPDATES_DELIMITER" | |
| printf '%s\n' "$OUTPUT" | |
| echo "$UPDATES_DELIMITER" | |
| } >> "$GITHUB_OUTPUT" | |
| # Check if updates are available (exit code 2) | |
| if [ "$EXIT_CODE" -eq 2 ]; then | |
| echo "has_updates=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_updates=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Fail on actual errors (exit code 1) | |
| if [ "$EXIT_CODE" -eq 1 ]; then | |
| exit 1 | |
| fi | |
| notify-failure: | |
| needs: check-versions | |
| if: failure() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Create failure issue | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| with: | |
| script: | | |
| const title = `Version check failed - ${new Date().toISOString().split('T')[0]}`; | |
| const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; | |
| // Check if issue already exists today | |
| const existingIssues = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'version-check-failure', | |
| per_page: 10 | |
| }); | |
| const todayIssue = existingIssues.data.find(i => i.title === title); | |
| if (todayIssue) { | |
| // Add comment to existing issue | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: todayIssue.number, | |
| body: `Another failure occurred.\n\n**Run:** ${runUrl}` | |
| }); | |
| } else { | |
| // Create new issue | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: title, | |
| body: `The automated version check workflow failed.\n\n**Run:** ${runUrl}\n\nPlease investigate the failure.`, | |
| labels: ['version-check-failure', 'automated'] | |
| }); | |
| } | |
| apply-updates: | |
| needs: check-versions | |
| if: needs.check-versions.outputs.has_updates == 'true' && github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || inputs.apply == true || inputs.apply == 'true') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.14" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6 | |
| with: | |
| node-version: "lts/*" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 | |
| - name: Apply version updates | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| INPUT_AGENTS: ${{ inputs.agents }} | |
| run: | | |
| ARGS=(--apply) | |
| if [ -n "$INPUT_AGENTS" ]; then | |
| if [[ ! "$INPUT_AGENTS" =~ ^[a-z0-9-]+(,[a-z0-9-]+)*$ ]]; then | |
| echo "Invalid agents input: $INPUT_AGENTS" >&2 | |
| exit 1 | |
| fi | |
| ARGS+=(--agents "$INPUT_AGENTS") | |
| fi | |
| python .github/workflows/update_versions.py "${ARGS[@]}" | |
| - name: Stage updated agent manifests | |
| env: | |
| UPDATES_JSON: ${{ needs.check-versions.outputs.updates_json }} | |
| UPDATE_ARTIFACT_DIR: ${{ runner.temp }}/agent-updates | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| data = json.loads(os.environ["UPDATES_JSON"]) | |
| updates = data.get("updates", []) | |
| # path -> {channel -> version}: both channels of one agent live in the | |
| # same file, so a path-only key would let one channel mask the other. | |
| expected_versions = {} | |
| for update in updates: | |
| agent_id = update.get("agent_id", "") | |
| channel = update.get("channel", "stable") | |
| latest_version = update.get("latest_version", "") | |
| if not re.fullmatch(r"[a-z][a-z0-9-]*", agent_id): | |
| print(f"Invalid agent id in update JSON: {agent_id!r}", file=sys.stderr) | |
| sys.exit(1) | |
| if channel not in ("stable", "preview"): | |
| print(f"Invalid channel in update JSON: {channel!r}", file=sys.stderr) | |
| sys.exit(1) | |
| expected_versions.setdefault(f"{agent_id}/agent.json", {})[channel] = latest_version | |
| if not expected_versions: | |
| print("No expected update files found", file=sys.stderr) | |
| sys.exit(1) | |
| status_lines = subprocess.check_output( | |
| ["git", "status", "--porcelain=v1"], | |
| text=True, | |
| ).splitlines() | |
| changed_paths = set() | |
| for line in status_lines: | |
| path = line[3:] | |
| if " -> " in path: | |
| path = path.split(" -> ", 1)[1] | |
| changed_paths.add(path) | |
| expected_paths = set(expected_versions) | |
| unexpected = sorted(changed_paths - expected_paths) | |
| missing = sorted(expected_paths - changed_paths) | |
| if unexpected or missing: | |
| if unexpected: | |
| print("Unexpected changed paths:", file=sys.stderr) | |
| for path in unexpected: | |
| print(f" {path}", file=sys.stderr) | |
| if missing: | |
| print("Expected paths were not changed:", file=sys.stderr) | |
| for path in missing: | |
| print(f" {path}", file=sys.stderr) | |
| sys.exit(1) | |
| artifact_dir = Path(os.environ["UPDATE_ARTIFACT_DIR"]) | |
| if artifact_dir.exists(): | |
| shutil.rmtree(artifact_dir) | |
| artifact_dir.mkdir(parents=True) | |
| base_sha = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() | |
| (artifact_dir / "base-sha.txt").write_text(base_sha + "\n") | |
| for rel_path, channel_versions in sorted(expected_versions.items()): | |
| source = Path(rel_path) | |
| agent = json.loads(source.read_text()) | |
| for channel, expected_version in sorted(channel_versions.items()): | |
| if channel == "preview": | |
| actual_version = (agent.get("preview") or {}).get("version") | |
| else: | |
| actual_version = agent.get("version") | |
| if actual_version != expected_version: | |
| print( | |
| f"{rel_path} has {channel} version {actual_version!r}, " | |
| f"expected {expected_version!r}", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| destination = artifact_dir / rel_path | |
| destination.parent.mkdir(parents=True, exist_ok=True) | |
| shutil.copy2(source, destination) | |
| (artifact_dir / "files.json").write_text( | |
| json.dumps(sorted(expected_paths), indent=2) + "\n", | |
| ) | |
| PY | |
| - name: Validate registry build | |
| run: uv run --with jsonschema .github/workflows/build_registry.py | |
| - name: Upload update artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: agent-updates | |
| path: ${{ runner.temp }}/agent-updates/ | |
| if-no-files-found: error | |
| retention-days: 1 | |
| verify-updates: | |
| needs: [check-versions, apply-updates] | |
| if: needs.apply-updates.result == 'success' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| outputs: | |
| has_verified_updates: ${{ steps.filter.outputs.has_verified_updates }} | |
| verified_updates_json: ${{ steps.filter.outputs.verified_updates_json }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Download update artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: agent-updates | |
| path: ${{ runner.temp }}/agent-updates | |
| - name: Apply staged update manifests | |
| env: | |
| UPDATE_ARTIFACT_DIR: ${{ runner.temp }}/agent-updates | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| artifact_dir = Path(os.environ["UPDATE_ARTIFACT_DIR"]) | |
| base_sha = (artifact_dir / "base-sha.txt").read_text().strip() | |
| if not re.fullmatch(r"[0-9a-f]{40}", base_sha): | |
| print(f"Invalid artifact base SHA: {base_sha!r}", file=sys.stderr) | |
| sys.exit(1) | |
| current_sha = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() | |
| if current_sha != base_sha: | |
| print( | |
| f"Update artifact was generated from {base_sha}, " | |
| f"but this checkout is {current_sha}; refusing stale verification.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| files = json.loads((artifact_dir / "files.json").read_text()) | |
| for rel_path in files: | |
| if not re.fullmatch(r"[a-z][a-z0-9-]*/agent\.json", rel_path): | |
| print(f"Unexpected artifact path: {rel_path}", file=sys.stderr) | |
| sys.exit(1) | |
| source = artifact_dir / rel_path | |
| if not source.is_file(): | |
| print(f"Missing artifact file: {source}", file=sys.stderr) | |
| sys.exit(1) | |
| destination = Path(rel_path) | |
| shutil.copy2(source, destination) | |
| PY | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.14" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6 | |
| with: | |
| node-version: "lts/*" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 | |
| - name: Validate registry build | |
| run: uv run --with jsonschema .github/workflows/build_registry.py | |
| - name: Verify agent auth support | |
| env: | |
| UPDATES_JSON: ${{ needs.check-versions.outputs.updates_json }} | |
| VERIFICATION_RESULTS: ${{ runner.temp }}/verification-results.tsv | |
| #language=bash | |
| run: | | |
| # The preview channel is explicitly unverified: preview distributions | |
| # are never launched, so only stable bumps are auth-checked here. | |
| mapfile -t STABLE_UPDATES < <(echo "$UPDATES_JSON" | python3 -c " | |
| import sys, json | |
| data = json.load(sys.stdin) | |
| versions = { | |
| u['agent_id']: u['latest_version'] | |
| for u in data.get('updates', []) | |
| if u.get('channel', 'stable') == 'stable' | |
| } | |
| for agent_id, version in sorted(versions.items()): | |
| print(f'{agent_id}\t{version}') | |
| ") | |
| : > "$VERIFICATION_RESULTS" | |
| if [ "${#STABLE_UPDATES[@]}" -eq 0 ]; then | |
| echo "No stable agent updates in this run; skipping auth verification" | |
| exit 0 | |
| fi | |
| for STABLE_UPDATE in "${STABLE_UPDATES[@]}"; do | |
| IFS=$'\t' read -r AGENT_ID AGENT_VERSION <<< "$STABLE_UPDATE" | |
| echo "::group::Verify $AGENT_ID" | |
| if python3 .github/workflows/verify_agents.py --auth-check --agent "$AGENT_ID"; then | |
| printf '%s\tpassed\n' "$AGENT_ID" >> "$VERIFICATION_RESULTS" | |
| else | |
| printf '%s\tfailed\n' "$AGENT_ID" >> "$VERIFICATION_RESULTS" | |
| echo "::warning title=Agent update skipped::$AGENT_ID@$AGENT_VERSION failed auth verification; its version updates will not be committed." | |
| fi | |
| echo "::endgroup::" | |
| done | |
| - name: Filter verified updates | |
| id: filter | |
| if: always() | |
| env: | |
| UPDATES_JSON: ${{ needs.check-versions.outputs.updates_json }} | |
| UPDATE_ARTIFACT_DIR: ${{ runner.temp }}/agent-updates | |
| VERIFIED_ARTIFACT_DIR: ${{ runner.temp }}/verified-agent-updates | |
| VERIFICATION_RESULTS: ${{ runner.temp }}/verification-results.tsv | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import shutil | |
| import sys | |
| from pathlib import Path | |
| updates = json.loads(os.environ["UPDATES_JSON"]).get("updates", []) | |
| source_dir = Path(os.environ["UPDATE_ARTIFACT_DIR"]) | |
| destination_dir = Path(os.environ["VERIFIED_ARTIFACT_DIR"]) | |
| results_path = Path(os.environ["VERIFICATION_RESULTS"]) | |
| results = {} | |
| if results_path.exists(): | |
| for line in results_path.read_text().splitlines(): | |
| agent_id, status = line.split("\t", 1) | |
| if status not in ("passed", "failed"): | |
| print(f"Invalid verification status for {agent_id}: {status}", file=sys.stderr) | |
| sys.exit(1) | |
| results[agent_id] = status | |
| stable_agents = { | |
| update["agent_id"] | |
| for update in updates | |
| if update.get("channel", "stable") == "stable" | |
| } | |
| missing_results = sorted(stable_agents - results.keys()) | |
| if missing_results: | |
| print( | |
| "Missing verification results for: " + ", ".join(missing_results), | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| failed_agents = { | |
| agent_id for agent_id, status in results.items() if status == "failed" | |
| } | |
| verified_updates = [ | |
| update for update in updates if update["agent_id"] not in failed_agents | |
| ] | |
| verified_agent_ids = {update["agent_id"] for update in verified_updates} | |
| files = json.loads((source_dir / "files.json").read_text()) | |
| verified_files = [ | |
| rel_path | |
| for rel_path in files | |
| if rel_path.split("/", 1)[0] in verified_agent_ids | |
| ] | |
| if {path.split("/", 1)[0] for path in verified_files} != verified_agent_ids: | |
| print("Verified update artifact is missing an agent manifest", file=sys.stderr) | |
| sys.exit(1) | |
| if destination_dir.exists(): | |
| shutil.rmtree(destination_dir) | |
| destination_dir.mkdir(parents=True) | |
| shutil.copy2(source_dir / "base-sha.txt", destination_dir / "base-sha.txt") | |
| for rel_path in verified_files: | |
| destination = destination_dir / rel_path | |
| destination.parent.mkdir(parents=True, exist_ok=True) | |
| shutil.copy2(source_dir / rel_path, destination) | |
| (destination_dir / "files.json").write_text( | |
| json.dumps(verified_files, indent=2) + "\n" | |
| ) | |
| verified_json = json.dumps({"updates": verified_updates}, separators=(",", ":")) | |
| with open(os.environ["GITHUB_OUTPUT"], "a") as output: | |
| print(f"has_verified_updates={'true' if verified_updates else 'false'}", file=output) | |
| print("verified_updates_json<<EOF", file=output) | |
| print(verified_json, file=output) | |
| print("EOF", file=output) | |
| passed_agents = sorted(results.keys() - failed_agents) | |
| with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as summary: | |
| print("## Agent update verification", file=summary) | |
| print(file=summary) | |
| if failed_agents: | |
| print("> [!WARNING]", file=summary) | |
| print( | |
| f"> {len(failed_agents)} agent update(s) failed auth verification and " | |
| "were excluded from the commit.", | |
| file=summary, | |
| ) | |
| print(file=summary) | |
| print("### Skipped updates", file=summary) | |
| print(file=summary) | |
| print("| Agent | Updates |", file=summary) | |
| print("| --- | --- |", file=summary) | |
| for agent_id in sorted(failed_agents): | |
| versions = ", ".join( | |
| f"{u.get('channel', 'stable')} {u['current_version']} → {u['latest_version']}" | |
| for u in updates | |
| if u["agent_id"] == agent_id | |
| ) | |
| print(f"| `{agent_id}` | {versions} |", file=summary) | |
| print(file=summary) | |
| print("### Updates proceeding to commit", file=summary) | |
| print(file=summary) | |
| if verified_updates: | |
| print("| Agent | Updates | Verification |", file=summary) | |
| print("| --- | --- | --- |", file=summary) | |
| for agent_id in sorted(verified_agent_ids): | |
| versions = ", ".join( | |
| f"{u.get('channel', 'stable')} {u['current_version']} → {u['latest_version']}" | |
| for u in verified_updates | |
| if u["agent_id"] == agent_id | |
| ) | |
| verification = ( | |
| "passed" if agent_id in passed_agents else "preview only (not required)" | |
| ) | |
| print(f"| `{agent_id}` | {versions} | {verification} |", file=summary) | |
| else: | |
| print("No updates passed verification; no commit will be created.", file=summary) | |
| PY | |
| - name: Upload verified update artifact | |
| if: steps.filter.outputs.has_verified_updates == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: verified-agent-updates | |
| path: ${{ runner.temp }}/verified-agent-updates/ | |
| if-no-files-found: error | |
| retention-days: 1 | |
| commit-updates: | |
| needs: [check-versions, apply-updates, verify-updates] | |
| if: needs.verify-updates.result == 'success' && needs.verify-updates.outputs.has_verified_updates == 'true' | |
| concurrency: | |
| group: registry-main-write | |
| cancel-in-progress: false | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: main | |
| persist-credentials: false | |
| - name: Download update artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: verified-agent-updates | |
| path: ${{ runner.temp }}/agent-updates | |
| - name: Apply staged update manifests | |
| id: apply_artifact | |
| env: | |
| UPDATE_ARTIFACT_DIR: ${{ runner.temp }}/agent-updates | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| artifact_dir = Path(os.environ["UPDATE_ARTIFACT_DIR"]) | |
| base_sha = (artifact_dir / "base-sha.txt").read_text().strip() | |
| if not re.fullmatch(r"[0-9a-f]{40}", base_sha): | |
| print(f"Invalid artifact base SHA: {base_sha!r}", file=sys.stderr) | |
| sys.exit(1) | |
| current_sha = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() | |
| if current_sha != base_sha: | |
| print( | |
| f"Update artifact was generated from {base_sha}, " | |
| f"but main is now {current_sha}; refusing to overwrite newer main.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| files = json.loads((artifact_dir / "files.json").read_text()) | |
| safe_files = [] | |
| for rel_path in files: | |
| if not re.fullmatch(r"[a-z][a-z0-9-]*/agent\.json", rel_path): | |
| print(f"Unexpected artifact path: {rel_path}", file=sys.stderr) | |
| sys.exit(1) | |
| source = artifact_dir / rel_path | |
| if not source.is_file(): | |
| print(f"Missing artifact file: {source}", file=sys.stderr) | |
| sys.exit(1) | |
| destination = Path(rel_path) | |
| shutil.copy2(source, destination) | |
| safe_files.append(rel_path) | |
| with open(os.environ["GITHUB_OUTPUT"], "a") as output: | |
| print(f"files={' '.join(safe_files)}", file=output) | |
| PY | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 | |
| - name: Validate registry build | |
| run: uv run --with jsonschema .github/workflows/build_registry.py | |
| - name: Generate commit message | |
| id: commit_msg | |
| env: | |
| UPDATES_JSON: ${{ needs.verify-updates.outputs.verified_updates_json }} | |
| run: | | |
| # Generate summary of updates | |
| SUMMARY=$(echo "$UPDATES_JSON" | python3 -c " | |
| import sys, json | |
| data = json.load(sys.stdin) | |
| updates = data.get('updates', []) | |
| if len(updates) == 1: | |
| u = updates[0] | |
| channel = u.get('channel', 'stable') | |
| suffix = '' if channel == 'stable' else ' (' + channel + ')' | |
| print('Update ' + u['agent_id'] + suffix + ' to ' + u['latest_version']) | |
| else: | |
| print('Update ' + str(len(updates)) + ' agents to latest versions') | |
| ") | |
| # Generate details | |
| DETAILS=$(echo "$UPDATES_JSON" | python3 -c " | |
| import sys, json | |
| data = json.load(sys.stdin) | |
| for u in data.get('updates', []): | |
| channel = u.get('channel', 'stable') | |
| suffix = '' if channel == 'stable' else ' (' + channel + ')' | |
| print('- ' + u['agent_id'] + suffix + ': ' + u['current_version'] + ' -> ' + u['latest_version']) | |
| ") | |
| echo "summary=$SUMMARY" >> "$GITHUB_OUTPUT" | |
| DETAILS_DELIMITER="$(uuidgen)" | |
| { | |
| echo "details<<$DETAILS_DELIMITER" | |
| printf '%s\n' "$DETAILS" | |
| echo "$DETAILS_DELIMITER" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Commit updated manifests | |
| id: commit | |
| env: | |
| COMMIT_SUMMARY: ${{ steps.commit_msg.outputs.summary }} | |
| COMMIT_DETAILS: ${{ steps.commit_msg.outputs.details }} | |
| UPDATED_FILES: ${{ steps.apply_artifact.outputs.files }} | |
| run: | | |
| git config user.name "acp-release[bot]" | |
| git config user.email "2373403+acp-release[bot]@users.noreply.github.com" | |
| read -r -a FILES <<< "$UPDATED_FILES" | |
| git add -- "${FILES[@]}" | |
| if git diff --cached --quiet; then | |
| echo "No staged update files to commit" >&2 | |
| exit 1 | |
| fi | |
| git commit -m "$COMMIT_SUMMARY" -m "$COMMIT_DETAILS" | |
| echo "committed=true" >> "$GITHUB_OUTPUT" | |
| # Generating a GitHub token, so that commits created by the | |
| # action can trigger the registry publication workflow. | |
| - name: Generate GitHub token | |
| if: steps.commit.outputs.committed == 'true' | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 | |
| id: generate-token | |
| with: | |
| # GitHub App ID secret name | |
| app-id: ${{ secrets.RELEASE_PLZ_APP_ID }} | |
| # GitHub App private key secret name | |
| private-key: ${{ secrets.RELEASE_PLZ_APP_PRIVATE_KEY }} | |
| - name: Push updates to main | |
| if: steps.commit.outputs.committed == 'true' | |
| env: | |
| GH_APP_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| run: | | |
| AUTH_HEADER="$(printf 'x-access-token:%s' "$GH_APP_TOKEN" | base64 | tr -d '\n')" | |
| echo "::add-mask::$AUTH_HEADER" | |
| git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${AUTH_HEADER}" push origin main | |
| notify-apply-failure: | |
| needs: [check-versions, apply-updates, verify-updates, commit-updates] | |
| if: always() && (needs.apply-updates.result == 'failure' || needs.verify-updates.result == 'failure' || needs.commit-updates.result == 'failure') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Create failure issue | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| with: | |
| script: | | |
| const title = `Version update failed - ${new Date().toISOString().split('T')[0]}`; | |
| const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; | |
| // Check if issue already exists today | |
| const existingIssues = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'version-update-failure', | |
| per_page: 10 | |
| }); | |
| const todayIssue = existingIssues.data.find(i => i.title === title); | |
| if (todayIssue) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: todayIssue.number, | |
| body: `Another failure occurred.\n\n**Run:** ${runUrl}` | |
| }); | |
| } else { | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: title, | |
| body: `The automated version update workflow failed while applying updates.\n\nThis could be due to:\n- Registry validation failure\n- Auth verification failure\n- Git push conflict\n- Network issues\n\n**Run:** ${runUrl}\n\nPlease investigate.`, | |
| labels: ['version-update-failure', 'automated'] | |
| }); | |
| } |