mirror of
https://github.com/lukaszraczylo/shared-actions.git
synced 2026-10-12 04:54:48 +00:00
feat(preflight): fail early when a secret or repository access is missing
A release run used to fail at the GoReleaser step when a secret was empty, after the tests and the version job had finished. A new preflight action checks the required names, the .Env references in the GoReleaser config and push access to optional repositories. It lists every problem in one run. The release and release-cgo workflows run it in a first job, so a missing secret fails in seconds. The release-cgo workflow requires GORELEASER_PRO. The release workflow requires the secrets that the GoReleaser config references. Set the preflight input to false to skip the check. Set preflight-repos to check access to a Homebrew tap or helm-charts repository.
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
name: "Preflight"
|
||||||
|
description: "Fail early when a required secret, env var or repository access is missing"
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
require:
|
||||||
|
description: "Comma-separated names that must be set and non-empty in the step env"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
goreleaser-scan:
|
||||||
|
description: "Also require every .Env.NAME referenced by the GoReleaser config"
|
||||||
|
required: false
|
||||||
|
default: "true"
|
||||||
|
repo-access:
|
||||||
|
description: "Comma-separated owner/repo list the token must have push access to"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
repo-token-env:
|
||||||
|
description: "Name of the env var that holds the token for repo-access"
|
||||||
|
required: false
|
||||||
|
default: "HOMEBREW_TAP_TOKEN"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Check prerequisites
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
PF_REQUIRE: ${{ inputs.require }}
|
||||||
|
PF_SCAN: ${{ inputs.goreleaser-scan }}
|
||||||
|
PF_REPOS: ${{ inputs.repo-access }}
|
||||||
|
PF_TOKEN_ENV: ${{ inputs.repo-token-env }}
|
||||||
|
run: |
|
||||||
|
missing=()
|
||||||
|
add_missing() { # name reason
|
||||||
|
case " ${missing[*]-} " in *" $1 "*) return ;; esac
|
||||||
|
missing+=("$1")
|
||||||
|
echo "::error title=Missing $1::$2"
|
||||||
|
}
|
||||||
|
is_set() { [ -n "${!1:-}" ]; }
|
||||||
|
|
||||||
|
IFS=',' read -r -a required <<< "$PF_REQUIRE"
|
||||||
|
for name in "${required[@]-}"; do
|
||||||
|
name="${name// /}"
|
||||||
|
[ -z "$name" ] && continue
|
||||||
|
is_set "$name" || add_missing "$name" "Required by this workflow but empty. Set the repository or organisation secret $name and make sure the caller passes 'secrets: inherit'."
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$PF_SCAN" = "true" ]; then
|
||||||
|
for cfg in .goreleaser.yaml .goreleaser.yml goreleaser.yaml goreleaser.yml; do
|
||||||
|
[ -f "$cfg" ] || continue
|
||||||
|
echo "Scanning $cfg for .Env references"
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
name="${ref#.Env.}"
|
||||||
|
is_set "$name" || add_missing "$name" "$cfg references .Env.$name but it is not available to the GoReleaser step. GoReleaser would fail at the end of the run."
|
||||||
|
done < <(grep -vE '^[[:space:]]*#' "$cfg" | grep -oE '\.Env\.[A-Za-z_][A-Za-z0-9_]*' | sort -u)
|
||||||
|
break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$PF_REPOS" ]; then
|
||||||
|
token="${!PF_TOKEN_ENV:-}"
|
||||||
|
if [ -z "$token" ]; then
|
||||||
|
add_missing "$PF_TOKEN_ENV" "repo-access needs this token but it is empty."
|
||||||
|
else
|
||||||
|
IFS=',' read -r -a repos <<< "$PF_REPOS"
|
||||||
|
for repo in "${repos[@]}"; do
|
||||||
|
repo="${repo// /}"
|
||||||
|
[ -z "$repo" ] && continue
|
||||||
|
body="$(mktemp)"
|
||||||
|
code="$(curl -sS -o "$body" -w '%{http_code}' \
|
||||||
|
-H "Authorization: Bearer $token" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"${GITHUB_API_URL:-https://api.github.com}/repos/$repo")" || code="000"
|
||||||
|
if [ "$code" != "200" ]; then
|
||||||
|
add_missing "access:$repo" "$PF_TOKEN_ENV cannot read $repo (HTTP $code). Check the token scope, expiry and repository access."
|
||||||
|
elif ! grep -qE '"push": *true' "$body"; then
|
||||||
|
add_missing "access:$repo" "$PF_TOKEN_ENV can read $repo but has no push access."
|
||||||
|
fi
|
||||||
|
rm -f "$body"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#missing[@]}" -gt 0 ]; then
|
||||||
|
echo "Preflight failed: ${missing[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Preflight passed"
|
||||||
@@ -65,6 +65,16 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
|
preflight:
|
||||||
|
description: "Fail early when a required secret, env var or GoReleaser .Env reference is missing"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
preflight-repos:
|
||||||
|
description: "Comma-separated owner/repo list the token must have push access to (e.g. a Homebrew tap or helm-charts repo)"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
outputs:
|
outputs:
|
||||||
version:
|
version:
|
||||||
description: "The calculated version (without v prefix)"
|
description: "The calculated version (without v prefix)"
|
||||||
@@ -78,8 +88,31 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
preflight:
|
||||||
|
name: Preflight
|
||||||
|
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
if: inputs.preflight
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
lfs: false
|
||||||
|
|
||||||
|
- name: Check secrets and access
|
||||||
|
if: inputs.preflight
|
||||||
|
uses: lukaszraczylo/shared-actions/.github/actions/preflight@main
|
||||||
|
with:
|
||||||
|
require: GITHUB_TOKEN,GORELEASER_KEY
|
||||||
|
repo-access: ${{ inputs.preflight-repos }}
|
||||||
|
repo-token-env: GITHUB_TOKEN
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_KEY: ${{ secrets.GORELEASER_PRO }}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
|
needs: preflight
|
||||||
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
@@ -105,6 +138,7 @@ jobs:
|
|||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
name: Build Frontend
|
name: Build Frontend
|
||||||
|
needs: preflight
|
||||||
if: inputs.node-enabled && inputs.node-build-script != ''
|
if: inputs.node-enabled && inputs.node-build-script != ''
|
||||||
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
|
preflight:
|
||||||
|
description: "Fail early when a required secret, env var or GoReleaser .Env reference is missing"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
preflight-repos:
|
||||||
|
description: "Comma-separated owner/repo list the token must have push access to (e.g. a Homebrew tap or helm-charts repo)"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
outputs:
|
outputs:
|
||||||
version:
|
version:
|
||||||
description: "The calculated version (without v prefix)"
|
description: "The calculated version (without v prefix)"
|
||||||
@@ -51,8 +61,30 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
preflight:
|
||||||
|
name: Preflight
|
||||||
|
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
if: inputs.preflight
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
lfs: false
|
||||||
|
|
||||||
|
- name: Check secrets and access
|
||||||
|
if: inputs.preflight
|
||||||
|
uses: lukaszraczylo/shared-actions/.github/actions/preflight@main
|
||||||
|
with:
|
||||||
|
require: GITHUB_TOKEN
|
||||||
|
repo-access: ${{ inputs.preflight-repos }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
|
needs: preflight
|
||||||
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest' || toJSON(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -154,6 +154,26 @@ Run GoReleaser with mode support.
|
|||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `actions/preflight`
|
||||||
|
|
||||||
|
Fail in seconds when a prerequisite is missing, instead of at the end of a long run. Reads names from the step `env`, so map secrets there.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: lukaszraczylo/shared-actions/.github/actions/preflight@main
|
||||||
|
with:
|
||||||
|
require: GITHUB_TOKEN,NPM_TOKEN # must be non-empty
|
||||||
|
repo-access: lukaszraczylo/helm-charts # token needs push access
|
||||||
|
repo-token-env: HOMEBREW_TAP_TOKEN
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
It also scans `.goreleaser.y*ml` and requires every `{{ .Env.NAME }}` (comment lines ignored) to be set. All problems are listed in one run.
|
||||||
|
|
||||||
|
`go-release.yaml` and `go-release-cgo.yaml` run it in a first `preflight` job before tests. Set `preflight: false` to skip it, and `preflight-repos` to check access to a tap or helm-charts repo. An empty secret usually means the caller omitted `secrets: inherit`.
|
||||||
|
|
||||||
### `actions/rolling-release`
|
### `actions/rolling-release`
|
||||||
|
|
||||||
Create/update a rolling release tag.
|
Create/update a rolling release tag.
|
||||||
|
|||||||
Reference in New Issue
Block a user