-
Notifications
You must be signed in to change notification settings - Fork 24
feat(publish): gate auto-publish on releases + publish summary table + docs #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
3d932eb
feat(publish): add publish-summary table + GitHub Release upsert
felickz e5b4080
fix(publish): avoid tee+pipe fragility in Build summary table step
felickz cb01617
Merge remote-tracking branch 'origin/main' into publish-summary-table
felickz 75e4b88
feat(publish): gate auto-publish on .release.yml changes; document re…
felickz cd50d6a
fix: address Copilot review feedback on PR #160
felickz 113ed4a
Merge main (PR #159 - bump remaining packs to 0.2.3) into publish-sum…
felickz ec6dd17
feat(publish): add CodeQL standard library versions cross-check table
felickz 7b75ce1
fix: mark build-codeql-lib-versions-table.sh executable
felickz 0659595
fix(build-codeql-lib-versions-table): check src lock file, not lib
felickz 16c7cb0
feat(publish): commit/tag-anchored links + queries pack in lib-versio…
felickz 1341fae
feat: automate CodeQL CLI version bump detection and dependency refresh
felickz d518638
fix(publish): gate release-table upsert to push trigger, clarify fail…
felickz 0843ff8
fix(publish): check both src and lib lock files for CodeQL version drift
felickz faa5fe6
docs: fix publish.yml job count in CONTRIBUTING.md (five jobs, not four)
felickz bda4965
fix(publish): don't fail check_version when a pack's GHCR container d…
felickz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| #!/usr/bin/env bash | ||
| # Builds a markdown section documenting the pinned CodeQL CLI version and | ||
| # comparing every direct `codeql/*` dependency declared in each language's | ||
| # `src/qlpack.yml` AND `lib/qlpack.yml` (e.g. `codeql/<lang>-all`, and | ||
| # `codeql/<lang>-queries` for the languages that also depend on the standard | ||
| # queries pack) against the canonical version github/codeql itself bundles | ||
| # for that CLI release. | ||
| # | ||
| # Both `src` and `lib` are checked independently, not just `src`. Each pack | ||
| # directory has its own `codeql-pack.lock.yml`, resolved separately by | ||
| # `codeql pack upgrade <dir>` - the `codeql/<lang>-all: '*'` dependency each | ||
| # of them declares can therefore drift out of sync with each other (e.g. if | ||
| # someone runs `codeql pack upgrade <lang>/src` alone to fix a compile error | ||
| # after a CLI bump, and forgets `<lang>/lib`). Nothing in this repo enforces | ||
| # they stay identical, so both are treated as independent sources of truth | ||
| # here rather than assuming one implies the other. | ||
| # `ext`/`ext-library-sources` packs are deliberately NOT checked: they pin | ||
| # their target via `extensionTargets:`, not `dependencies:`, which CodeQL | ||
| # does not resolve into a version-locked `codeql-pack.lock.yml` entry (their | ||
| # lock files have an empty `dependencies: {}`), so there is no locked version | ||
| # to compare there. | ||
| # | ||
| # Dependencies are discovered dynamically from each qlpack.yml (not | ||
| # hardcoded) so this stays correct if a language adds/drops a direct | ||
| # `codeql/*` dependency. | ||
| # | ||
| # Both the "our locked" and "upstream" version cells link directly to the | ||
| # exact file+line that pins that version, at the exact commit (ours) or CLI | ||
| # tag (upstream) being compared - so a maintainer can verify the claim with | ||
| # one click instead of trusting the table. | ||
| # | ||
| # This exists because CI's "codeql pack install" step is non-resolving: it | ||
| # only installs whatever is already pinned in the checked-in lock file, it | ||
| # never re-resolves/upgrades versions. If a maintainer bumps .codeqlversion | ||
| # but forgets to run `codeql pack upgrade` for one of a language's pack | ||
| # directories, that directory's lock file silently stays on a stale version | ||
| # forever, and CI stays green. This table is a machine-checkable tripwire for | ||
| # that drift - see CONTRIBUTING.md's "Updating the pinned CodeQL CLI/library | ||
| # version" section. | ||
| # | ||
| # Usage: build-codeql-lib-versions-table.sh | ||
| # Requires: gh (authenticated), git, awk, base64 | ||
| set -euo pipefail | ||
|
|
||
| REPO="${GITHUB_REPOSITORY:-GitHubSecurityLab/CodeQL-Community-Packs}" | ||
| REPO_SHA="${GITHUB_SHA:-$(git rev-parse HEAD)}" | ||
|
|
||
| CODEQL_VERSION=$(tr -d '[:space:]' < .codeqlversion) | ||
| if [ -z "$CODEQL_VERSION" ]; then | ||
| echo "::error::Could not read .codeqlversion" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| LANGUAGES=(cpp csharp go java javascript python ruby) | ||
| PACKTYPES=(src lib) | ||
|
|
||
| lang_label() { | ||
| case "$1" in | ||
| cpp) echo "C++" ;; | ||
| csharp) echo "C#" ;; | ||
| go) echo "Go" ;; | ||
| java) echo "Java" ;; | ||
| javascript) echo "JavaScript" ;; | ||
| python) echo "Python" ;; | ||
| ruby) echo "Ruby" ;; | ||
| *) echo "$1" ;; | ||
| esac | ||
| } | ||
|
|
||
| # Lists the direct `codeql/*` dependencies declared in a qlpack.yml's | ||
| # `dependencies:` block (skips our own `githubsecuritylab/*` packages). | ||
| qlpack_codeql_deps() { | ||
| local qlpackfile="$1" | ||
| awk ' | ||
| { sub(/\r$/, "") } | ||
| /^dependencies:/ { found=1; next } | ||
| found && /^[[:space:]]+codeql\// { | ||
| line=$0 | ||
| sub(/^[[:space:]]+/, "", line) | ||
| sub(/:.*/, "", line) | ||
| print line | ||
| next | ||
| } | ||
| found && /^[^[:space:]]/ { exit } | ||
| ' "$qlpackfile" | ||
| } | ||
|
|
||
| # Reads the version pinned to a package in a codeql-pack.lock.yml, plus the | ||
| # line numbers of the package key and its version line (for permalinks). | ||
| # Prints: "<version> <key_line> <version_line>" | ||
| locked_version_and_lines() { | ||
| local lockfile="$1" pkg="$2" | ||
| awk -v pkg=" ${pkg}:" ' | ||
| { sub(/\r$/, "") } | ||
| $0 == pkg { found=1; keyline=NR; next } | ||
| found && /^[[:space:]]+version:/ { | ||
| gsub(/\r/, "") | ||
| print $2, keyline, NR | ||
| exit | ||
| } | ||
| found && /^[^[:space:]]/ { exit } | ||
| ' "$lockfile" | ||
| } | ||
|
|
||
| # Maps a direct dependency name to the path github/codeql uses for the | ||
| # corresponding pack's qlpack.yml. Returns empty if unmapped (unknown dep | ||
| # shape) so the caller can surface that instead of guessing. | ||
| upstream_path_for_pkg() { | ||
| local lang="$1" pkg="$2" | ||
| case "$pkg" in | ||
| "codeql/${lang}-all") echo "${lang}/ql/lib/qlpack.yml" ;; | ||
| "codeql/${lang}-queries") echo "${lang}/ql/src/qlpack.yml" ;; | ||
| *) echo "" ;; | ||
| esac | ||
| } | ||
|
|
||
| # Fetches a file from github/codeql at the tag for our pinned CLI version, | ||
| # and prints "<version> <version_line>" from its top-level `version:` field. | ||
| upstream_version_and_line() { | ||
| local path="$1" | ||
| local content | ||
| content=$(gh api "repos/github/codeql/contents/${path}?ref=refs%2Ftags%2Fcodeql-cli%2Fv${CODEQL_VERSION}" \ | ||
| --jq '.content' 2>/dev/null | base64 -d 2>/dev/null) || return 1 | ||
| [ -n "$content" ] || return 1 | ||
| echo "$content" | awk '/^version:/ { print $2, NR; exit }' | ||
| } | ||
|
|
||
| MISMATCH=0 | ||
|
|
||
| echo "## CodeQL standard library & query pack versions" | ||
| echo | ||
| echo "Pinned CodeQL CLI/library version ([\`.codeqlversion\`](https://github.com/${REPO}/blob/${REPO_SHA}/.codeqlversion)): [\`v${CODEQL_VERSION}\`](https://github.com/github/codeql-cli-binaries/releases/tag/v${CODEQL_VERSION})" | ||
| echo | ||
| echo "_Comparing each language's direct \`codeql/*\` dependencies (from both \`src/qlpack.yml\` and \`lib/qlpack.yml\`, each resolved in its own \`codeql-pack.lock.yml\`) against the versions [github/codeql](https://github.com/github/codeql) itself ships for CLI \`v${CODEQL_VERSION}\` (tag [\`codeql-cli/v${CODEQL_VERSION}\`](https://github.com/github/codeql/tree/codeql-cli/v${CODEQL_VERSION})). \`src\` and \`lib\` are checked independently because they're separately-resolved lock files that can drift from each other. Each cell links to the exact file/line backing it - our side at the commit this table was generated from, upstream at the CLI tag - so the claim can be verified with one click. A mismatch means \`codeql pack upgrade <lang>/<pack>\` hasn't been run for that specific directory since the last \`.codeqlversion\` bump - see [CONTRIBUTING.md: Updating the pinned CodeQL CLI/library version](https://github.com/${REPO}/blob/${REPO_SHA}/CONTRIBUTING.md#updating-the-pinned-codeql-clilibrary-version)._" | ||
| echo | ||
| echo "| Language | Pack | Dependency | Our locked version | Upstream (CLI v${CODEQL_VERSION} ships) | Status |" | ||
| echo "| --- | --- | --- | --- | --- | --- |" | ||
|
|
||
| for lang in "${LANGUAGES[@]}"; do | ||
| label=$(lang_label "$lang") | ||
|
|
||
| for packtype in "${PACKTYPES[@]}"; do | ||
| qlpackfile="${lang}/${packtype}/qlpack.yml" | ||
| lockfile="${lang}/${packtype}/codeql-pack.lock.yml" | ||
|
|
||
| if [ ! -f "$qlpackfile" ] || [ ! -f "$lockfile" ]; then | ||
| echo "| $label | \`$packtype\` | - | ❓ missing qlpack.yml or lock file | - | ❓ |" | ||
| MISMATCH=1 | ||
| continue | ||
| fi | ||
|
|
||
| deps=$(qlpack_codeql_deps "$qlpackfile") | ||
| if [ -z "$deps" ]; then | ||
| echo "| $label | \`$packtype\` | - | ❓ no direct \`codeql/*\` dependency found | - | ❓ |" | ||
| MISMATCH=1 | ||
| continue | ||
| fi | ||
|
|
||
| while IFS= read -r pkg; do | ||
| [ -n "$pkg" ] || continue | ||
|
|
||
| read -r locked keyline verline <<< "$(locked_version_and_lines "$lockfile" "$pkg")" | ||
| if [ -z "${locked:-}" ]; then | ||
| echo "| $label | \`$packtype\` | \`$pkg\` | ❓ not found in lock file | - | ❓ |" | ||
| MISMATCH=1 | ||
| continue | ||
| fi | ||
| locked_link="https://github.com/${REPO}/blob/${REPO_SHA}/${lockfile}#L${keyline}-L${verline}" | ||
| locked_cell="[\`$locked\`]($locked_link)" | ||
|
|
||
| upath=$(upstream_path_for_pkg "$lang" "$pkg") | ||
| if [ -z "$upath" ]; then | ||
| echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | ❓ no known upstream mapping | ❓ |" | ||
| MISMATCH=1 | ||
| continue | ||
| fi | ||
|
|
||
| read -r upstream uverline <<< "$(upstream_version_and_line "$upath" || true)" | ||
| if [ -z "${upstream:-}" ]; then | ||
| echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | ❓ fetch failed | ❓ |" | ||
| MISMATCH=1 | ||
| continue | ||
| fi | ||
| upstream_link="https://github.com/github/codeql/blob/codeql-cli/v${CODEQL_VERSION}/${upath}#L${uverline}" | ||
| upstream_cell="[\`$upstream\`]($upstream_link)" | ||
|
|
||
| if [ "$locked" == "$upstream" ]; then | ||
| echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | $upstream_cell | ✅ |" | ||
| else | ||
| echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | $upstream_cell | ⚠️ drift |" | ||
| MISMATCH=1 | ||
| fi | ||
| done <<< "$deps" | ||
| done | ||
| done | ||
|
|
||
| if [ "$MISMATCH" -eq 1 ]; then | ||
| echo "::warning::One or more CodeQL standard library/query pack versions are out of sync with CLI v${CODEQL_VERSION}. Run 'codeql pack upgrade <lang>/<pack>' for the affected language/pack directory(ies)." >&2 | ||
| fi |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| #!/usr/bin/env bash | ||
| # Builds a markdown table summarizing the package versions produced by a | ||
| # publish.yml run, from the per-job JSON result fragments it wrote. | ||
| # | ||
| # Usage: build-publish-summary.sh <results-dir> | ||
| # | ||
| # Each fragment in <results-dir> looks like: | ||
| # { | ||
| # "language": "cpp", | ||
| # "type": "src", | ||
| # "package": "codeql-cpp-queries", | ||
| # "previous_version": "0.2.1", | ||
| # "target_version": "0.2.2", | ||
| # "status": "published" # published | up-to-date | failed | ||
| # } | ||
| set -euo pipefail | ||
|
|
||
| RESULTS_DIR="${1:?usage: build-publish-summary.sh <results-dir>}" | ||
| REPO="${GITHUB_REPOSITORY:-GitHubSecurityLab/CodeQL-Community-Packs}" | ||
| RUN_URL="${GITHUB_SERVER_URL:-https://github.com}/${REPO}/actions/runs/${GITHUB_RUN_ID:-}" | ||
|
|
||
| shopt -s nullglob | ||
| FRAGMENTS=("$RESULTS_DIR"/*.json) | ||
| if [ "${#FRAGMENTS[@]}" -eq 0 ]; then | ||
| MERGED='[]' | ||
| else | ||
| MERGED=$(jq -s '.' "${FRAGMENTS[@]}") | ||
| fi | ||
|
|
||
| LANGUAGES=(cpp csharp go java javascript python ruby) | ||
| TYPES=(src lib ext ext-library-sources) | ||
| EXT_LANGUAGES=(csharp java) | ||
|
|
||
| lang_label() { | ||
| case "$1" in | ||
| cpp) echo "C++" ;; | ||
| csharp) echo "C#" ;; | ||
| go) echo "Go" ;; | ||
| java) echo "Java" ;; | ||
| javascript) echo "JavaScript" ;; | ||
| python) echo "Python" ;; | ||
| ruby) echo "Ruby" ;; | ||
| *) echo "$1" ;; | ||
| esac | ||
| } | ||
|
|
||
| is_ext_language() { | ||
| local lang="$1" | ||
| for l in "${EXT_LANGUAGES[@]}"; do | ||
| [ "$l" == "$lang" ] && return 0 | ||
| done | ||
| return 1 | ||
| } | ||
|
|
||
| cell() { | ||
| local lang="$1" type="$2" | ||
| local entry | ||
| entry=$(echo "$MERGED" | jq -c --arg l "$lang" --arg t "$type" \ | ||
| '[.[] | select(.language == $l and .type == $t)] | first // empty') | ||
|
|
||
| if [ -z "$entry" ] || [ "$entry" == "null" ]; then | ||
| # This combo is expected to publish (unlike the "n/a" combos filtered out | ||
| # by the caller) but no result fragment was found for it. | ||
| echo "❓ no result" | ||
| return | ||
| fi | ||
|
|
||
| local package version status previous | ||
| package=$(echo "$entry" | jq -r '.package') | ||
| version=$(echo "$entry" | jq -r '.target_version') | ||
| status=$(echo "$entry" | jq -r '.status') | ||
| previous=$(echo "$entry" | jq -r '.previous_version') | ||
| local url="https://github.com/${REPO}/pkgs/container/${package}?tag=${version}" | ||
|
|
||
| case "$status" in | ||
| published) echo "[${version}](${url}) 🆕" ;; | ||
| up-to-date) echo "[${version}](${url})" ;; | ||
| failed) | ||
| if [ -n "$previous" ] && [ "$previous" != "null" ]; then | ||
| echo "⚠️ publish failed (still \`${previous}\`)" | ||
| else | ||
| echo "⚠️ publish failed (previous version unknown - version-check step itself failed)" | ||
| fi | ||
| ;; | ||
| *) echo "❓ unknown" ;; | ||
| esac | ||
| } | ||
|
|
||
| echo "## Publish summary" | ||
| echo | ||
| echo "_Generated by [publish.yml run #${GITHUB_RUN_NUMBER:-}](${RUN_URL}) — 🆕 marks a package republished by this run._" | ||
| echo | ||
| echo "| Language | Queries (\`src\`) | Library (\`lib\`) | Extensions (\`ext\`) | Library sources (\`ext-library-sources\`) |" | ||
| echo "| --- | --- | --- | --- | --- |" | ||
| for lang in "${LANGUAGES[@]}"; do | ||
| row="| $(lang_label "$lang") |" | ||
| for type in "${TYPES[@]}"; do | ||
| if { [ "$type" == "ext" ] || [ "$type" == "ext-library-sources" ]; } && ! is_ext_language "$lang"; then | ||
| row="$row n/a |" | ||
| continue | ||
| fi | ||
| row="$row $(cell "$lang" "$type") |" | ||
| done | ||
| echo "$row" | ||
| done |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| #!/usr/bin/env bash | ||
| # Upserts a generated markdown block (e.g. the publish-summary table from | ||
| # build-publish-summary.sh, or the CodeQL library-versions table from | ||
| # build-codeql-lib-versions-table.sh) into the GitHub Release matching | ||
| # .release.yml's current version, creating that release as a pre-release if | ||
| # it doesn't exist yet. | ||
| # | ||
| # Usage: upsert-release-table.sh <table-markdown-file> [block-name] | ||
| # | ||
| # block-name defaults to "publish-summary" for backwards compatibility. Each | ||
| # distinct block-name gets its own marker-comment pair, so multiple blocks | ||
| # (e.g. "publish-summary" and "codeql-lib-versions") can be upserted | ||
| # independently into the same release body without clobbering each other. | ||
| # Re-runs only replace the matching block and leave the rest of the release | ||
| # body (e.g. "What's Changed") alone. | ||
| set -euo pipefail | ||
|
|
||
| TABLE_FILE="${1:?usage: upsert-release-table.sh <table-markdown-file> [block-name]}" | ||
| BLOCK_NAME="${2:-publish-summary}" | ||
| START_MARKER="<!-- ${BLOCK_NAME}:start -->" | ||
| END_MARKER="<!-- ${BLOCK_NAME}:end -->" | ||
|
|
||
| VERSION=$(grep -E '^version:' .release.yml | head -1 | sed -E 's/^version:[[:space:]]*"?([^"[:space:]]*)"?/\1/') | ||
| if [ -z "$VERSION" ]; then | ||
| echo "::error::Could not read version from .release.yml" >&2 | ||
| exit 1 | ||
| fi | ||
| TAG="v${VERSION}" | ||
|
|
||
| BLOCK_FILE=$(mktemp) | ||
| { | ||
| echo "$START_MARKER" | ||
| cat "$TABLE_FILE" | ||
| echo "$END_MARKER" | ||
| } > "$BLOCK_FILE" | ||
|
|
||
| if gh release view "$TAG" >/dev/null 2>&1; then | ||
| echo "Release $TAG exists; refreshing its $BLOCK_NAME block." | ||
| CURRENT_BODY=$(mktemp) | ||
| gh release view "$TAG" --json body -q .body > "$CURRENT_BODY" | ||
|
|
||
| NEW_BODY=$(mktemp) | ||
| if grep -qF "$START_MARKER" "$CURRENT_BODY" && grep -qF "$END_MARKER" "$CURRENT_BODY"; then | ||
| awk -v start="$START_MARKER" -v end="$END_MARKER" -v blockfile="$BLOCK_FILE" ' | ||
| $0 == start { | ||
| while ((getline line < blockfile) > 0) print line | ||
| skip = 1 | ||
| next | ||
| } | ||
| $0 == end { | ||
| skip = 0 | ||
| next | ||
| } | ||
| skip { next } | ||
| { print } | ||
| ' "$CURRENT_BODY" > "$NEW_BODY" | ||
| else | ||
| # Older release predating this automation, or a corrupted/partial marker | ||
| # pair: append the block once rather than risk truncating the body. | ||
| cat "$CURRENT_BODY" > "$NEW_BODY" | ||
| echo "" >> "$NEW_BODY" | ||
| cat "$BLOCK_FILE" >> "$NEW_BODY" | ||
| fi | ||
|
|
||
| gh release edit "$TAG" --notes-file "$NEW_BODY" | ||
| else | ||
| echo "Release $TAG does not exist yet; creating it as a pre-release." | ||
| gh release create "$TAG" \ | ||
| --title "$TAG" \ | ||
| --notes-file "$BLOCK_FILE" \ | ||
| --generate-notes \ | ||
| --prerelease \ | ||
| --target "${GITHUB_SHA:-main}" | ||
| fi | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.