Skip to content

feat(frontend): show workspace name in sidebar #3403

feat(frontend): show workspace name in sidebar

feat(frontend): show workspace name in sidebar #3403

name: Build and Push Component Docker Images
on:
push:
branches: [main, alpha]
paths:
- '.github/workflows/components-build-deploy.yml'
- 'components/manifests/**'
- 'components/runners/**'
- 'components/operator/**'
- 'components/backend/**'
- 'components/frontend/**'
- 'components/public-api/**'
- 'components/ambient-api-server/**'
pull_request:
branches: [main, alpha]
paths:
- '.github/workflows/components-build-deploy.yml'
- 'components/manifests/**'
- 'components/runners/**'
- 'components/operator/**'
- 'components/backend/**'
- 'components/frontend/**'
- 'components/public-api/**'
- 'components/ambient-api-server/**'
workflow_dispatch:
inputs:
components:
description: 'Components to build (comma-separated: frontend,backend,operator,ambient-runner,state-sync,public-api,ambient-api-server) - leave empty for all'
required: false
type: string
default: ''
concurrency:
group: components-build-deploy-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
detect-changes:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
outputs:
build-matrix: ${{ steps.matrix.outputs.build-matrix }}
merge-matrix: ${{ steps.matrix.outputs.merge-matrix }}
has-builds: ${{ steps.matrix.outputs.has-builds }}
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
operator: ${{ steps.filter.outputs.operator }}
claude-runner: ${{ steps.filter.outputs.claude-runner }}
state-sync: ${{ steps.filter.outputs.state-sync }}
ambient-api-server: ${{ steps.filter.outputs.ambient-api-server }}
public-api: ${{ steps.filter.outputs.public-api }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check for component changes
uses: dorny/paths-filter@v4
id: filter
with:
filters: |
frontend:
- 'components/frontend/**'
backend:
- 'components/backend/**'
operator:
- 'components/operator/**'
claude-runner:
- 'components/runners/ambient-runner/**'
state-sync:
- 'components/runners/state-sync/**'
public-api:
- 'components/public-api/**'
ambient-api-server:
- 'components/ambient-api-server/**'
- name: Build component matrices
id: matrix
run: |
# All components definition
ALL_COMPONENTS='[
{"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":"ambient-runner","context":"./components/runners","image":"quay.io/ambient_code/vteam_claude_runner","dockerfile":"./components/runners/ambient-runner/Dockerfile"},
{"name":"state-sync","context":"./components/runners/state-sync","image":"quay.io/ambient_code/vteam_state_sync","dockerfile":"./components/runners/state-sync/Dockerfile"},
{"name":"public-api","context":"./components/public-api","image":"quay.io/ambient_code/vteam_public_api","dockerfile":"./components/public-api/Dockerfile"},
{"name":"ambient-api-server","context":"./components/ambient-api-server","image":"quay.io/ambient_code/vteam_api_server","dockerfile":"./components/ambient-api-server/Dockerfile"}
]'
SELECTED="${{ github.event.inputs.components }}"
if [ -n "$SELECTED" ]; then
# Dispatch with specific components
FILTERED=$(echo "$ALL_COMPONENTS" | jq -c --arg sel "$SELECTED" '[.[] | select(.name as $n | $sel | split(",") | map(gsub("^\\s+|\\s+$";"")) | index($n))]')
else
# Build all components — cache makes unchanged components fast
FILTERED="$ALL_COMPONENTS"
fi
# Build matrix includes context/dockerfile; merge matrix only needs name/image
BUILD_MATRIX=$(echo "$FILTERED" | jq -c '.')
MERGE_MATRIX=$(echo "$FILTERED" | jq -c '[.[] | {name, image}]')
HAS_BUILDS=$(echo "$FILTERED" | jq -c 'length > 0')
echo "build-matrix=$BUILD_MATRIX" >> $GITHUB_OUTPUT
echo "merge-matrix=$MERGE_MATRIX" >> $GITHUB_OUTPUT
echo "has-builds=$HAS_BUILDS" >> $GITHUB_OUTPUT
echo "Components to build:"
echo "$FILTERED" | jq -r '.[].name'
build:
needs: detect-changes
if: needs.detect-changes.outputs.has-builds == 'true'
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
strategy:
fail-fast: false
matrix:
# IMPORTANT: suffix values must match the hardcoded suffixes in
# merge-manifests. Update both together if arches change.
arch:
- runner: ubuntu-latest
platform: linux/amd64
suffix: amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
suffix: arm64
component: ${{ fromJSON(needs.detect-changes.outputs.build-matrix) }}
runs-on: ${{ matrix.arch.runner }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- 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 }} (${{ matrix.arch.suffix }})
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v7
with:
context: ${{ matrix.component.context }}
file: ${{ matrix.component.dockerfile }}
platforms: ${{ matrix.arch.platform }}
push: true
tags: ${{ matrix.component.image }}:${{ github.sha }}-${{ matrix.arch.suffix }}
build-args: AMBIENT_VERSION=${{ github.sha }}
cache-from: type=gha,scope=${{ matrix.component.name }}-${{ matrix.arch.suffix }}
cache-to: type=gha,mode=max,scope=${{ matrix.component.name }}-${{ matrix.arch.suffix }}
- name: Build and push ${{ matrix.component.name }} (${{ matrix.arch.suffix }}) for pull request
if: github.event_name == 'pull_request'
uses: docker/build-push-action@v7
with:
context: ${{ matrix.component.context }}
file: ${{ matrix.component.dockerfile }}
platforms: ${{ matrix.arch.platform }}
push: true
tags: ${{ matrix.component.image }}:pr-${{ github.event.pull_request.number }}-${{ matrix.arch.suffix }}
build-args: AMBIENT_VERSION=${{ github.sha }}
cache-from: type=gha,scope=${{ matrix.component.name }}-${{ matrix.arch.suffix }}
cache-to: type=gha,mode=max,scope=${{ matrix.component.name }}-${{ matrix.arch.suffix }}
merge-manifests:
needs: [detect-changes, build]
if: needs.detect-changes.outputs.has-builds == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
component: ${{ fromJSON(needs.detect-changes.outputs.merge-matrix) }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Quay.io
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Create multi-arch manifest for ${{ matrix.component.name }} (main)
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
# Suffixes (-amd64, -arm64) must match the arch matrix in the build job above.
# Arch-suffixed tags remain in the registry after merging. Clean these up
# via Quay tag expiration policies or a periodic job.
run: |
docker buildx imagetools create \
-t ${{ matrix.component.image }}:latest \
-t ${{ matrix.component.image }}:${{ github.sha }} \
-t ${{ matrix.component.image }}:stage \
${{ matrix.component.image }}:${{ github.sha }}-amd64 \
${{ matrix.component.image }}:${{ github.sha }}-arm64
- name: Create multi-arch manifest for ${{ matrix.component.name }} (alpha)
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/alpha'
run: |
docker buildx imagetools create \
-t ${{ matrix.component.image }}:alpha \
-t ${{ matrix.component.image }}:${{ github.sha }} \
${{ matrix.component.image }}:${{ github.sha }}-amd64 \
${{ matrix.component.image }}:${{ github.sha }}-arm64
- name: Create multi-arch manifest for ${{ matrix.component.name }} (pull request)
if: github.event_name == 'pull_request'
run: |
docker buildx imagetools create \
-t ${{ matrix.component.image }}:pr-${{ github.event.pull_request.number }} \
${{ matrix.component.image }}:pr-${{ github.event.pull_request.number }}-amd64 \
${{ matrix.component.image }}:pr-${{ github.event.pull_request.number }}-arm64
update-rbac-and-crd:
runs-on: ubuntu-latest
needs: [detect-changes, merge-manifests]
if: always() && !cancelled() && (github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install oc
uses: redhat-actions/oc-installer@v1
with:
oc_version: 'latest'
- name: Log in to OpenShift Cluster
run: |
oc login ${{ secrets.OPENSHIFT_SERVER }} --token=${{ secrets.OPENSHIFT_TOKEN }} --insecure-skip-tls-verify
- name: Apply RBAC and CRD manifests
run: |
oc apply -k components/manifests/base/crds/
oc apply -k components/manifests/base/rbac/
oc apply -f components/manifests/overlays/production/operator-config-openshift.yaml -n ambient-code
- name: Deploy observability stack
run: |
oc apply -k components/manifests/observability/
deploy-to-openshift:
runs-on: ubuntu-latest
needs: [detect-changes, merge-manifests, update-rbac-and-crd]
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && (needs.detect-changes.outputs.frontend == 'true' || needs.detect-changes.outputs.backend == 'true' || needs.detect-changes.outputs.operator == 'true' || needs.detect-changes.outputs.claude-runner == 'true' || needs.detect-changes.outputs.state-sync == 'true' || needs.detect-changes.outputs.ambient-api-server == 'true' || needs.detect-changes.outputs.public-api == 'true')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install oc
uses: redhat-actions/oc-installer@v1
with:
oc_version: 'latest'
- name: Install kustomize
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
kustomize version
- name: Log in to OpenShift Cluster
run: |
oc login ${{ secrets.OPENSHIFT_SERVER }} --token=${{ secrets.OPENSHIFT_TOKEN }} --insecure-skip-tls-verify
- name: Determine image tags
id: image-tags
run: |
if [ "${{ needs.detect-changes.outputs.frontend }}" == "true" ]; then
echo "frontend_tag=${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "frontend_tag=stage" >> $GITHUB_OUTPUT
fi
if [ "${{ needs.detect-changes.outputs.backend }}" == "true" ]; then
echo "backend_tag=${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "backend_tag=stage" >> $GITHUB_OUTPUT
fi
if [ "${{ needs.detect-changes.outputs.operator }}" == "true" ]; then
echo "operator_tag=${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "operator_tag=stage" >> $GITHUB_OUTPUT
fi
if [ "${{ needs.detect-changes.outputs.claude-runner }}" == "true" ]; then
echo "runner_tag=${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "runner_tag=latest" >> $GITHUB_OUTPUT
fi
if [ "${{ needs.detect-changes.outputs.state-sync }}" == "true" ]; then
echo "state_sync_tag=${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "state_sync_tag=latest" >> $GITHUB_OUTPUT
fi
if [ "${{ needs.detect-changes.outputs.ambient-api-server }}" == "true" ]; then
echo "api_server_tag=${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "api_server_tag=stage" >> $GITHUB_OUTPUT
fi
if [ "${{ needs.detect-changes.outputs.public-api }}" == "true" ]; then
echo "public_api_tag=${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "public_api_tag=stage" >> $GITHUB_OUTPUT
fi
- name: Update kustomization with image tags
working-directory: components/manifests/overlays/production
run: |
kustomize edit set image quay.io/ambient_code/vteam_frontend:latest=quay.io/ambient_code/vteam_frontend:${{ steps.image-tags.outputs.frontend_tag }}
kustomize edit set image quay.io/ambient_code/vteam_backend:latest=quay.io/ambient_code/vteam_backend:${{ steps.image-tags.outputs.backend_tag }}
kustomize edit set image quay.io/ambient_code/vteam_operator:latest=quay.io/ambient_code/vteam_operator:${{ steps.image-tags.outputs.operator_tag }}
kustomize edit set image quay.io/ambient_code/vteam_claude_runner:latest=quay.io/ambient_code/vteam_claude_runner:${{ steps.image-tags.outputs.runner_tag }}
kustomize edit set image quay.io/ambient_code/vteam_state_sync:latest=quay.io/ambient_code/vteam_state_sync:${{ steps.image-tags.outputs.state_sync_tag }}
kustomize edit set image quay.io/ambient_code/vteam_api_server:latest=quay.io/ambient_code/vteam_api_server:${{ steps.image-tags.outputs.api_server_tag }}
kustomize edit set image quay.io/ambient_code/vteam_public_api:latest=quay.io/ambient_code/vteam_public_api:${{ steps.image-tags.outputs.public_api_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
if: needs.detect-changes.outputs.frontend == 'true'
run: |
oc set env deployment/frontend -n ambient-code -c frontend \
GITHUB_APP_SLUG="ambient-code-stage" \
FEEDBACK_URL="https://forms.gle/7XiWrvo6No922DUz6"
- name: Update operator environment variables
if: needs.detect-changes.outputs.operator == 'true' || needs.detect-changes.outputs.backend == 'true' || needs.detect-changes.outputs.claude-runner == 'true' || needs.detect-changes.outputs.state-sync == 'true'
run: |
oc set env deployment/agentic-operator -n ambient-code -c agentic-operator \
AMBIENT_CODE_RUNNER_IMAGE="quay.io/ambient_code/vteam_claude_runner:${{ steps.image-tags.outputs.runner_tag }}" \
STATE_SYNC_IMAGE="quay.io/ambient_code/vteam_state_sync:${{ steps.image-tags.outputs.state_sync_tag }}"
- name: Update agent registry ConfigMap with pinned image tags
if: needs.detect-changes.outputs.claude-runner == 'true' || needs.detect-changes.outputs.state-sync == 'true'
run: |
# Fetch live JSON from cluster so unchanged images keep their current tag
REGISTRY=$(oc get configmap ambient-agent-registry -n ambient-code \
-o jsonpath='{.data.agent-registry\.json}')
# Only replace images that were actually rebuilt this run.
# Pattern [@:][^"]* matches both :tag and @sha256:digest refs.
if [ "${{ needs.detect-changes.outputs.claude-runner }}" == "true" ]; then
REGISTRY=$(echo "$REGISTRY" | sed \
"s|quay.io/ambient_code/vteam_claude_runner[@:][^\"]*|quay.io/ambient_code/vteam_claude_runner:${{ github.sha }}|g")
fi
if [ "${{ needs.detect-changes.outputs.state-sync }}" == "true" ]; then
REGISTRY=$(echo "$REGISTRY" | sed \
"s|quay.io/ambient_code/vteam_state_sync[@:][^\"]*|quay.io/ambient_code/vteam_state_sync:${{ github.sha }}|g")
fi
oc patch configmap ambient-agent-registry -n ambient-code --type=merge \
-p "{\"data\":{\"agent-registry.json\":$(echo "$REGISTRY" | jq -Rs .)}}"
deploy-with-disptach:
runs-on: ubuntu-latest
needs: [detect-changes, merge-manifests, update-rbac-and-crd]
if: github.event_name == 'workflow_dispatch'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install oc
uses: redhat-actions/oc-installer@v1
with:
oc_version: 'latest'
- name: Install kustomize
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
kustomize version
- name: Log in to OpenShift Cluster
run: |
oc login ${{ secrets.OPENSHIFT_SERVER }} --token=${{ secrets.OPENSHIFT_TOKEN }} --insecure-skip-tls-verify
- name: Update kustomization with stage image tags
working-directory: components/manifests/overlays/production
run: |
kustomize edit set image quay.io/ambient_code/vteam_frontend:latest=quay.io/ambient_code/vteam_frontend:stage
kustomize edit set image quay.io/ambient_code/vteam_backend:latest=quay.io/ambient_code/vteam_backend:stage
kustomize edit set image quay.io/ambient_code/vteam_operator:latest=quay.io/ambient_code/vteam_operator:stage
kustomize edit set image quay.io/ambient_code/vteam_claude_runner:latest=quay.io/ambient_code/vteam_claude_runner:stage
kustomize edit set image quay.io/ambient_code/vteam_state_sync:latest=quay.io/ambient_code/vteam_state_sync:stage
kustomize edit set image quay.io/ambient_code/vteam_api_server:latest=quay.io/ambient_code/vteam_api_server:stage
- 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-stage" \
FEEDBACK_URL="https://forms.gle/7XiWrvo6No922DUz6"
- name: Determine which components were built
id: built
run: |
MATRIX='${{ needs.detect-changes.outputs.build-matrix }}'
echo "has-runner=$(echo "$MATRIX" | jq -r '[.[].name] | if index("ambient-runner") then "true" else "false" end')" >> $GITHUB_OUTPUT
echo "has-state-sync=$(echo "$MATRIX" | jq -r '[.[].name] | if index("state-sync") then "true" else "false" end')" >> $GITHUB_OUTPUT
- name: Update operator environment variables
if: steps.built.outputs.has-runner == 'true' || steps.built.outputs.has-state-sync == 'true'
run: |
ARGS=""
if [ "${{ steps.built.outputs.has-runner }}" == "true" ]; then
ARGS="$ARGS AMBIENT_CODE_RUNNER_IMAGE=quay.io/ambient_code/vteam_claude_runner:${{ github.sha }}"
fi
if [ "${{ steps.built.outputs.has-state-sync }}" == "true" ]; then
ARGS="$ARGS STATE_SYNC_IMAGE=quay.io/ambient_code/vteam_state_sync:${{ github.sha }}"
fi
oc set env deployment/agentic-operator -n ambient-code -c agentic-operator $ARGS
- name: Update agent registry ConfigMap with pinned image tags
if: steps.built.outputs.has-runner == 'true' || steps.built.outputs.has-state-sync == 'true'
run: |
REGISTRY=$(oc get configmap ambient-agent-registry -n ambient-code \
-o jsonpath='{.data.agent-registry\.json}')
if [ "${{ steps.built.outputs.has-runner }}" == "true" ]; then
REGISTRY=$(echo "$REGISTRY" | sed \
"s|quay.io/ambient_code/vteam_claude_runner[@:][^\"]*|quay.io/ambient_code/vteam_claude_runner:${{ github.sha }}|g")
fi
if [ "${{ steps.built.outputs.has-state-sync }}" == "true" ]; then
REGISTRY=$(echo "$REGISTRY" | sed \
"s|quay.io/ambient_code/vteam_state_sync[@:][^\"]*|quay.io/ambient_code/vteam_state_sync:${{ github.sha }}|g")
fi
oc patch configmap ambient-agent-registry -n ambient-code --type=merge \
-p "{\"data\":{\"agent-registry.json\":$(echo "$REGISTRY" | jq -Rs .)}}"