Skip to content

SonarQube: fix tag to 26.3.0.120487-community #51

SonarQube: fix tag to 26.3.0.120487-community

SonarQube: fix tag to 26.3.0.120487-community #51

Workflow file for this run

# BeeCompose OCI Artifact Publishing
#
# This workflow publishes Docker Compose stacks as OCI artifacts to GitHub Container Registry.
# Services can be deployed directly from GHCR using:
# docker compose -f oci://ghcr.io/beevelop/<service>:<version> up -d
#
# Triggers:
# - Push to main: Publishes ALL services with 'latest' tag (ensures consistency)
# - Manual dispatch: Publish specific service with optional version override
# - Release: Publishes all services with release tag (e.g., v1.0.0)
name: Publish OCI Artifacts
on:
push:
branches: [main]
release:
types: [published]
workflow_dispatch:
inputs:
service:
description: 'Service to publish (empty for all changed)'
required: false
type: string
version_override:
description: 'Override version tag (e.g., v1.0.0)'
required: false
type: string
dry_run:
description: 'Dry run - validate only, do not publish'
required: false
default: false
type: boolean
env:
REGISTRY: ghcr.io
REGISTRY_NAMESPACE: beevelop
# Prevent concurrent publishing runs to avoid race conditions
concurrency:
group: publish-oci-${{ github.ref }}
cancel-in-progress: false
# Default permissions (restrictive) - jobs override as needed
permissions:
contents: read
jobs:
# ============================================================================
# Job 1: Detect Changed Services
# ============================================================================
detect-changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
services: ${{ steps.detect.outputs.services }}
service_count: ${{ steps.detect.outputs.service_count }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Detect changed services
id: detect
run: |
set -euo pipefail
echo "=== Detecting changed services ==="
# If specific service provided via workflow dispatch
if [[ -n "${{ inputs.service }}" ]]; then
if [[ -d "services/${{ inputs.service }}" ]]; then
echo "Manual trigger for: ${{ inputs.service }}"
echo 'services=["${{ inputs.service }}"]' >> $GITHUB_OUTPUT
echo "service_count=1" >> $GITHUB_OUTPUT
exit 0
else
echo "::error::Service '${{ inputs.service }}' not found"
exit 1
fi
fi
# For push to main or release: publish ALL services
# This ensures consistency - all OCI artifacts are always in sync with main
# Change detection via HEAD~1 is unreliable when multiple commits are pushed
if [[ "${{ github.event_name }}" == "push" || "${{ github.event_name }}" == "release" ]]; then
echo "${{ github.event_name }} trigger - publishing all services"
SERVICES=$(find services -maxdepth 1 -mindepth 1 -type d -exec basename {} \; | sort | jq -R -s -c 'split("\n") | map(select(. != ""))')
COUNT=$(echo "$SERVICES" | jq 'length')
echo "services=$SERVICES" >> $GITHUB_OUTPUT
echo "service_count=$COUNT" >> $GITHUB_OUTPUT
exit 0
fi
# Fallback for other event types (shouldn't reach here normally)
echo "::error::Unexpected event type: ${{ github.event_name }}"
exit 1
# ============================================================================
# Job 2: Validate OCI Compatibility
# ============================================================================
validate:
name: Validate
needs: detect-changes
if: needs.detect-changes.outputs.service_count != '0'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Validate Compose syntax
working-directory: services/${{ matrix.service }}
run: |
echo "=== Validating ${{ matrix.service }} ==="
# Check compose syntax
docker compose config > /dev/null
echo "✓ Compose syntax valid"
- name: Check OCI compatibility
working-directory: services/${{ matrix.service }}
run: |
# Check for bind mounts (except docker.sock which is allowed for traefik)
# Use awk to detect bind mounts and check if source is docker.sock
CONFIG=$(docker compose config 2>/dev/null)
# Find bind mounts that are NOT docker.sock
# The config format has 'type: bind' followed by 'source: <path>' on next lines
BINDS=$(echo "$CONFIG" | awk '
/type: bind/ { in_bind=1; bind_line=NR; next }
in_bind && /source:/ {
if ($2 !~ /docker\.sock/) {
print "Bind mount at line " bind_line ": " $0
}
in_bind=0
}
/^[^ ]/ { in_bind=0 }
')
if [[ -n "$BINDS" ]]; then
echo "::error::Service contains bind mounts (not OCI compatible):"
echo "$BINDS"
exit 1
fi
echo "✓ No bind mounts (OCI compatible)"
# ============================================================================
# Job 3: Publish OCI Artifacts
# ============================================================================
publish:
name: Publish
needs: [detect-changes, validate]
if: ${{ !inputs.dry_run && needs.detect-changes.outputs.service_count != '0' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine version
id: version
run: |
# Version priority:
# 1. Release event: use git tag
# 2. Manual dispatch with override: use provided version
# 3. Push to main: use 'latest'
if [[ "${{ github.event_name }}" == "release" ]]; then
VERSION="${{ github.event.release.tag_name }}"
elif [[ -n "${{ inputs.version_override }}" ]]; then
VERSION="${{ inputs.version_override }}"
# Normalize: add 'v' prefix if starts with number
[[ "$VERSION" =~ ^[0-9] ]] && VERSION="v$VERSION"
else
VERSION="latest"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Publishing version: $VERSION"
- name: Publish OCI artifact
working-directory: services/${{ matrix.service }}
run: |
VERSION="${{ steps.version.outputs.version }}"
IMAGE="${{ env.REGISTRY }}/${{ env.REGISTRY_NAMESPACE }}/${{ matrix.service }}"
echo "=== Publishing ${IMAGE}:${VERSION} ==="
# Publish the determined version tag
# --with-env includes environment variables with defaults in the OCI artifact
# -y auto-confirms prompts in non-interactive CI environment
# Using 'yes |' to pipe 'y' responses for bind mount prompts (workaround for docker/compose#13148)
yes | docker compose publish --with-env -y "${IMAGE}:${VERSION}"
echo "✓ Published ${IMAGE}:${VERSION}"
# On release, also publish 'latest' tag alongside the version tag
if [[ "${{ github.event_name }}" == "release" ]]; then
yes | docker compose publish --with-env -y "${IMAGE}:latest"
echo "✓ Published ${IMAGE}:latest"
fi
- name: Generate summary
run: |
VERSION="${{ steps.version.outputs.version }}"
IMAGE="${{ env.REGISTRY }}/${{ env.REGISTRY_NAMESPACE }}/${{ matrix.service }}"
cat >> $GITHUB_STEP_SUMMARY << EOF
### ✅ Published: ${{ matrix.service }}
| Property | Value |
|----------|-------|
| Service | \`${{ matrix.service }}\` |
| Version | \`${VERSION}\` |
| Image | \`${IMAGE}\` |
**Deploy with:**
\`\`\`bash
docker compose -f oci://${IMAGE}:${VERSION} up -d
\`\`\`
EOF
# ============================================================================
# Job 4: Summary
# ============================================================================
summary:
name: Summary
needs: [detect-changes, validate, publish]
if: always()
runs-on: ubuntu-latest
steps:
- name: Generate pipeline summary
run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
## OCI Publishing Summary
| Metric | Value |
|--------|-------|
| Services detected | ${{ needs.detect-changes.outputs.service_count }} |
| Validation | ${{ needs.validate.result }} |
| Publishing | ${{ needs.publish.result }} |
### Usage
Deploy any published service:
```bash
# Create environment file
cat > .env << 'ENVEOF'
COMPOSE_PROJECT_NAME=myapp
SERVICE_DOMAIN=app.example.com
# Add service-specific variables...
ENVEOF
# Deploy from OCI
docker compose -f oci://ghcr.io/beevelop/<service>:<version> --env-file .env up -d
```
### Available Services
Browse all artifacts at: https://github.com/orgs/beevelop/packages
EOF