feat(ci): add code queue dev smoke

This commit is contained in:
Codex
2026-05-18 13:22:43 +00:00
parent bd74492bb9
commit ca10682063
7 changed files with 468 additions and 117 deletions
@@ -88,7 +88,7 @@ rules:
resources: ["namespaces"]
verbs: ["get", "list", "watch", "create", "delete", "patch"]
- apiGroups: [""]
resources: ["configmaps", "services", "pods", "pods/log"]
resources: ["configmaps", "secrets", "services", "services/proxy", "pods", "pods/log"]
verbs: ["get", "list", "watch", "create", "delete", "patch"]
- apiGroups: ["apps"]
resources: ["deployments"]
@@ -701,6 +701,8 @@ spec:
backend_commit="$(params.backend-core-commit)"
frontend_commit="$(params.frontend-commit)"
code_queue_commit="$(params.code-queue-commit)"
app_image="$(params.app-image)"
database_url="postgres://unidesk_ci:unidesk_ci_password@postgres-dev.$ns.svc.cluster.local:5432/unidesk_ci"
kube_api="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}"
kube_token="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
kube_ca="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
@@ -719,6 +721,210 @@ spec:
cat /tmp/unidesk-dev-e2e-delete-response >&2
return 1
}
proxy_get() {
local service_name="$1"
local path="$2"
curl -fsS --cacert "$kube_ca" -H "Authorization: Bearer $kube_token" \
"$kube_api/api/v1/namespaces/$ns/services/${service_name}:4222/proxy$path"
}
proxy_json() {
local method="$1"
local service_name="$2"
local path="$3"
local body="${4:-}"
if [ -n "$body" ]; then
curl -fsS --cacert "$kube_ca" -H "Authorization: Bearer $kube_token" -H "Content-Type: application/json" \
-X "$method" --data "$body" \
"$kube_api/api/v1/namespaces/$ns/services/${service_name}:4222/proxy$path"
else
curl -fsS --cacert "$kube_ca" -H "Authorization: Bearer $kube_token" \
-X "$method" \
"$kube_api/api/v1/namespaces/$ns/services/${service_name}:4222/proxy$path"
fi
}
wait_deployment_available() {
local deployment="$1"
local timeout_seconds="$2"
local deadline=$((SECONDS + timeout_seconds))
while [ "$SECONDS" -lt "$deadline" ]; do
status="$(kube GET "$kube_api/apis/apps/v1/namespaces/$ns/deployments/$deployment")"
replicas="$(printf '%s' "$status" | jq -r '.spec.replicas // 1')"
available="$(printf '%s' "$status" | jq -r '.status.availableReplicas // 0')"
updated="$(printf '%s' "$status" | jq -r '.status.updatedReplicas // 0')"
observed="$(printf '%s' "$status" | jq -r '.status.observedGeneration // 0')"
generation="$(printf '%s' "$status" | jq -r '.metadata.generation // 0')"
if [ "$available" -ge "$replicas" ] && [ "$updated" -ge "$replicas" ] && [ "$observed" -ge "$generation" ]; then
echo "dev_e2e_rollout=available deployment=$deployment namespace=$ns replicas=$available generation=$generation"
return 0
fi
sleep 2
done
echo "dev_e2e_rollout=timeout deployment=$deployment namespace=$ns" >&2
kube GET "$kube_api/apis/apps/v1/namespaces/$ns/deployments/$deployment" >&2
return 1
}
apply_code_queue_role() {
local role="$1"
local readiness_path="$2"
local scheduler_enabled="$3"
local name="code-queue-${role}-dev"
cat >/tmp/dev-e2e-code-queue-$role.yaml <<YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: $name
namespace: $ns
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: $role
app.kubernetes.io/part-of: unidesk
unidesk.ai/ci-run-id: "$(params.run-id)"
unidesk.ai/deploy-service-id: code-queue
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: $role
unidesk.ai/ci-run-id: "$(params.run-id)"
template:
metadata:
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: $role
app.kubernetes.io/part-of: unidesk
unidesk.ai/ci-run-id: "$(params.run-id)"
unidesk.ai/node-id: D601
unidesk.ai/deploy-service-id: code-queue
spec:
nodeSelector:
unidesk.ai/node-id: D601
terminationGracePeriodSeconds: 10
containers:
- name: code-queue
image: "$app_image"
imagePullPolicy: Never
ports:
- name: http
containerPort: 4222
envFrom:
- secretRef:
name: code-queue-dev-e2e-env
env:
- name: HOST
value: "0.0.0.0"
- name: PORT
value: "4222"
- name: UNIDESK_ENV
value: dev
- name: UNIDESK_NAMESPACE
value: "$ns"
- name: UNIDESK_DATABASE_NAME
value: unidesk_ci
- name: UNIDESK_DEPLOY_REF
value: origin/master:deploy.json#environments.dev
- name: UNIDESK_DEPLOY_SERVICE_ID
value: code-queue
- name: CODE_QUEUE_DEPLOY_COMMIT
value: "$code_queue_commit"
- name: CODE_QUEUE_DEPLOY_REQUESTED_COMMIT
value: "$code_queue_commit"
- name: CODE_QUEUE_INSTANCE_ID
value: D601-dev-ci-$role
- name: CODE_QUEUE_SERVICE_ROLE
value: "$role"
- name: CODE_QUEUE_SCHEDULER_ENABLED
value: "$scheduler_enabled"
- name: CODE_QUEUE_MAX_ACTIVE_QUEUES
value: "0"
- name: CODE_QUEUE_DATABASE_POOL_MAX
value: "2"
- name: CODE_QUEUE_MAIN_PROVIDER_ID
value: D601-dev
- name: CODE_QUEUE_EXECUTION_PROVIDER_IDS
value: D601-dev
- name: CODE_QUEUE_WORKDIR
value: /workspace-dev
- name: CODE_QUEUE_REMOTE_WORKDIR
value: /home/ubuntu/unidesk-dev-workspace
- name: CODE_QUEUE_DATA_DIR
value: /var/lib/unidesk-dev-e2e/code-queue
- name: CODE_QUEUE_CODEX_HOME
value: /var/lib/unidesk-dev-e2e/code-queue/codex-home
- name: CODE_QUEUE_OPENCODE_XDG_DIR
value: /var/lib/unidesk-dev-e2e/code-queue/opencode-xdg
- name: CODE_QUEUE_EGRESS_PROXY_ENABLED
value: "false"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED
value: "false"
- name: CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED
value: "false"
- name: CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_ENABLED
value: "false"
- name: OA_EVENT_FLOW_BASE_URL
value: http://127.0.0.1:9
- name: LOG_FILE
value: /var/log/unidesk/code-queue-$role-dev-e2e.jsonl
- name: NODE_OPTIONS
value: --max-old-space-size=512
readinessProbe:
httpGet:
path: $readiness_path
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 24
startupProbe:
httpGet:
path: /live
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 60
resources:
requests:
cpu: 80m
memory: 192Mi
limits:
cpu: 300m
memory: 768Mi
volumeMounts:
- name: state
mountPath: /var/lib/unidesk-dev-e2e/code-queue
- name: logs
mountPath: /var/log/unidesk
volumes:
- name: state
emptyDir: {}
- name: logs
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: $name
namespace: $ns
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: $role
app.kubernetes.io/part-of: unidesk
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: $role
unidesk.ai/ci-run-id: "$(params.run-id)"
ports:
- name: http
port: 4222
targetPort: http
YAML
csplit -s -f /tmp/dev-e2e-code-queue-$role- /tmp/dev-e2e-code-queue-$role.yaml '/^---$/' '{*}'
kube PATCH -H "Content-Type: application/apply-patch+yaml" --data-binary @/tmp/dev-e2e-code-queue-$role-00 "$kube_api/apis/apps/v1/namespaces/$ns/deployments/$name?fieldManager=unidesk-ci&force=true" >/dev/null
kube PATCH -H "Content-Type: application/apply-patch+yaml" --data-binary @/tmp/dev-e2e-code-queue-$role-01 "$kube_api/api/v1/namespaces/$ns/services/$name?fieldManager=unidesk-ci&force=true" >/dev/null
}
cleanup() {
if [ "$keep" = "true" ]; then
echo "dev_e2e_namespace_retained=$ns"
@@ -767,161 +973,181 @@ spec:
--data-binary @/tmp/dev-e2e-configmap.yaml \
"$kube_api/api/v1/namespaces/$ns/configmaps/desired-manifest?fieldManager=unidesk-ci&force=true" >/dev/null
cat >/tmp/dev-e2e-target.yaml <<YAML
database_url_b64="$(printf '%s' "$database_url" | base64 -w0)"
cat >/tmp/dev-e2e-secret.yaml <<YAML
apiVersion: v1
kind: Secret
metadata:
name: code-queue-dev-e2e-env
namespace: $ns
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: ci-dev-e2e
app.kubernetes.io/part-of: unidesk
type: Opaque
data:
DATABASE_URL: "$database_url_b64"
YAML
kube PATCH \
-H "Content-Type: application/apply-patch+yaml" \
--data-binary @/tmp/dev-e2e-secret.yaml \
"$kube_api/api/v1/namespaces/$ns/secrets/code-queue-dev-e2e-env?fieldManager=unidesk-ci&force=true" >/dev/null
cat >/tmp/dev-e2e-postgres.yaml <<YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: dev-e2e-target
name: postgres-dev
namespace: $ns
labels:
app.kubernetes.io/name: unidesk-dev-namespace-e2e
app.kubernetes.io/component: smoke-target
app.kubernetes.io/name: postgres-dev
app.kubernetes.io/component: database
app.kubernetes.io/part-of: unidesk
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: unidesk-dev-namespace-e2e
app.kubernetes.io/component: smoke-target
app.kubernetes.io/name: postgres-dev
app.kubernetes.io/component: database
template:
metadata:
labels:
app.kubernetes.io/name: unidesk-dev-namespace-e2e
app.kubernetes.io/component: smoke-target
app.kubernetes.io/name: postgres-dev
app.kubernetes.io/component: database
app.kubernetes.io/part-of: unidesk
spec:
nodeSelector:
unidesk.ai/node-id: D601
terminationGracePeriodSeconds: 5
containers:
- name: smoke-target
image: "$(params.app-image)"
- name: postgres
image: postgres:16-alpine
imagePullPolicy: IfNotPresent
command:
- bun
- -e
- |
const port = Number(process.env.PORT || 8080);
const payload = {
ok: true,
environment: "dev",
namespace: process.env.CI_E2E_NAMESPACE,
deployCommit: process.env.CI_E2E_DEPLOY_COMMIT,
backendCoreCommit: process.env.BACKEND_CORE_COMMIT,
frontendCommit: process.env.FRONTEND_COMMIT,
codeQueueCommit: process.env.CODE_QUEUE_COMMIT
};
Bun.serve({
hostname: "0.0.0.0",
port,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/health" || url.pathname === "/") {
return Response.json(payload);
}
return new Response("not found", { status: 404 });
}
});
console.log(JSON.stringify({ listening: port, ...payload }));
await new Promise(() => {});
ports:
- name: http
containerPort: 8080
- name: postgres
containerPort: 5432
env:
- name: PORT
value: "8080"
- name: CI_E2E_NAMESPACE
value: "$ns"
- name: CI_E2E_DEPLOY_COMMIT
value: "$(params.deploy-commit)"
- name: BACKEND_CORE_COMMIT
value: "$backend_commit"
- name: FRONTEND_COMMIT
value: "$frontend_commit"
- name: CODE_QUEUE_COMMIT
value: "$code_queue_commit"
- name: POSTGRES_USER
value: unidesk_ci
- name: POSTGRES_PASSWORD
value: unidesk_ci_password
- name: POSTGRES_DB
value: unidesk_ci
readinessProbe:
httpGet:
path: /health
port: http
periodSeconds: 3
timeoutSeconds: 2
failureThreshold: 20
exec:
command:
- pg_isready
- -U
- unidesk_ci
- -d
- unidesk_ci
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 24
resources:
requests:
cpu: 20m
memory: 64Mi
cpu: 50m
memory: 128Mi
limits:
memory: 256Mi
cpu: 250m
memory: 512Mi
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: dev-e2e-target
name: postgres-dev
namespace: $ns
labels:
app.kubernetes.io/name: unidesk-dev-namespace-e2e
app.kubernetes.io/component: smoke-target
app.kubernetes.io/name: postgres-dev
app.kubernetes.io/component: database
app.kubernetes.io/part-of: unidesk
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: unidesk-dev-namespace-e2e
app.kubernetes.io/component: smoke-target
app.kubernetes.io/name: postgres-dev
app.kubernetes.io/component: database
ports:
- name: http
port: 8080
targetPort: http
- name: postgres
port: 5432
targetPort: postgres
YAML
csplit -s -f /tmp/dev-e2e-target- /tmp/dev-e2e-target.yaml '/^---$/' '{*}'
csplit -s -f /tmp/dev-e2e-postgres- /tmp/dev-e2e-postgres.yaml '/^---$/' '{*}'
kube PATCH \
-H "Content-Type: application/apply-patch+yaml" \
--data-binary @/tmp/dev-e2e-target-00 \
"$kube_api/apis/apps/v1/namespaces/$ns/deployments/dev-e2e-target?fieldManager=unidesk-ci&force=true" >/dev/null
--data-binary @/tmp/dev-e2e-postgres-00 \
"$kube_api/apis/apps/v1/namespaces/$ns/deployments/postgres-dev?fieldManager=unidesk-ci&force=true" >/dev/null
kube PATCH \
-H "Content-Type: application/apply-patch+yaml" \
--data-binary @/tmp/dev-e2e-target-01 \
"$kube_api/api/v1/namespaces/$ns/services/dev-e2e-target?fieldManager=unidesk-ci&force=true" >/dev/null
--data-binary @/tmp/dev-e2e-postgres-01 \
"$kube_api/api/v1/namespaces/$ns/services/postgres-dev?fieldManager=unidesk-ci&force=true" >/dev/null
wait_deployment_available postgres-dev 180
deadline=$((SECONDS + 180))
while [ "$SECONDS" -lt "$deadline" ]; do
status="$(kube GET "$kube_api/apis/apps/v1/namespaces/$ns/deployments/dev-e2e-target")"
available="$(printf '%s' "$status" | jq -r '.status.availableReplicas // 0')"
observed="$(printf '%s' "$status" | jq -r '.status.observedGeneration // 0')"
generation="$(printf '%s' "$status" | jq -r '.metadata.generation // 0')"
if [ "$available" -ge 1 ] && [ "$observed" -ge "$generation" ]; then
echo "dev_e2e_target_rollout=available namespace=$ns"
break
fi
sleep 2
done
if [ "$SECONDS" -ge "$deadline" ]; then
echo "dev_e2e_target_rollout=timeout namespace=$ns" >&2
kube GET "$kube_api/apis/apps/v1/namespaces/$ns/deployments/dev-e2e-target" >&2
exit 1
fi
apply_code_queue_role scheduler /health true
apply_code_queue_role read /live false
apply_code_queue_role write /health false
wait_deployment_available code-queue-scheduler-dev 420
wait_deployment_available code-queue-read-dev 420
wait_deployment_available code-queue-write-dev 420
bun - "$ns" "$(params.deploy-commit)" "$backend_commit" "$frontend_commit" "$code_queue_commit" "$result_json" <<'BUN'
const [ns, deployCommit, backendCommit, frontendCommit, codeQueueCommit, resultPath] = process.argv.slice(2);
const url = `http://dev-e2e-target.${ns}.svc.cluster.local:8080/health`;
const started = performance.now();
const response = await fetch(url);
const elapsedMs = Math.round(performance.now() - started);
const body = await response.json();
proxy_get code-queue-write-dev /health >"$run_dir/code-queue-write-health.json"
proxy_get code-queue-scheduler-dev /health >"$run_dir/code-queue-scheduler-health.json"
proxy_get code-queue-read-dev /live >"$run_dir/code-queue-read-live.json"
proxy_get code-queue-read-dev /api/workdirs >"$run_dir/code-queue-read-workdirs.json"
proxy_json POST code-queue-write-dev /api/workdirs '{"providerId":"D601-dev","executionMode":"default","path":"/home/ubuntu/unidesk-dev-workspace/ci-workdirs-smoke"}' >"$run_dir/code-queue-workdir-created.json"
proxy_get code-queue-write-dev '/api/workdirs?providerId=D601-dev&executionMode=default' >"$run_dir/code-queue-write-workdirs.json"
proxy_json DELETE code-queue-write-dev /api/workdirs/D601-dev/default/%2Fhome%2Fubuntu%2Funidesk-dev-workspace%2Fci-workdirs-smoke >"$run_dir/code-queue-workdir-deleted.json"
bun - "$ns" "$(params.deploy-commit)" "$backend_commit" "$frontend_commit" "$code_queue_commit" "$app_image" "$result_json" "$run_dir" <<'BUN'
const [ns, deployCommit, backendCommit, frontendCommit, codeQueueCommit, appImage, resultPath, runDir] = process.argv.slice(2);
const runBase = new URL(`file://${runDir.replace(/\/+$/u, "")}/`);
async function readJson(name) {
return await Bun.file(new URL(name, runBase)).json();
}
const health = await readJson("code-queue-write-health.json");
const scheduler = await readJson("code-queue-scheduler-health.json");
const readLive = await readJson("code-queue-read-live.json");
const initialWorkdirs = await readJson("code-queue-read-workdirs.json");
const created = await readJson("code-queue-workdir-created.json");
const listed = await readJson("code-queue-write-workdirs.json");
const deleted = await readJson("code-queue-workdir-deleted.json");
const checks = [
response.ok,
body.ok === true,
body.environment === "dev",
body.namespace === ns,
body.deployCommit === deployCommit,
body.backendCoreCommit === backendCommit,
body.frontendCommit === frontendCommit,
body.codeQueueCommit === codeQueueCommit
health?.ok === true && health?.role === "write" && health?.deploy?.commit === codeQueueCommit,
scheduler?.ok === true && scheduler?.role === "scheduler" && scheduler?.schedulerEnabled === true,
readLive?.ok === true && readLive?.role === "read",
initialWorkdirs?.ok === true && Array.isArray(initialWorkdirs?.workdirs),
created?.ok === true && created?.workdir?.providerId === "D601-dev" && created?.workdir?.path === "/home/ubuntu/unidesk-dev-workspace/ci-workdirs-smoke",
Array.isArray(listed?.workdirs) && listed.workdirs.some((item) => item?.path === "/home/ubuntu/unidesk-dev-workspace/ci-workdirs-smoke"),
deleted?.ok === true,
];
const result = { ok: checks.every(Boolean), elapsedMs, url, body };
await Bun.write(resultPath, JSON.stringify(result, null, 2) + "\n");
const result = {
ok: checks.every(Boolean),
namespace: ns,
deployCommit,
backendCoreCommit: backendCommit,
frontendCommit,
codeQueueCommit,
codeQueueImage: appImage,
accessPath: "kubernetes-api-service-proxy",
services: ["code-queue-scheduler-dev", "code-queue-read-dev", "code-queue-write-dev"],
health,
scheduler,
readLive,
initialWorkdirs,
created,
listed,
deleted,
};
await Bun.write(resultPath, `${JSON.stringify(result, null, 2)}\n`);
console.log(JSON.stringify(result));
if (!result.ok) process.exit(1);
if (!result.ok) {
process.exit(1);
}
BUN
---
apiVersion: tekton.dev/v1