// SPEC: PJ2026-01060305 AgentRun execution policy + PJ2026-01020108 cancel lifecycle draft-2026-06-25-p0. // Renders AgentRun YAML lane policy into runtime manager environment. import { createHash } from "node:crypto"; import type { AgentRunLaneSpec } from "./agentrun-lanes"; export interface AgentRunArtifactService { readonly serviceId: string; readonly image: string; readonly digest: string; readonly repositoryDigest: string; readonly imageTag: string; readonly artifactKind: string; readonly status: string; readonly envIdentity: string; readonly envImage: string; readonly envDigest: string; readonly envRepositoryDigest: string; readonly bootCommit: string; readonly bootScript: string; readonly provenance: Record; } export interface AgentRunArtifactCatalog { readonly lane: string; readonly sourceBranch: string; readonly gitopsBranch: string; readonly sourceCommitId: string; readonly summary: string; readonly services: readonly AgentRunArtifactService[]; } export interface AgentRunGitopsRenderInput { readonly sourceCommit: string; readonly image: AgentRunArtifactService; } export interface AgentRunRenderedFile { readonly path: string; readonly content: string; } export function renderAgentRunControlPlaneManifests(spec: AgentRunLaneSpec): readonly Record[] { return [ { apiVersion: "v1", kind: "Namespace", metadata: { name: spec.ci.namespace } }, { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: spec.ci.serviceAccountName, namespace: spec.ci.namespace, labels: agentRunLabels(spec), }, }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: spec.ci.serviceAccountName, namespace: spec.ci.namespace, labels: agentRunLabels(spec), }, rules: [ { apiGroups: ["tekton.dev"], resources: ["pipelineruns", "taskruns"], verbs: ["get", "list", "watch", "create", "patch", "update"] }, { apiGroups: [""], resources: ["pods", "pods/log", "secrets", "configmaps", "persistentvolumeclaims"], verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] }, ], }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: spec.ci.serviceAccountName, namespace: spec.ci.namespace, labels: agentRunLabels(spec), }, subjects: [{ kind: "ServiceAccount", name: spec.ci.serviceAccountName }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: spec.ci.serviceAccountName }, }, agentRunPipelineManifest(spec), agentRunArgoProjectManifest(spec), agentRunArgoApplicationManifest(spec), ]; } export function renderAgentRunGitopsFiles(spec: AgentRunLaneSpec, input: AgentRunGitopsRenderInput): readonly AgentRunRenderedFile[] { const catalog = agentRunArtifactCatalog(spec, input.sourceCommit, input.image); const source = { lane: spec.version, sourceCommit: input.sourceCommit, generatedBy: "unidesk config/agentrun.yaml", configSource: "config/agentrun.yaml", }; return [ { path: "source.json", content: `${JSON.stringify(source, null, 2)}\n` }, { path: spec.deployment.artifactCatalogPath, content: `${JSON.stringify(catalog, null, 2)}\n` }, { path: `${spec.deployment.gitopsRoot}/argocd/project.yaml`, content: yaml(agentRunArgoProjectManifest(spec)) }, { path: `${spec.deployment.gitopsRoot}/argocd/${spec.deployment.argocd.applicationFile}`, content: yaml(agentRunArgoApplicationManifest(spec)) }, { path: `${spec.deployment.gitopsRoot}/${spec.deployment.runtimeRenderDir}/kustomization.yaml`, content: yaml(agentRunKustomizationManifest(spec)) }, { path: `${spec.deployment.gitopsRoot}/${spec.deployment.runtimeRenderDir}/namespace.yaml`, content: yaml(agentRunRuntimeNamespaceManifest(spec)) }, ...(spec.deployment.localPostgres.enabled ? [{ path: `${spec.deployment.gitopsRoot}/${spec.deployment.runtimeRenderDir}/postgres.yaml`, content: yaml(agentRunPostgresManifest(spec)) }] : []), { path: `${spec.deployment.gitopsRoot}/${spec.deployment.runtimeRenderDir}/mgr.yaml`, content: yamlAll(agentRunManagerManifests(spec, input.sourceCommit, input.image)) }, { path: `${spec.deployment.gitopsRoot}/${spec.deployment.runtimeRenderDir}/runner-rbac.yaml`, content: yamlAll(agentRunRunnerRbacManifests(spec)) }, ]; } export function placeholderAgentRunImage(spec: AgentRunLaneSpec, sourceCommit: string): AgentRunArtifactService { const digest = `sha256:${"0".repeat(64)}`; const image = `${spec.ci.registryPrefix}/agentrun-mgr-env:${sourceCommit}`; return { serviceId: "agentrun-mgr", artifactKind: "env-reuse", status: "placeholder", image, digest, repositoryDigest: `${spec.ci.registryPrefix}/agentrun-mgr-env@${digest}`, imageTag: sourceCommit, envIdentity: sourceCommit, envImage: image, envDigest: digest, envRepositoryDigest: `${spec.ci.registryPrefix}/agentrun-mgr-env@${digest}`, bootCommit: sourceCommit, bootScript: "deploy/runtime/boot/agentrun-boot.sh", provenance: { sourceCommitId: sourceCommit, source: "placeholder", valuesPrinted: false, }, }; } export function agentRunImageArtifact(spec: AgentRunLaneSpec, input: { sourceCommit: string; envIdentity: string; digest: string; status: string; }): AgentRunArtifactService { const image = `${spec.ci.registryPrefix}/${spec.deployment.manager.imageBuild.repository}:${input.envIdentity}`; return { serviceId: "agentrun-mgr", artifactKind: "env-reuse", status: input.status, image, digest: input.digest, repositoryDigest: `${spec.ci.registryPrefix}/${spec.deployment.manager.imageBuild.repository}@${input.digest}`, imageTag: input.envIdentity, envIdentity: input.envIdentity, envImage: image, envDigest: input.digest, envRepositoryDigest: `${spec.ci.registryPrefix}/${spec.deployment.manager.imageBuild.repository}@${input.digest}`, bootCommit: input.sourceCommit, bootScript: "deploy/runtime/boot/agentrun-boot.sh", provenance: { sourceCommitId: input.sourceCommit, source: "unidesk-yaml-only", configSource: "config/agentrun.yaml", valuesPrinted: false, }, }; } export function renderedFilesDigest(files: readonly AgentRunRenderedFile[]): string { const hash = createHash("sha256"); for (const file of [...files].sort((left, right) => left.path.localeCompare(right.path))) { hash.update(file.path); hash.update("\0"); hash.update(file.content); hash.update("\0"); } return `sha256:${hash.digest("hex")}`; } export function renderedObjectsDigest(objects: readonly Record[]): string { return `sha256:${createHash("sha256").update(yamlAll(objects)).digest("hex")}`; } function agentRunPipelineManifest(spec: AgentRunLaneSpec): Record { const build = spec.deployment.manager.imageBuild; if (spec.ci.buildkitImage === null) throw new Error(`config/agentrun.yaml controlPlane.lanes.${spec.lane}.ci.buildkitImage is required for AgentRun Tekton image build`); return { apiVersion: "tekton.dev/v1", kind: "Pipeline", metadata: { name: spec.ci.pipeline, namespace: spec.ci.namespace, labels: agentRunLabels(spec), }, spec: { params: [ { name: "git-url", type: "string", default: spec.source.remote }, { name: "git-read-url", type: "string", default: spec.gitMirror.readUrl }, { name: "git-write-url", type: "string", default: spec.gitMirror.writeUrl }, { name: "source-branch", type: "string", default: spec.source.branch }, { name: "gitops-branch", type: "string", default: spec.gitops.branch }, { name: "revision", type: "string" }, { name: "source-stage-ref", type: "string" }, { name: "registry-prefix", type: "string", default: spec.ci.registryPrefix }, { name: "tools-image", type: "string", default: spec.ci.toolsImage }, { name: "buildkit-image", type: "string", default: spec.ci.buildkitImage }, { name: "containerfile", type: "string", default: build.containerfile }, { name: "context-dir", type: "string", default: build.context }, { name: "image-repository", type: "string", default: `${spec.ci.registryPrefix}/${build.repository}` }, { name: "build-network", type: "string", default: build.network }, { name: "build-args-json", type: "string", default: JSON.stringify(Object.entries(build.buildArgs).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`)) }, { name: "build-http-proxy", type: "string", default: build.httpProxy ?? "" }, { name: "build-https-proxy", type: "string", default: build.httpsProxy ?? "" }, { name: "build-no-proxy", type: "string", default: build.noProxy.join(",") }, { name: "container-http-proxy", type: "string", default: build.buildContainerProxy.httpProxy ?? "" }, { name: "container-https-proxy", type: "string", default: build.buildContainerProxy.httpsProxy ?? "" }, { name: "container-no-proxy", type: "string", default: build.buildContainerProxy.noProxy.join(",") }, { name: "env-identity-files-json", type: "string", default: JSON.stringify(build.envIdentityFiles) }, { name: "gitops-root", type: "string", default: spec.deployment.gitopsRoot }, { name: "artifact-catalog", type: "string", default: spec.deployment.artifactCatalogPath }, ], workspaces: [{ name: "source" }], tasks: [ agentRunBuildPublishTask(spec), ], }, }; } function agentRunBuildPublishTask(spec: AgentRunLaneSpec): Record { return { name: "build-publish", workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [ { name: "git-read-url" }, { name: "git-write-url" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "revision" }, { name: "source-stage-ref" }, { name: "registry-prefix" }, { name: "tools-image" }, { name: "buildkit-image" }, { name: "containerfile" }, { name: "context-dir" }, { name: "image-repository" }, { name: "build-network" }, { name: "build-args-json" }, { name: "build-http-proxy" }, { name: "build-https-proxy" }, { name: "build-no-proxy" }, { name: "container-http-proxy" }, { name: "container-https-proxy" }, { name: "container-no-proxy" }, { name: "env-identity-files-json" }, { name: "gitops-root" }, { name: "artifact-catalog" }, ], workspaces: [{ name: "source" }], steps: [ { name: "source-checkout", image: "$(params.tools-image)", script: agentRunTektonSourceCheckoutScript(), }, { name: "probe-env-image", image: "$(params.tools-image)", script: agentRunTektonProbeImageScript(), }, { name: "build-env-image", image: "$(params.buildkit-image)", env: [ { name: "BUILDKITD_FLAGS", value: "--oci-worker-no-process-sandbox --oci-worker-net=host --allow-insecure-entitlement network.host" }, { name: "HTTP_PROXY", value: "$(params.build-http-proxy)" }, { name: "http_proxy", value: "$(params.build-http-proxy)" }, { name: "HTTPS_PROXY", value: "$(params.build-https-proxy)" }, { name: "https_proxy", value: "$(params.build-https-proxy)" }, { name: "ALL_PROXY", value: "$(params.build-https-proxy)" }, { name: "all_proxy", value: "$(params.build-https-proxy)" }, { name: "NO_PROXY", value: "$(params.build-no-proxy)" }, { name: "no_proxy", value: "$(params.build-no-proxy)" }, ], securityContext: { privileged: true, runAsUser: 1000, runAsGroup: 1000 }, script: agentRunTektonBuildImageScript(), }, { name: "publish-gitops", image: "$(params.tools-image)", env: [ { name: "GITEA_TOKEN", valueFrom: { secretKeyRef: { name: "pac-gitea-agentrun-jd01-v02", key: "token", optional: true } } }, ], script: agentRunTektonGitopsPublishScript(spec), }, ], }, params: [ { name: "git-read-url", value: "$(params.git-read-url)" }, { name: "git-write-url", value: "$(params.git-write-url)" }, { name: "source-branch", value: "$(params.source-branch)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" }, { name: "revision", value: "$(params.revision)" }, { name: "source-stage-ref", value: "$(params.source-stage-ref)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "tools-image", value: "$(params.tools-image)" }, { name: "buildkit-image", value: "$(params.buildkit-image)" }, { name: "containerfile", value: "$(params.containerfile)" }, { name: "context-dir", value: "$(params.context-dir)" }, { name: "image-repository", value: "$(params.image-repository)" }, { name: "build-network", value: "$(params.build-network)" }, { name: "build-args-json", value: "$(params.build-args-json)" }, { name: "build-http-proxy", value: "$(params.build-http-proxy)" }, { name: "build-https-proxy", value: "$(params.build-https-proxy)" }, { name: "build-no-proxy", value: "$(params.build-no-proxy)" }, { name: "container-http-proxy", value: "$(params.container-http-proxy)" }, { name: "container-https-proxy", value: "$(params.container-https-proxy)" }, { name: "container-no-proxy", value: "$(params.container-no-proxy)" }, { name: "env-identity-files-json", value: "$(params.env-identity-files-json)" }, { name: "gitops-root", value: "$(params.gitops-root)" }, { name: "artifact-catalog", value: "$(params.artifact-catalog)" }, ], when: [{ input: spec.deployment.format, operator: "in", values: ["unidesk-yaml-only"] }], }; } function agentRunTektonSourceCheckoutScript(): string { return [ "#!/bin/sh", "set -eu", "root=\"$(workspaces.source.path)\"", "rm -rf \"$root/repo\"", "git clone --no-checkout \"$(params.git-read-url)\" \"$root/repo\"", "cd \"$root/repo\"", "git fetch origin \"+$(params.source-stage-ref):refs/remotes/origin/unidesk-source-snapshot\"", "git checkout --detach \"$(params.revision)\"", "actual=$(git rev-parse HEAD)", "test \"$actual\" = \"$(params.revision)\"", "ENV_IDENTITY_FILES='$(params.env-identity-files-json)' BUILD_ARGS_JSON='$(params.build-args-json)' CONTAINER_HTTP_PROXY='$(params.container-http-proxy)' CONTAINER_HTTPS_PROXY='$(params.container-https-proxy)' CONTAINER_NO_PROXY='$(params.container-no-proxy)' node <<'NODE' > \"$root/env-identity\"", "const { createHash } = require('node:crypto');", "const { existsSync, lstatSync, readdirSync, readFileSync } = require('node:fs');", "const { join } = require('node:path');", "const files = JSON.parse(process.env.ENV_IDENTITY_FILES || '[]');", "const buildArgs = JSON.parse(process.env.BUILD_ARGS_JSON || '[]');", "const proxy = [`HTTP_PROXY=${process.env.CONTAINER_HTTP_PROXY || ''}`, `HTTPS_PROXY=${process.env.CONTAINER_HTTPS_PROXY || ''}`, `NO_PROXY=${process.env.CONTAINER_NO_PROXY || ''}`];", "const skip = new Set(['.git', '.worktree', '.state', 'node_modules', 'coverage', 'tmp', '.tmp']);", "const hash = createHash('sha256');", "function collect(input) {", " if (!existsSync(input)) return [{ path: input, missing: true }];", " const stat = lstatSync(input);", " if (stat.isFile()) return [{ path: input, missing: false }];", " if (!stat.isDirectory()) return [{ path: input, missing: true }];", " const out = [];", " const stack = [input];", " while (stack.length) {", " const dir = stack.pop();", " for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {", " if (entry.isDirectory() && skip.has(entry.name)) continue;", " const child = join(dir, entry.name);", " if (entry.isDirectory()) stack.push(child);", " else if (entry.isFile()) out.push({ path: child, missing: false });", " }", " }", " return out.sort((a, b) => a.path.localeCompare(b.path));", "}", "for (const item of buildArgs) { hash.update('build-arg\\0'); hash.update(item); hash.update('\\0'); }", "for (const item of proxy) { hash.update('build-container-proxy\\0'); hash.update(item); hash.update('\\0'); }", "for (const file of files) for (const entry of collect(file)) { hash.update(entry.path); hash.update('\\0'); if (!entry.missing) hash.update(readFileSync(entry.path)); hash.update('\\0'); }", "process.stdout.write(hash.digest('hex').slice(0, 24));", "NODE", "BUILD_ARGS='$(params.build-args-json)' node <<'NODE' > \"$root/build-args.txt\"", "const values = JSON.parse(process.env.BUILD_ARGS || '[]');", "for (const value of values) console.log(`build-arg:${value}`);", "NODE", "chmod -R a+rwX \"$root\"", "env_identity=$(cat \"$root/env-identity\")", "printf '{\"ok\":true,\"phase\":\"source-checkout\",\"sourceCommit\":\"%s\",\"sourceStageRef\":\"%s\",\"envIdentity\":\"%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$(params.source-stage-ref)\" \"$env_identity\"", ].join("\n"); } function agentRunTektonProbeImageScript(): string { return [ "#!/bin/sh", "set -eu", "root=\"$(workspaces.source.path)\"", "env_identity=$(cat \"$root/env-identity\")", "image_repository='$(params.image-repository)'", "manifest_accept='application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json'", "repo_path=${image_repository#127.0.0.1:5000/}", "headers=$(mktemp)", "digest=''", "if curl -fsSI -H \"Accept: $manifest_accept\" \"http://127.0.0.1:5000/v2/$repo_path/manifests/$env_identity\" >\"$headers\"; then", " digest=$(awk -F': ' 'tolower($1)==\"docker-content-digest\"{print $2}' \"$headers\" | tr -d '\\r' | head -n 1)", "fi", "rm -f \"$headers\"", "if [ -n \"$digest\" ]; then", " image=\"$image_repository:$env_identity\"", " printf '{\"ok\":true,\"status\":\"reused\",\"sourceCommit\":\"%s\",\"envIdentity\":\"%s\",\"image\":\"%s\",\"digest\":\"%s\",\"repositoryDigest\":\"%s@%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$env_identity\" \"$image\" \"$digest\" \"$image_repository\" \"$digest\" > \"$root/build-result.json\"", " touch \"$root/skip-build\"", "else", " printf '{\"ok\":false,\"status\":\"cache-miss\",\"sourceCommit\":\"%s\",\"envIdentity\":\"%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$env_identity\" > \"$root/build-result.json\"", "fi", "chmod a+rw \"$root/build-result.json\"", "cat \"$root/build-result.json\"", ].join("\n"); } function agentRunTektonBuildImageScript(): string { return [ "#!/bin/sh", "set -eu", "root=\"$(workspaces.source.path)\"", "if [ -f \"$root/skip-build\" ]; then cat \"$root/build-result.json\"; exit 0; fi", "env_identity=$(cat \"$root/env-identity\")", "image_repository='$(params.image-repository)'", "image=\"$image_repository:$env_identity\"", "context_dir='$(params.context-dir)'", "if [ \"$context_dir\" = \".\" ]; then context_path=\"$root/repo\"; else context_path=\"$root/repo/${context_dir#./}\"; fi", "args=\"build --allow network.host --frontend dockerfile.v0 --local context=$context_path --local dockerfile=$root/repo --opt filename=$(params.containerfile) --opt network=$(params.build-network)\"", "add_opt() { args=\"$args --opt $1\"; }", "if [ -n '$(params.container-http-proxy)' ]; then add_opt 'build-arg:HTTP_PROXY=$(params.container-http-proxy)'; add_opt 'build-arg:http_proxy=$(params.container-http-proxy)'; fi", "if [ -n '$(params.container-https-proxy)' ]; then add_opt 'build-arg:HTTPS_PROXY=$(params.container-https-proxy)'; add_opt 'build-arg:https_proxy=$(params.container-https-proxy)'; fi", "if [ -n '$(params.container-https-proxy)' ]; then add_opt 'build-arg:ALL_PROXY=$(params.container-https-proxy)'; add_opt 'build-arg:all_proxy=$(params.container-https-proxy)'; elif [ -n '$(params.container-http-proxy)' ]; then add_opt 'build-arg:ALL_PROXY=$(params.container-http-proxy)'; add_opt 'build-arg:all_proxy=$(params.container-http-proxy)'; fi", "if [ -n '$(params.container-no-proxy)' ]; then add_opt 'build-arg:NO_PROXY=$(params.container-no-proxy)'; add_opt 'build-arg:no_proxy=$(params.container-no-proxy)'; fi", "while IFS= read -r opt; do [ -n \"$opt\" ] && add_opt \"$opt\"; done < \"$root/build-args.txt\"", "args=\"$args --metadata-file $root/build-metadata.json --output type=image,name=$image,push=true,registry.insecure=true\"", "buildctl-daemonless.sh $args", "metadata_compact=$(tr -d '\\n' < \"$root/build-metadata.json\")", "digest=$(printf '%s' \"$metadata_compact\" | sed -n 's/.*\"containerimage.digest\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n 1)", "test -n \"$digest\"", "printf '{\"ok\":true,\"status\":\"built\",\"sourceCommit\":\"%s\",\"envIdentity\":\"%s\",\"image\":\"%s\",\"digest\":\"%s\",\"repositoryDigest\":\"%s@%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$env_identity\" \"$image\" \"$digest\" \"$image_repository\" \"$digest\" > \"$root/build-result.json\"", "cat \"$root/build-result.json\"", ].join("\n"); } function agentRunTektonGitopsPublishScript(spec: AgentRunLaneSpec): string { const sourcePlaceholder = "__AGENTRUN_SOURCE_COMMIT__"; const envPlaceholder = "__AGENTRUN_ENV_IDENTITY__"; const digestPlaceholder = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const statusPlaceholder = "__AGENTRUN_IMAGE_STATUS__"; const templateImage = agentRunImageArtifact(spec, { sourceCommit: sourcePlaceholder, envIdentity: envPlaceholder, digest: digestPlaceholder, status: statusPlaceholder }); const templateFiles = renderAgentRunGitopsFiles(spec, { sourceCommit: sourcePlaceholder, image: templateImage }); const templateB64 = Buffer.from(JSON.stringify(templateFiles), "utf8").toString("base64"); return [ "#!/bin/sh", "set -eu", "root=\"$(workspaces.source.path)\"", "build_result=\"$root/build-result.json\"", "test -s \"$build_result\"", `templates_b64=${JSON.stringify(templateB64)}`, "git_write_url='$(params.git-write-url)'", "git_auth_url=\"$git_write_url\"", "if printf '%s' \"$git_write_url\" | grep -q '^http://gitea-http\\.'; then", " test -n \"${GITEA_TOKEN:-}\"", " git_auth_url=$(printf '%s' \"$git_write_url\" | sed \"s#^http://#http://x-access-token:${GITEA_TOKEN}@#\")", "fi", "rm -rf \"$root/gitops\"", "git clone \"$git_auth_url\" \"$root/gitops\"", "cd \"$root/gitops\"", "git fetch origin \"$(params.gitops-branch)\" || true", "if git rev-parse --verify \"refs/remotes/origin/$(params.gitops-branch)^{commit}\" >/dev/null 2>&1; then git checkout -B \"$(params.gitops-branch)\" \"refs/remotes/origin/$(params.gitops-branch)\"; else git checkout --orphan \"$(params.gitops-branch)\"; git rm -rf . >/dev/null 2>&1 || true; fi", "git rm -rf --ignore-unmatch \"$(params.gitops-root)\" \"$(params.artifact-catalog)\" source.json >/dev/null 2>&1 || true", "rm -rf \"$(params.gitops-root)\" \"$(params.artifact-catalog)\" source.json", "TEMPLATES_B64=\"$templates_b64\" BUILD_RESULT=\"$build_result\" node <<'NODE'", "const { mkdirSync, readFileSync, writeFileSync } = require('node:fs');", "const { dirname } = require('node:path');", "const templates = JSON.parse(Buffer.from(process.env.TEMPLATES_B64, 'base64').toString('utf8'));", "const build = JSON.parse(readFileSync(process.env.BUILD_RESULT, 'utf8'));", "const replacements = new Map([", " ['__AGENTRUN_SOURCE_COMMIT__', build.sourceCommit],", " ['__AGENTRUN_ENV_IDENTITY__', build.envIdentity],", " ['sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', build.digest],", " ['__AGENTRUN_IMAGE_STATUS__', build.status],", "]);", "for (const file of templates) {", " let content = file.content;", " for (const [from, to] of replacements) content = content.split(from).join(to);", " mkdirSync(dirname(file.path), { recursive: true });", " writeFileSync(file.path, content);", "}", "NODE", "git add source.json \"$(params.artifact-catalog)\" \"$(params.gitops-root)\"", "if git diff --quiet --cached; then changed=false; else changed=true; git -c user.email=agentrun@unidesk.local -c user.name='UniDesk AgentRun PaC' commit -m \"deploy: render AgentRun $(params.gitops-branch) from PaC\"; fi", "git remote set-url origin \"$git_auth_url\"", "git push -u origin \"$(params.gitops-branch)\"", "gitops_commit=$(git rev-parse HEAD)", "BUILD_RESULT=\"$build_result\" CHANGED=\"$changed\" GITOPS_COMMIT=\"$gitops_commit\" node <<'NODE'", "const { readFileSync } = require('node:fs');", "const build = JSON.parse(readFileSync(process.env.BUILD_RESULT, 'utf8'));", "console.log(JSON.stringify({ ok: true, status: 'succeeded', phase: 'gitops-publish', changed: process.env.CHANGED === 'true', gitopsCommit: process.env.GITOPS_COMMIT, sourceCommit: build.sourceCommit, envIdentity: build.envIdentity, imageStatus: build.status, digest: build.digest, valuesPrinted: false }));", "NODE", ].join("\n"); } function agentRunArgoProjectManifest(spec: AgentRunLaneSpec): Record { return { apiVersion: "argoproj.io/v1alpha1", kind: "AppProject", metadata: { name: spec.deployment.argocd.project, namespace: spec.gitops.argoNamespace, labels: agentRunLabels(spec), }, spec: { description: `AgentRun ${spec.version} GitOps lane`, sourceRepos: [spec.gitops.repoURL, spec.source.remote], destinations: [{ server: "https://kubernetes.default.svc", namespace: spec.runtime.namespace }], clusterResourceWhitelist: [{ group: "", kind: "Namespace" }], namespaceResourceWhitelist: [{ group: "*", kind: "*" }], }, }; } function agentRunArgoApplicationManifest(spec: AgentRunLaneSpec): Record { return { apiVersion: "argoproj.io/v1alpha1", kind: "Application", metadata: { name: spec.gitops.argoApplication, namespace: spec.gitops.argoNamespace, labels: agentRunLabels(spec), }, spec: { project: spec.deployment.argocd.project, source: { repoURL: spec.gitops.repoURL, targetRevision: spec.gitops.branch, path: spec.gitops.path, }, destination: { server: "https://kubernetes.default.svc", namespace: spec.runtime.namespace, }, syncPolicy: { automated: { prune: false, selfHeal: true }, syncOptions: ["CreateNamespace=true", "ApplyOutOfSyncOnly=true"], }, }, }; } function agentRunKustomizationManifest(spec: AgentRunLaneSpec): Record { return { apiVersion: "kustomize.config.k8s.io/v1beta1", kind: "Kustomization", resources: [ "namespace.yaml", ...(spec.deployment.localPostgres.enabled ? ["postgres.yaml"] : []), "mgr.yaml", "runner-rbac.yaml", ], }; } function agentRunRuntimeNamespaceManifest(spec: AgentRunLaneSpec): Record { return { apiVersion: "v1", kind: "Namespace", metadata: { name: spec.runtime.namespace, labels: agentRunLabels(spec), }, }; } function agentRunPostgresManifest(spec: AgentRunLaneSpec): Record { const localPostgres = spec.deployment.localPostgres; if (!localPostgres.enabled || localPostgres.serviceName === null || localPostgres.image === null || localPostgres.storage === null || localPostgres.port === null || localPostgres.database === null || localPostgres.user === null) { throw new Error(`localPostgres is enabled for ${spec.version} without renderable YAML fields`); } const name = localPostgres.serviceName; const secretName = spec.database.secretRef.name; return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "Service", metadata: { name, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, spec: { selector: { "app.kubernetes.io/name": name }, ports: [{ name: "postgres", port: localPostgres.port, targetPort: "postgres" }] }, }, { apiVersion: "apps/v1", kind: "StatefulSet", metadata: { name, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, spec: { serviceName: name, replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": name } }, template: { metadata: { labels: { ...agentRunLabels(spec), "app.kubernetes.io/name": name } }, spec: { containers: [ { name: "postgres", image: localPostgres.image, ports: [{ name: "postgres", containerPort: localPostgres.port }], env: [ { name: "POSTGRES_DB", valueFrom: { secretKeyRef: { name: secretName, key: "POSTGRES_DB" } } }, { name: "POSTGRES_USER", valueFrom: { secretKeyRef: { name: secretName, key: "POSTGRES_USER" } } }, { name: "POSTGRES_PASSWORD", valueFrom: { secretKeyRef: { name: secretName, key: "POSTGRES_PASSWORD" } } }, { name: "PGDATA", value: "/var/lib/postgresql/data/pgdata" }, ], volumeMounts: [{ name: "data", mountPath: "/var/lib/postgresql/data" }], }, ], }, }, volumeClaimTemplates: [ { metadata: { name: "data" }, spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: localPostgres.storage } } }, }, ], }, }, ], }; } function agentRunManagerManifests(spec: AgentRunLaneSpec, sourceCommit: string, image: AgentRunArtifactService): readonly Record[] { const imageRef = image.envRepositoryDigest || image.repositoryDigest; return [ { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: spec.deployment.manager.serviceAccount, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) } }, { apiVersion: "v1", kind: "Service", metadata: { name: spec.runtime.managerService, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, spec: { selector: { "app.kubernetes.io/name": spec.runtime.managerDeployment }, ports: [{ name: "http", port: spec.runtime.managerPort, targetPort: "http" }], }, }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: spec.runtime.managerDeployment, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, spec: { replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": spec.runtime.managerDeployment } }, template: { metadata: { labels: { ...agentRunLabels(spec), "app.kubernetes.io/name": spec.runtime.managerDeployment }, annotations: { "agentrun.pikastech.local/lane": spec.version, "agentrun.pikastech.local/source-commit": sourceCommit, "agentrun.pikastech.local/env-identity": image.envIdentity, }, }, spec: { serviceAccountName: spec.deployment.manager.serviceAccount, containers: [ { name: "mgr", image: imageRef, imagePullPolicy: "IfNotPresent", ports: [{ name: "http", containerPort: 8080 }], env: managerEnv(spec, sourceCommit, imageRef, image.envIdentity), readinessProbe: { httpGet: { path: "/health/readiness", port: "http" } }, livenessProbe: { httpGet: { path: "/health/live", port: "http" } }, resources: spec.deployment.manager.resources, }, ], }, }, }, }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: `${spec.deployment.manager.serviceAccount}-runner-job-controller`, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, rules: [ { apiGroups: ["batch"], resources: ["jobs"], verbs: ["create", "delete", "get", "list", "watch"] }, { apiGroups: [""], resources: ["pods"], verbs: ["delete", "get", "list", "watch"] }, { apiGroups: [""], resources: ["persistentvolumeclaims"], verbs: ["create", "get", "list", "watch", "delete"] }, ], }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: `${spec.deployment.manager.serviceAccount}-runner-job-controller`, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, subjects: [{ kind: "ServiceAccount", name: spec.deployment.manager.serviceAccount }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: `${spec.deployment.manager.serviceAccount}-runner-job-controller` }, }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: `${spec.deployment.manager.serviceAccount}-provider-secret-manager`, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, rules: [{ apiGroups: [""], resources: ["secrets"], verbs: ["create", "delete", "get", "list", "patch", "update"] }], }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: `${spec.deployment.manager.serviceAccount}-provider-secret-manager`, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, subjects: [{ kind: "ServiceAccount", name: spec.deployment.manager.serviceAccount }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: `${spec.deployment.manager.serviceAccount}-provider-secret-manager` }, }, ]; } function managerEnv(spec: AgentRunLaneSpec, sourceCommit: string, imageRef: string, envIdentity: string): readonly Record[] { return [ { name: "AGENTRUN_LANE", value: spec.version }, { name: "DATABASE_URL", valueFrom: { secretKeyRef: spec.database.secretRef } }, { name: "AGENTRUN_SOURCE_COMMIT", value: sourceCommit }, { name: "AGENTRUN_BOOT_COMMIT", value: sourceCommit }, { name: "AGENTRUN_BOOT_MODE", value: "mgr" }, { name: "AGENTRUN_BOOT_REPO_URL", value: spec.deployment.manager.bootRepoUrl }, { name: "AGENTRUN_ENV_IDENTITY", value: envIdentity }, { name: "AGENTRUN_RUNTIME_NAMESPACE", value: spec.runtime.namespace }, { name: "AGENTRUN_INTERNAL_MGR_URL", value: spec.runtime.internalBaseUrl }, { name: "AGENTRUN_RUNNER_IMAGE", value: imageRef }, { name: "AGENTRUN_RUNNER_SERVICE_ACCOUNT", value: spec.deployment.runner.serviceAccount }, { name: "AGENTRUN_RUNNER_JOB_NAME_PREFIX", value: spec.deployment.runner.jobNamePrefix }, { name: "AGENTRUN_RUNNER_IDLE_TIMEOUT_MS", value: String(spec.deployment.runner.idleTimeoutMs) }, { name: "AGENTRUN_BACKEND_RETRY_MAX_ATTEMPTS", value: String(spec.deployment.runner.backendRetry.maxAttempts) }, { name: "AGENTRUN_BACKEND_RETRY_INITIAL_BACKOFF_MS", value: String(spec.deployment.runner.backendRetry.initialBackoffMs) }, { name: "AGENTRUN_BACKEND_RETRY_MAX_BACKOFF_MS", value: String(spec.deployment.runner.backendRetry.maxBackoffMs) }, { name: "AGENTRUN_RUNNER_RETENTION_MAX_RUNNERS", value: String(spec.deployment.runner.retention.maxRunners) }, { name: "AGENTRUN_RUNNER_RETENTION_CLEANUP_ORDER", value: spec.deployment.runner.retention.cleanupOrder }, { name: "AGENTRUN_RUNNER_RETENTION_ACTIVE_HEARTBEAT_MAX_AGE_MS", value: String(spec.deployment.runner.retention.activeHeartbeatMaxAgeMs) }, { name: "AGENTRUN_RUNNER_RETENTION_MATCH_LABELS_JSON", value: JSON.stringify(spec.deployment.runner.retention.selectors.matchLabels) }, { name: "AGENTRUN_RUNNER_RETENTION_JOB_NAME_PREFIXES", value: spec.deployment.runner.retention.selectors.jobNamePrefixes.join(",") }, { name: "AGENTRUN_RUNNER_RETENTION_AGE_BASED_CLEANUP_ENABLED", value: String(spec.deployment.runner.retention.ageBasedCleanup.enabled) }, ...(spec.deployment.runner.retention.ageBasedCleanup.maxAgeHours === null ? [] : [{ name: "AGENTRUN_RUNNER_RETENTION_AGE_BASED_MAX_AGE_HOURS", value: String(spec.deployment.runner.retention.ageBasedCleanup.maxAgeHours) }]), { name: "AGENTRUN_CANCEL_DELIVERY_MODE", value: spec.deployment.runner.cancelLifecycle.deliveryMode }, { name: "AGENTRUN_CANCEL_GRACEFUL_ABORT_MS", value: String(spec.deployment.runner.cancelLifecycle.gracefulAbortMs) }, { name: "AGENTRUN_CANCEL_KILL_ESCALATION_MS", value: String(spec.deployment.runner.cancelLifecycle.killEscalationMs) }, { name: "AGENTRUN_CANCEL_STALE_HEARTBEAT_FENCING_MS", value: String(spec.deployment.runner.cancelLifecycle.staleHeartbeatFencingMs) }, { name: "AGENTRUN_CANCEL_LATE_WRITE_FENCING_ENABLED", value: String(spec.deployment.runner.cancelLifecycle.lateWriteFencing.enabled) }, { name: "AGENTRUN_CANCEL_EVENT_STAGES", value: spec.deployment.runner.cancelLifecycle.eventStages.join(",") }, ...(spec.deployment.runner.egressProxyUrl === null ? [] : [{ name: "AGENTRUN_RUNNER_EGRESS_PROXY_URL", value: spec.deployment.runner.egressProxyUrl }]), ...(spec.deployment.runner.noProxyExtra.length === 0 ? [] : [{ name: "AGENTRUN_RUNNER_NO_PROXY_EXTRA", value: spec.deployment.runner.noProxyExtra.join(",") }]), { name: "AGENTRUN_API_KEY", valueFrom: { secretKeyRef: spec.deployment.manager.apiKeySecretRef } }, ...Object.entries(spec.deployment.manager.env).sort(([left], [right]) => left.localeCompare(right)).map(([name, value]) => ({ name, value })), ...(spec.deployment.manager.unideskSshEndpointEnv === null ? [] : [{ name: spec.deployment.manager.unideskSshEndpointEnv.name, value: spec.deployment.manager.unideskSshEndpointEnv.value }]), ]; } function agentRunRunnerRbacManifests(spec: AgentRunLaneSpec): readonly Record[] { return [ { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: spec.deployment.runner.serviceAccount, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: `${spec.deployment.runner.serviceAccount}-secret-reader`, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, rules: [{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] }], }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: `${spec.deployment.runner.serviceAccount}-secret-reader`, namespace: spec.runtime.namespace, labels: agentRunLabels(spec) }, subjects: [{ kind: "ServiceAccount", name: spec.deployment.runner.serviceAccount }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: `${spec.deployment.runner.serviceAccount}-secret-reader` }, }, ]; } function agentRunArtifactCatalog(spec: AgentRunLaneSpec, sourceCommit: string, image: AgentRunArtifactService): AgentRunArtifactCatalog { return { lane: spec.version, sourceBranch: spec.source.branch, gitopsBranch: spec.gitops.branch, sourceCommitId: sourceCommit, summary: image.status === "placeholder" ? "build=0 reuse=0 placeholder=1" : image.status === "reused" ? "build=0 reuse=1 placeholder=0" : "build=1 reuse=0 placeholder=0", services: [image], }; } function agentRunLabels(spec: AgentRunLaneSpec): Record { return { "app.kubernetes.io/part-of": "agentrun", "agentrun.pikastech.local/lane": spec.version, "agentrun.pikastech.local/node": spec.nodeId, }; } function yaml(value: unknown): string { return `${Bun.YAML.stringify(value).trim()}\n`; } function yamlAll(values: readonly unknown[]): string { return `${values.map((value) => Bun.YAML.stringify(value).trim()).join("\n---\n")}\n`; }