mirror of
https://github.com/Sea-Haven-Industries/.github.git
synced 2026-10-02 12:03:16 +00:00
Add QEMU, Node 24, conventions check, pre-flight, and health checks to workflows (#29)
* Add QEMU support to CI CDK workflow for cross-platform Docker builds Mirrors the enable-qemu input from cd-cdk.yaml. Required when CDK stacks use PythonFunction or other Docker-bundled constructs targeting arm64 Lambda on x86 CI runners. * Increase CI timeout for QEMU CDK builds * Bump default Node.js version to 24 across all reusable workflows npm 11 (Node 24) generates lockfileVersion 3 which breaks npm ci on Node 22's npm 10 for repos with aws-cdk-lib bundled deps. * Add lightweight conventions check to CI workflows Validates README exists, .env in .gitignore, arm64 architecture, and log retention in synthesized templates. Runs by default, opt-out via run-conventions-check: false. * Add pre-flight stack status checks to CD workflows Blocks deploy if the CloudFormation stack is in ROLLBACK_COMPLETE, FAILED, or IN_PROGRESS state. Prevents wasted deploy attempts on stacks that need manual intervention. * Add post-deploy health checks to CD workflows Verifies stack status after deploy, prints outputs, and runs project-specific scripts/health-check.sh if present. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
parent
7e03d635fb
commit
937e4d8daa
4 changed files with 208 additions and 4 deletions
57
.github/workflows/cd-cdk.yaml
vendored
57
.github/workflows/cd-cdk.yaml
vendored
|
|
@ -6,7 +6,7 @@ on:
|
||||||
node-version:
|
node-version:
|
||||||
description: "Node.js version to use"
|
description: "Node.js version to use"
|
||||||
type: string
|
type: string
|
||||||
default: "22"
|
default: "24"
|
||||||
python-version:
|
python-version:
|
||||||
description: "Python version for Python CDK repos (leave empty for TypeScript CDK)"
|
description: "Python version for Python CDK repos (leave empty for TypeScript CDK)"
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -23,6 +23,10 @@ on:
|
||||||
description: "Enable QEMU for cross-platform Docker builds (arm64 on x86 runners)"
|
description: "Enable QEMU for cross-platform Docker builds (arm64 on x86 runners)"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
stack-name:
|
||||||
|
description: "CloudFormation stack name (for pre-flight checks)"
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
secrets:
|
secrets:
|
||||||
deploy-role-arn:
|
deploy-role-arn:
|
||||||
description: "OIDC deploy role ARN"
|
description: "OIDC deploy role ARN"
|
||||||
|
|
@ -67,6 +71,57 @@ jobs:
|
||||||
role-to-assume: ${{ secrets.deploy-role-arn }}
|
role-to-assume: ${{ secrets.deploy-role-arn }}
|
||||||
aws-region: ${{ inputs.region }}
|
aws-region: ${{ inputs.region }}
|
||||||
|
|
||||||
|
- name: Pre-flight checks
|
||||||
|
if: ${{ inputs.stack-name != '' }}
|
||||||
|
run: |
|
||||||
|
echo "Pre-flight: checking stack ${{ inputs.stack-name }}..."
|
||||||
|
STATUS=$(aws cloudformation describe-stacks \
|
||||||
|
--stack-name "${{ inputs.stack-name }}" \
|
||||||
|
--query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NOT_FOUND")
|
||||||
|
case "$STATUS" in
|
||||||
|
*ROLLBACK_COMPLETE|*FAILED)
|
||||||
|
echo "::error::Stack ${{ inputs.stack-name }} is in $STATUS — manual intervention required."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*IN_PROGRESS)
|
||||||
|
echo "::error::Stack ${{ inputs.stack-name }} has an operation in progress ($STATUS) — wait for it to complete."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
NOT_FOUND)
|
||||||
|
echo "Pre-flight: stack not found — will be created on first deploy."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Pre-flight: stack status is $STATUS — OK to deploy."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
- name: CDK deploy
|
- name: CDK deploy
|
||||||
working-directory: ${{ inputs.cdk-dir }}
|
working-directory: ${{ inputs.cdk-dir }}
|
||||||
run: npx -y cdk deploy --all --require-approval never
|
run: npx -y cdk deploy --all --require-approval never
|
||||||
|
|
||||||
|
- name: Post-deploy health check
|
||||||
|
if: ${{ inputs.stack-name != '' }}
|
||||||
|
run: |
|
||||||
|
echo "Health check: verifying stack ${{ inputs.stack-name }}..."
|
||||||
|
|
||||||
|
STATUS=$(aws cloudformation describe-stacks \
|
||||||
|
--stack-name "${{ inputs.stack-name }}" \
|
||||||
|
--query 'Stacks[0].StackStatus' --output text)
|
||||||
|
if [[ "$STATUS" != *"COMPLETE" ]] || [[ "$STATUS" == *"ROLLBACK"* ]]; then
|
||||||
|
echo "::error::Stack ${{ inputs.stack-name }} ended in $STATUS after deploy."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Stack status: $STATUS"
|
||||||
|
|
||||||
|
echo "Stack outputs:"
|
||||||
|
aws cloudformation describe-stacks \
|
||||||
|
--stack-name "${{ inputs.stack-name }}" \
|
||||||
|
--query 'Stacks[0].Outputs[*].[OutputKey,OutputValue]' --output table
|
||||||
|
|
||||||
|
# Run project-specific health check if it exists
|
||||||
|
if [[ -f scripts/health-check.sh ]]; then
|
||||||
|
echo "Running project health check..."
|
||||||
|
bash scripts/health-check.sh "${{ inputs.stack-name }}" "${{ inputs.region }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Health check passed."
|
||||||
|
|
|
||||||
49
.github/workflows/cd-sam.yaml
vendored
49
.github/workflows/cd-sam.yaml
vendored
|
|
@ -53,6 +53,29 @@ jobs:
|
||||||
role-to-assume: ${{ secrets.deploy-role-arn }}
|
role-to-assume: ${{ secrets.deploy-role-arn }}
|
||||||
aws-region: ${{ inputs.region }}
|
aws-region: ${{ inputs.region }}
|
||||||
|
|
||||||
|
- name: Pre-flight checks
|
||||||
|
run: |
|
||||||
|
echo "Pre-flight: checking stack ${{ inputs.stack-name }}..."
|
||||||
|
STATUS=$(aws cloudformation describe-stacks \
|
||||||
|
--stack-name "${{ inputs.stack-name }}" \
|
||||||
|
--query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NOT_FOUND")
|
||||||
|
case "$STATUS" in
|
||||||
|
*ROLLBACK_COMPLETE|*FAILED)
|
||||||
|
echo "::error::Stack ${{ inputs.stack-name }} is in $STATUS — manual intervention required."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*IN_PROGRESS)
|
||||||
|
echo "::error::Stack ${{ inputs.stack-name }} has an operation in progress ($STATUS) — wait for it to complete."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
NOT_FOUND)
|
||||||
|
echo "Pre-flight: stack not found — will be created on first deploy."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Pre-flight: stack status is $STATUS — OK to deploy."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
- name: SAM build
|
- name: SAM build
|
||||||
run: sam build --template ${{ inputs.sam-template }}
|
run: sam build --template ${{ inputs.sam-template }}
|
||||||
|
|
||||||
|
|
@ -71,3 +94,29 @@ jobs:
|
||||||
--no-fail-on-empty-changeset \
|
--no-fail-on-empty-changeset \
|
||||||
--role-arn ${{ inputs.cfn-role-arn }} \
|
--role-arn ${{ inputs.cfn-role-arn }} \
|
||||||
$PARAMS
|
$PARAMS
|
||||||
|
|
||||||
|
- name: Post-deploy health check
|
||||||
|
run: |
|
||||||
|
echo "Health check: verifying stack ${{ inputs.stack-name }}..."
|
||||||
|
|
||||||
|
STATUS=$(aws cloudformation describe-stacks \
|
||||||
|
--stack-name "${{ inputs.stack-name }}" \
|
||||||
|
--query 'Stacks[0].StackStatus' --output text)
|
||||||
|
if [[ "$STATUS" != *"COMPLETE" ]] || [[ "$STATUS" == *"ROLLBACK"* ]]; then
|
||||||
|
echo "::error::Stack ${{ inputs.stack-name }} ended in $STATUS after deploy."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Stack status: $STATUS"
|
||||||
|
|
||||||
|
echo "Stack outputs:"
|
||||||
|
aws cloudformation describe-stacks \
|
||||||
|
--stack-name "${{ inputs.stack-name }}" \
|
||||||
|
--query 'Stacks[0].Outputs[*].[OutputKey,OutputValue]' --output table
|
||||||
|
|
||||||
|
# Run project-specific health check if it exists
|
||||||
|
if [[ -f scripts/health-check.sh ]]; then
|
||||||
|
echo "Running project health check..."
|
||||||
|
bash scripts/health-check.sh "${{ inputs.stack-name }}" "${{ inputs.region }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Health check passed."
|
||||||
|
|
|
||||||
52
.github/workflows/ci-python-sam.yaml
vendored
52
.github/workflows/ci-python-sam.yaml
vendored
|
|
@ -34,7 +34,11 @@ on:
|
||||||
node-version:
|
node-version:
|
||||||
description: "Node.js version for CDK CLI"
|
description: "Node.js version for CDK CLI"
|
||||||
type: string
|
type: string
|
||||||
default: "22"
|
default: "24"
|
||||||
|
run-conventions-check:
|
||||||
|
description: "Run lightweight conventions audit"
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
|
|
@ -81,6 +85,52 @@ jobs:
|
||||||
working-directory: ${{ inputs.cdk-dir }}
|
working-directory: ${{ inputs.cdk-dir }}
|
||||||
run: npx -y cdk synth --quiet
|
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
|
||||||
|
# Check for secrets in environment variables
|
||||||
|
if grep -qiE '(API_KEY|SECRET|TOKEN|PASSWORD|WEBHOOK)' "$TEMPLATE" 2>/dev/null; then
|
||||||
|
if grep -A5 'Environment:' "$TEMPLATE" | grep -qiE '(API_KEY|SECRET|TOKEN|PASSWORD|WEBHOOK)' 2>/dev/null; then
|
||||||
|
fail "SAM template: possible secret in Lambda environment variables — use Secrets Manager"
|
||||||
|
fi
|
||||||
|
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
|
- name: Setup SAM CLI
|
||||||
if: ${{ inputs.run-sam-validate }}
|
if: ${{ inputs.run-sam-validate }}
|
||||||
uses: aws-actions/setup-sam@v2
|
uses: aws-actions/setup-sam@v2
|
||||||
|
|
|
||||||
54
.github/workflows/ci-typescript-cdk.yaml
vendored
54
.github/workflows/ci-typescript-cdk.yaml
vendored
|
|
@ -6,7 +6,7 @@ on:
|
||||||
node-version:
|
node-version:
|
||||||
description: "Node.js version to use"
|
description: "Node.js version to use"
|
||||||
type: string
|
type: string
|
||||||
default: "22"
|
default: "24"
|
||||||
run-typecheck:
|
run-typecheck:
|
||||||
description: "Run tsc --noEmit"
|
description: "Run tsc --noEmit"
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
@ -27,6 +27,10 @@ on:
|
||||||
description: "Enable QEMU for cross-platform Docker builds (arm64 on x86 runners)"
|
description: "Enable QEMU for cross-platform Docker builds (arm64 on x86 runners)"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
run-conventions-check:
|
||||||
|
description: "Run lightweight conventions audit"
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
run-sam-validate:
|
run-sam-validate:
|
||||||
description: "Run sam validate --lint (for Node.js SAM repos)"
|
description: "Run sam validate --lint (for Node.js SAM repos)"
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
@ -39,7 +43,7 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|
@ -70,6 +74,52 @@ jobs:
|
||||||
if: ${{ inputs.run-cdk-synth }}
|
if: ${{ inputs.run-cdk-synth }}
|
||||||
run: npx cdk synth --quiet
|
run: npx 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
|
||||||
|
|
||||||
|
# CDK: check synth output for non-arm64 Lambdas and missing log retention
|
||||||
|
if [[ -d cdk.out ]]; then
|
||||||
|
for tmpl in cdk.out/*.template.json; do
|
||||||
|
[[ -f "$tmpl" ]] || continue
|
||||||
|
|
||||||
|
# Check for x86_64 Lambdas
|
||||||
|
if grep -q '"Architectures".*x86_64' "$tmpl" 2>/dev/null; then
|
||||||
|
fail "$(basename "$tmpl"): Lambda using x86_64 instead of arm64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Lambdas exist but no log retention set
|
||||||
|
if grep -q '"AWS::Lambda::Function"' "$tmpl" 2>/dev/null; then
|
||||||
|
if ! grep -q '"AWS::Logs::LogGroup"' "$tmpl" 2>/dev/null; then
|
||||||
|
warn "$(basename "$tmpl"): Lambda found but no explicit LogGroup with retention"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
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
|
- name: Setup SAM CLI
|
||||||
if: ${{ inputs.run-sam-validate }}
|
if: ${{ inputs.run-sam-validate }}
|
||||||
uses: aws-actions/setup-sam@v2
|
uses: aws-actions/setup-sam@v2
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue