Deploy 2026.02.18 #132
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: CI | |
| on: | |
| pull_request: | |
| types: ["opened", "synchronize"] | |
| push: | |
| branches: ["prod", "develop"] | |
| env: | |
| COMPOSE_FILE_DEV: docker-compose.yaml | |
| COMPOSE_FILE_PROD: prod-docker-compose.yaml | |
| # See here for valid options: | |
| # https://github.com/docker/setup-docker-action?tab=readme-ov-file#inputs.version | |
| VERSION_DOCKER: latest | |
| # See here for valid options: | |
| # https://hub.docker.com/r/curlimages/curl/tags | |
| VERSION_CURL: 7.80.0 | |
| permissions: | |
| contents: read | |
| actions: read | |
| jobs: | |
| lint-actions: | |
| name: Lint:Actions | |
| uses: freedomofpress/actionslib/.github/workflows/lint-actions.yaml@main | |
| build-dev: | |
| name: Build:Dev | |
| uses: freedomofpress/actionslib/.github/workflows/oci-build.yaml@main | |
| with: | |
| context: '.' | |
| containerfile: devops/docker/DevDjangoDockerfile | |
| build-args: USERID=1001 | |
| requires-deep-history: true | |
| registry: localhost/securedroporg-django | |
| build-node: | |
| name: Build:Node | |
| uses: freedomofpress/actionslib/.github/workflows/oci-build.yaml@main | |
| with: | |
| context: '.' | |
| containerfile: devops/docker/NodeDockerfile | |
| build-args: USERID=1001 | |
| registry: localhost/securedroporg-node | |
| build-prod: | |
| name: Build:Prod | |
| uses: freedomofpress/actionslib/.github/workflows/oci-build.yaml@main | |
| with: | |
| context: '.' | |
| containerfile: devops/docker/ProdDjangoDockerfile | |
| build-args: USERID=12345 | |
| registry: localhost/securedroporg | |
| requires-deep-history: true | |
| test: | |
| name: Test:${{ matrix.target }} | |
| runs-on: ubuntu-latest | |
| needs: | |
| - build-dev | |
| - build-node | |
| strategy: | |
| matrix: | |
| target: | |
| - check-migrations | |
| - bandit | |
| - dev-tests | |
| - flake8 | |
| steps: | |
| - name: Install Docker | |
| uses: docker/setup-docker-action@v4 | |
| with: | |
| version: ${{ env.VERSION_DOCKER }} | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: true | |
| - name: Download dev image | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ${{ needs.build-dev.outputs.artifact-name }} | |
| path: ${{ runner.temp }}/images | |
| - name: Download node image | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ${{ needs.build-node.outputs.artifact-name }} | |
| path: ${{ runner.temp }}/images | |
| - name: Restore dev images | |
| env: | |
| LOAD_DEV_FILE: ${{ runner.temp }}/images/${{ needs.build-dev.outputs.artifact-image }} | |
| IMAGE_DEV_URL: ${{ needs.build-dev.outputs.image-url }} | |
| LOAD_NODE_FILE: ${{ runner.temp }}/images/${{ needs.build-node.outputs.artifact-image }} | |
| IMAGE_NODE_URL: ${{ needs.build-node.outputs.image-url }} | |
| run: | | |
| docker image load --input "$LOAD_DEV_FILE" | |
| docker image load --input "$LOAD_NODE_FILE" | |
| docker image tag "$IMAGE_DEV_URL" localhost/securedroporg-django | |
| docker image tag "$IMAGE_NODE_URL" localhost/securedroporg-node | |
| - name: Create env config | |
| run: make dev-init | |
| - name: Start | |
| run: | | |
| docker compose --file="$COMPOSE_FILE_DEV" up --wait --pull=missing --no-build | |
| # Check for failed services and error if any are found | |
| sleep 5 | |
| if [[ $(docker compose --file="$COMPOSE_FILE_DEV" ps --status='[restarting, dead, exited]' --format=json) ]]; then | |
| echo "ERROR: One or more services failed to start" | |
| docker compose ps | |
| exit 1 | |
| fi | |
| - name: Make target ${{ matrix.target }} | |
| run: make ${{ matrix.target }} | |
| - name: Export logs | |
| run: | | |
| mkdir --parents ${{ runner.temp }}/logs | |
| docker compose --file="$COMPOSE_FILE_DEV" logs django >${{ runner.temp }}/logs/django.log | |
| docker compose --file="$COMPOSE_FILE_DEV" logs node >${{ runner.temp }}/logs/node.log | |
| docker compose --file="$COMPOSE_FILE_DEV" logs postgresql >${{ runner.temp }}/logs/postgresql.log | |
| - name: Save logs | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-${{ matrix.target }}-${{ github.run_id }}-logs | |
| path: ${{ runner.temp }}/logs | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: 5 | |
| - name: Save HTML coverage | |
| uses: actions/upload-artifact@v4 | |
| if: ${{ matrix.target == 'dev-tests' }} | |
| with: | |
| name: test-${{ matrix.target }}-${{ github.run_id }}-htmlcov | |
| path: htmlcov | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: 5 | |
| test-prod: | |
| name: Test:Prod | |
| runs-on: ubuntu-latest | |
| needs: | |
| - build-prod | |
| steps: | |
| - name: Install Docker | |
| uses: docker/setup-docker-action@v4 | |
| with: | |
| version: ${{ env.VERSION_DOCKER }} | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: true | |
| - name: Download prod image | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ${{ needs.build-prod.outputs.artifact-name }} | |
| path: ${{ runner.temp }}/images | |
| - name: Restore prod image | |
| env: | |
| LOAD_FILE: ${{ runner.temp }}/images/${{ needs.build-prod.outputs.artifact-image }} | |
| IMAGE_URL: ${{ needs.build-prod.outputs.image-url }} | |
| run: | | |
| docker image load --input "$LOAD_FILE" | |
| docker image tag "$IMAGE_URL" quay.io/freedomofpress/securedroporg | |
| - name: Start | |
| run: | | |
| docker compose --file="$COMPOSE_FILE_PROD" up --wait --pull=missing --no-build | |
| # Check for failed services and error if any are found | |
| sleep 5 | |
| if [[ $(docker compose --file="$COMPOSE_FILE_PROD" ps --status='[restarting, dead, exited]' --format=json) ]]; then | |
| echo "ERROR: One or more services failed to start" | |
| docker compose ps | |
| exit 1 | |
| fi | |
| - name: Verify django | |
| run: | | |
| docker run --pull=missing --rm --network=securedroporg_app "docker.io/curlimages/curl:$VERSION_CURL" \ | |
| --ipv4 --retry 24 --retry-delay 5 --retry-all-errors http://django:8000/health/ok/ | |
| - name: Verify database | |
| run: | | |
| docker compose --file="$COMPOSE_FILE_PROD" exec django \ | |
| /bin/bash -c "./manage.py createdevdata" | |
| - name: Export logs | |
| run: | | |
| mkdir --parents ${{ runner.temp }}/logs | |
| docker compose --file="$COMPOSE_FILE_PROD" logs django >${{ runner.temp }}/logs/django.log | |
| docker compose --file="$COMPOSE_FILE_PROD" logs postgresql >${{ runner.temp }}/logs/postgresql.log | |
| - name: Save logs | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-prod-${{ github.run_id }}-logs | |
| path: ${{ runner.temp }}/logs | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: 5 | |
| cleanup: | |
| name: Cleanup | |
| runs-on: ubuntu-latest | |
| needs: | |
| - build-dev | |
| - build-node | |
| - build-prod | |
| - test | |
| - test-prod | |
| permissions: | |
| contents: read | |
| actions: write | |
| if: ${{ always() }} | |
| steps: | |
| - name: Delete dev image | |
| if: ${{ needs.build-dev.result == 'success' && always() }} | |
| uses: freedomofpress/actionslib/act/delete-artifact@main | |
| with: | |
| artifact: ${{ needs.build-dev.outputs.artifact-name }} | |
| error-if-absent: 'false' | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Delete node image | |
| if: ${{ needs.build-node.result == 'success' && always() }} | |
| uses: freedomofpress/actionslib/act/delete-artifact@main | |
| with: | |
| artifact: ${{ needs.build-node.outputs.artifact-name }} | |
| error-if-absent: 'false' | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Delete prod image | |
| if: ${{ needs.build-prod.result == 'success' && always() }} | |
| uses: freedomofpress/actionslib/act/delete-artifact@main | |
| with: | |
| artifact: ${{ needs.build-prod.outputs.artifact-name }} | |
| error-if-absent: 'false' | |
| github-token: ${{ secrets.GITHUB_TOKEN }} |