Skip to content
Merged
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
File renamed without changes.
2 changes: 1 addition & 1 deletion .releaserc.json → .github/.releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
{
"assets": [
"CHANGELOG.md",
"package.json"
".github/package.json"
],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
Expand Down
114 changes: 114 additions & 0 deletions .github/GITHUB_ACTIONS_CICD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# GitHub Actions CI/CD Guide

Reference for CI validation and deployment workflows in this repository.

## Workflows

- `ci.yml` — lint, tests, security/dependency checks, Terraform plan validation
- `staging.yml` — staging build/apply/deploy/smoke-test
- `release.yml` — semantic release and production deploy flow
- `_smoke-test.yml` — reusable post-deploy health check

## `ci.yml`

Triggers:

- `pull_request` to `main` and `develop`
- `push` to `main` and `develop`
- `workflow_dispatch`

Responsibilities:

- Conventional commit check (PR)
- Backend lint + unit/integration tests
- Frontend lint + type-check + build
- Trivy + GitGuardian scan
- Snyk dependency audit
- Terraform fmt/validate/plan (no apply)
- Final `quality-gate` status

## `staging.yml`

Triggers:

- successful `CI` workflow run on `develop`
- manual dispatch

Responsibilities:

- Build/push backend and frontend images to ECR
- Apply staging Terraform
- Force ECS rolling deploy for both services
- Run reusable smoke test against `vars.APP_URL`

Config model:

- Non-secret values loaded from `config/.env.staging`
- Secrets from GitHub Environment `staging`

## `release.yml`

Two flows:

1. Semantic release flow
- Trigger: successful `CI` workflow run on `main` (or manual dispatch)
- Runs semantic-release (creates version tag/release)
2. Production deploy flow
- Trigger: tag push `v*`
- Builds/pushes images to ECR
- Runs Terraform apply for production
- Forces ECS deploy
- Runs smoke test against `vars.APP_URL`

Config model:

- Uses GitHub Environment `production` secrets

## Required GitHub configuration

### Repository secrets

- `DATABASE_USER`
- `DATABASE_PASSWORD`
- `DATABASE_NAME`
- `DATABASE_PORT`
- `AWS_ROLE_TO_ASSUME`
- `GITGUARDIAN_API_KEY`
- `SNYK_TOKEN`

### Environment `staging` secrets

- `AWS_ROLE_TO_ASSUME`
- `TERRAFORM_STATE_BUCKET`
- `TERRAFORM_LOCK_TABLE` (compatibility input)
- `JWT_SECRET_KEY`

### Environment `staging` vars

- `APP_URL`

### Environment `production` secrets

- `AWS_ROLE_TO_ASSUME`
- `AWS_REGION`
- `TF_VERSION`
- `TERRAFORM_STATE_BUCKET`
- `TERRAFORM_LOCK_TABLE` (compatibility input)
- `JWT_SECRET_KEY`

### Environment `production` vars

- `APP_URL`

## Terraform backend lock note

Infrastructure init now uses `use_lockfile=true` for backend locking.

`TERRAFORM_LOCK_TABLE` remains exposed in current workflow inputs for backward compatibility.

## Related files

- `.github/workflows/ci.yml`
- `.github/workflows/staging.yml`
- `.github/workflows/release.yml`
- `.github/workflows/_smoke-test.yml`
11 changes: 11 additions & 0 deletions .github/actions/aws-auth/action.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
###############################################################################
# composite action: aws-auth
#
# Authenticates the GitHub Actions runner with AWS by assuming an IAM role
# via OIDC (no long-lived credentials stored in secrets).
#
# Callers: ci.yml (terraform-plan), staging.yml (terraform-staging,
# deploy-staging), release.yml (terraform-production,
# deploy-production)
# Inputs: role-arn, aws-region
###############################################################################
name: "AWS Authentication"
description: "Authenticate with AWS using OIDC"

Expand Down
57 changes: 47 additions & 10 deletions .github/actions/docker-build/action.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
###############################################################################
# composite action: docker-build
#
# Builds a Docker image, optionally pushes it to a registry, and runs a
# Trivy vulnerability scan on the resulting image.
#
# Callers: staging.yml (build job), release.yml (build-production job)
# Inputs: context, dockerfile, image-name, registry, tags, build-args,
# scan, scan-severity, scan-exit-code, cache-scope,
# registry-username, registry-password
###############################################################################
name: "Docker Build"
description: "Build and push Docker image to registry"

Expand All @@ -17,10 +28,12 @@ inputs:
default: "ghcr.io"
registry-username:
description: "Registry username"
required: true
required: false
default: ""
registry-password:
description: "Registry password/token"
required: true
required: false
default: ""
push:
description: "Push to registry after build"
required: false
Expand All @@ -29,6 +42,30 @@ inputs:
description: "Run Trivy vulnerability scan"
required: false
default: "true"
scan-severity:
description: "Trivy severities to include"
required: false
default: "CRITICAL,HIGH"
scan-exit-code:
description: "Trivy exit code on findings"
required: false
default: "0"
tags:
description: "docker/metadata-action tag rules"
required: false
default: ""
build-args:
description: "Additional Docker build args"
required: false
default: ""
cache-scope:
description: "GHA cache scope"
required: false
default: ""
platforms:
description: "Target platforms for image build"
required: false
default: "linux/amd64"
cache:
description: "Use GitHub Actions cache"
required: false
Expand Down Expand Up @@ -56,6 +93,7 @@ runs:
uses: docker/setup-buildx-action@v2

- name: Login to registry
if: inputs.registry-username != '' && inputs.registry-password != ''
uses: docker/login-action@v2
with:
registry: ${{ inputs.registry }}
Expand All @@ -78,27 +116,25 @@ runs:
uses: docker/metadata-action@v4
with:
images: ${{ inputs.registry }}/${{ inputs.image-name }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=semver,pattern={{version}}
type=raw,value=latest,enable={{is_default_branch}}
tags: ${{ inputs.tags != '' && inputs.tags || 'type=ref,event=branch\ntype=sha,prefix={{branch}}-\ntype=semver,pattern={{version}}\ntype=raw,value=latest,enable={{is_default_branch}}' }}

- name: Build and push Docker image
id: build
uses: docker/build-push-action@v4
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
platforms: ${{ inputs.platforms }}
push: ${{ inputs.push }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: ${{ inputs.cache == 'true' && 'type=gha' || '' }}
cache-to: ${{ inputs.cache == 'true' && format('type=gha,mode=max,key={0}', steps.cache-key.outputs.key) || '' }}
cache-from: ${{ inputs.cache == 'true' && format('type=gha,scope={0}', inputs.cache-scope != '' && inputs.cache-scope || inputs.image-name) || '' }}
cache-to: ${{ inputs.cache == 'true' && format('type=gha,mode=max,scope={0}', inputs.cache-scope != '' && inputs.cache-scope || inputs.image-name) || '' }}
build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }}
VERSION=${{ github.ref_name }}
${{ inputs.build-args }}

- name: Run Trivy vulnerability scan
id: trivy
Expand All @@ -108,7 +144,8 @@ runs:
image-ref: ${{ steps.meta.outputs.tags }}
format: "sarif"
output: "trivy-results.sarif"
severity: "CRITICAL,HIGH"
severity: ${{ inputs.scan-severity }}
exit-code: ${{ inputs.scan-exit-code }}
continue-on-error: true

- name: Upload Trivy results
Expand Down
11 changes: 10 additions & 1 deletion .github/actions/ecs/deploy/action.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
###############################################################################
# composite action: ecs/deploy
#
# Renders a new ECS task definition from a base JSON template with updated
# image URIs, registers the new task definition, and updates the ECS service
# to trigger a rolling deployment.
#
# Callers: release.yml (deploy-production job)
# Inputs: cluster, service, task-definition, container-name, image
###############################################################################
name: "ECS Deploy"
description: "Deploy updated task definition to ECS Fargate service"

Expand Down Expand Up @@ -76,7 +86,6 @@ runs:
service: ${{ inputs.service }}
cluster: ${{ inputs.cluster }}
wait-for-service-stability: ${{ inputs.wait-for-stability }}
wait-for-service-stability-timeout: ${{ inputs.timeout-seconds }}

- name: Verify deployment
shell: bash
Expand Down
27 changes: 23 additions & 4 deletions .github/actions/publish-test-results/action.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
name: "Publish Pytest Results"
description: "Upload pytest junit xml"
###############################################################################
# composite action: publish-test-results
#
# Uploads JUnit XML test result files as a workflow artifact so they are
# visible in the GitHub Actions UI and retained for post-run analysis.
#
# Callers: ci.yml (backend-ci job)
# Inputs: files (glob pattern), check_name (artifact display name)
###############################################################################
name: "Publish Test Results"
description: "Upload JUnit XML test results as a workflow artifact"

inputs:
files:
description: "Glob pattern(s) for JUnit XML files (newline-separated)"
required: true
check_name:
description: "Artifact name shown in the Actions UI"
required: false
default: "Test Results"

runs:
using: "composite"
steps:
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: pytest-results
path: backend/test-results.xml
name: ${{ inputs.check_name }}
path: ${{ inputs.files }}
if-no-files-found: warn
Loading