Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
name: CodeQL

# The `selector` job decides whether a pull request runs the analysis, and
# `summary` reports the result (ADR 0003).

on:
push:
branches:
- main
# The trigger paths are in the `selector` job.
pull_request:
branches:
- main
Expand All @@ -16,8 +20,83 @@ permissions:
actions: read

jobs:
# The selector job: it decides which jobs a pull request runs. It runs on
# every event and is never skipped, because the gated jobs need it. When it
# cannot decide it fails, and that fails the summary job: a selector that
# answered nothing would skip every gated job, and the summary job counts a
# skipped job as passed (ADR 0003).
#
# On a GitHub-hosted runner because the gated jobs wait for it. No checkout:
# on a pull request, dorny/paths-filter lists the changed files through the
# GitHub API.
selector:
name: Selector
runs-on: ubuntu-latest
timeout-minutes: 10
# Not the workflow's `security-events: write`: this job writes nothing.
permissions:
contents: read
# Each output is named after the filter that decides it.
# `scripts/__tests__/workflow-selectors.test.mjs` follows a job's `if:` to
# its filter by that name.
outputs:
javascript: ${{ steps.decide.outputs.javascript }}
steps:
# Only on a pull request, which has a base to compare with. Other events
# select every job in the step below.
- id: filter
if: github.event_name == 'pull_request'
uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3
with:
filters: |
# Every file type the CodeQL JavaScript extractor reads, from
# `FileType` in its FileExtractor.java: HTML and templates,
# JavaScript, JSON, TypeScript, and YAML (which includes `.lock`).
javascript:
- "**/*.{htm,html,xhtm,xhtml,vue,hbs,ejs,njk,erb,jsp,dot}"
- "**/*.{js,jsx,mjs,cjs,es6,es,xsjs,xsjslib}"
- "**/*.{ts,tsx,mts,cts}"
- "**/*.json"
- "**/*.{lock,raml,yaml,yml}"

# Explicit default, not `|| 'true'`, which trips GitHub's inconsistent
# treatment of the string 'false'. On a pull request, an answer other than
# `true` or `false` means the filter did not decide, so the step fails
# rather than write it.
#
# The event and the answers arrive through `env:`, not through a `${{ }}`
# in the script. That lets `scripts/__tests__/workflow-selectors.test.mjs`
# run this script and read back what it wrote.
- id: decide
env:
EVENT_NAME: ${{ github.event_name }}
JAVASCRIPT: ${{ steps.filter.outputs.javascript }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" != "pull_request" ]; then
JAVASCRIPT=true
fi
answers=(
"javascript=$JAVASCRIPT"
)
for answer in "${answers[@]}"; do
case "${answer#*=}" in
true|false) echo "$answer" >> "$GITHUB_OUTPUT" ;;
*)
echo "::error::The path filter answered '${answer#*=}' for ${answer%%=*}. The selector cannot decide which jobs to run."
exit 1
;;
esac
done

analyze:
name: Analyze
needs: [selector]
# Written as an exclusion, not a list of events: every event other
# than a pull request runs this job.
if: >-
github.event_name != 'pull_request'
|| needs.selector.outputs.javascript == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404

strategy:
Expand All @@ -39,3 +118,34 @@ jobs:
uses: github/codeql-action/init@v4.38.0
with:
category: "/language:${{ matrix.language }}"

# The summary job: the one job of this workflow that branch rules require.
# It has the same name on every event. It passes when every needed job
# passed or was skipped. Skipped counts as passed because the selector skips
# a job whose trigger paths a change does not touch. A failed job still
# fails this one, because it is in `needs` and reports its failure.
summary:
name: CodeQL summary
needs: [selector, analyze]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 10
# Not the workflow's `security-events: write`: this job writes nothing.
permissions:
contents: read
steps:
- name: Check that every needed job passed or was skipped
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
set -euo pipefail
echo "Needed results: $RESULTS"
for result in $RESULTS; do
case "$result" in
success|skipped) ;;
*)
echo "::error::A needed job reported '$result'."
exit 1
;;
esac
done
149 changes: 122 additions & 27 deletions .github/workflows/lint-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,15 @@ name: Lint release tooling
# where an EQL-local copy of this file linted them and ran on nothing — along
# with its two unique checks, the `prepare-bindings-assets` shellcheck and that
# wrapper's bash unit test, neither of which ran anywhere here until now.
#
# The `selector` job decides whether a pull request runs the lint, and
# `summary` reports the result (ADR 0003).

on:
pull_request:
# Exactly what the job reads: the workflows actionlint is pointed at, the
# config it resolves the Blacksmith label from, and the shell scripts the
# later steps check and run. The filter also named scripts/release-gate.mjs,
# scripts/ffi-release-matrix.mjs, scripts/lint-no-workflow-caching.mjs and
# package.json — none of which this job looks at, since it deliberately does
# not run `test:scripts` (see the job comment). Editing one booted a runner
# and downloaded a Go binary to lint four unchanged files, which is how a
# job trains reviewers to ignore it. `tests.yml`'s `lint` job runs
# `test:scripts` unfiltered on every PR, so those four are already covered.
#
# `lint-release-scope.test.mjs` asserts the `.github/workflows/` half of
# this list and the actionlint argument list below stay the same set.
paths:
- .github/workflows/release.yml
- .github/workflows/_build-ffi-artifacts.yml
- .github/workflows/ffi-preflight.yml
- .github/workflows/_build-auth-artifacts.yml
- .github/workflows/auth-preflight.yml
- .github/workflows/_build-eql-sql.yml
- .github/workflows/_build-eql-docs.yml
- .github/workflows/release-plz.yml
- .github/workflows/release-postgres-eql-image.yml
- .github/workflows/lint-release.yml
- .github/actionlint.yaml
- packages/eql/tasks/release/*.sh
- scripts/check-c-library.sh
# No path filter: a workflow that a path filter skips leaves its required
# checks pending, and GitHub's documentation on required status checks says
# that blocks a merge. The trigger paths are in the `selector` job instead.
pull_request: {}
workflow_dispatch: {}

permissions:
Expand All @@ -58,6 +38,87 @@ concurrency:
cancel-in-progress: true

jobs:
# The selector job: it decides which jobs a pull request runs. It runs on
# every event and is never skipped, because the gated jobs need it. When it
# cannot decide it fails, and that fails the summary job: a selector that
# answered nothing would skip every gated job, and the summary job counts a
# skipped job as passed (ADR 0003).
#
# On a GitHub-hosted runner because the gated jobs wait for it. No checkout:
# on a pull request, dorny/paths-filter lists the changed files through the
# GitHub API.
selector:
name: Selector
runs-on: ubuntu-latest
timeout-minutes: 10
# Each output is named after the filter that decides it.
# `scripts/__tests__/workflow-selectors.test.mjs` follows a job's `if:` to
# its filter by that name.
outputs:
release_lint: ${{ steps.decide.outputs.release_lint }}
steps:
# Only on a pull request, which has a base to compare with. Other events
# select every job in the step below.
- id: filter
if: github.event_name == 'pull_request'
uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3
with:
filters: |
# Exactly what the job reads: the workflows actionlint is pointed at,
# the config it resolves the Blacksmith label from, and the shell
# scripts the later steps check and run. It does not run
# `test:scripts` (see the job comment), so scripts/release-gate.mjs and
# the other release scripts are not here: `tests.yml` runs
# `test:scripts` for them.
#
# `lint-release-scope.test.mjs` asserts the `.github/workflows/` half
# of this list and the actionlint argument list below stay the same
# set.
release_lint:
- ".github/workflows/release.yml"
- ".github/workflows/_build-ffi-artifacts.yml"
- ".github/workflows/ffi-preflight.yml"
- ".github/workflows/_build-auth-artifacts.yml"
- ".github/workflows/auth-preflight.yml"
- ".github/workflows/_build-eql-sql.yml"
- ".github/workflows/_build-eql-docs.yml"
- ".github/workflows/release-plz.yml"
- ".github/workflows/release-postgres-eql-image.yml"
- ".github/workflows/lint-release.yml"
- ".github/actionlint.yaml"
- "packages/eql/tasks/release/*.sh"
- "scripts/check-c-library.sh"

# Explicit default, not `|| 'true'`, which trips GitHub's inconsistent
# treatment of the string 'false'. On a pull request, an answer other than
# `true` or `false` means the filter did not decide, so the step fails
# rather than write it.
#
# The event and the answers arrive through `env:`, not through a `${{ }}`
# in the script. That lets `scripts/__tests__/workflow-selectors.test.mjs`
# run this script and read back what it wrote.
- id: decide
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_LINT: ${{ steps.filter.outputs.release_lint }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" != "pull_request" ]; then
RELEASE_LINT=true
fi
answers=(
"release_lint=$RELEASE_LINT"
)
for answer in "${answers[@]}"; do
case "${answer#*=}" in
true|false) echo "$answer" >> "$GITHUB_OUTPUT" ;;
*)
echo "::error::The path filter answered '${answer#*=}' for ${answer%%=*}. The selector cannot decide which jobs to run."
exit 1
;;
esac
done

# No pnpm, no Node, no install: actionlint is a downloaded Go binary and
# shellcheck ships in the runner image, so this job is a checkout and a few
# commands.
Expand All @@ -70,6 +131,12 @@ jobs:
# this workflow uniquely has is actionlint, and the two shell checks below.
lint:
name: actionlint (release workflows)
needs: [selector]
# Written as an exclusion, not a list of events: every event other
# than a pull request runs this job.
if: >-
github.event_name != 'pull_request'
|| needs.selector.outputs.release_lint == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand Down Expand Up @@ -120,3 +187,31 @@ jobs:
# `test:scripts`.
- name: shellcheck (C library check)
run: shellcheck scripts/check-c-library.sh

# The summary job: the one job of this workflow that branch rules require.
# It has the same name on every event. It passes when every needed job
# passed or was skipped. Skipped counts as passed because the selector skips
# a job whose trigger paths a change does not touch. A failed job still
# fails this one, because it is in `needs` and reports its failure.
summary:
name: Lint release tooling summary
needs: [selector, lint]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check that every needed job passed or was skipped
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
set -euo pipefail
echo "Needed results: $RESULTS"
for result in $RESULTS; do
case "$result" in
success|skipped) ;;
*)
echo "::error::A needed job reported '$result'."
exit 1
;;
esac
done
Loading
Loading