Skip to content

Merge pull request #153 from google/fedebongio-openclaw #118

Merge pull request #153 from google/fedebongio-openclaw

Merge pull request #153 from google/fedebongio-openclaw #118

Workflow file for this run

name: Deploy SAM Hub
on:
push:
branches:
- main
tags:
- 'v*.*.*'
workflow_dispatch:
env:
REGISTRY: ghcr.io
jobs:
publish:
name: Publish Images
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
timeout-minutes: 100
steps:
- name: Check out code
uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Hub
id: meta-hub
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/google/sam-hub
tags: |
type=ref,event=branch
type=ref,event=tag
type=raw,value=${{ github.sha }}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
- name: Build and push Hub image
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile.sam-hub
platforms: ${{ github.ref_type == 'tag' && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
push: true
tags: ${{ steps.meta-hub.outputs.tags }}
labels: ${{ steps.meta-hub.outputs.labels }}
- name: Extract metadata for Node
id: meta-node
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/google/sam-node
tags: |
type=ref,event=branch
type=ref,event=tag
type=raw,value=${{ github.sha }}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
- name: Build and push Node image
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile.sam-node
platforms: ${{ github.ref_type == 'tag' && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
push: true
tags: ${{ steps.meta-node.outputs.tags }}
labels: ${{ steps.meta-node.outputs.labels }}
deploy:
name: Deploy to GKE
runs-on: ubuntu-latest
needs: publish
# Dynamically select the GitHub Environment based on the trigger
environment: ${{ github.ref_type == 'tag' && 'hub' || 'bananas' }}
permissions:
contents: 'read'
id-token: 'write'
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Determine Image Tag
run: |
if [[ "${{ github.ref_type }}" == "tag" ]]; then
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
else
echo "IMAGE_TAG=${{ github.sha }}" >> $GITHUB_ENV
fi
- name: Google Auth
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ vars.WIF_PROVIDER_NAME }}
service_account: ${{ vars.SERVICE_ACCOUNT_EMAIL }}
- name: Set up GKE credentials
uses: google-github-actions/get-gke-credentials@v3
with:
cluster_name: ${{ vars.CLUSTER_NAME }}
location: ${{ vars.CLUSTER_REGION }}
- name: Prepare Namespaces
run: |
export ENV_NAME="${{ vars.ENV_NAME }}"
export NAMESPACE="sam-${ENV_NAME}"
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
kubectl create namespace dex --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace ${NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace ${CANARY_NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
- name: Provision Dex Secrets
env:
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
GITHUB_CLIENT_ID: ${{ secrets.GHUB_CLIENT_ID }}
GITHUB_CLIENT_SECRET: ${{ secrets.GHUB_CLIENT_SECRET }}
CLI_OAUTH_SECRET: ${{ secrets.CLI_OAUTH_SECRET }}
run: |
# Declaratively apply secrets without writing them to disk
kubectl create secret generic dex-secrets \
--namespace=dex \
--from-literal=google-client-id="${GOOGLE_CLIENT_ID}" \
--from-literal=google-client-secret="${GOOGLE_CLIENT_SECRET}" \
--from-literal=github-client-id="${GITHUB_CLIENT_ID}" \
--from-literal=github-client-secret="${GITHUB_CLIENT_SECRET}" \
--from-literal=cli-oauth-secret="${CLI_OAUTH_SECRET}" \
--dry-run=client -o yaml | kubectl apply -f -
- name: Deploy Dex Resources
run: |
print_rollout_diagnostics() {
local namespace="$1"
local deployment="$2"
local selector="$3"
echo "Collecting diagnostics for deployment/${deployment} in namespace ${namespace}..."
kubectl describe deployment/${deployment} -n ${namespace} || true
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
kubectl describe pods -n ${namespace} -l "${selector}" || true
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
echo "==== Describe ${pod} ===="
kubectl describe -n ${namespace} "${pod}" || true
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
echo "==== Logs for ${pod} container ${container} ===="
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
done
done
}
# Apply the Dex manifests
kubectl apply -f .github/k8s/dex-config.yaml
kubectl apply -f .github/k8s/dex-deployment.yaml
# Force a restart to pick up any changes to the secrets, and wait for readiness
kubectl rollout restart deployment/dex -n dex
kubectl rollout status deployment/dex -n dex --timeout=120s || {
echo "Dex Deployment failed!"
print_rollout_diagnostics "dex" "dex" "app=dex"
echo "Rolling back Dex deployment..."
kubectl rollout undo deployment/dex -n dex
exit 1
}
- name: Provision Hub Secrets
run: |
export ENV_NAME="${{ vars.ENV_NAME }}"
export NAMESPACE="sam-${ENV_NAME}"
# Idempotently provision the sam-hub-secret if it doesn't already exist
if ! kubectl get secret sam-hub-secret -n ${NAMESPACE} >/dev/null 2>&1; then
echo "Generating a new random 32-byte hex key for SAM_HUB_KEY..."
HUB_KEY=$(openssl rand -hex 32)
kubectl create secret generic sam-hub-secret \
--namespace=${NAMESPACE} \
--from-literal=sam-hub-key="${HUB_KEY}"
else
echo "sam-hub-secret already exists in namespace ${NAMESPACE}. Skipping generation."
fi
- name: Deploy Hub
run: |
print_rollout_diagnostics() {
local namespace="$1"
local resource="$2"
local selector="$3"
echo "Collecting diagnostics for ${resource} in namespace ${namespace}..."
kubectl describe ${resource} -n ${namespace} || true
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
kubectl describe pods -n ${namespace} -l "${selector}" || true
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
echo "==== Describe ${pod} ===="
kubectl describe -n ${namespace} "${pod}" || true
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
echo "==== Logs for ${pod} container ${container} ===="
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
done
done
}
sudo apt-get install -y gettext-base
export ENV_NAME="${{ vars.ENV_NAME }}"
export LOAD_BALANCER_IP="${{ vars.LOAD_BALANCER_IP }}"
export EXTERNAL_MULTIADDR="${{ vars.EXTERNAL_MULTIADDR }}"
export NAMESPACE="sam-${ENV_NAME}"
export IMAGE_TAG="${{ env.IMAGE_TAG }}"
export GCP_PROJECT_ID="${{ vars.GCP_PROJECT_ID }}"
export CLUSTER_REGION="${{ vars.CLUSTER_REGION }}"
export CLUSTER_NAME="${{ vars.CLUSTER_NAME }}"
envsubst '${ENV_NAME} ${NAMESPACE} ${GCP_PROJECT_ID} ${CLUSTER_NAME} ${CLUSTER_REGION} ${IMAGE_TAG}' < .github/k8s/sam-hub-template.yaml | kubectl apply -f -
envsubst '${ENV_NAME} ${NAMESPACE} ${GCP_PROJECT_ID}' < .github/k8s/dns-sync-cronjob-template.yaml | kubectl apply -f -
kubectl rollout status statefulset/sam-hub-${ENV_NAME} -n ${NAMESPACE} --timeout=120s || {
echo "Hub Deployment failed!"
print_rollout_diagnostics "${NAMESPACE}" "statefulset/sam-hub-${ENV_NAME}" "app=sam-hub-${ENV_NAME}"
echo "Rolling back Hub deployment..."
kubectl rollout undo statefulset/sam-hub-${ENV_NAME} -n ${NAMESPACE}
kubectl rollout status statefulset/sam-hub-${ENV_NAME} -n ${NAMESPACE}
exit 1
}
echo "Triggering immediate DNS sync after Hub rollout..."
kubectl delete job dns-sync-rollout-${ENV_NAME} -n ${NAMESPACE} --ignore-not-found || true
kubectl create job --from=cronjob/dns-sync-cronjob dns-sync-rollout-${ENV_NAME} -n ${NAMESPACE}
echo "Waiting for DNS sync job to complete..."
kubectl wait --for=condition=complete job/dns-sync-rollout-${ENV_NAME} -n ${NAMESPACE} --timeout=180s || {
echo "DNS sync job failed or timed out!"
kubectl describe job/dns-sync-rollout-${ENV_NAME} -n ${NAMESPACE}
kubectl logs -n ${NAMESPACE} -l job-name=dns-sync-rollout-${ENV_NAME} --tail=-1 || true
exit 1
}
- name: Deploy Canaries
run: |
print_rollout_diagnostics() {
local namespace="$1"
local deployment="$2"
local selector="$3"
echo "Collecting diagnostics for deployment/${deployment} in namespace ${namespace}..."
kubectl describe deployment/${deployment} -n ${namespace} || true
kubectl get pods -n ${namespace} -l "${selector}" -o wide || true
kubectl describe pods -n ${namespace} -l "${selector}" || true
kubectl get events -n ${namespace} --sort-by=.lastTimestamp || true
for pod in $(kubectl get pods -n ${namespace} -l "${selector}" -o name 2>/dev/null); do
echo "==== Describe ${pod} ===="
kubectl describe -n ${namespace} "${pod}" || true
for container in $(kubectl get -n ${namespace} "${pod}" -o jsonpath='{.spec.containers[*].name}' 2>/dev/null); do
echo "==== Logs for ${pod} container ${container} ===="
kubectl logs -n ${namespace} "${pod#pod/}" -c "${container}" --tail=-1 || true
done
done
}
export ENV_NAME="${{ vars.ENV_NAME }}"
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
export NAMESPACE="sam-${ENV_NAME}"
export IMAGE_TAG="${{ env.IMAGE_TAG }}"
export GEMINI_API_KEY="${{ secrets.GEMINI_API_KEY }}"
export HF_TOKEN="${{ secrets.HF_TOKEN }}"
# Read and indent banana bot playground script for the ConfigMap template
export BANANA_BOT_SCRIPT=$(cat docs/snippets/banana_bot_playground.py | sed 's/^/ /')
# Provision the openclaw-secret, preserving the gateway-token if it exists
GATEWAY_TOKEN=$(kubectl get secret openclaw-secret-${ENV_NAME} -n ${CANARY_NAMESPACE} -o jsonpath="{.data.gateway-token}" 2>/dev/null | base64 -d || openssl rand -hex 16)
kubectl create secret generic openclaw-secret-${ENV_NAME} \
--namespace=${CANARY_NAMESPACE} \
--from-literal=gateway-token="${GATEWAY_TOKEN}" \
--from-literal=gemini-api-key="${GEMINI_API_KEY}" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic vllm-secret-${ENV_NAME} \
--namespace=${CANARY_NAMESPACE} \
--from-literal=hf-token="${HF_TOKEN}" \
--dry-run=client -o yaml | kubectl apply -f -
envsubst < .github/k8s/sam-node-template.yaml | kubectl apply -f -
envsubst < .github/k8s/sam-node-openclaw-template.yaml | kubectl apply -f -
envsubst < .github/k8s/sam-node-vllm-template.yaml | kubectl apply -f -
envsubst < .github/k8s/sam-node-everything-template.yaml | kubectl apply -f -
envsubst < .github/k8s/sam-node-cop-template.yaml | kubectl apply -f -
kubectl rollout status deployment/cop-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "Cop Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "cop-canary-${ENV_NAME}" "app=cop-canary-${ENV_NAME}"
exit 1
}
kubectl rollout status deployment/sam-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "sam-canary-${ENV_NAME}" "app=sam-canary-${ENV_NAME}"
echo "Rolling back Canary deployment..."
kubectl rollout undo deployment/sam-canary-${ENV_NAME} -n ${CANARY_NAMESPACE}
kubectl rollout status deployment/sam-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} || true
exit 1
}
kubectl rollout status deployment/openclaw-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "OpenClaw Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "openclaw-canary-${ENV_NAME}" "app=openclaw-canary-${ENV_NAME}"
echo "Rolling back OpenClaw Canary deployment..."
kubectl rollout undo deployment/openclaw-canary-${ENV_NAME} -n ${CANARY_NAMESPACE}
kubectl rollout status deployment/openclaw-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} || true
exit 1
}
kubectl rollout status deployment/vllm-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=300s || {
echo "vLLM Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "vllm-canary-${ENV_NAME}" "app=vllm-canary-${ENV_NAME}"
echo "Rolling back vLLM Canary deployment..."
kubectl rollout undo deployment/vllm-canary-${ENV_NAME} -n ${CANARY_NAMESPACE}
kubectl rollout status deployment/vllm-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} || true
exit 1
}
kubectl rollout status deployment/everything-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "Everything Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "everything-canary-${ENV_NAME}" "app=everything-canary-${ENV_NAME}"
echo "Rolling back Everything Canary deployment..."
kubectl rollout undo deployment/everything-canary-${ENV_NAME} -n ${CANARY_NAMESPACE}
kubectl rollout status deployment/everything-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} || true
exit 1
}