Continuous Delivery #219
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Continuous Delivery | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| merge: | |
| type: boolean | |
| description: Merge staging into master first? | |
| required: false | |
| default: false | |
| ignore_metadata_diff: | |
| type: boolean | |
| description: Perform all jobs, regardless of whether there are actual changes? | |
| required: false | |
| default: false | |
| target_env: | |
| type: choice | |
| description: Target environment | |
| options: | |
| - staging | |
| - csvalpha | |
| - luxadmosam | |
| - euros | |
| - demo | |
| default: 'staging' | |
| concurrency: | |
| group: cd-${{ github.ref_name }} | |
| env: | |
| PROJECT_NAME: sofia | |
| jobs: | |
| branch_check: | |
| name: Branch Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Validate branch | |
| env: | |
| TARGET_ENV: ${{ github.event.inputs.target_env }} | |
| run: | | |
| if [ "$GITHUB_REF_NAME" != 'staging' ] && [ "$GITHUB_REF_NAME" != 'master' ]; then | |
| echo 'This workflow can only be run on branches staging and master.' | |
| exit 1 | |
| fi | |
| # Check branch restriction from deploy_targets.yml | |
| BRANCH_RESTRICTION=$(python3 << 'PYTHON_EOF' | |
| import yaml | |
| with open('config/deploy_targets.yml', 'r') as f: | |
| config = yaml.safe_load(f) | |
| target = config['targets']['${{ github.event.inputs.target_env }}'] | |
| restriction = target.get('branch_restriction') | |
| print(restriction if restriction else '') | |
| PYTHON_EOF | |
| ) | |
| if [ -n "$BRANCH_RESTRICTION" ] && [ "$GITHUB_REF_NAME" != "$BRANCH_RESTRICTION" ]; then | |
| echo "Target $TARGET_ENV can only be deployed from branch $BRANCH_RESTRICTION, but current branch is $GITHUB_REF_NAME." | |
| exit 1 | |
| fi | |
| metadata: | |
| name: Metadata | |
| runs-on: ubuntu-latest | |
| needs: branch_check | |
| outputs: | |
| has_diff: ${{ steps.get_metadata.outputs.has_diff }} | |
| stage: ${{ steps.get_metadata.outputs.stage }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Get metadata | |
| id: get_metadata | |
| env: | |
| INPUT_MERGE: ${{ inputs.merge }} | |
| TARGET_ENV: ${{ github.event.inputs.target_env }} | |
| run: | | |
| if [ "$GITHUB_REF_NAME" = 'master' ]; then | |
| if [ "$INPUT_MERGE" = 'true' ]; then | |
| git fetch origin staging | |
| if ! git diff origin/master origin/staging --exit-code; then | |
| echo 'has_diff=true' >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'has_diff=false' >> "$GITHUB_OUTPUT" | |
| fi | |
| fi | |
| fi | |
| # Get stage from deploy_targets.yml | |
| STAGE=$(python3 << 'PYTHON_EOF' | |
| import yaml | |
| with open('config/deploy_targets.yml', 'r') as f: | |
| config = yaml.safe_load(f) | |
| target_env = '${{ github.event.inputs.target_env }}' | |
| current_branch = '${{ github.ref_name }}' | |
| # If on staging branch, always use staging stage unless targeting external party | |
| if current_branch == 'staging': | |
| print('staging') | |
| else: | |
| # On master: use the stage from the target environment | |
| print(config['targets'][target_env]['stage']) | |
| PYTHON_EOF | |
| ) | |
| echo "stage=$STAGE" >> "$GITHUB_OUTPUT" | |
| merge: | |
| name: Merge | |
| runs-on: ubuntu-latest | |
| needs: metadata | |
| if: inputs.merge | |
| outputs: | |
| sha: ${{ steps.get_sha.outputs.sha }} | |
| steps: | |
| - name: Validate inputs | |
| env: | |
| HAS_DIFF: ${{ fromJSON(needs.metadata.outputs.has_diff || false) }} | |
| run: | | |
| if [ "$GITHUB_REF_NAME" != 'master' ]; then | |
| echo 'Can only merge when the workflow target branch is master.' | |
| exit 1 | |
| fi | |
| if ! $HAS_DIFF; then | |
| echo 'There is no diff so a merge is not necessary, skipping next steps.' | |
| fi | |
| - name: Checkout code | |
| if: fromJSON(needs.metadata.outputs.has_diff) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Run merge | |
| if: fromJSON(needs.metadata.outputs.has_diff) || github.event.inputs.ignore_metadata_diff | |
| uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f # v1.4.0 | |
| with: | |
| type: now | |
| from_branch: staging | |
| target_branch: master | |
| github_token: ${{ github.token }} | |
| - name: Get merge commit SHA | |
| id: get_sha | |
| if: fromJSON(needs.metadata.outputs.has_diff) || github.event.inputs.ignore_metadata_diff | |
| run: | | |
| git fetch origin master | |
| echo 'sha='"$(git rev-parse origin/master)" >> "$GITHUB_OUTPUT" | |
| continuous_integration: | |
| name: Continuous Integration | |
| needs: [metadata, merge] | |
| if: fromJSON(needs.metadata.outputs.has_diff) || github.event.inputs.ignore_metadata_diff | |
| uses: ./.github/workflows/continuous-integration.yml | |
| with: | |
| sha: ${{ needs.merge.outputs.sha }} | |
| secrets: | |
| codecov_token: ${{ secrets.CODECOV_TOKEN }} | |
| rails_master_key: ${{ secrets.RAILS_MASTER_KEY }} | |
| publish_image: | |
| name: Publish Image | |
| needs: [metadata, merge] | |
| if: fromJSON(needs.metadata.outputs.has_diff) || github.event.inputs.ignore_metadata_diff | |
| uses: ./.github/workflows/publish-image.yml | |
| with: | |
| sha: ${{ needs.merge.outputs.sha }} | |
| secrets: | |
| sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| deploy: | |
| name: Deploy | |
| runs-on: ubuntu-latest | |
| needs: [metadata, merge, continuous_integration, publish_image] | |
| if: | | |
| (github.ref_name == 'staging' || github.ref_name == 'master') && ((github.ref_name == 'master' && | |
| inputs.merge && fromJSON(needs.metadata.outputs.has_diff) || github.event.inputs.ignore_metadata_diff && success()) || | |
| ((!inputs.merge|| !fromJSON(needs.metadata.outputs.has_diff)) || github.event.inputs.ignore_metadata_diff && !cancelled())) | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: ${{ needs.merge.outputs.sha }} | |
| - name: Get environment URL | |
| id: get_url | |
| env: | |
| TARGET_ENV: ${{ github.event.inputs.target_env }} | |
| run: | | |
| # Get URL from deploy_targets.yml based on current branch and target | |
| URL=$(python3 << 'PYTHON_EOF' | |
| import yaml | |
| with open('config/deploy_targets.yml', 'r') as f: | |
| config = yaml.safe_load(f) | |
| current_branch = '${{ github.ref_name }}' | |
| target_env = '${{ github.event.inputs.target_env }}' | |
| # If on staging, use stagingstreep URL; otherwise use the target's URL | |
| if current_branch == 'staging': | |
| print(config['targets']['staging']['url']) | |
| else: | |
| print(config['targets'][target_env]['url']) | |
| PYTHON_EOF | |
| ) | |
| echo "environment_url=$URL" >> "$GITHUB_OUTPUT" | |
| - name: Start deployment | |
| uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8 # v1.5.0 | |
| id: start_deployment | |
| with: | |
| step: start | |
| env: ${{ needs.metadata.outputs.stage }} | |
| - name: Deploy | |
| uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5 | |
| env: | |
| STAGE: ${{ needs.metadata.outputs.stage }} | |
| with: | |
| host: ssh.csvalpha.nl | |
| username: github-actions | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| envs: PROJECT_NAME,STAGE | |
| script: | | |
| cd /opt/docker/$PROJECT_NAME/$STAGE | |
| docker-compose pull | |
| docker-compose run --rm web rails db:migrate | |
| docker-compose up -d | |
| - name: Finalize Sentry release | |
| uses: getsentry/action-release@dab6548b3c03c4717878099e43782cf5be654289 # v3.5.0 | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ vars.SENTRY_ORG_NAME }} | |
| SENTRY_PROJECT: ${{ env.PROJECT_NAME }} | |
| with: | |
| environment: ${{ needs.metadata.outputs.stage }} | |
| release: ${{ needs.merge.outputs.sha }} | |
| set_commits: skip | |
| - name: Finish deployment | |
| uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8 # v1.5.0 | |
| if: steps.start_deployment.conclusion == 'success' && always() | |
| with: | |
| step: finish | |
| status: ${{ job.status }} | |
| deployment_id: ${{ steps.start_deployment.outputs.deployment_id }} | |
| env: ${{ needs.metadata.outputs.stage }} | |
| env_url: ${{ steps.get_url.outputs.environment_url }} | |
| update_check_run: | |
| name: Update Check Run | |
| runs-on: ubuntu-latest | |
| needs: [branch_check, metadata, merge, continuous_integration, publish_image, deploy] | |
| if: (github.ref_name == 'staging' || github.ref_name == 'master') && always() | |
| permissions: | |
| checks: write | |
| steps: | |
| - name: Get conclusion | |
| id: get_conclusion | |
| env: | |
| RESULTS: ${{ join(needs.*.result, ' ') }} | |
| run: | | |
| echo 'conclusion=success' >> "$GITHUB_OUTPUT" | |
| for RESULT in $RESULTS; do | |
| if [ "$RESULT" = 'cancelled' ] || [ "$RESULT" = 'failure' ]; then | |
| echo 'conclusion='"$RESULT" >> "$GITHUB_OUTPUT" | |
| break | |
| fi | |
| done | |
| - name: Update Continuous Delivery check run | |
| uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 # v2.0.0 | |
| with: | |
| sha: ${{ needs.merge.outputs.sha }} | |
| token: ${{ github.token }} | |
| name: Continuous Delivery | |
| conclusion: ${{ steps.get_conclusion.outputs.conclusion }} | |
| details_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |