diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcc1384..301a695 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,6 @@ name: CI/CD Pipeline on: pull_request: branches: [staging, main] - push: - branches: [staging, main] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -131,7 +129,6 @@ jobs: name: Build & Push Docker Image to GHCR runs-on: ubuntu-latest needs: [build-and-test, codeql-analysis, security-scan-fs] - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') outputs: image_ref: ${{ steps.image_ref.outputs.image_ref }} @@ -196,8 +193,7 @@ jobs: name: Trivy Image Vulnerability Scan runs-on: ubuntu-latest needs: docker-build - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') - + steps: - name: Login to GitHub Container Registry uses: docker/login-action@v3.7.0 @@ -262,9 +258,8 @@ jobs: zap-scan: name: OWASP ZAP Baseline Scan runs-on: ubuntu-latest - needs: [docker-build, security-scan-image] - if: github.event_name == 'pull_request' && github.base_ref == 'main' - + needs: [docker-build] + steps: - name: Checkout code uses: actions/checkout@v6.0.2 diff --git a/.github/workflows/deploy-to-production.yml b/.github/workflows/deploy-to-production.yml new file mode 100644 index 0000000..da11f6e --- /dev/null +++ b/.github/workflows/deploy-to-production.yml @@ -0,0 +1,78 @@ +name: Deploy to Heroku (production) + +on: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + packages: read # To pull from GHCR + contents: write # To create GitHub releases + +jobs: + + deploy-production: + name: Deploy to Production + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6.0.2 + + - name: Install Heroku CLI + run: curl https://cli-assets.heroku.com/install.sh | sh + + - name: Login to Heroku Container Registry + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku container:login + + - name: Ensure container stack (production) + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku stack:set container --app ${{ vars.HEROKU_PROD_APP }} + + - name: Prepare lowercase image name + id: lowercase + run: | + repo_owner="${{ github.repository_owner }}" + repo_name="${{ github.event.repository.name }}" + echo "repo_owner=$(echo "$repo_owner" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + echo "repo_name=$(echo "$repo_name" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + + - name: Pull tested image from GHCR & deploy to Heroku Production + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + IMAGE_NAME: ghcr.io/${{ steps.lowercase.outputs.repo_owner }}/${{ steps.lowercase.outputs.repo_name }} + run: | + SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) + echo "Deploying tested image: ${IMAGE_NAME}:sha-${SHORT_SHA} → ${{ vars.HEROKU_PROD_APP }}" + + # Login to GHCR + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + docker pull ${IMAGE_NAME}:sha-${SHORT_SHA} + + echo "Pushing to Heroku..." + docker tag ${IMAGE_NAME}:sha-${SHORT_SHA} registry.heroku.com/${{ vars.HEROKU_PROD_APP }}/web:latest + + heroku container:push web --app ${{ vars.HEROKU_PROD_APP }} + + echo "Releasing..." + heroku container:release web --app ${{ vars.HEROKU_PROD_APP }} + + echo "Health check..." + sleep 12 + curl --fail "${{ vars.HEALTH_ENDPOINT }}" || echo "⚠️ Health check failed – check Heroku logs" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2.5.0 + with: + tag_name: v${{ github.run_number }} + name: Release v${{ github.run_number }} + draft: false + prerelease: false + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/deploy-to-staging.yml b/.github/workflows/deploy-to-staging.yml new file mode 100644 index 0000000..8489924 --- /dev/null +++ b/.github/workflows/deploy-to-staging.yml @@ -0,0 +1,69 @@ +name: Deploy to Heroku (staging) + +on: + push: + branches: [staging] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + packages: read # To pull from GHCR + contents: write # To create GitHub releases + +jobs: + deploy-staging: + name: Deploy to Staging + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6.0.2 + + - name: Install Heroku CLI + run: curl https://cli-assets.heroku.com/install.sh | sh + + - name: Login to Heroku Container Registry + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku container:login + + - name: Ensure container stack (staging) + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku stack:set container --app ${{ vars.HEROKU_STAGING_APP }} + + - name: Prepare lowercase image name + id: lowercase + run: | + repo_owner="${{ github.repository_owner }}" + repo_name="${{ github.event.repository.name }}" + echo "repo_owner=$(echo "$repo_owner" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + echo "repo_name=$(echo "$repo_name" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + + - name: Pull tested image from GHCR & deploy to Heroku Staging + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + IMAGE_NAME: ghcr.io/${{ steps.lowercase.outputs.repo_owner }}/${{ steps.lowercase.outputs.repo_name }} + run: | + SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) + echo "Deploying tested image: ${IMAGE_NAME}:sha-${SHORT_SHA} → ${{ vars.HEROKU_STAGING_APP }}" + + # Login to GHCR + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + docker pull ${IMAGE_NAME}:sha-${SHORT_SHA} + + # Tag the image for Heroku + docker tag ${IMAGE_NAME}:sha-${SHORT_SHA} registry.heroku.com/${{ vars.HEROKU_STAGING_APP }}/web:latest + + echo "Pushing to Heroku..." + heroku container:push web --app ${{ vars.HEROKU_STAGING_APP }} + + echo "Releasing..." + heroku container:release web --app ${{ vars.HEROKU_STAGING_APP }} + + echo "Health check..." + sleep 12 + curl --fail "${{ vars.STAGING_HEALTH_ENDPOINT }}" || echo "⚠️ Health check failed – check Heroku logs" \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 260eb0e..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,145 +0,0 @@ -name: Deploy to Heroku - -on: - workflow_run: - workflows: ["CI/CD Pipeline"] - types: - - completed - branches: [staging, main] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - packages: read # To pull from GHCR - contents: write # To create GitHub releases - -jobs: - deploy-staging: - name: Deploy to Staging - runs-on: ubuntu-latest - if: > - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'push' && - github.event.workflow_run.actor.login != 'dependabot[bot]' && - github.event.workflow_run.head_branch == 'staging' - - steps: - - name: Checkout code - uses: actions/checkout@v6.0.2 - - - name: Install Heroku CLI - run: curl https://cli-assets.heroku.com/install.sh | sh - - - name: Login to Heroku Container Registry - env: - HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} - run: heroku container:login - - - name: Ensure container stack (staging) - env: - HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} - run: heroku stack:set container --app ${{ vars.HEROKU_STAGING_APP }} - - - name: Prepare lowercase image name - id: lowercase - run: | - repo_owner="${{ github.repository_owner }}" - repo_name="${{ github.event.repository.name }}" - echo "repo_owner=$(echo "$repo_owner" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - echo "repo_name=$(echo "$repo_name" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - - - name: Pull tested image from GHCR & deploy to Heroku Staging - env: - HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} - IMAGE_NAME: ghcr.io/${{ steps.lowercase.outputs.repo_owner }}/${{ steps.lowercase.outputs.repo_name }} - run: | - SHORT_SHA=$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7) - echo "Deploying tested image: ${IMAGE_NAME}:sha-${SHORT_SHA} → ${{ vars.HEROKU_STAGING_APP }}" - - # Login to GHCR - echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - docker pull ${IMAGE_NAME}:sha-${SHORT_SHA} - - # Tag the image for Heroku - docker tag ${IMAGE_NAME}:sha-${SHORT_SHA} registry.heroku.com/${{ vars.HEROKU_STAGING_APP }}/web:latest - - echo "Pushing to Heroku..." - heroku container:push web --app ${{ vars.HEROKU_STAGING_APP }} - - echo "Releasing..." - heroku container:release web --app ${{ vars.HEROKU_STAGING_APP }} - - echo "Health check..." - sleep 12 - curl --fail "${{ vars.STAGING_HEALTH_ENDPOINT }}" || echo "⚠️ Health check failed – check Heroku logs" - - deploy-production: - name: Deploy to Production - runs-on: ubuntu-latest - if: > - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'push' && - github.event.workflow_run.actor.login != 'dependabot[bot]' && - github.event.workflow_run.head_branch == 'main' - - steps: - - name: Checkout code - uses: actions/checkout@v6.0.2 - - - name: Install Heroku CLI - run: curl https://cli-assets.heroku.com/install.sh | sh - - - name: Login to Heroku Container Registry - env: - HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} - run: heroku container:login - - - name: Ensure container stack (production) - env: - HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} - run: heroku stack:set container --app ${{ vars.HEROKU_PROD_APP }} - - - name: Prepare lowercase image name - id: lowercase - run: | - repo_owner="${{ github.repository_owner }}" - repo_name="${{ github.event.repository.name }}" - echo "repo_owner=$(echo "$repo_owner" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - echo "repo_name=$(echo "$repo_name" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - - - name: Pull tested image from GHCR & deploy to Heroku Production - env: - HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} - IMAGE_NAME: ghcr.io/${{ steps.lowercase.outputs.repo_owner }}/${{ steps.lowercase.outputs.repo_name }} - run: | - SHORT_SHA=$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7) - echo "Deploying tested image: ${IMAGE_NAME}:sha-${SHORT_SHA} → ${{ vars.HEROKU_PROD_APP }}" - - # Login to GHCR - echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - docker pull ${IMAGE_NAME}:sha-${SHORT_SHA} - - echo "Pushing to Heroku..." - docker tag ${IMAGE_NAME}:sha-${SHORT_SHA} registry.heroku.com/${{ vars.HEROKU_PROD_APP }}/web:latest - - heroku container:push web --app ${{ vars.HEROKU_PROD_APP }} - - echo "Releasing..." - heroku container:release web --app ${{ vars.HEROKU_PROD_APP }} - - echo "Health check..." - sleep 12 - curl --fail "${{ vars.HEALTH_ENDPOINT }}" || echo "⚠️ Health check failed – check Heroku logs" - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2.5.0 - with: - tag_name: v${{ github.event.workflow_run.run_number }} - name: Release v${{ github.event.workflow_run.run_number }} - draft: false - prerelease: false - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file