Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
name: Build and Push Images

on:
pull_request:
branches: [master]
push:
branches: [master]

env:
REGISTRY: ghcr.io
DOCKER_BUILDKIT: 1
BUILDKIT_PROGRESS: plain

jobs:
# ============================================================================
# Pre-checks: Validate commits and version
# ============================================================================
pre-checks:
runs-on: ubuntu-latest
outputs:
skip_build: ${{ steps.check-commits.outputs.skip_build }}
version: ${{ steps.version.outputs.version }}
image_tag: ${{ steps.image-tag.outputs.tag }}
latest_tag: ${{ steps.image-tag.outputs.latest_tag }}
is_release: ${{ steps.check-event.outputs.is_release }}
image_prefix: ${{ steps.lowercase.outputs.image_prefix }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Convert repository owner to lowercase
id: lowercase
run: |
echo "image_prefix=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_OUTPUT

- name: Check event type
id: check-event
run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/master" ]]; then
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "is_release=false" >> $GITHUB_OUTPUT
fi

- name: Validate commit messages (commitlint style)
id: commitlint
run: |
# Get commits to check
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
COMMITS=$(git log --format="%s" origin/master..HEAD)
else
# On push, check the pushed commit
COMMITS=$(git log --format="%s" -1)
fi

# Conventional commit regex
PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+"

echo "Checking commits..."
FAILED=false
while IFS= read -r msg; do
if [[ -z "$msg" ]]; then
continue
fi
# Skip merge commits
if [[ "$msg" =~ ^Merge ]]; then
echo "Skipping merge commit: $msg"
continue
fi
if [[ ! "$msg" =~ $PATTERN ]]; then
echo "Invalid commit message: $msg"
echo "Expected format: type(scope)?: description"
echo "Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
FAILED=true
else
echo "Valid: $msg"
fi
done <<< "$COMMITS"

if [[ "$FAILED" == "true" ]]; then
echo "Commit message validation failed"
exit 1
fi

- name: Check if docs-only commits
id: check-commits
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
COMMITS=$(git log --format="%s" origin/master..HEAD)
else
COMMITS=$(git log --format="%s" -1)
fi

# Check if ALL commits are docs-prefixed
ALL_DOCS=true
while IFS= read -r msg; do
if [[ -z "$msg" ]]; then
continue
fi
if [[ ! "$msg" =~ ^docs ]]; then
ALL_DOCS=false
break
fi
done <<< "$COMMITS"

if [[ "$ALL_DOCS" == "true" ]]; then
echo "All commits are docs-only, skipping build"
echo "skip_build=true" >> $GITHUB_OUTPUT
else
echo "skip_build=false" >> $GITHUB_OUTPUT
fi

- name: Get version from Cargo.toml
id: version
run: |
VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"

- name: Generate image tag
id: image-tag
run: |
VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')
IS_RELEASE="${{ steps.check-event.outputs.is_release }}"

if [[ "$IS_RELEASE" == "true" ]]; then
# Push to master: use version tag and latest
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
echo "latest_tag=latest" >> $GITHUB_OUTPUT
else
# PR: use version with timestamp suffix and latest-dev
TIMESTAMP=$(date +"%Y%m%d%H%M%S")
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
echo "tag=${VERSION}-${TIMESTAMP}-${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "latest_tag=latest-dev" >> $GITHUB_OUTPUT
fi

- name: Check version differs from master (PR only)
if: github.event_name == 'pull_request'
run: |
PR_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')

git fetch origin master
git checkout origin/master -- Cargo.toml
MASTER_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')
git checkout HEAD -- Cargo.toml

echo "PR version: $PR_VERSION"
echo "Master version: $MASTER_VERSION"

if [[ "$PR_VERSION" == "$MASTER_VERSION" ]]; then
echo "Version must be bumped from master version ($MASTER_VERSION)"
exit 1
fi

- name: Check breaking change requires major bump (PR only)
if: github.event_name == 'pull_request'
run: |
COMMITS=$(git log --format="%s" origin/master..HEAD)

HAS_BREAKING=false
while IFS= read -r msg; do
# Check for breaking change indicator (! before :) or BREAKING CHANGE in body
if [[ "$msg" =~ !: ]] || [[ "$msg" =~ BREAKING\ CHANGE ]]; then
HAS_BREAKING=true
break
fi
done <<< "$COMMITS"

if [[ "$HAS_BREAKING" == "true" ]]; then
echo "Breaking change detected in commits"

PR_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')

git fetch origin master
git checkout origin/master -- Cargo.toml
MASTER_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')
git checkout HEAD -- Cargo.toml

# Extract major versions
PR_MAJOR=$(echo "$PR_VERSION" | cut -d. -f1)
MASTER_MAJOR=$(echo "$MASTER_VERSION" | cut -d. -f1)

echo "PR major: $PR_MAJOR, Master major: $MASTER_MAJOR"

if [[ "$PR_MAJOR" -le "$MASTER_MAJOR" ]]; then
echo "Breaking change requires major version bump (current: $MASTER_VERSION, PR: $PR_VERSION)"
exit 1
fi
fi

# ============================================================================
# Build images
# ============================================================================
build:
needs: pre-checks
if: needs.pre-checks.outputs.skip_build != 'true'
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
strategy:
matrix:
service: [agent, hive, hive-hq]
include:
- service: agent
image_name: hive-agent
dockerfile: agent/Dockerfile
- service: hive
image_name: hive-server
dockerfile: hive/Dockerfile
- service: hive-hq
image_name: hive-hq
dockerfile: hive-hq/Dockerfile
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64,linux/arm64

- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Generate image tags
id: tags
run: |
IMAGE_TAG="${{ needs.pre-checks.outputs.image_tag }}"
LATEST_TAG="${{ needs.pre-checks.outputs.latest_tag }}"
IMAGE="${{ needs.pre-checks.outputs.image_prefix }}/${{ matrix.image_name }}"

echo "tags=${IMAGE}:${IMAGE_TAG},${IMAGE}:${LATEST_TAG}" >> $GITHUB_OUTPUT
echo "version=${IMAGE_TAG}" >> $GITHUB_OUTPUT

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.dockerfile }}
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.tags.outputs.tags }}
build-args: |
BUILD_VERSION=${{ steps.tags.outputs.version }}
IMAGE_SOURCE=https://github.com/${{ github.repository }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.version=${{ steps.tags.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

# ============================================================================
# Create git tag on release
# ============================================================================
tag-release:
needs: [pre-checks, build]
if: needs.pre-checks.outputs.is_release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Create and push tag
run: |
VERSION="v${{ needs.pre-checks.outputs.version }}"

# Check if tag already exists
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Tag $VERSION already exists, skipping"
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
echo "Created tag $VERSION"
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# Cargo.lock - committed for reproducible builds

# These are backup files generated by rustfmt
**/*.rs.bk
Expand Down
Loading
Loading