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-base-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
# ==============================================================================
# A.R.C. Platform - Base Image Build & Publish Workflow
# ==============================================================================
# Purpose: Build, test, and publish base Docker images to GHCR
#
# Triggers:
# - Push to .docker/base/** files on main
# - Manual workflow dispatch
# - Weekly schedule (security updates)
#
# Images Built:
# - ghcr.io/arc-framework/arc-base-python-ai:3.11-alpine3.19
# - ghcr.io/arc-framework/arc-base-go-infra:1.24-alpine3.21 (future)
# ==============================================================================

name: Build Base Images

on:
push:
branches:
- main
paths:
- '.docker/base/**'
- '.github/workflows/build-base-images.yml'

pull_request:
paths:
- '.docker/base/**'
- '.github/workflows/build-base-images.yml'

# Weekly rebuild for security patches
schedule:
- cron: '0 6 * * 0' # Sunday 6:00 UTC

workflow_dispatch:
inputs:
push_images:
description: 'Push images to registry'
required: false
default: 'false'
type: boolean

env:
REGISTRY: ghcr.io
REGISTRY_NAMESPACE: ${{ github.repository_owner }}

jobs:
# ============================================================================
# Detect which base images changed
# ============================================================================
detect-changes:
runs-on: ubuntu-latest
outputs:
python-ai: ${{ steps.filter.outputs.python-ai }}
go-infra: ${{ steps.filter.outputs.go-infra }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for paths-filter to compare commits

- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
python-ai:
- '.docker/base/python-ai/**'
go-infra:
- '.docker/base/go-infra/**'

# ============================================================================
# Build Python AI Base Image
# ============================================================================
build-python-ai:
runs-on: ubuntu-latest
needs: detect-changes
if: needs.detect-changes.outputs.python-ai == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'

permissions:
contents: read
packages: write
security-events: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.REGISTRY_NAMESPACE }}/arc-base-python-ai
tags: |
# Version tag (primary)
type=raw,value=3.11-alpine3.19
# SHA tag for traceability
type=sha,prefix=sha-
# Date tag for scheduled builds
type=raw,value={{date 'YYYYMMDD'}},enable=${{ github.event_name == 'schedule' }}
# Latest for main branch
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .docker/base/python-ai
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.event.inputs.push_images == 'true') }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILD_DATE=${{ github.event.head_commit.timestamp || github.event.repository.updated_at }}
VCS_REF=${{ github.sha }}
VERSION=3.11-alpine3.19
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.REGISTRY_NAMESPACE }}/arc-base-python-ai:3.11-alpine3.19
format: 'sarif'
output: 'trivy-results-python-ai.sarif'
severity: 'CRITICAL,HIGH'

- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results-python-ai.sarif'

- name: Verify image size
run: |
SIZE=$(docker inspect ${{ env.REGISTRY }}/${{ env.REGISTRY_NAMESPACE }}/arc-base-python-ai:3.11-alpine3.19 --format='{{.Size}}' 2>/dev/null || echo "0")
SIZE_MB=$((SIZE / 1024 / 1024))
echo "Image size: ${SIZE_MB}MB"
if [ "$SIZE_MB" -gt 300 ]; then
echo "::warning::Image size (${SIZE_MB}MB) exceeds target of 300MB"
fi

# ============================================================================
# Build Go Infrastructure Base Image (placeholder for future)
# ============================================================================
build-go-infra:
runs-on: ubuntu-latest
needs: detect-changes
if: needs.detect-changes.outputs.go-infra == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'

permissions:
contents: read
packages: write
security-events: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Check if Go base image exists
id: check
run: |
if [ -f ".docker/base/go-infra/Dockerfile" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Go infrastructure base image not yet implemented"
fi

- name: Set up Docker Buildx
if: steps.check.outputs.exists == 'true'
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
if: steps.check.outputs.exists == 'true' && github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
if: steps.check.outputs.exists == 'true'
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.REGISTRY_NAMESPACE }}/arc-base-go-infra
tags: |
type=raw,value=1.24-alpine3.21
type=sha,prefix=sha-
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

- name: Build and push
if: steps.check.outputs.exists == 'true'
uses: docker/build-push-action@v5
with:
context: .docker/base/go-infra
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILD_DATE=${{ github.event.head_commit.timestamp || github.event.repository.updated_at }}
VCS_REF=${{ github.sha }}
VERSION=1.24-alpine3.21
cache-from: type=gha
cache-to: type=gha,mode=max

# ============================================================================
# Trigger dependent service rebuilds
# ============================================================================
trigger-rebuilds:
runs-on: ubuntu-latest
needs: [build-python-ai, build-go-infra]
if: always() && github.ref == 'refs/heads/main' && (needs.build-python-ai.result == 'success' || needs.build-go-infra.result == 'success')

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Analyze rebuild impact
run: |
echo "Base images updated. The following services may need rebuilding:"
./scripts/validate/check-build-impact.sh .docker/base/ || true

- name: Create issue for service rebuilds
if: github.event_name == 'schedule'
uses: actions/github-script@v7
with:
script: |
const title = `Weekly Base Image Update - ${new Date().toISOString().split('T')[0]}`;
const body = `
## Base Images Updated

The weekly base image rebuild has completed. This ensures all base images include the latest security patches.

### Action Required

Review and rebuild dependent services if needed:

**Python AI Base Image:**
- arc-sherlock-brain
- arc-scarlett-voice
- arc-piper-tts

**Go Infrastructure Base Image:**
- raymond

### Commands

\`\`\`bash
# Check build impact
make build-impact FILE=.docker/base/

# Rebuild all services
make build-services
\`\`\`

---
*This issue was automatically created by the weekly base image update workflow.*
`;

// Check if similar issue exists
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'base-image-update'
});

if (issues.data.length === 0) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: title,
body: body,
labels: ['base-image-update', 'maintenance']
});
}
Loading
Loading