mirror of
https://github.com/Sea-Haven-Industries/.github.git
synced 2026-09-30 22:13:12 +00:00
The self-CI gate ran `./actionlint -shellcheck=`, and the empty value silently disabled the shell-linting half of the check — so every `run:` body in the reusable workflows this repo publishes was unlinted, on the exact path that deploys to AWS. Measured against the pinned actionlint 1.7.12 and the shellcheck the ubuntu-latest runner ships (0.9.0-1), the real backlog was 5 findings, not the 4 the old comment claimed. Three were genuine and are fixed in the shell: - cd-cdk.yaml "Publish .NET project" (SC2046): the project path was interpolated inline and `$(dirname ...)` was unquoted, so a path containing whitespace split into several arguments. Now passed via env indirection and quoted, which also removes the last inline expression interpolation from that step. - cd-cdk.yaml / ci-python-sam.yaml "Install Python dependencies" (SC2044 x2): `for req in $(find ...)` word-split and globbed every path found. Replaced with a NUL-delimited `while read` loop. Two are deliberate and are suppressed per-line, with the reasoning in a comment directly above: - cd-sam.yaml `sam deploy ... $PARAMS` and cd-cdk.yaml `cdk deploy $STACKS` (SC2086 x2) rely on word-splitting so multiple parameter overrides / stack selectors reach the CLI as separate argv entries. Quoting them would collapse each into a single argument and break every parameterised or multi-stack deploy, so they keep the unquoted expansion and carry a scoped `# shellcheck disable=SC2086`. The gate now runs plain `./actionlint` (shellcheck defaults to the binary on PATH) and prints `shellcheck --version` first, so the check fails loudly if a future runner image drops it instead of quietly linting less.
160 lines
5.4 KiB
YAML
160 lines
5.4 KiB
YAML
name: CI — Python / SAM
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
python-version:
|
|
description: "Python version to use"
|
|
type: string
|
|
default: "3.12"
|
|
source-dirs:
|
|
description: "Space-separated directories for ruff (default: repo root)"
|
|
type: string
|
|
default: "."
|
|
run-tests:
|
|
description: "Run pytest"
|
|
type: boolean
|
|
default: false
|
|
run-sam-validate:
|
|
description: "Run sam validate --lint"
|
|
type: boolean
|
|
default: true
|
|
sam-template:
|
|
description: "Path to SAM template file"
|
|
type: string
|
|
default: "template.yaml"
|
|
run-cdk-synth:
|
|
description: "Run cdk synth (for Python CDK repos)"
|
|
type: boolean
|
|
default: false
|
|
cdk-dir:
|
|
description: "Directory containing cdk.json"
|
|
type: string
|
|
default: "cdk"
|
|
node-version:
|
|
description: "Node.js version for CDK CLI"
|
|
type: string
|
|
default: "24"
|
|
enable-qemu:
|
|
description: "Enable QEMU so cdk synth can bundle arm64 Lambda assets on x86 runners"
|
|
type: boolean
|
|
default: false
|
|
run-conventions-check:
|
|
description: "Run lightweight conventions audit"
|
|
type: boolean
|
|
default: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
ci:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
concurrency:
|
|
group: ci-python-sam-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-python@v7
|
|
with:
|
|
python-version: ${{ inputs.python-version }}
|
|
|
|
- name: Install ruff
|
|
run: pip install 'ruff==0.15.22'
|
|
|
|
- name: Ruff check
|
|
run: ruff check ${{ inputs.source-dirs }}
|
|
|
|
- name: Ruff format check
|
|
run: ruff format --check ${{ inputs.source-dirs }}
|
|
|
|
- name: Install Python dependencies
|
|
if: ${{ inputs.run-tests || inputs.run-cdk-synth }}
|
|
# NUL-delimited read loop rather than `for req in $(find ...)`: the
|
|
# command-substitution form word-splits and globs every path it finds.
|
|
shell: bash
|
|
run: |
|
|
if [ "${{ inputs.run-tests }}" = "true" ]; then
|
|
pip install pytest
|
|
fi
|
|
while IFS= read -r -d '' req; do
|
|
pip install -r "$req"
|
|
done < <(find . -name requirements.txt -not -path './.aws-sam/*' -print0)
|
|
|
|
- name: Run tests
|
|
if: ${{ inputs.run-tests }}
|
|
run: pytest
|
|
|
|
- name: Setup Node.js
|
|
if: ${{ inputs.run-cdk-synth }}
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: ${{ inputs.node-version }}
|
|
|
|
- name: Set up QEMU
|
|
if: ${{ inputs.run-cdk-synth && inputs.enable-qemu }}
|
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
|
|
|
|
- name: CDK synth
|
|
if: ${{ inputs.run-cdk-synth }}
|
|
working-directory: ${{ inputs.cdk-dir }}
|
|
run: npx -y cdk synth --quiet
|
|
|
|
- name: Conventions check
|
|
if: ${{ inputs.run-conventions-check }}
|
|
run: |
|
|
errors=0
|
|
warn() { echo "::warning::$1"; }
|
|
fail() { echo "::error::$1"; errors=$((errors + 1)); }
|
|
|
|
# README must exist
|
|
if [[ ! -f README.md ]]; then
|
|
fail "Missing README.md"
|
|
fi
|
|
|
|
# .gitignore must cover .env
|
|
if [[ -f .gitignore ]]; then
|
|
if ! grep -qE '^\.env$|^\.env\b' .gitignore; then
|
|
fail ".gitignore does not include .env"
|
|
fi
|
|
else
|
|
fail "Missing .gitignore"
|
|
fi
|
|
|
|
# SAM: check template for non-arm64 and missing log retention
|
|
TEMPLATE="${{ inputs.sam-template }}"
|
|
if [[ -f "$TEMPLATE" ]]; then
|
|
if grep -qi 'x86_64' "$TEMPLATE" 2>/dev/null; then
|
|
fail "SAM template: Lambda using x86_64 instead of arm64"
|
|
fi
|
|
if grep -qi 'AWS::Serverless::Function' "$TEMPLATE" 2>/dev/null; then
|
|
if ! grep -qi 'RetentionInDays\|AWS::Logs::LogGroup' "$TEMPLATE" 2>/dev/null; then
|
|
warn "SAM template: Lambda found but no explicit log retention"
|
|
fi
|
|
fi
|
|
# Flag HARDCODED secret values in the template. Secrets Manager
|
|
# references (e.g. SLACK_BOT_TOKEN_SECRET: my-app/slack-token) and
|
|
# intrinsic functions (!Ref/!Sub/{{resolve:...}}) are the correct
|
|
# pattern, so match on the value's shape — not the key name, which
|
|
# legitimately contains words like TOKEN/SECRET when pointing at a
|
|
# Secrets Manager id.
|
|
if grep -qiE '(xox[abprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|gh[posu]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9]{20,}|-----BEGIN[[:space:]][A-Z ]*PRIVATE KEY-----)' "$TEMPLATE" 2>/dev/null; then
|
|
fail "SAM template: hardcoded secret in template — use Secrets Manager"
|
|
fi
|
|
fi
|
|
|
|
if [[ $errors -gt 0 ]]; then
|
|
echo "Conventions check failed with $errors error(s)."
|
|
exit 1
|
|
fi
|
|
echo "Conventions check passed."
|
|
|
|
- name: Setup SAM CLI
|
|
if: ${{ inputs.run-sam-validate }}
|
|
uses: aws-actions/setup-sam@89ddb14d60e682855e3fea4be85b3c56485de310 # v3
|
|
|
|
- name: SAM validate
|
|
if: ${{ inputs.run-sam-validate }}
|
|
run: sam validate --lint --template ${{ inputs.sam-template }}
|