diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..f62d188 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,241 @@ +name: CI/CD Pipeline - Node.js REST API + +on: + push: + branches: + - main + - develop + paths: + - 'src/**' + - 'tests/**' + - 'Dockerfile' + - 'package*.json' + - '.github/workflows/cicd.yml' + pull_request: + branches: + - main + - develop + workflow_dispatch: + +env: + AWS_REGION: us-east-1 + ECR_REPOSITORY: node-rest-api + ECS_SERVICE: node-rest-api-service + ECS_CLUSTER: node-rest-api-cluster + ECS_TASK_DEFINITION: node-rest-api-task + +permissions: + contents: read + id-token: write + +jobs: + lint: + name: Lint Code + runs-on: ubuntu-22.04 + strategy: + matrix: + node-version: [18.x] + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Setup Node.js + uses: actions/setup-node@v4.0.2 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint --if-present + + - name: Check code formatting with Prettier + run: npm run format:check --if-present + + test: + name: Run Tests + runs-on: ubuntu-22.04 + strategy: + matrix: + node-version: [18.x] + services: + postgres: + image: postgres:15.3-alpine + env: + POSTGRES_DB: test_db + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Setup Node.js + uses: actions/setup-node@v4.0.2 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test --if-present + env: + DB_HOST: localhost + DB_PORT: 5432 + DB_NAME: test_db + DB_USER: testuser + DB_PASSWORD: testpass + NODE_ENV: test + + - name: Generate coverage report + run: npm run test:coverage --if-present + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3.1.4 + with: + files: ./coverage/coverage-final.json + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false + + build: + name: Build Application + runs-on: ubuntu-22.04 + needs: [lint, test] + strategy: + matrix: + node-version: [18.x] + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Setup Node.js + uses: actions/setup-node@v4.0.2 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build --if-present + + - name: Upload build artifacts + uses: actions/upload-artifact@v3.1.3 + with: + name: build-artifacts-${{ github.sha }} + path: dist/ + retention-days: 5 + + docker-build-push: + name: Build & Push Docker Image + runs-on: ubuntu-22.04 + needs: [lint, test, build] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + outputs: + image: ${{ steps.image.outputs.image }} + image-tag: ${{ steps.image.outputs.image-tag }} + permissions: + contents: read + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Configure AWS credentials via OIDC + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ env.AWS_REGION }} + role-duration-seconds: 3600 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2.0.1 + + - name: Build Docker image + id: docker-build + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ env.ECR_REPOSITORY }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker build \ + -t $REGISTRY/$REPOSITORY:$IMAGE_TAG \ + -t $REGISTRY/$REPOSITORY:latest \ + --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ + --build-arg VCS_REF=${{ github.sha }} \ + --build-arg VERSION=${{ github.sha }} \ + . + echo "image=$REGISTRY/$REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Scan Docker image with Trivy + uses: aquasecurity/trivy-action@0.20.0 + with: + image-ref: ${{ steps.docker-build.outputs.image }} + format: sarif + output: trivy-results.sarif + severity: HIGH,CRITICAL + + - name: Upload Trivy results to GitHub Security + uses: github/codeql-action/upload-sarif@v2.22.5 + with: + sarif_file: trivy-results.sarif + + - name: Push Docker image to ECR + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ env.ECR_REPOSITORY }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG + docker push $REGISTRY/$REPOSITORY:latest + echo "image=$REGISTRY/$REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Output image URI + id: image + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ env.ECR_REPOSITORY }} + IMAGE_TAG: ${{ github.sha }} + run: | + echo "image=$REGISTRY/$REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + + deploy-staging: + name: Deploy to Staging + runs-on: ubuntu-22.04 + needs: docker-build-push + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + environment: + name: staging + url: https://staging-api.example.com + permissions: + contents: read + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Configure AWS credentials via OIDC + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ env.AWS_REGION }} + role-duration-seconds: 3600 + + - name: Download task definition + run: | + aws ecs describe-task-definition \ + --task-definition ${{ env.ECS_TASK_DEFINITION }}:1 \ No newline at end of file