Skip to content

Latest commit

 

History

History
379 lines (360 loc) · 15.5 KB

File metadata and controls

379 lines (360 loc) · 15.5 KB
import-schema
packages app-id private-key owner repositories apps
type items required description
array
type
string
false
Public APM packages or packages reachable via the default token cascade (GH_AW_PLUGINS_TOKEN, GH_AW_GITHUB_TOKEN, GITHUB_TOKEN). Optional. At least one of `packages`, the single-app inputs, or `apps` must be provided. Format: owner/repo or owner/repo/path/to/skill.
type required description
string
false
GitHub App ID. With `private-key`, mints an installation token for the packages listed in `packages:`. For multiple orgs, use `apps:` instead.
type required description
string
false
PEM private key matching `app-id`. Required when `app-id` is set. Pass via a repository or organization secret.
type required description
string
false
App installation owner. Defaults to the current repository owner when omitted. Only used when `app-id` is set.
type required description
string
false
Repositories the minted token is scoped to. Comma- or newline-separated. Empty defaults to the calling repo or the App installation default scope. Note: literal "*" is NOT a wildcard for actions/create-github-app-token; leave empty for org-wide access via App installation config.
type required description items
array
false
List of GitHub App credential groups. Each entry mints its own installation token and packs its own packages. Use when packages span multiple orgs requiring different App installations.
type properties
object
id app-id private-key owner repositories packages
type required description
string
false
Stable identifier used for matrix-row and artifact naming. Auto-derived from `owner` (slugified) when omitted. Required when two entries share the same owner.
type required
string
true
type required
string
true
type required
string
false
type required
string
false
type items required
array
type
string
true
jobs
apm-prep apm
runs-on needs permissions outputs steps
ubuntu-slim
activation
matrix
${{ steps.compute.outputs.matrix }}
name id env run
Compute APM credential-group matrix
compute
AW_APM_PACKAGES AW_APM_APPS AW_APM_LEGACY_APP_ID AW_APM_LEGACY_PRIVATE_KEY AW_APM_LEGACY_OWNER AW_APM_LEGACY_REPOS
${{ github.aw.import-inputs.packages }}
${{ github.aw.import-inputs.apps }}
${{ github.aw.import-inputs.app-id }}
${{ github.aw.import-inputs.private-key }}
${{ github.aw.import-inputs.owner }}
${{ github.aw.import-inputs.repositories }}
set -euo pipefail packages_json=${AW_APM_PACKAGES:-null} apps_json=${AW_APM_APPS:-null} legacy_id=${AW_APM_LEGACY_APP_ID:-} # gh-aw substitutes `${{ github.aw.import-inputs.packages }}` at # compile time using Go's default slice formatter, which emits # `[a b c]` (space-separated, no quotes) instead of valid JSON. # That breaks `jq --argjson` below. Repair string-array inputs # in place; leave already-valid JSON untouched. apps[] (objects) # is not repairable this way -- consumers must use the legacy # single-app inputs until upstream gh-aw exposes a JSON-encoding # helper for import-inputs. repair_string_array() { local raw="$1" if [ -z "$raw" ] || [ "$raw" = "null" ]; then echo "$raw"; return fi if printf '%s' "$raw" | jq -e 'type=="array"' >/dev/null 2>&1; then echo "$raw"; return fi python3 -c 'import json, re, sys; s=sys.argv[1].strip(); s=s[1:-1] if s.startswith("[") and s.endswith("]") else s; print(json.dumps([t for t in re.split(r"[\s,]+", s) if t]))' "$raw" } packages_json=$(repair_string_array "$packages_json") groups=$(jq -nc \ --argjson packages "$packages_json" \ --argjson apps "$apps_json" \ --arg legacy_id "$legacy_id" \ --arg legacy_pk "${AW_APM_LEGACY_PRIVATE_KEY:-}" \ --arg legacy_owner "${AW_APM_LEGACY_OWNER:-}" \ --arg legacy_repos "${AW_APM_LEGACY_REPOS:-}" \ 'def slug(s): s | gsub("[^a-zA-Z0-9-]"; "-") | ascii_downcase | .[0:32]; def with_id(g): g + (if (g.id // "") == "" then {id: ("auto-" + slug(g.owner // "default"))} else {} end); [ (if (($packages // []) | length) > 0 and $legacy_id == "" then [{id:"default",("app-id"):"",("private-key"):"",owner:"",repositories:"",packages:$packages}] else [] end), (if $legacy_id != "" then [with_id({id:"legacy",("app-id"):$legacy_id,("private-key"):$legacy_pk,owner:$legacy_owner,repositories:$legacy_repos,packages:($packages // [])})] else [] end), (($apps // []) | map(with_id(.))) ] | add // []') count=$(echo "$groups" | jq 'length') if [ "$count" = "0" ]; then echo "::error::shared/apm.md import provided no packages. Add packages: <list>, single-app inputs (app-id + private-key), or apps: <list> in the with: block." exit 1 fi dups=$(echo "$groups" | jq -r '[.[].id] | group_by(.) | map(select(length > 1) | first) | join(", ")') if [ -n "$dups" ]; then echo "::error::duplicate apm group ids after auto-derivation: $dups. Set apps[].id explicitly when two entries share the same owner." exit 1 fi while IFS= read -r id; do if ! echo "$id" | grep -Eq '^[a-z0-9-]{1,32}$'; then echo "::error::invalid apm group id: '$id' (lowercase alphanumeric and dashes, 1-32 chars). Set apps[].id explicitly." exit 1 fi done < <(echo "$groups" | jq -r '.[].id') # SAFE: emit only id + package-count to logs. Never $groups in full. { echo "matrix={\"group\":$groups}" } >> "$GITHUB_OUTPUT" printf "::notice::APM matrix: %d credential group(s)\n" "$count" echo "$groups" | jq -r '.[] | " - " + .id + " (" + (.packages | length | tostring) + " package(s))"'
runs-on needs permissions strategy steps
ubuntu-slim
activation
apm-prep
fail-fast matrix
false
${{ fromJSON(needs.apm-prep.outputs.matrix) }}
name id if uses with
Mint installation token
token
${{ matrix.group.app-id != '' }}
app-id private-key owner repositories
${{ matrix.group.app-id }}
${{ matrix.group.private-key }}
${{ matrix.group.owner != '' && matrix.group.owner || github.repository_owner }}
${{ matrix.group.repositories }}
name id env run
Render package list
list
AW_PKG
${{ toJSON(matrix.group.packages) }}
DEPS=$(echo "$AW_PKG" | jq -r '.[] | "- " + .') { echo "deps<<APMDEPS" printf '%s\n' "$DEPS" echo "APMDEPS" } >> "$GITHUB_OUTPUT"
name id uses env with
Pack APM packages
pack
GITHUB_TOKEN
${{ steps.token.outputs.token || secrets.GH_AW_PLUGINS_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
dependencies isolated pack archive target working-directory
${{ steps.list.outputs.deps }}
true
true
true
all
/tmp/gh-aw/apm-workspace
name if uses with
Upload APM bundle artifact
success()
actions/upload-artifact@v7
name path retention-days
${{ needs.activation.outputs.artifact_prefix }}apm-${{ matrix.group.id }}
${{ steps.pack.outputs.bundle-path }}
1
steps
name uses with
Download APM bundle artifacts (all groups)
pattern path merge-multiple
${{ needs.activation.outputs.artifact_prefix }}apm-*
/tmp/gh-aw/apm-bundles
false
name env run
Normalise bundle layout (single-artifact flatten workaround)
EXPECTED_MATRIX ARTIFACT_PREFIX
${{ needs.apm-prep.outputs.matrix }}
${{ needs.activation.outputs.artifact_prefix }}
set -euo pipefail # actions/download-artifact (>=v5) flattens contents directly into `path/` # whenever exactly one artifact matches the pattern, ignoring # `merge-multiple: false`. Re-shape into the per-group subdir layout so # downstream validation sees a stable structure regardless of matrix size. # Upstream reference: # https://github.com/actions/download-artifact/blob/v8.0.1/src/download-artifact.ts # (see the `isSingleArtifactDownload || mergeMultiple || artifacts.length === 1` # branch). Remove this step once download-artifact stops flattening or # exposes an opt-out. expected_count=$(echo "$EXPECTED_MATRIX" | jq '.group // [] | length') if [ "$expected_count" -eq 1 ]; then group_id=$(echo "$EXPECTED_MATRIX" | jq -r '.group[0].id') # Defence-in-depth: group_id is interpolated into a shell path. apm-prep # produces a sanitised id today, but enforce a strict allowlist here so # any future schema drift cannot smuggle traversal sequences. if ! printf '%s' "$group_id" | grep -Eq '^[A-Za-z0-9_-]+$'; then echo "::error::unsafe group_id '$group_id' (must match ^[A-Za-z0-9_-]+$)" exit 1 fi group_dir="/tmp/gh-aw/apm-bundles/${ARTIFACT_PREFIX}apm-${group_id}" if [ ! -d "$group_dir" ]; then mkdir -p "$group_dir" find /tmp/gh-aw/apm-bundles -mindepth 1 -maxdepth 1 ! -path "$group_dir" -exec mv {} "$group_dir/" \; fi fi
name env run
Validate downloaded bundles match matrix manifest
EXPECTED_MATRIX ARTIFACT_PREFIX
${{ needs.apm-prep.outputs.matrix }}
${{ needs.activation.outputs.artifact_prefix }}
set -euo pipefail expected=$(echo "$EXPECTED_MATRIX" | jq -r --arg prefix "$ARTIFACT_PREFIX" '.group | map($prefix + "apm-" + .id) | sort | .[]') actual=$(ls /tmp/gh-aw/apm-bundles | sort) missing=$(comm -23 <(echo "$expected") <(echo "$actual") || true) unexpected=$(comm -13 <(echo "$expected") <(echo "$actual") || true) if [ -n "$missing" ]; then echo "::error::missing APM bundles (group did not pack successfully): $missing" exit 1 fi if [ -n "$unexpected" ]; then echo "::error::unexpected artifact in apm bundle download (collision attack?): $unexpected" exit 1 fi
name id run
Build bundle list
bundles
set -euo pipefail mapfile -t list < <(find /tmp/gh-aw/apm-bundles -name '*.tar.gz' | sort) [ ${#list[@]} -gt 0 ] || { echo '::error::no apm bundles found'; exit 1; } printf '%s\n' "${list[@]}" > /tmp/gh-aw/apm-bundle-list.txt
name uses with
Restore APM packages (all bundles)
bundles-file
/tmp/gh-aw/apm-bundle-list.txt