ci: add PaC-driven AgentRun JD01 v02 pipeline

This commit is contained in:
Codex
2026-07-05 10:54:34 +00:00
parent d5a97318a3
commit 714a61d226
5 changed files with 1325 additions and 12 deletions
@@ -0,0 +1,250 @@
#!/bin/sh
set -eu
json_escape() {
sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g' | tr '\n' ' '
}
json_string() {
printf '%s' "$1" | json_escape
}
now_epoch() {
date +%s
}
duration_seconds() {
start="$1"
end="$2"
if [ -n "$start" ] && [ -n "$end" ]; then
s=$(date -d "$start" +%s 2>/dev/null || echo "")
e=$(date -d "$end" +%s 2>/dev/null || echo "")
if [ -n "$s" ] && [ -n "$e" ]; then
echo $((e - s))
return
fi
fi
echo null
}
api_url() {
printf '%s/api/v1/%s' "$UNIDESK_PAC_GITEA_BASE_URL" "$1"
}
gitea_api() {
method="$1"
path="$2"
body="${3:-}"
if [ -n "$body" ]; then
curl -fsS -u "$UNIDESK_PAC_GITEA_ADMIN_USERNAME:$UNIDESK_PAC_GITEA_ADMIN_PASSWORD" \
-H 'Content-Type: application/json' \
-X "$method" \
--data "$body" \
"$(api_url "$path")"
else
curl -fsS -u "$UNIDESK_PAC_GITEA_ADMIN_USERNAME:$UNIDESK_PAC_GITEA_ADMIN_PASSWORD" \
-X "$method" \
"$(api_url "$path")"
fi
}
ensure_token() {
name="unidesk-pac-$(date +%Y%m%d%H%M%S)"
body=$(printf '{"name":"%s"}' "$name")
out=$(gitea_api POST "users/$UNIDESK_PAC_GITEA_API_USERNAME/tokens" "$body")
printf '%s' "$out" | sed -n 's/.*"sha1"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p'
}
ensure_webhook() {
hooks=$(gitea_api GET "repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO/hooks")
hook_id=$(printf '%s' "$hooks" | tr '{' '\n' | awk -v url="$UNIDESK_PAC_WEBHOOK_URL" 'index($0, url)>0 { if (match($0, /"id"[[:space:]]*:[[:space:]]*[0-9]+/)) { print substr($0, RSTART); exit } }' | sed -n 's/[^0-9]*\([0-9][0-9]*\).*/\1/p')
body=$(printf '{"type":"gitea","config":{"url":"%s","content_type":"json","secret":"%s"},"events":["push"],"active":true}' "$UNIDESK_PAC_WEBHOOK_URL" "$UNIDESK_PAC_WEBHOOK_SECRET")
if [ -n "$hook_id" ]; then
gitea_api PATCH "repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO/hooks/$hook_id" "$body" >/dev/null
else
created=$(gitea_api POST "repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO/hooks" "$body")
hook_id=$(printf '%s' "$created" | sed -n 's/.*"id"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p')
fi
printf '%s' "$hook_id"
}
repository_manifest() {
cat <<EOF
apiVersion: pipelinesascode.tekton.dev/v1alpha1
kind: Repository
metadata:
name: $UNIDESK_PAC_REPOSITORY_NAME
namespace: $UNIDESK_PAC_TARGET_NAMESPACE
labels:
app.kubernetes.io/managed-by: unidesk
app.kubernetes.io/part-of: agentrun
unidesk.ai/spec: GH-1552
spec:
url: $UNIDESK_PAC_REPOSITORY_URL
git_provider:
type: gitea
url: $UNIDESK_PAC_GITEA_BASE_URL
secret:
name: $UNIDESK_PAC_SECRET_NAME
key: $UNIDESK_PAC_TOKEN_KEY
webhook_secret:
name: $UNIDESK_PAC_SECRET_NAME
key: $UNIDESK_PAC_WEBHOOK_SECRET_KEY
concurrency_limit: $UNIDESK_PAC_CONCURRENCY_LIMIT
params:
$(printf '%s' "$UNIDESK_PAC_PARAMS_JSON" | node -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync(0,"utf8")); for (const [name,value] of Object.entries(p)) console.log(` - name: ${name}\n value: ${JSON.stringify(String(value))}`);')
EOF
}
apply_release() {
tmp=$(mktemp)
printf '%s' "$UNIDESK_PAC_RELEASE_MANIFEST_B64" | base64 -d > "$tmp"
kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_PAC_FIELD_MANAGER" -f "$tmp" >/dev/null
rm -f "$tmp"
}
wait_ready() {
timeout="${UNIDESK_PAC_WAIT_TIMEOUT_SECONDS:-55}"
end=$(( $(now_epoch) + timeout ))
while [ "$(now_epoch)" -lt "$end" ]; do
ready=$(kubectl -n "$UNIDESK_PAC_RELEASE_NAMESPACE" get deploy "$UNIDESK_PAC_CONTROLLER_SERVICE_NAME" -o jsonpath='{.status.readyReplicas}' 2>/dev/null || true)
desired=$(kubectl -n "$UNIDESK_PAC_RELEASE_NAMESPACE" get deploy "$UNIDESK_PAC_CONTROLLER_SERVICE_NAME" -o jsonpath='{.spec.replicas}' 2>/dev/null || true)
if [ "${ready:-0}" = "${desired:-1}" ] && [ "${ready:-0}" != "0" ]; then
return 0
fi
sleep 2
done
return 1
}
apply_action() {
if [ "$UNIDESK_PAC_DRY_RUN" = "1" ]; then
printf '{"ok":true,"mode":"dry-run","mutation":false,"valuesPrinted":false}\n'
return
fi
apply_release
kubectl create ns "$UNIDESK_PAC_TARGET_NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - >/dev/null
token=$(ensure_token)
test -n "$token"
kubectl -n "$UNIDESK_PAC_TARGET_NAMESPACE" create secret generic "$UNIDESK_PAC_SECRET_NAME" \
--from-literal="$UNIDESK_PAC_TOKEN_KEY=$token" \
--from-literal="$UNIDESK_PAC_WEBHOOK_SECRET_KEY=$UNIDESK_PAC_WEBHOOK_SECRET" \
--dry-run=client -o yaml | kubectl apply -f - >/dev/null
repository_manifest | kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_PAC_FIELD_MANAGER" -f - >/dev/null
hook_id=$(ensure_webhook)
ready=false
if wait_ready; then ready=true; fi
printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ready" "$ready" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")"
}
condition_status() {
kubectl -n "$1" get "$2" "$3" -o jsonpath='{range .status.conditions[*]}{.type}={.status}:{.reason}{";"}{end}' 2>/dev/null || true
}
pipeline_rows() {
payload=$(kubectl -n "$UNIDESK_PAC_TARGET_NAMESPACE" get pipelinerun -o json 2>/dev/null || echo '{"items":[]}')
PIPELINE_JSON="$payload" node <<'NODE'
const input = process.env.PIPELINE_JSON || '{"items":[]}';
const data = input ? JSON.parse(input) : { items: [] };
function cond(item) {
const c = (item.status?.conditions || []).find((x) => x.type === 'Succeeded') || {};
return { status: c.status || '', reason: c.reason || '' };
}
function durationSeconds(item) {
const start = item.status?.startTime;
const done = item.status?.completionTime;
if (!start) return null;
return Math.max(0, Math.round(((done ? Date.parse(done) : Date.now()) - Date.parse(start)) / 1000));
}
const prefix = process.env.UNIDESK_PAC_PIPELINE_RUN_PREFIX;
const rows = (data.items || [])
.filter((item) => item.metadata?.name?.startsWith(prefix))
.sort((a, b) => Date.parse(b.metadata?.creationTimestamp || 0) - Date.parse(a.metadata?.creationTimestamp || 0))
.slice(0, 8)
.map((item) => {
const c = cond(item);
return {
name: item.metadata.name,
status: c.status,
reason: c.reason,
createdAt: item.metadata.creationTimestamp || null,
startTime: item.status?.startTime || null,
completionTime: item.status?.completionTime || null,
durationSeconds: durationSeconds(item),
sourceCommit: item.metadata.labels?.['pipelinesascode.tekton.dev/sha'] || item.metadata.labels?.['agentrun.pikastech.local/source-commit'] || null,
};
});
process.stdout.write(JSON.stringify(rows));
NODE
}
task_rows() {
payload=$(kubectl -n "$UNIDESK_PAC_TARGET_NAMESPACE" get taskrun -o json 2>/dev/null || echo '{"items":[]}')
TASK_JSON="$payload" node <<'NODE'
const input = process.env.TASK_JSON || '{"items":[]}';
const data = input ? JSON.parse(input) : { items: [] };
const pr = process.env.UNIDESK_PAC_TARGET_PIPELINERUN || '';
function cond(item) {
const c = (item.status?.conditions || []).find((x) => x.type === 'Succeeded') || {};
return { status: c.status || '', reason: c.reason || '' };
}
function durationSeconds(item) {
const start = item.status?.startTime;
const done = item.status?.completionTime;
if (!start) return null;
return Math.max(0, Math.round(((done ? Date.parse(done) : Date.now()) - Date.parse(start)) / 1000));
}
const rows = (data.items || [])
.filter((item) => !pr || item.metadata?.labels?.['tekton.dev/pipelineRun'] === pr)
.sort((a, b) => Date.parse(a.metadata?.creationTimestamp || 0) - Date.parse(b.metadata?.creationTimestamp || 0))
.slice(0, 20)
.map((item) => {
const c = cond(item);
return { name: item.metadata.name, pipelineRun: item.metadata.labels?.['tekton.dev/pipelineRun'] || null, status: c.status, reason: c.reason, startTime: item.status?.startTime || null, completionTime: item.status?.completionTime || null, durationSeconds: durationSeconds(item) };
});
process.stdout.write(JSON.stringify(rows));
NODE
}
hook_summary() {
hooks=$(gitea_api GET "repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO/hooks" 2>/dev/null || echo '[]')
HOOKS_JSON="$hooks" node <<'NODE'
const data = JSON.parse(process.env.HOOKS_JSON || '[]');
const url = process.env.UNIDESK_PAC_WEBHOOK_URL;
const rows = data.filter((item) => item?.config?.url === url).map((item) => ({ id: item.id, active: item.active, type: item.type, events: item.events || [], url: item.config?.url || '' }));
process.stdout.write(JSON.stringify(rows));
NODE
}
status_action() {
crd=$(kubectl get crd repositories.pipelinesascode.tekton.dev -o name 2>/dev/null || true)
controller_ready=$(kubectl -n "$UNIDESK_PAC_RELEASE_NAMESPACE" get deploy "$UNIDESK_PAC_CONTROLLER_SERVICE_NAME" -o jsonpath='{.status.readyReplicas}/{.spec.replicas}' 2>/dev/null || echo "0/0")
repository_condition=$(condition_status "$UNIDESK_PAC_TARGET_NAMESPACE" repository "$UNIDESK_PAC_REPOSITORY_NAME")
pipelines=$(pipeline_rows)
latest=$(printf '%s' "$pipelines" | node -e 'const fs=require("fs"); const a=JSON.parse(fs.readFileSync(0,"utf8")||"[]"); process.stdout.write(a[0]?.name||"")')
export UNIDESK_PAC_TARGET_PIPELINERUN="$latest"
tasks=$(task_rows)
hooks=$(hook_summary)
argo=$(kubectl -n "$UNIDESK_PAC_ARGO_NAMESPACE" get application "$UNIDESK_PAC_ARGO_APPLICATION" -o json 2>/dev/null | node -e 'const fs=require("fs"); const s=fs.readFileSync(0,"utf8").trim(); if(!s){process.stdout.write("{}"); process.exit(0)} const a=JSON.parse(s); process.stdout.write(JSON.stringify({sync:a.status?.sync?.status||null, health:a.status?.health?.status||null, revision:a.status?.sync?.revision||null}))' || echo '{}')
printf '{"ok":%s,"crdPresent":%s,"controllerReady":"%s","repositoryCondition":"%s","webhooks":%s,"pipelineRuns":%s,"taskRuns":%s,"argo":%s,"valuesPrinted":false}\n' \
"$( [ -n "$crd" ] && [ "$controller_ready" != "0/0" ] && echo true || echo false )" \
"$( [ -n "$crd" ] && echo true || echo false )" \
"$(json_string "$controller_ready")" \
"$(json_string "$repository_condition")" \
"$hooks" "$pipelines" "$tasks" "$argo"
}
webhook_test_action() {
hooks=$(hook_summary)
hook_id=$(printf '%s' "$hooks" | node -e 'const fs=require("fs"); const a=JSON.parse(fs.readFileSync(0,"utf8")||"[]"); process.stdout.write(String(a[0]?.id||""))')
test -n "$hook_id"
gitea_api POST "repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO/hooks/$hook_id/tests" '{}' >/dev/null
printf '{"ok":true,"mutation":true,"webhookTestSent":true,"hookId":"%s","valuesPrinted":false}\n' "$(json_string "$hook_id")"
}
case "$UNIDESK_PAC_ACTION" in
apply) apply_action ;;
status) status_action ;;
webhook-test) webhook_test_action ;;
*) printf '{"ok":false,"error":"unsupported-action","valuesPrinted":false}\n'; exit 2 ;;
esac