ai-code-battle/manifests/acb-evolved-bot-deploy-workflowtemplate.yml
jedarden f4352c6304 feat(evolver): add workflow completion polling to promoter
Per plan §10.8 (deployment pipeline) and §9.8 (Argo Workflows):

- Add waitForWorkflowCompletion() that polls Argo Workflow API
- Add getWorkflowStatus() to fetch workflow phase/status
- Update Promote() to wait for workflow completion before inserting bot record
- Update Promote() to wait for K8s deployment readiness (waitForDeployment)
- Update triggerArgoWorkflow() to return workflow name for polling
- Add acb-evolved-bot-deploy-workflowtemplate.yml to manifests

The promotion flow now:
1. Writes bot source to bots/evolved/<bot_name>/
2. Commits and pushes source to git
3. Triggers Argo WorkflowTemplate
4. Waits for workflow completion (build + manifest commit)
5. Waits for K8s deployment to be ready
6. Inserts bot record into bots table
7. Updates programs table with bot_id/bot_name

This ensures evolved bots have running containers before being marked active.
2026-04-22 17:46:33 -04:00

450 lines
14 KiB
YAML

# Argo WorkflowTemplate for deploying evolved bots
# Sync to: declarative-config/k8s/apexalgo-iad/argo-workflows/
#
# Triggered by the evolver when a candidate is promoted.
# The promoter commits bot source to bots/evolved/<bot_name>/ before triggering.
#
# This workflow:
# 1. Clones the ai-code-battle repo (bot source already committed)
# 2. Generates Dockerfile for the language
# 3. Builds container image with Kaniko
# 4. Pushes to Forgejo registry
# 5. Creates K8s Secret, Deployment, Service manifests
# 6. Commits manifests to declarative-config repo (ArgoCD syncs them)
---
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: acb-evolved-bot-deploy
namespace: argo-workflows
labels:
app: acb-evolved-bot-deploy
spec:
entrypoint: deploy-evolved-bot
serviceAccountName: argo-workflow
arguments:
parameters:
- name: bot_name
# e.g., acb-evo-123
- name: bot_secret
# Base64-encoded bot shared secret
- name: language
# go, python, rust, typescript, java, php
- name: island
# Evolution island identifier (alpha, beta, gamma, delta)
- name: generation
# Generation number
- name: program_id
# Program ID from database
- name: bot_repo
value: https://forgejo.ardenone.com/ai-code-battle/ai-code-battle.git
- name: bot_branch
value: master
- name: bot_path
# Path to bot source in repo (relative to repo root)
# The promoter writes to bots/evolved/<bot_name>/
- name: declarative_config_repo
value: https://forgejo.ardenone.com/infra/ardenone-cluster.git
- name: declarative_config_branch
value: main
- name: registry
value: forgejo.ardenone.com/ai-code-battle
- name: namespace
value: ai-code-battle
- name: bot_port
value: "8080"
volumes:
- name: workspace
emptyDir: {}
- name: docker-config
secret:
secretName: forgejo-registry
items:
- key: .dockerconfigjson
path: config.json
templates:
- name: deploy-evolved-bot
dag:
tasks:
- name: clone
template: clone-bot-source
- name: dockerfile
template: generate-dockerfile
dependencies: [clone]
- name: build
template: build-and-push
dependencies: [dockerfile]
- name: manifest
template: create-manifests
dependencies: [build]
- name: commit
template: commit-manifests
dependencies: [manifest]
- name: clone-bot-source
script:
image: alpine:3.21
command: [sh, -c]
source: |
set -e
apk add --no-cache git >/dev/null 2>&1
# Clone the ai-code-battle repo to get bot source
# The promoter has already committed the bot source before triggering this workflow
git clone --depth 1 --branch "{{workflow.parameters.bot_branch}}" \
"{{workflow.parameters.bot_repo}}" /workspace/bot-src 2>/dev/null
# Verify bot source exists at expected path
BOT_SRC_PATH="/workspace/bot-src/{{workflow.parameters.bot_path}}"
if [ ! -d "$BOT_SRC_PATH" ]; then
echo "ERROR: Bot source directory not found: $BOT_SRC_PATH"
echo "Contents of /workspace/bot-src:"
find /workspace/bot-src -type d -name "evo*" | head -20 || true
exit 1
fi
# Copy bot source to workspace
mkdir -p /workspace/bot
cp -r "$BOT_SRC_PATH"/* /workspace/bot/
echo "Bot source copied from $BOT_SRC_PATH:"
ls -la /workspace/bot/
volumeMounts:
- name: workspace
mountPath: /workspace
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
activeDeadlineSeconds: 300
- name: generate-dockerfile
script:
image: alpine:3.21
command: [sh, -c]
source: |
set -e
BOT_DIR="/workspace/bot"
LANG="{{workflow.parameters.language}}"
PORT="{{workflow.parameters.bot_port}}"
case "$LANG" in
go)
cat > "${BOT_DIR}/Dockerfile" <<'EOF'
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.mod
COPY *.go .
RUN go build -o bot .
FROM alpine:3.21
WORKDIR /app
COPY --from=builder /app/bot .
ENV BOT_PORT=${PORT}
ENV BOT_SECRET=""
EXPOSE ${PORT}
CMD ["./bot"]
EOF
;;
python)
cat > "${BOT_DIR}/Dockerfile" <<'EOF'
FROM python:3.12-slim
WORKDIR /app
COPY *.py .
ENV BOT_PORT=${PORT}
ENV BOT_SECRET=""
EXPOSE ${PORT}
CMD ["python3", "bot.py"]
EOF
;;
rust)
cat > "${BOT_DIR}/Dockerfile" <<'EOF'
FROM rust:1.85-alpine AS builder
WORKDIR /app
COPY Cargo.toml Cargo.toml
COPY src ./src
RUN cargo build --release
FROM alpine:3.21
WORKDIR /app
COPY --from=builder /app/target/release/bot .
ENV BOT_PORT=${PORT}
ENV BOT_SECRET=""
EXPOSE ${PORT}
CMD ["./bot"]
EOF
;;
typescript)
cat > "${BOT_DIR}/Dockerfile" <<'EOF'
FROM node:22-alpine AS builder
WORKDIR /app
COPY *.ts .
RUN npm install -g typescript && tsc --target ES2020 --module commonjs bot.ts
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/bot.js .
ENV BOT_PORT=${PORT}
ENV BOT_SECRET=""
EXPOSE ${PORT}
CMD ["node", "bot.js"]
EOF
;;
java)
cat > "${BOT_DIR}/Dockerfile" <<'EOF'
FROM eclipse-temurin:21-alpine AS builder
WORKDIR /app
COPY *.java .
RUN javac *.java
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/*.class .
ENV BOT_PORT=${PORT}
ENV BOT_SECRET=""
EXPOSE ${PORT}
CMD ["java", "Bot"]
EOF
;;
php)
cat > "${BOT_DIR}/Dockerfile" <<'EOF'
FROM php:8.3-cli-alpine
WORKDIR /app
COPY *.php .
ENV BOT_PORT=${PORT}
ENV BOT_SECRET=""
EXPOSE ${PORT}
CMD ["php", "bot.php"]
EOF
;;
*)
echo "Unsupported language: $LANG" >&2
exit 1
;;
esac
# Replace ${PORT} with actual value
sed -i "s/\${PORT}/${PORT}/g" "${BOT_DIR}/Dockerfile"
echo "Dockerfile generated:"
cat "${BOT_DIR}/Dockerfile"
volumeMounts:
- name: workspace
mountPath: /workspace
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
activeDeadlineSeconds: 60
- name: build-and-push
script:
image: gcr.io/kaniko-project/executor:latest
command: [kaniko]
args:
- --context=/workspace/bot
- --dockerfile=/workspace/bot/Dockerfile
- --destination={{workflow.parameters.registry}}/{{workflow.parameters.bot_name}}:latest
- --destination={{workflow.parameters.registry}}/{{workflow.parameters.bot_name}}:gen-{{workflow.parameters.generation}}
- --cache=false
volumeMounts:
- name: workspace
mountPath: /workspace
- name: docker-config
mountPath: /kaniko/.docker
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 4000m
memory: 8Gi
activeDeadlineSeconds: 1800
- name: create-manifests
script:
image: alpine:3.21
command: [sh, -c]
source: |
set -e
apk add --no-cache git >/dev/null 2>&1
# Clone declarative-config repo
git clone --depth 1 --branch "{{workflow.parameters.declarative_config_branch}}" \
"{{workflow.parameters.declarative_config_repo}}" /tmp/config 2>/dev/null
cd /tmp/config
# Create directory for manifests (flat structure per CLAUDE.md norms)
MANIFEST_BASE="declarative-config/k8s/apexalgo-iad/ai-code-battle"
mkdir -p "${MANIFEST_BASE}"
BOT_NAME="{{workflow.parameters.bot_name}}"
NAMESPACE="{{workflow.parameters.namespace}}"
ISLAND="{{workflow.parameters.island}}"
GENERATION="{{workflow.parameters.generation}}"
REGISTRY="{{workflow.parameters.registry}}"
PORT="{{workflow.parameters.bot_port}}"
SECRET="{{workflow.parameters.bot_secret}}"
# Secret manifest
cat > "${MANIFEST_BASE}/${BOT_NAME}-secret.yaml" <<EOF
apiVersion: v1
kind: Secret
metadata:
name: ${BOT_NAME}-secret
namespace: ${NAMESPACE}
labels:
app.kubernetes.io/name: ${BOT_NAME}
app.kubernetes.io/part-of: ai-code-battle
app.kubernetes.io/component: evolved-bot
type: Opaque
data:
bot-secret: ${SECRET}
EOF
# Deployment manifest
cat > "${MANIFEST_BASE}/${BOT_NAME}-deployment.yaml" <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${BOT_NAME}
namespace: ${NAMESPACE}
labels:
app.kubernetes.io/name: ${BOT_NAME}
app.kubernetes.io/part-of: ai-code-battle
app.kubernetes.io/component: evolved-bot
acb/island: ${ISLAND}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ${BOT_NAME}
template:
metadata:
labels:
app.kubernetes.io/name: ${BOT_NAME}
app.kubernetes.io/part-of: ai-code-battle
app.kubernetes.io/component: evolved-bot
acb/island: ${ISLAND}
spec:
containers:
- name: bot
image: ${REGISTRY}/${BOT_NAME}:latest
env:
- name: BOT_PORT
value: "${PORT}"
- name: BOT_SECRET
valueFrom:
secretKeyRef:
name: ${BOT_NAME}-secret
key: bot-secret
ports:
- name: http
containerPort: ${PORT}
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 3
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
restartPolicy: Always
EOF
# Service manifest
cat > "${MANIFEST_BASE}/${BOT_NAME}-service.yaml" <<EOF
apiVersion: v1
kind: Service
metadata:
name: ${BOT_NAME}
namespace: ${NAMESPACE}
labels:
app.kubernetes.io/name: ${BOT_NAME}
app.kubernetes.io/part-of: ai-code-battle
app.kubernetes.io/component: evolved-bot
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: ${BOT_NAME}
ports:
- name: http
port: ${PORT}
targetPort: http
protocol: TCP
EOF
echo "Manifests created in ${MANIFEST_BASE}:"
ls -la "${MANIFEST_BASE}" | grep "${BOT_NAME}" || true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
activeDeadlineSeconds: 300
- name: commit-manifests
script:
image: alpine:3.21
command: [sh, -c]
source: |
set -e
apk add --no-cache git >/dev/null 2>&1
cd /tmp/config
# Configure git
git config user.name "ACB Evolver"
git config user.email "evolver@ai-code-battle.internal"
MANIFEST_BASE="declarative-config/k8s/apexalgo-iad/ai-code-battle"
BOT_NAME="{{workflow.parameters.bot_name}}"
PROGRAM_ID="{{workflow.parameters.program_id}}"
ISLAND="{{workflow.parameters.island}}"
GENERATION="{{workflow.parameters.generation}}"
# Stage new manifests
git add "${MANIFEST_BASE}/${BOT_NAME}-secret.yaml" || true
git add "${MANIFEST_BASE}/${BOT_NAME}-deployment.yaml" || true
git add "${MANIFEST_BASE}/${BOT_NAME}-service.yaml" || true
# Check if there's anything to commit
if git diff --cached --quiet; then
echo "No changes to commit (bot may already exist)"
exit 0
fi
# Commit and push
git commit -m "Add evolved bot ${BOT_NAME} (island=${ISLAND} gen=${GENERATION} program_id=${PROGRAM_ID})"
git push origin "{{workflow.parameters.declarative_config_branch}}"
echo "Manifests committed and pushed to declarative-config"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
activeDeadlineSeconds: 300