Skip to content

Release Pipeline

Release Pipeline #20

name: Release Pipeline
on:
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- major
- minor
- patch
force_build_all:
description: 'Force rebuild all components'
required: false
type: boolean
default: true
components:
description: 'Components to build (comma-separated: frontend,backend,operator,claude-runner,state-sync) - leave empty for all'
required: false
type: string
default: ''
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
new_tag: ${{ steps.next_version.outputs.new_tag }}
steps:
- name: Checkout Repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # Fetch all history for changelog generation
- name: Get Latest Tag
id: get_latest_tag
run: |
# List all existing tags for debugging
echo "All existing tags:"
git tag --list 'v*.*.*' --sort=-version:refname
# Get the latest tag using version sort, or use v0.0.0 if no tags exist
LATEST_TAG=$(git tag --list 'v*.*.*' --sort=-version:refname | head -n 1)
if [ -z "$LATEST_TAG" ]; then
exit 1
fi
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "Latest tag: $LATEST_TAG"
- name: Calculate Next Version
id: next_version
run: |
LATEST_TAG="${{ steps.get_latest_tag.outputs.latest_tag }}"
# Remove 'v' prefix for calculation
VERSION=${LATEST_TAG#v}
# Split version into components
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
# Bump version based on input
case "${{ github.event.inputs.bump_type }}" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
echo "new_tag=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "New version: $NEW_VERSION"
- name: Generate Changelog
id: changelog
run: |
LATEST_TAG="${{ steps.get_latest_tag.outputs.latest_tag }}"
NEW_TAG="${{ steps.next_version.outputs.new_tag }}"
echo "# Release $NEW_TAG" > RELEASE_CHANGELOG.md
echo "" >> RELEASE_CHANGELOG.md
echo "## Changes since $LATEST_TAG" >> RELEASE_CHANGELOG.md
echo "" >> RELEASE_CHANGELOG.md
# Generate changelog from commits
if [ "$LATEST_TAG" = "v0.0.0" ]; then
# First release - include all commits
git log --pretty=format:"- %s (%h)" >> RELEASE_CHANGELOG.md
else
# Get commits since last tag
git log ${LATEST_TAG}..HEAD --pretty=format:"- %s (%h)" >> RELEASE_CHANGELOG.md
fi
echo "" >> RELEASE_CHANGELOG.md
echo "" >> RELEASE_CHANGELOG.md
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LATEST_TAG}...${NEW_TAG}" >> RELEASE_CHANGELOG.md
cat RELEASE_CHANGELOG.md
- name: Create Tag
id: create_tag
uses: rickstaa/action-create-tag@v1
with:
tag: ${{ steps.next_version.outputs.new_tag }}
message: "Release ${{ steps.next_version.outputs.new_tag }}"
force_push_tag: false
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release Archive
id: create_archive
run: |
NEW_TAG="${{ steps.next_version.outputs.new_tag }}"
ARCHIVE_NAME="vteam-${NEW_TAG}.tar.gz"
# Create archive of entire repository at this tag
git archive --format=tar.gz --prefix=vteam-${NEW_TAG}/ HEAD > $ARCHIVE_NAME
echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.next_version.outputs.new_tag }}
name: "Release ${{ steps.next_version.outputs.new_tag }}"
body_path: RELEASE_CHANGELOG.md
draft: false
prerelease: false
files: |
${{ steps.create_archive.outputs.archive_name }}
RELEASE_CHANGELOG.md
build-and-push:
runs-on: ubuntu-latest
needs: release
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
strategy:
matrix:
component:
- name: frontend
context: ./components/frontend
image: quay.io/ambient_code/vteam_frontend
dockerfile: ./components/frontend/Dockerfile
- name: backend
context: ./components/backend
image: quay.io/ambient_code/vteam_backend
dockerfile: ./components/backend/Dockerfile
- name: operator
context: ./components/operator
image: quay.io/ambient_code/vteam_operator
dockerfile: ./components/operator/Dockerfile
- name: claude-code-runner
context: ./components/runners
image: quay.io/ambient_code/vteam_claude_runner
dockerfile: ./components/runners/claude-code-runner/Dockerfile
- name: state-sync
context: ./components/runners/state-sync
image: quay.io/ambient_code/vteam_state_sync
dockerfile: ./components/runners/state-sync/Dockerfile
steps:
- name: Checkout code from the tag generated above
uses: actions/checkout@v5
with:
ref: ${{ needs.release.outputs.new_tag }}
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
- name: Log in to Quay.io
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Log in to Red Hat Container Registry
uses: docker/login-action@v3
with:
registry: registry.redhat.io
username: ${{ secrets.REDHAT_USERNAME }}
password: ${{ secrets.REDHAT_PASSWORD }}
- name: Build and push ${{ matrix.component.name }} image
if: github.event.inputs.force_build_all == 'true' || contains(github.event.inputs.components, matrix.component.name) || (github.event.inputs.components == '' && github.event.inputs.force_build_all != 'false')
uses: docker/build-push-action@v6
with:
context: ${{ matrix.component.context }}
file: ${{ matrix.component.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ matrix.component.image }}:${{ needs.release.outputs.new_tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-to-openshift:
runs-on: ubuntu-latest
needs: [release, build-and-push]
steps:
- name: Checkout code from release tag
uses: actions/checkout@v5
with:
ref: ${{ needs.release.outputs.new_tag }}
- name: Install oc
uses: redhat-actions/oc-installer@v1
with:
oc_version: 'latest'
- name: Install kustomize
uses: imranismail/setup-kustomize@v2
with:
kustomize-version: '5.4.3'
- name: Log in to OpenShift Cluster
run: |
oc login ${{ secrets.PROD_OPENSHIFT_SERVER }} --token=${{ secrets.PROD_OPENSHIFT_TOKEN }} --insecure-skip-tls-verify
- name: Deploy observability stack
run: |
oc apply -k components/manifests/observability/
- name: Update kustomization with release image tags
working-directory: components/manifests/overlays/production
run: |
RELEASE_TAG="${{ needs.release.outputs.new_tag }}"
kustomize edit set image quay.io/ambient_code/vteam_frontend:latest=quay.io/ambient_code/vteam_frontend:${RELEASE_TAG}
kustomize edit set image quay.io/ambient_code/vteam_backend:latest=quay.io/ambient_code/vteam_backend:${RELEASE_TAG}
kustomize edit set image quay.io/ambient_code/vteam_operator:latest=quay.io/ambient_code/vteam_operator:${RELEASE_TAG}
kustomize edit set image quay.io/ambient_code/vteam_claude_runner:latest=quay.io/ambient_code/vteam_claude_runner:${RELEASE_TAG}
kustomize edit set image quay.io/ambient_code/vteam_state_sync:latest=quay.io/ambient_code/vteam_state_sync:${RELEASE_TAG}
- name: Validate kustomization
working-directory: components/manifests/overlays/production
run: |
kustomize build . > /dev/null
echo "✅ Kustomization validation passed"
- name: Apply production overlay with kustomize
working-directory: components/manifests/overlays/production
run: |
oc apply -k . -n ambient-code
- name: Update frontend environment variables
run: |
oc set env deployment/frontend -n ambient-code -c frontend \
GITHUB_APP_SLUG="ambient-code" \
VTEAM_VERSION="${{ needs.release.outputs.new_tag }}"
- name: Update backend environment variables
run: |
oc set env deployment/backend-api -n ambient-code -c backend-api \
CONTENT_SERVICE_IMAGE="quay.io/ambient_code/vteam_backend:${{ needs.release.outputs.new_tag }}"
- name: Update operator environment variables
run: |
oc set env deployment/agentic-operator -n ambient-code -c agentic-operator \
AMBIENT_CODE_RUNNER_IMAGE="quay.io/ambient_code/vteam_claude_runner:${{ needs.release.outputs.new_tag }}" \
CONTENT_SERVICE_IMAGE="quay.io/ambient_code/vteam_backend:${{ needs.release.outputs.new_tag }}" \
STATE_SYNC_IMAGE="quay.io/ambient_code/vteam_state_sync:${{ needs.release.outputs.new_tag }}"