diff --git a/.github/workflows/build_api/action.yml b/.github/workflows/build_api/action.yml index dacb001..621b855 100644 --- a/.github/workflows/build_api/action.yml +++ b/.github/workflows/build_api/action.yml @@ -22,6 +22,9 @@ inputs: default: '.' outputs: + image_digest: + description: Immutable registry digest of the published image + value: ${{ steps.published.outputs.image }} image_url: description: 'Url of the uploaded image with the SHA tag' value: ${{ steps.image_tags.outputs.sha }} @@ -35,15 +38,15 @@ runs: - name: Generate Image Url id: image_url shell: bash - run: echo "::set-output name=value::registry.digitalocean.com/${{ inputs.image_path }}" + run: echo "value=registry.digitalocean.com/${{ inputs.image_path }}" >> "$GITHUB_OUTPUT" - name: Generate Tagged Urls id: image_tags shell: bash run: | SHORT_SHA=$(echo $GITHUB_SHA | cut -c1-${{ inputs.sha_size }}) - echo "::set-output name=sha::${{ steps.image_url.outputs.value }}:$SHORT_SHA" - echo "::set-output name=latest::${{ steps.image_url.outputs.value }}:latest" + echo "sha=${{ steps.image_url.outputs.value }}:$SHORT_SHA" >> "$GITHUB_OUTPUT" + echo "latest=${{ steps.image_url.outputs.value }}:latest" >> "$GITHUB_OUTPUT" - name: Build image shell: bash run: > @@ -62,3 +65,14 @@ runs: shell: bash if: github.ref == 'refs/heads/main' run: docker push -a ${{ steps.image_url.outputs.value }} + + - name: Resolve the published image digest + id: published + shell: bash + if: github.ref == 'refs/heads/main' + env: + IMAGE_TAG: ${{ steps.image_tags.outputs.sha }} + run: | + image=$(docker image inspect "$IMAGE_TAG" --format '{{index .RepoDigests 0}}') + [[ "$image" =~ ^registry\.digitalocean\.com/whitewhale/opensensor@sha256:[a-f0-9]{64}$ ]] + printf 'image=%s\n' "$image" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index 0a7efe9..c320819 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -2,6 +2,14 @@ name: CI +# Keep main releases in order and let an active rollout finish. +concurrency: + group: opensensor-api-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the main branch @@ -21,10 +29,10 @@ jobs: environment: DigitalOcean-PROD steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: - python-version: 3.11 + python-version: '3.11' - run: | python -m pip install pre-commit pre-commit run --all-files --verbose --show-diff-on-failure @@ -33,15 +41,57 @@ jobs: # The type of runner that the job will run on runs-on: ubuntu-latest environment: DigitalOcean-PROD + outputs: + image: ${{ steps.build.outputs.image_digest }} steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install doctl uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Run Docker Build; main Publishes Image to Container Registry + id: build uses: ./.github/workflows/build_api with: dockerfile: ./Dockerfile image_path: whitewhale/opensensor + + deploy_api: + needs: [lint_codebase, build_api] + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + runs-on: ubuntu-latest + environment: DigitalOcean-PROD + timeout-minutes: 15 + env: + IMAGE: ${{ needs.build_api.outputs.image }} + KUBE_CONTEXT: do-nyc1-k8s-1-33-1-do-1-nyc1-1752275240114 + steps: + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + + - name: Configure production Kubernetes access + run: doctl kubernetes cluster kubeconfig save k8s-1-33-1-do-1-nyc1-1752275240114 + + - name: Deploy the exact published image + run: | + [[ "$IMAGE" =~ ^registry\.digitalocean\.com/whitewhale/opensensor@sha256:[a-f0-9]{64}$ ]] + kubectl --context "$KUBE_CONTEXT" -n whitewhale set image deployment/opensensor-api opensensor-api="$IMAGE" + kubectl --context "$KUBE_CONTEXT" -n whitewhale rollout status deployment/opensensor-api --timeout=600s + + - name: Verify the running release + run: | + actual=$(kubectl --context "$KUBE_CONTEXT" -n whitewhale get deployment opensensor-api -o jsonpath='{.spec.template.spec.containers[?(@.name=="opensensor-api")].image}') + test "$actual" = "$IMAGE" + kubectl --context "$KUBE_CONTEXT" -n whitewhale exec deployment/opensensor-api -- /usr/src/.venv/bin/python -c ' + import os, sys, urllib.request + import sentry_sdk + assert os.environ["SENTRY_RELEASE"] == "opensensor-api@" + sys.argv[1] + assert os.environ.get("SENTRY_DSN") + with urllib.request.urlopen("http://127.0.0.1:8010/health", timeout=10) as response: + assert response.status == 200 + print("Application health and Sentry release configuration verified") + ' "$GITHUB_SHA" + printf 'Deployed %s after lint and image build passed.\n' "$IMAGE" >> "$GITHUB_STEP_SUMMARY"