Skip to content

docs(deploy-queue): updated readme with bulk cancellation, listing ou… #213

docs(deploy-queue): updated readme with bulk cancellation, listing ou…

docs(deploy-queue): updated readme with bulk cancellation, listing ou… #213

Workflow file for this run

name: Deploy Queue
on:
pull_request:
types: ["opened", "synchronize", "reopened"]
branches: ["main"]
paths: ["deploy-queue/**"]
push:
branches: ["main"]
paths: ["deploy-queue/**"]
tags: ["deploy-queue-v*.*.*"]
permissions:
contents: read
env:
GHCR_REPO: ghcr.io/neondatabase/dev-actions
jobs:
lint:
runs-on:
group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: deploy_queue_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/deploy_queue_test
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Fetch deploy-queue folder
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
sparse-checkout: deploy-queue
- name: Verify migrations work
working-directory: deploy-queue
run: |
# PostgreSQL is already healthy thanks to health checks
# Install sqlx-cli to run migrations
cargo install sqlx-cli --no-default-features --features postgres
# Apply migrations to verify they work correctly
sqlx migrate run
echo "Database migrations completed successfully"
- name: Verify SQLx query cache is up-to-date
working-directory: deploy-queue
run: |
# Check that the query cache matches current queries
cargo sqlx prepare --check
echo "SQLx query cache is up-to-date"
- name: Check code formatting
working-directory: deploy-queue
run: |
# Verify code is properly formatted
cargo fmt --all -- --check
echo "Code formatting check passed"
- name: Lint and verify lockfile
working-directory: deploy-queue
run: |
# Run clippy with locked dependencies to ensure lockfile is up-to-date
cargo clippy --locked --all-targets --all-features -- -D warnings
echo "Linting and lockfile verification completed"
test-integration:
runs-on:
group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
strategy:
fail-fast: false
matrix:
# Test against supported versions + current
version:
- current
- v0.5.1
- v0.5.2
- v0.6.0
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
TEST_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Fetch deploy-queue folder
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
sparse-checkout: deploy-queue
- name: Restore code from matrix version (keeping current migrations)
if: matrix.version != 'current'
working-directory: deploy-queue
env:
VERSION: deploy-queue-${{ matrix.version }}
CURRENT: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
# Restore all files from the matrix version except migrations
git restore --source ${VERSION} -- .
# Restore migrations from current version
git restore --source ${CURRENT} -- migrations
- name: Run tests
working-directory: deploy-queue
run: |
# Run all tests with output using cached sqlx queries (SQLX_OFFLINE=true)
cargo test --locked --all-targets --all-features
echo "All tests completed successfully"
build-image:
runs-on:
group: ${{ matrix.runner.group }}
labels: ${{ matrix.runner.labels }}
outputs:
digest_x86_64: ${{ steps.export_digest.outputs.digest_x86_64 }}
strategy:
fail-fast: false
matrix:
runner:
- group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Fetch deploy-queue folder
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
sparse-checkout: deploy-queue
- name: Docker meta
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ env.GHCR_REPO }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: deploy-queue
labels: ${{ steps.meta.outputs.labels }}
attests: |
type=provenance,mode=max
type=sbom,generator=docker.io/docker/buildkit-syft-scanner:1
outputs: type=image,name=${{ env.GHCR_REPO }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
id: export_digest
run: |
digest="${{ steps.build.outputs.digest }}"
echo "digest_$(uname -m)=${digest#sha256:}" | tee -a "$GITHUB_OUTPUT"
merge-image:
runs-on:
group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
needs: [build-image]
permissions:
contents: read
packages: write
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Docker meta
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ env.GHCR_REPO }}
tags: |
# branch event
type=ref,enable=true,priority=600,prefix=deploy-queue-,suffix=,event=branch
# pull request event
type=ref,enable=true,priority=600,prefix=deploy-queue-pr-,suffix=,event=pr
# tags event
type=match,pattern=deploy-queue-v(.*)
- name: Create manifest list and push
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
${{ env.GHCR_REPO }}@sha256:${{ needs.build-image.outputs.digest_x86_64 }}
- name: Inspect image
run: docker buildx imagetools inspect ${{ env.GHCR_REPO }}:${{ steps.meta.outputs.version }}
build-binary:
runs-on:
group: ${{ matrix.runner.group }}
labels: ${{ matrix.runner.labels }}
strategy:
matrix:
runner:
- group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Fetch deploy-queue folder
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
sparse-checkout: deploy-queue
- name: Build binary
working-directory: deploy-queue
run: |
TARGET="$(uname -m)-unknown-linux-musl"
echo "TARGET=${TARGET}" | tee -a "${GITHUB_ENV}"
rustup target add "${TARGET}"
sudo apt-get update && sudo apt-get install -y musl-tools musl-dev
cargo install cargo-auditable --locked
cargo auditable build --release --target "${TARGET}"
cp "target/${TARGET}/release/deploy-queue" assets/
mv assets "deploy-queue-${TARGET}"
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: deploy-queue-${{ env.TARGET }}
path: deploy-queue/deploy-queue-${{ env.TARGET }}
create-release:
needs: [build-binary]
runs-on:
group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
permissions:
contents: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Fetch artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: "!*.dockerbuild"
- name: Create archives
run: |
for artifact in $(ls); do
chmod a+x "${artifact}/deploy-queue"
tar cvf "${artifact}.tar.zst" "${artifact}"
done
- name: Create release from tag
if: github.ref_type == 'tag'
run: |
gh release create "${{ github.ref_name }}" \
--repo "${GITHUB_REPOSITORY}" \
--title "Release ${{ github.ref_name }}" \
--notes "Release for ${{ github.ref_name }}" \
--verify-tag \
deploy-queue-*.tar.zst
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create or update pre-release
if: github.event_name == 'pull_request'
run: |
TAG="deploy-queue-pr-${{ github.event.pull_request.number }}"
# Delete existing pre-release if it exists
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
echo "Deleting existing release $TAG"
gh release delete "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--yes \
--cleanup-tag
fi
gh release create "$TAG" \
--repo "${GITHUB_REPOSITORY}" \
--target "${COMMIT_SHA}" \
--prerelease \
--title "Pre-release for $TAG" \
--notes "Pre-release for $TAG" \
deploy-queue-*.tar.zst
env:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test-e2e:
if: github.event_name == 'pull_request' || github.ref_type == 'tag'
needs: [create-release]
runs-on:
group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: deploy_queue_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DEPLOY_QUEUE_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/deploy_queue_test
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Fetch deploy-queue folder
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
sparse-checkout: deploy-queue
# ==============================================================================
# Test 1: Complete Happy Path Lifecycle (start → finish)
# ==============================================================================
- name: Start deployment (happy path test)
id: start-deployment
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: test-region-happy
cell-index: 1
component: test-component-happy
version: v1.0.0
note: Happy path test deployment
- name: Finish the deployment successfully
uses: ./deploy-queue
with:
mode: finish
deployment-id: ${{ steps.start-deployment.outputs.deployment-id }}
- name: Verify finished deployment info
uses: ./deploy-queue
with:
mode: info
deployment-id: ${{ steps.start-deployment.outputs.deployment-id }}
# ==============================================================================
# Test 2: Blocking Behavior (same environment, region, cell index & component)
# ==============================================================================
- name: Start blocking deployment
id: blocking-deployment
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: test-region-block
cell-index: 1
component: test-component-block
version: v1.1.0
note: Blocking deployment test
- name: Try to start blocked deployment (should fail due to blocking)
id: blocked-deployment-attempt
uses: ./deploy-queue
continue-on-error: true
timeout-minutes: 1
with:
mode: start
environment: dev
cloud-provider: aws
region: test-region-block # Same region
cell-index: 1
component: test-component-block # Same component → should be blocked
version: v1.2.0
note: Should be blocked deployment
- name: Verify blocking actually occurred
run: |
if [[ "${{ steps.blocked-deployment-attempt.outcome }}" == "success" ]]; then
echo "ERROR: Blocked deployment should have failed but succeeded!"
exit 1
elif [[ "${{ steps.blocked-deployment-attempt.outcome }}" == "failure" ]]; then
echo "SUCCESS: Deployment was correctly blocked as expected"
else
echo "UNKNOWN: Unexpected outcome: ${{ steps.blocked-deployment-attempt.outcome }}"
exit 1
fi
- name: Cancel the second deployment
uses: ./deploy-queue
with:
mode: cancel
deployment-id: ${{ steps.blocked-deployment-attempt.outputs.deployment-id }}
cancellation-note: E2E test cancellation
- name: Finish blocking deployment to unblock
uses: ./deploy-queue
with:
mode: finish
deployment-id: ${{ steps.blocking-deployment.outputs.deployment-id }}
# ==============================================================================
# Test 3: Non-Blocking Behavior (different region or cell index)
# ==============================================================================
- name: Start deployment in region A
id: region-a-deployment
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: test-region-a
cell-index: 1
component: shared-component
version: v2.0.0
note: Region A deployment
- name: Start deployment in region B (should NOT be blocked)
id: region-b-deployment
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: test-region-b # Different region
cell-index: 1
component: shared-component # Same component but different region
version: v2.1.0
note: Region B deployment (not blocked)
- name: Start deployment in region B with different cell index (should NOT be blocked)
id: region-b-deployment-different-cell-index
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: test-region-b
cell-index: 2 # Different cell index
component: shared-component # Same component but different cell index
version: v2.1.0
note: Region B deployment (not blocked)
- name: Finish region A deployment
uses: ./deploy-queue
with:
mode: finish
deployment-id: ${{ steps.region-a-deployment.outputs.deployment-id }}
- name: Finish region B deployment
uses: ./deploy-queue
with:
mode: finish
deployment-id: ${{ steps.region-b-deployment.outputs.deployment-id }}
- name: Finish region B deployment with different cell index
uses: ./deploy-queue
with:
mode: finish
deployment-id: ${{ steps.region-b-deployment-different-cell-index.outputs.deployment-id }}
# ==============================================================================
# Test 4: Cancellation Flow
# ==============================================================================
- name: Start deployment for cancellation test
id: cancel-deployment
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: test-region-cancel
cell-index: 1
component: test-component-cancel
version: v3.0.0
note: Cancellation test deployment
- name: Cancel the deployment
uses: ./deploy-queue
with:
mode: cancel
deployment-id: ${{ steps.cancel-deployment.outputs.deployment-id }}
cancellation-note: E2E test cancellation
- name: Verify cancelled deployment info
uses: ./deploy-queue
with:
mode: info
deployment-id: ${{ steps.cancel-deployment.outputs.deployment-id }}
# ==============================================================================
# Test 5: Cancel Multiple Deployments by Component + Version + Environment
# ==============================================================================
- name: Start deployment 1 for bulk cancel test (aws us-east-1)
id: bulk-cancel-1
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: us-east-1
cell-index: 1
component: bulk-test-component
version: v4.0.0
note: Test bulk component-version cancel 1
- name: Start deployment 2 for bulk cancel test (aws us-west-2)
id: bulk-cancel-2
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: us-west-2
cell-index: 2
component: bulk-test-component
version: v4.0.0
note: Test bulk component-version cancel 2
- name: Start deployment 3 for bulk cancel test (azure east-us)
id: bulk-cancel-3
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: azure
region: east-us
cell-index: 1
component: bulk-test-component
version: v4.0.0
note: Test bulk component-version cancel 3
- name: Start deployment 4 for a different version (should NOT be cancelled)
id: bulk-cancel-different-version
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: us-west-1
cell-index: 1
component: bulk-test-component
version: v4.0.1
note: Test bulk component-version cancel DIFFERENT
- name: Cancel all deployments for bulk-test-component v4.0.0 in dev
uses: ./deploy-queue
with:
mode: cancel
environment: dev
component: bulk-test-component
version: v4.0.0
cancellation-note: Bulk cancellation test - cancel all v4.0.0
- name: Verify deployment 1 was cancelled
run: |
INFO=$(./deploy-queue/deploy-queue-$(uname -m)-unknown-linux-musl/deploy-queue info ${{ steps.bulk-cancel-1.outputs.deployment-id }})
if echo "$INFO" | grep -q "cancelled"; then
echo "✓ Deployment 1 was correctly cancelled"
else
echo "✗ Deployment 1 should have been cancelled"
exit 1
fi
env:
DEPLOY_QUEUE_DATABASE_URL: ${{ env.DEPLOY_QUEUE_DATABASE_URL }}
- name: Verify deployment 2 was cancelled
run: |
INFO=$(./deploy-queue/deploy-queue-$(uname -m)-unknown-linux-musl/deploy-queue info ${{ steps.bulk-cancel-2.outputs.deployment-id }})
if echo "$INFO" | grep -q "cancelled"; then
echo "✓ Deployment 2 was correctly cancelled"
else
echo "✗ Deployment 2 should have been cancelled"
exit 1
fi
env:
DEPLOY_QUEUE_DATABASE_URL: ${{ env.DEPLOY_QUEUE_DATABASE_URL }}
- name: Verify deployment 3 was cancelled
run: |
INFO=$(./deploy-queue/deploy-queue-$(uname -m)-unknown-linux-musl/deploy-queue info ${{ steps.bulk-cancel-3.outputs.deployment-id }})
if echo "$INFO" | grep -q "cancelled"; then
echo "✓ Deployment 3 was correctly cancelled"
else
echo "✗ Deployment 3 should have been cancelled"
exit 1
fi
env:
DEPLOY_QUEUE_DATABASE_URL: ${{ env.DEPLOY_QUEUE_DATABASE_URL }}
- name: Verify deployment 4 (different version) was NOT cancelled
run: |
INFO=$(./deploy-queue/deploy-queue-$(uname -m)-unknown-linux-musl/deploy-queue info ${{ steps.bulk-cancel-different-version.outputs.deployment-id }})
if echo "$INFO" | grep -q "cancelled"; then
echo "✗ Deployment 4 should NOT have been cancelled (different version)"
exit 1
else
echo "✓ Deployment 4 was correctly NOT cancelled"
fi
env:
DEPLOY_QUEUE_DATABASE_URL: ${{ env.DEPLOY_QUEUE_DATABASE_URL }}
- name: Finish deployment 4 (cleanup)
uses: ./deploy-queue
with:
mode: finish
deployment-id: ${{ steps.bulk-cancel-different-version.outputs.deployment-id }}
# ==============================================================================
# Compatibility Tests - Test older versions with current schema
# ==============================================================================
compat-tests:
if: github.event_name == 'pull_request' || github.ref_type == 'tag'
needs: [create-release]
runs-on:
group: neondatabase-protected-runner-group
labels: linux-ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: deploy_queue_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DEPLOY_QUEUE_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/deploy_queue_test
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: Fetch deploy-queue folder
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
sparse-checkout: deploy-queue
# Run current version first to apply latest migrations
- name: Start deployment (current version)
id: start-current
uses: ./deploy-queue
with:
mode: start
environment: dev
cloud-provider: aws
region: compat-current
cell-index: 1
component: compat-test
version: v1.0.0-current
note: Compatibility test - current version to apply migrations
- name: Finish deployment (current version)
uses: ./deploy-queue
with:
mode: finish
deployment-id: ${{ steps.start-current.outputs.deployment-id }}
# Test v0.5.2 with the updated schema
- name: Start deployment (v0.5.2)
id: start-v0-5-2
uses: neondatabase/dev-actions/[email protected]
with:
mode: start
environment: dev
cloud-provider: aws
region: compat-v0-5-2
cell-index: 1
component: compat-test
version: v0.5.2
note: Compatibility test - v0.5.2
- name: Finish deployment (v0.5.2)
uses: neondatabase/dev-actions/[email protected]
with:
mode: finish
deployment-id: ${{ steps.start-v0-5-2.outputs.deployment-id }}
# Test v0.6.0 with the updated schema
- name: Start deployment (v0.6.0)
id: start-v0-6-0
uses: neondatabase/dev-actions/[email protected]
with:
mode: start
environment: dev
cloud-provider: aws
region: compat-v0-6-0
cell-index: 1
component: compat-test
version: v0.6.0
note: Compatibility test - v0.6.0
- name: Finish deployment (v0.6.0)
uses: neondatabase/dev-actions/[email protected]
with:
mode: finish
deployment-id: ${{ steps.start-v0-6-0.outputs.deployment-id }}