Skip to content
Open
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
184 changes: 184 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: Node.js API CI/CD Pipeline

on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop

env:
AWS_REGION: us-east-1
ECR_REPOSITORY: node-api
ECS_SERVICE: node-api-service
ECS_CLUSTER: node-api-cluster
ECS_TASK_DEFINITION: node-api-task

jobs:
lint-test:
name: Lint and Test
runs-on: ubuntu-22.04
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@60edb5dd545a775178fac7f3a11ecda6e5a4f69f # v4.0.2
with:
node-version: ${{ matrix.node-version }}
cache: npm

- name: Install dependencies
run: npm ci

- name: Run linter
run: npm run lint --if-present

- name: Run tests
run: npm test --if-present

- name: Generate coverage report
run: npm run coverage --if-present
continue-on-error: true

- name: Upload coverage to Codecov
uses: codecov/codecov-action@125fc84a3e5e4183e5e9c34b23b9ef1d16cbf484 # v3.1.6
if: always()
with:
files: ./coverage/coverage-final.json
fail_ci_if_error: false

security-scan:
name: Security Scan
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@6e7b7d1fd3e4692731fcf9cdff0f8244cfad8103 # v0.24.0
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-results.sarif

- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@cf7e9f23492505046de19cd2d8569798fc6f4eac # v3.25.4
with:
sarif_file: trivy-results.sarif

build-image:
name: Build and Push Docker Image
runs-on: ubuntu-22.04
needs: [lint-test, security-scan]
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
permissions:
id-token: write
contents: read
outputs:
image-uri: ${{ steps.image.outputs.image-uri }}
steps:
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6

- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e93febb1 # v4.0.2
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION }}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@062b18b96a7aff0 # v2.0.1

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16eca30d26365 # v3.0.0

- name: Build and push Docker image
id: docker-build
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
push: true
tags: |
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }}

- name: Output image URI
id: image
run: |
echo "image-uri=${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}" >> $GITHUB_OUTPUT

terraform-plan:
name: Terraform Plan
runs-on: ubuntu-22.04
needs: build-image
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6

- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e93febb1 # v4.0.2
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION }}

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_version: 1.7.0

- name: Terraform Format Check
run: terraform fmt -check -recursive ./terraform

- name: Terraform Init
run: cd terraform && terraform init -backend-config="bucket=${{ secrets.TERRAFORM_STATE_BUCKET }}" -backend-config="key=prod/terraform.tfstate" -backend-config="region=${{ env.AWS_REGION }}"

- name: Terraform Validate
run: cd terraform && terraform validate

- name: Terraform Plan
id: plan
run: |
cd terraform && terraform plan \
-var="image_uri=${{ needs.build-image.outputs.image-uri }}" \
-var="environment=staging" \
-out=tfplan
continue-on-error: true

- name: Comment Terraform Plan on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('terraform/tfplan', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`\n${plan}\n\`\`\``
});

deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-22.04
needs: [build-image, terraform-plan]
if: github.event_name == '