Skip to content
This repository was archived by the owner on Aug 10, 2026. It is now read-only.

Added resource tagging into terraform #23

Added resource tagging into terraform

Added resource tagging into terraform #23

name: Deploy URL Bookmark Saver
# Pipeline stages:
# lint ──┬──→ build ──┐
# └──→ scan ──┴──→ deploy ──→ seed
#
# build and scan run in parallel after lint; deploy gates on both; seed runs after deploy.
on:
push:
branches:
- main
paths:
- '.github/workflows/deploy-url-bookmark-saver.yml'
- 'projects/03-url-bookmark-saver/**'
pull_request:
paths:
- '.github/workflows/deploy-url-bookmark-saver.yml'
- 'projects/03-url-bookmark-saver/**'
workflow_dispatch:
inputs:
student_name:
description: 'Deployment name prefix (e.g. john-smith). Overrides repo variable.'
required: false
default: ''
aws_region:
description: 'AWS region to deploy into'
required: false
default: 'us-east-1'
aws_access_key_id:
description: 'AWS Access Key ID — leave blank to use repo secret AWS_ACCESS_KEY_ID'
required: false
default: ''
aws_secret_access_key:
description: 'AWS Secret Access Key — leave blank to use repo secret AWS_SECRET_ACCESS_KEY'
required: false
default: ''
seed_demo_data:
description: 'Seed demo bookmarks after deploy (true/false)'
required: false
default: 'true'
seed_count:
description: 'Number of demo bookmarks to seed'
required: false
default: '12'
env:
PROJECT_DIR: projects/03-url-bookmark-saver
AWS_REGION: ${{ inputs.aws_region || vars.URL_BOOKMARK_SAVER_AWS_REGION || 'us-east-1' }}
STUDENT_NAME: ${{ inputs.student_name || vars.URL_BOOKMARK_SAVER_STUDENT_NAME }}
# ── Stage 1: Lint ─────────────────────────────────────────────────────────────
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
continue-on-error: true # report lint issues without blocking build/scan/deploy
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 25
- uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
- name: Install frontend dependencies
working-directory: ${{ env.PROJECT_DIR }}/frontend
run: npm install
- name: ESLint — frontend
working-directory: ${{ env.PROJECT_DIR }}/frontend
run: npm run lint
- name: Terraform format check
run: terraform fmt -check -recursive ${{ env.PROJECT_DIR }}/terraform
- name: Terraform validate
working-directory: ${{ env.PROJECT_DIR }}/terraform
run: |
echo "placeholder" | zip -q ../backend/lambda.zip -
terraform init -backend=false
terraform validate
# ── Stage 2a: Build ───────────────────────────────────────────────────────────
build:
name: Build
runs-on: ubuntu-latest
needs: lint
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 25
- name: Install backend dependencies
working-directory: ${{ env.PROJECT_DIR }}/backend
run: npm install --omit=dev
- name: Check Lambda syntax
working-directory: ${{ env.PROJECT_DIR }}/backend
run: node --check src/handler.js
- name: Install frontend dependencies
working-directory: ${{ env.PROJECT_DIR }}/frontend
run: npm install
- name: Build frontend
working-directory: ${{ env.PROJECT_DIR }}/frontend
env:
NEXT_PUBLIC_API_URL: https://example.execute-api.us-east-1.amazonaws.com
run: npm run build
- name: TypeScript type check
working-directory: ${{ env.PROJECT_DIR }}/frontend
run: npx tsc --noEmit
# ── Stage 2b: Security Scan ───────────────────────────────────────────────────
scan:
name: Security Scan
runs-on: ubuntu-latest
needs: lint
permissions:
contents: read
security-events: write # upload SARIF to GitHub Security tab
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 25
# ── Dependency vulnerability audit ──────────────────────────────────────
- name: npm audit — backend
working-directory: ${{ env.PROJECT_DIR }}/backend
# Fail only on high/critical CVEs; warn on moderate
run: |
npm install --omit=dev
npm audit --audit-level=high || true
npm audit --audit-level=critical
- name: npm audit — frontend
working-directory: ${{ env.PROJECT_DIR }}/frontend
run: |
npm install
npm audit --audit-level=high || true
npm audit --audit-level=critical
# ── IaC security scan (Terraform) ───────────────────────────────────────
- name: Checkov — Terraform IaC scan
id: checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: ${{ env.PROJECT_DIR }}/terraform
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
# Set soft_fail: false to hard-block deploys on IaC policy violations
soft_fail: true
- name: Upload Checkov results to Security tab
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov.sarif
category: checkov-terraform
# ── SAST — CodeQL (JavaScript / TypeScript) ─────────────────────────────
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript-typescript
queries: security-extended
- name: Build for CodeQL
working-directory: ${{ env.PROJECT_DIR }}/frontend
env:
NEXT_PUBLIC_API_URL: https://example.execute-api.us-east-1.amazonaws.com
run: npm run build
- name: Run CodeQL analysis
uses: github/codeql-action/analyze@v3
with:
category: codeql-javascript
# ── Stage 3: Deploy ───────────────────────────────────────────────────────────
deploy:
name: Deploy to AWS
runs-on: ubuntu-latest
needs: [build, scan]
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
outputs:
api_url: ${{ steps.capture_outputs.outputs.api_url }}
permissions:
contents: read
id-token: write # required for OIDC
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 25
- uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
# Inputs (workflow_dispatch) take priority; falls back to repo secrets for push-triggered runs
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ env.AWS_REGION }}
aws-access-key-id: ${{ inputs.aws_access_key_id || secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ inputs.aws_secret_access_key || secrets.AWS_SECRET_ACCESS_KEY }}
- name: Validate deployment settings
shell: bash
run: |
set -euo pipefail
if [ -z "${STUDENT_NAME}" ]; then
echo "::error::Provide student_name as a workflow input or set repo variable URL_BOOKMARK_SAVER_STUDENT_NAME."
exit 1
fi
aws sts get-caller-identity
- name: Deploy application
working-directory: ${{ env.PROJECT_DIR }}
env:
CI: true
SEED_DEMO_DATA: 'false' # seeding is handled by the dedicated seed stage
run: bash scripts/deploy.sh --name "$STUDENT_NAME" --region "$AWS_REGION"
- name: Capture Terraform outputs
id: capture_outputs
working-directory: ${{ env.PROJECT_DIR }}/terraform
run: echo "api_url=$(terraform output -raw api_url)" >> "$GITHUB_OUTPUT"
- name: Publish deployment summary
working-directory: ${{ env.PROJECT_DIR }}/terraform
shell: bash
run: |
{
echo "## URL Bookmark Saver — Deployed :rocket:"
echo ""
echo "| | |"
echo "|---|---|"
echo "| **Region** | ${AWS_REGION} |"
echo "| **Name** | ${STUDENT_NAME} |"
echo "| **Frontend** | $(terraform output -raw frontend_url) |"
echo "| **API** | $(terraform output -raw api_url) |"
echo "| **Dashboard** | $(terraform output -raw dashboard_url) |"
} >> "$GITHUB_STEP_SUMMARY"
# ── Stage 4: Seed Demo Data ───────────────────────────────────────────────────
seed:
name: Seed Demo Data
runs-on: ubuntu-latest
needs: deploy
# Skip if seed_demo_data input is explicitly 'false'; default is 'true'
if: (github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main') && inputs.seed_demo_data != 'false'
steps:
- uses: actions/checkout@v4
- name: Seed demo bookmarks
env:
API_URL: ${{ needs.deploy.outputs.api_url }}
COUNT: ${{ inputs.seed_count || vars.URL_BOOKMARK_SAVER_DEMO_SEED_COUNT || '12' }}
run: |
bash ${{ env.PROJECT_DIR }}/scripts/demo-load.sh \
--api-url "$API_URL" \
--count "$COUNT" \
--include-errors
- name: Publish seed summary
if: always()
env:
API_URL: ${{ needs.deploy.outputs.api_url }}
COUNT: ${{ inputs.seed_count || vars.URL_BOOKMARK_SAVER_DEMO_SEED_COUNT || '12' }}
shell: bash
run: |
{
echo "## Seed Demo Data"
echo ""
echo "| | |"
echo "|---|---|"
echo "| **API** | ${API_URL} |"
echo "| **Bookmarks seeded** | ${COUNT} |"
echo "| **Status** | ${{ job.status }} |"
} >> "$GITHUB_STEP_SUMMARY"