---
name: Deploy
on:
push:
tags:
- '*'
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
verify-main-status:
name: Verify main branch status
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
- name: Get latest commit on main
id: main-commit
run: |
MAIN_SHA=$(git rev-parse main)
echo "sha=${MAIN_SHA}" >> $GITHUB_OUTPUT
echo "Main branch latest commit: ${MAIN_SHA}"
- name: Verify tag is based on main branch
run: |
TAG_SHA=$(git rev-parse ${GITHUB_REF#refs/tags/})
MAIN_SHA=${{ steps.main-commit.outputs.sha }}
TAG_NAME=${GITHUB_REF#refs/tags/}
echo "🏷️ Tag: ${TAG_NAME}"
echo "📍 Tag commit: ${TAG_SHA}"
echo "🌿 Main commit: ${MAIN_SHA}"
# Check if tag commit is the same as main or in main's history
if [ "${TAG_SHA}" = "${MAIN_SHA}" ]; then
echo "✅ Tag points to latest main commit (created via 'just deploy')"
elif git merge-base --is-ancestor ${TAG_SHA} ${MAIN_SHA}; then
echo "✅ Tag is based on main branch history"
else
echo "::error::❌ Tag ${TAG_NAME} is not based on the main branch"
echo "::error::This prevents accidental deploys from feature branches"
echo "::error::Tag commit: ${TAG_SHA}"
echo "::error::Main branch: ${MAIN_SHA}"
echo "::error::"
echo "::error::To fix: Merge your branch to main first, then create the tag"
exit 1
fi
test:
uses: ./.github/workflows/test.yml
needs: verify-main-status
build:
name: Build and release
runs-on: ${{ matrix.os }}
needs: test
strategy:
matrix:
include:
- build: linux
os: ubuntu-latest
target: x86_64-unknown-linux-musl
- build: macos
os: macos-latest
target: x86_64-apple-darwin
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get the release version from the tag
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install system deps (musl)
if: matrix.build == 'linux'
run: |
sudo apt-get update -y
sudo apt-get install -y musl-dev musl-tools
- name: Install packaging tools (RPM/DEB)
if: matrix.build == 'linux'
run: |
cargo install cargo-generate-rpm
cargo install cargo-deb --force --locked
- name: Build Linux (musl)
if: matrix.build == 'linux'
run: cargo build --release --target ${{ matrix.target }}
- name: Strip and prepare packaging paths (Linux)
if: matrix.build == 'linux'
run: |
BIN="target/${{ matrix.target }}/release/pg_exporter"
ls -al "$BIN"
strip -s "$BIN" || true
# Ensure cargo-generate-rpm’s asset path exists without copying the file
mkdir -p target/release
ln -sf "../${{ matrix.target }}/release/pg_exporter" "target/release/pg_exporter"
ls -al target/release/pg_exporter
- name: Generate RPM
if: matrix.build == 'linux'
run: |
cargo generate-rpm
RPM_PATH="$(find target/generate-rpm -type f -name '*.rpm' -print -quit)"
echo "RPM_ASSET=$RPM_PATH" >> $GITHUB_ENV
echo "RPM: $RPM_PATH"
- name: Generate DEB (musl static)
if: matrix.build == 'linux'
run: |
cargo deb --no-build
DEB_PATH="$(find target/debian -maxdepth 1 -type f -name '*.deb' -print -quit)"
echo "DEB_ASSET=$DEB_PATH" >> $GITHUB_ENV
echo "DEB: $DEB_PATH"
- name: Build
if: matrix.build != 'linux'
run: cargo build --release --target ${{ matrix.target }}
- name: Build archive
shell: bash
run: |
binary_name="pg_exporter"
dirname="$binary_name-${{ env.VERSION }}-${{ matrix.target }}"
mkdir "$dirname"
mv "target/${{ matrix.target }}/release/$binary_name" "$dirname"
tar -czf "$dirname.tar.gz" "$dirname"
echo "ASSET=$dirname.tar.gz" >> $GITHUB_ENV
- name: Release
if: startsWith(github.ref, 'refs/tags/') && !startsWith(github.ref_name, 't')
uses: softprops/action-gh-release@v3
with:
files: |-
${{ env.ASSET }}
${{ env.RPM_ASSET }}
${{ env.DEB_ASSET }}
container:
name: Build and push container image
runs-on: ubuntu-latest
needs:
- test
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get the release version from the tag
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Determine build configuration
id: config
run: |
if [[ "${{ github.ref_name }}" =~ ^t- ]]; then
# Test tag: build all platforms, but don't push
echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
echo "push=false" >> $GITHUB_OUTPUT
echo "Test tag detected: building all platforms, no push"
else
# Production tag: build all platforms, push to registry
echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
echo "push=true" >> $GITHUB_OUTPUT
echo "Production tag detected: building all platforms, will push"
fi
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
if: steps.config.outputs.push == 'true'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
file: ./Containerfile
platforms: ${{ steps.config.outputs.platforms }}
push: ${{ steps.config.outputs.push }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
publish:
name: Publish
runs-on: ubuntu-latest
needs:
- build
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && !startsWith(github.ref_name,
't')
steps:
- name: Checkout sources
uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- run: cargo publish --token ${CRATES_TOKEN}
env:
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}
- name: Deployment Summary
if: success()
run: |-
echo "::notice::✅ Deployment completed successfully!"
echo "::notice::📦 Published ${GITHUB_REF#refs/tags/} to crates.io"
echo "::notice::🚀 GitHub release created with binaries (RPM, DEB, tar.gz)"
echo "::notice::🐳 Container images published to ghcr.io/${{ github.repository }}"
echo "::notice::⏱️ Total pipeline: verify → test → build → container → publish"