diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..ff0efee --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,214 @@ +name: CI/CD Pipeline - Node.js API to AWS ECS + +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 + +permissions: + contents: read + id-token: write + +jobs: + lint-and-test: + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Setup Node.js + uses: actions/setup-node@v4.0.2 + with: + node-version: '18.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint + + - name: Run tests + run: npm run test + + - name: Generate coverage report + run: npm run test:coverage + continue-on-error: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3.1.5 + with: + files: ./coverage/coverage-final.json + flags: unittests + fail_ci_if_error: false + + build-and-push: + needs: lint-and-test + runs-on: ubuntu-22.04 + if: github.event_name == 'push' + outputs: + image-uri: ${{ steps.image.outputs.image-uri }} + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@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@v2.0.1 + + - name: Build Docker image + id: docker-build + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker build -t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker tag $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $REGISTRY/$ECR_REPOSITORY:latest + + - name: Push image to Amazon ECR + id: image + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker push $REGISTRY/$ECR_REPOSITORY:latest + echo "image-uri=$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Create image definitions JSON + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + printf '[{"name":"%s","imageUri":"%s"}]' $ECS_TASK_DEFINITION $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG > image-definitions.json + + - name: Upload image definitions + uses: actions/upload-artifact@v4.3.1 + with: + name: image-definitions + path: image-definitions.json + retention-days: 1 + + deploy-staging: + needs: build-and-push + runs-on: ubuntu-22.04 + if: github.ref == 'refs/heads/develop' && github.event_name == 'push' + environment: + name: staging + url: https://staging-api.example.com + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Download image definitions + uses: actions/download-artifact@v4.1.0 + with: + name: image-definitions + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ env.AWS_REGION }} + + - name: Get current task definition + run: | + aws ecs describe-task-definition --task-definition $ECS_TASK_DEFINITION --query taskDefinition > task-definition.json + + - name: Update ECS task definition + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1.2.0 + with: + task-definition: task-definition.json + container-name: ${{ env.ECS_TASK_DEFINITION }} + image: ${{ needs.build-and-push.outputs.image-uri }} + + - name: Register new task definition + uses: aws-actions/amazon-ecs-register-task-definition@v1.3.0 + with: + task-definition: ${{ steps.task-def.outputs.task-definition }} + + - name: Deploy to ECS (Staging) + uses: aws-actions/amazon-ecs-deploy-task-definition@v1.4.11 + with: + service: ${{ env.ECS_SERVICE }}-staging + cluster: ${{ env.ECS_CLUSTER }}-staging + task-definition: ${{ steps.task-def.outputs.task-definition }} + wait-for-service-stability: true + + deploy-production: + needs: build-and-push + runs-on: ubuntu-22.04 + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + environment: + name: production + url: https://api.example.com + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Download image definitions + uses: actions/download-artifact@v4.1.0 + with: + name: image-definitions + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: ${{ env.AWS_REGION }} + + - name: Get current task definition + run: | + aws ecs describe-task-definition --task-definition $ECS_TASK_DEFINITION --query taskDefinition > task-definition.json + + - name: Update ECS task definition + id: task-def + uses: aws-actions/amazon-ecs-render-task-definition@v1.2.0 + with: + task-definition: task-definition.json + container-name: ${{ env.ECS_TASK_DEFINITION }} + image: ${{ needs.build-and-push.outputs.image-uri }} + + - name: Register new task definition + uses: aws-actions/amazon-ecs-register-task-definition@v1.3.0 + with: + task-definition: ${{ steps.task-def.outputs.task-definition }} + + - name: Deploy to ECS (Production) + uses: aws-actions/amazon-ecs-deploy-task-definition@v1.4.11 + with: + service: ${{ env.ECS_SERVICE }} + cluster: ${{ env.ECS_CLUSTER }} + task-definition: ${{ steps.task-def.outputs.task-definition }} + wait-for-service-stability: true + + terraform-plan: + runs-on: ubuntu-22.04 + if: github.event_name == 'pull_request' + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE \ No newline at end of file