feat: support env reuse and git mirror in v0.1 cicd
This commit is contained in:
@@ -52,7 +52,7 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Agent 执行基础设施。本仓
|
||||
|
||||
- `docs/reference/spec-v01-documentation-governance.md`:v0.1 文档治理、唯一入口、spec 权威和过程材料承载规则。
|
||||
- `docs/reference/spec-v01-services.md`:v0.1 服务总览、保留对象、deferred 对象和单服务规格索引。
|
||||
- `docs/reference/spec-v01-cicd.md`:v0.1 分支、source worktree、namespace、GitOps、registry、CI/CD 和发布验收规格。
|
||||
- `docs/reference/spec-v01-cicd.md`:v0.1 分支、source worktree、git mirror、env reuse、namespace、GitOps、registry、CI/CD 和发布验收规格。
|
||||
- `docs/reference/spec-v01-postgres.md`:v0.1 Postgres durable store、schema migration 和 SecretRef 规格。
|
||||
- `docs/reference/spec-v01-secret-distribution.md`:v0.1 Code Agent provider credential 和运行时 Secret 分发规格。
|
||||
- `docs/reference/spec-v01-runtime-assembly.md`:v0.1 runner/backend 启动前的装配 SPEC,覆盖 BackendImageRef、ProfileRef、SessionRef、Git-only ResourceBundleRef 和 tool credential SecretRef scope。
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
FROM oven/bun:1.2.15-alpine
|
||||
|
||||
WORKDIR /app
|
||||
WORKDIR /opt/agentrun
|
||||
ARG HTTP_PROXY
|
||||
ARG HTTPS_PROXY
|
||||
ARG NO_PROXY
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=8080
|
||||
ENV AGENTRUN_CODEX_COMMAND=/app/node_modules/.bin/codex
|
||||
ENV AGENTRUN_CODEX_COMMAND=/opt/agentrun/node_modules/.bin/codex
|
||||
ENV AGENTRUN_APP_ROOT=/workspace/agentrun
|
||||
ENV AGENTRUN_BOOT_REPO_URL=http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git
|
||||
|
||||
RUN HTTP_PROXY="$HTTP_PROXY" HTTPS_PROXY="$HTTPS_PROXY" NO_PROXY="$NO_PROXY" http_proxy="$HTTP_PROXY" https_proxy="$HTTPS_PROXY" no_proxy="$NO_PROXY" \
|
||||
apk add --no-cache ca-certificates curl git github-cli kubectl nodejs openssh-client
|
||||
|
||||
COPY package.json tsconfig.json ./
|
||||
COPY package.json bun.lock tsconfig.json ./
|
||||
RUN HTTP_PROXY="$HTTP_PROXY" HTTPS_PROXY="$HTTPS_PROXY" NO_PROXY="$NO_PROXY" http_proxy="$HTTP_PROXY" https_proxy="$HTTPS_PROXY" no_proxy="$NO_PROXY" \
|
||||
bun install --production
|
||||
RUN /app/node_modules/.bin/codex --version && /app/node_modules/.bin/codex app-server --help >/dev/null
|
||||
COPY scripts ./scripts
|
||||
COPY src ./src
|
||||
COPY deploy/deploy.json ./deploy/deploy.json
|
||||
RUN /opt/agentrun/node_modules/.bin/codex --version && /opt/agentrun/node_modules/.bin/codex app-server --help >/dev/null
|
||||
COPY deploy/runtime/boot ./deploy/runtime/boot
|
||||
RUN chmod +x /opt/agentrun/deploy/runtime/boot/*.sh
|
||||
|
||||
EXPOSE 8080
|
||||
CMD ["bun", "src/mgr/main.ts"]
|
||||
CMD ["/opt/agentrun/deploy/runtime/boot/agentrun-mgr.sh"]
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
entrypoint="${1:-src/mgr/main.ts}"
|
||||
repo_url="${AGENTRUN_BOOT_REPO_URL:-http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git}"
|
||||
commit="${AGENTRUN_BOOT_COMMIT:-${AGENTRUN_SOURCE_COMMIT:-}}"
|
||||
app_root="${AGENTRUN_APP_ROOT:-/workspace/agentrun}"
|
||||
|
||||
if [ -z "$commit" ] || ! printf '%s' "$commit" | grep -Eq '^[0-9a-f]{40}$'; then
|
||||
printf '{"ok":false,"event":"agentrun-boot","failureKind":"source-commit-invalid","message":"AGENTRUN_BOOT_COMMIT must be a full git SHA"}\n' >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$app_root")"
|
||||
rm -rf "$app_root"
|
||||
mkdir -p "$app_root"
|
||||
cd "$app_root"
|
||||
git init -q
|
||||
git remote add origin "$repo_url"
|
||||
|
||||
fetch_log=/tmp/agentrun-boot-fetch.log
|
||||
if ! git fetch --depth=1 origin "$commit" >"$fetch_log" 2>&1; then
|
||||
message=$(tail -n 20 "$fetch_log" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g')
|
||||
failure_kind=git-mirror-fetch-failed
|
||||
if printf '%s' "$message" | grep -Eiq 'not our ref|unadvertised|Server does not allow request'; then
|
||||
failure_kind=git-mirror-exact-commit-unavailable
|
||||
elif printf '%s' "$message" | grep -Eiq 'Could not resolve host|Connection timed out|Failed to connect'; then
|
||||
failure_kind=git-mirror-network-failed
|
||||
elif printf '%s' "$message" | grep -Eiq 'Authentication failed|Permission denied|repository.*not found'; then
|
||||
failure_kind=git-mirror-auth-failed
|
||||
fi
|
||||
printf '{"ok":false,"event":"agentrun-boot","failureKind":"%s","repoUrl":"%s","commit":"%s","message":%s}\n' "$failure_kind" "$repo_url" "$commit" "$(node -e 'console.log(JSON.stringify(process.argv[1] || ""))' "$message")" >&2
|
||||
exit 65
|
||||
fi
|
||||
git checkout -q --detach "$commit"
|
||||
actual=$(git rev-parse HEAD)
|
||||
if [ "$actual" != "$commit" ]; then
|
||||
printf '{"ok":false,"event":"agentrun-boot","failureKind":"source-commit-mismatch","expected":"%s","actual":"%s"}\n' "$commit" "$actual" >&2
|
||||
exit 66
|
||||
fi
|
||||
|
||||
ln -sfn /opt/agentrun/node_modules "$app_root/node_modules"
|
||||
printf '{"ok":true,"event":"agentrun-boot","repoUrl":"%s","commit":"%s","entrypoint":"%s","nodeModules":"/opt/agentrun/node_modules"}\n' "$repo_url" "$commit" "$entrypoint"
|
||||
exec bun "$entrypoint"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /opt/agentrun/deploy/runtime/boot/agentrun-boot.sh src/mgr/main.ts
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
exec /opt/agentrun/deploy/runtime/boot/agentrun-boot.sh src/runner/main.ts
|
||||
@@ -11,6 +11,12 @@ spec:
|
||||
- name: git-url
|
||||
type: string
|
||||
default: git@github.com:pikasTech/agentrun.git
|
||||
- name: git-read-url
|
||||
type: string
|
||||
default: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git
|
||||
- name: git-write-url
|
||||
type: string
|
||||
default: http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/agentrun.git
|
||||
- name: source-branch
|
||||
type: string
|
||||
default: v0.1
|
||||
@@ -33,17 +39,13 @@ spec:
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: source
|
||||
- name: git-ssh
|
||||
workspace: git-ssh
|
||||
taskSpec:
|
||||
params:
|
||||
- name: git-url
|
||||
- name: source-branch
|
||||
- name: git-read-url
|
||||
- name: revision
|
||||
- name: tools-image
|
||||
workspaces:
|
||||
- name: source
|
||||
- name: git-ssh
|
||||
steps:
|
||||
- name: clone-and-check
|
||||
image: $(params.tools-image)
|
||||
@@ -60,37 +62,132 @@ spec:
|
||||
value: http://127.0.0.1:10808
|
||||
- name: no_proxy
|
||||
value: hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.42.0.0/16,10.43.0.0/16,.svc,.cluster.local
|
||||
- name: GIT_TERMINAL_PROMPT
|
||||
value: "0"
|
||||
script: |
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
apk add --no-cache git openssh-client curl
|
||||
mkdir -p /root/.ssh
|
||||
cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa
|
||||
chmod 600 /root/.ssh/id_rsa
|
||||
ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null
|
||||
apk add --no-cache git curl
|
||||
rm -rf /workspace/source/repo
|
||||
git clone --branch "$(params.source-branch)" "$(params.git-url)" /workspace/source/repo
|
||||
mkdir -p /workspace/source/repo
|
||||
git init /workspace/source/repo
|
||||
cd /workspace/source/repo
|
||||
git config --global --add safe.directory /workspace/source/repo
|
||||
git checkout "$(params.revision)"
|
||||
git remote add origin "$(params.git-read-url)"
|
||||
git fetch --depth=1 origin "$(params.revision)"
|
||||
git checkout --detach FETCH_HEAD
|
||||
test "$(git rev-parse HEAD)" = "$(params.revision)"
|
||||
bun install
|
||||
bun install --frozen-lockfile
|
||||
bun run check
|
||||
bun scripts/agentrun-gitops-render.ts --out /tmp/agentrun-gitops-render-check --source-commit "$(params.revision)" --check
|
||||
AGENTRUN_SELFTEST_CODEX_COMMAND="$(command -v bun)" \
|
||||
AGENTRUN_SELFTEST_CODEX_ARGS="[\"$PWD/src/selftest/fake-codex-app-server.ts\"]" \
|
||||
bun run self-test
|
||||
params:
|
||||
- name: git-url
|
||||
value: $(params.git-url)
|
||||
- name: source-branch
|
||||
value: $(params.source-branch)
|
||||
- name: git-read-url
|
||||
value: $(params.git-read-url)
|
||||
- name: revision
|
||||
value: $(params.revision)
|
||||
- name: tools-image
|
||||
value: $(params.tools-image)
|
||||
- name: image-publish
|
||||
- name: plan-artifacts
|
||||
runAfter: [prepare-source]
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: source
|
||||
taskSpec:
|
||||
params:
|
||||
- name: git-read-url
|
||||
- name: gitops-branch
|
||||
- name: revision
|
||||
- name: registry-prefix
|
||||
results:
|
||||
- name: env-identity
|
||||
- name: build-count
|
||||
- name: reuse-count
|
||||
- name: summary
|
||||
workspaces:
|
||||
- name: source
|
||||
steps:
|
||||
- name: plan
|
||||
image: oven/bun:1.2.15-alpine
|
||||
env:
|
||||
- name: GIT_TERMINAL_PROMPT
|
||||
value: "0"
|
||||
script: |
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
apk add --no-cache git nodejs
|
||||
cd /workspace/source/repo
|
||||
node <<'NODE' > /workspace/source/env-identity
|
||||
const { createHash } = require("node:crypto");
|
||||
const { readFileSync } = require("node:fs");
|
||||
const inputs = [
|
||||
["baseImage", "oven/bun:1.2.15-alpine"],
|
||||
["systemPackages", "ca-certificates git kubectl nodejs openssh-client"],
|
||||
["containerfile", readFileSync("deploy/container/Containerfile", "utf8")],
|
||||
["bootScript", readFileSync("deploy/runtime/boot/agentrun-boot.sh", "utf8")],
|
||||
["bootMgr", readFileSync("deploy/runtime/boot/agentrun-mgr.sh", "utf8")],
|
||||
["bootRunner", readFileSync("deploy/runtime/boot/agentrun-runner.sh", "utf8")],
|
||||
["packageJson", readFileSync("package.json", "utf8")],
|
||||
["bunLock", readFileSync("bun.lock", "utf8")],
|
||||
["tsconfig", readFileSync("tsconfig.json", "utf8")],
|
||||
];
|
||||
process.stdout.write(createHash("sha256").update(JSON.stringify(inputs)).digest("hex").slice(0, 20));
|
||||
NODE
|
||||
env_identity="$(cat /workspace/source/env-identity)"
|
||||
rm -rf /workspace/source/gitops-prev
|
||||
if git clone --depth=1 --branch "$(params.gitops-branch)" "$(params.git-read-url)" /workspace/source/gitops-prev >/tmp/agentrun-prev-gitops.log 2>&1; then
|
||||
prev_catalog=/workspace/source/gitops-prev/deploy/artifact-catalog.v01.json
|
||||
else
|
||||
prev_catalog=/dev/null
|
||||
fi
|
||||
AGENTRUN_ENV_IDENTITY="$env_identity" \
|
||||
AGENTRUN_PREV_CATALOG="$prev_catalog" \
|
||||
AGENTRUN_REVISION="$(params.revision)" \
|
||||
AGENTRUN_GITOPS_BRANCH="$(params.gitops-branch)" \
|
||||
AGENTRUN_REGISTRY_PREFIX="$(params.registry-prefix)" \
|
||||
node <<'NODE'
|
||||
const { readFileSync, writeFileSync } = require("node:fs");
|
||||
const envIdentity = process.env.AGENTRUN_ENV_IDENTITY;
|
||||
const revision = process.env.AGENTRUN_REVISION;
|
||||
const gitopsBranch = process.env.AGENTRUN_GITOPS_BRANCH;
|
||||
let previousService = null;
|
||||
try {
|
||||
const catalog = JSON.parse(readFileSync(process.env.AGENTRUN_PREV_CATALOG, "utf8"));
|
||||
previousService = (catalog.services || []).find((item) => item.serviceId === "agentrun-mgr" && item.envIdentity === envIdentity && /^sha256:[a-f0-9]{64}$/.test(item.envDigest || item.digest || "")) || null;
|
||||
} catch {}
|
||||
const reused = previousService !== null;
|
||||
const plan = {
|
||||
lane: "v0.1",
|
||||
sourceBranch: "v0.1",
|
||||
gitopsBranch,
|
||||
sourceCommitId: revision,
|
||||
envIdentity,
|
||||
toolchainInputs: ["oven/bun:1.2.15-alpine", "deploy/container/Containerfile", "deploy/runtime/boot/*.sh", "package.json", "bun.lock", "tsconfig.json", "apk:ca-certificates git kubectl nodejs openssh-client"],
|
||||
buildServices: reused ? [] : ["agentrun-mgr"],
|
||||
reusedServices: reused ? ["agentrun-mgr"] : [],
|
||||
unsafeReuseServices: [],
|
||||
previousService,
|
||||
summary: `build=${reused ? 0 : 1} reuse=${reused ? 1 : 0} unsafeReuse=0`,
|
||||
};
|
||||
writeFileSync("/workspace/source/ci-plan.json", `${JSON.stringify(plan, null, 2)}\n`);
|
||||
writeFileSync("/tekton/results/env-identity", envIdentity);
|
||||
writeFileSync("/tekton/results/build-count", String(plan.buildServices.length));
|
||||
writeFileSync("/tekton/results/reuse-count", String(plan.reusedServices.length));
|
||||
writeFileSync("/tekton/results/summary", plan.summary);
|
||||
console.log(JSON.stringify({ event: "agentrun-ci-plan", status: "succeeded", sourceCommitId: revision, envIdentity, buildServices: plan.buildServices, reusedServices: plan.reusedServices, unsafeReuseServices: [], summary: plan.summary }));
|
||||
NODE
|
||||
params:
|
||||
- name: git-read-url
|
||||
value: $(params.git-read-url)
|
||||
- name: gitops-branch
|
||||
value: $(params.gitops-branch)
|
||||
- name: revision
|
||||
value: $(params.revision)
|
||||
- name: registry-prefix
|
||||
value: $(params.registry-prefix)
|
||||
- name: image-publish
|
||||
runAfter: [plan-artifacts]
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: source
|
||||
@@ -102,6 +199,8 @@ spec:
|
||||
- name: image
|
||||
- name: digest
|
||||
- name: repository-digest
|
||||
- name: env-identity
|
||||
- name: status
|
||||
sidecars:
|
||||
- name: buildkitd
|
||||
image: moby/buildkit:rootless
|
||||
@@ -145,7 +244,7 @@ spec:
|
||||
volumeMounts:
|
||||
- name: buildkit-bin
|
||||
mountPath: /workspace/buildkit-bin
|
||||
- name: build-and-push
|
||||
- name: build-or-reuse
|
||||
image: oven/bun:1.2.15-alpine
|
||||
env:
|
||||
- name: HTTP_PROXY
|
||||
@@ -157,9 +256,28 @@ spec:
|
||||
script: |
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
apk add --no-cache curl
|
||||
apk add --no-cache curl nodejs
|
||||
cd /workspace/source/repo
|
||||
image="$(params.registry-prefix)/agentrun-mgr:$(params.revision)"
|
||||
env_identity="$(cat /workspace/source/env-identity)"
|
||||
if node -e 'const p=require("/workspace/source/ci-plan.json"); process.exit((p.buildServices||[]).length===0 ? 0 : 1)'; then
|
||||
node <<'NODE'
|
||||
const { readFileSync, writeFileSync } = require("node:fs");
|
||||
const plan = JSON.parse(readFileSync("/workspace/source/ci-plan.json", "utf8"));
|
||||
const service = plan.previousService;
|
||||
if (!service) throw new Error("reuse plan missing previousService");
|
||||
const image = service.envImage || service.image;
|
||||
const digest = service.envDigest || service.digest;
|
||||
const repositoryDigest = service.envRepositoryDigest || service.repositoryDigest;
|
||||
writeFileSync("/tekton/results/image", image);
|
||||
writeFileSync("/tekton/results/digest", digest);
|
||||
writeFileSync("/tekton/results/repository-digest", repositoryDigest);
|
||||
writeFileSync("/tekton/results/env-identity", plan.envIdentity);
|
||||
writeFileSync("/tekton/results/status", "reused");
|
||||
console.log(JSON.stringify({ event: "agentrun-env-image", status: "reused", serviceId: "agentrun-mgr", envIdentity: plan.envIdentity, image, digest, summary: plan.summary }));
|
||||
NODE
|
||||
exit 0
|
||||
fi
|
||||
image="$(params.registry-prefix)/agentrun-mgr-env:${env_identity}"
|
||||
buildctl=/workspace/buildkit-bin/buildctl
|
||||
for attempt in $(seq 1 60); do
|
||||
if "$buildctl" --addr unix:///workspace/buildkit-run/buildkitd.sock debug workers >/dev/null 2>&1; then break; fi
|
||||
@@ -174,12 +292,15 @@ spec:
|
||||
--opt build-arg:HTTPS_PROXY=http://127.0.0.1:10808 \
|
||||
--opt build-arg:NO_PROXY=hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.42.0.0/16,10.43.0.0/16,.svc,.cluster.local \
|
||||
--output type=image,name="$image",push=true,registry.insecure=true
|
||||
digest="$(curl -fsSI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" "http://127.0.0.1:5000/v2/agentrun/agentrun-mgr/manifests/$(params.revision)" | awk -F': ' 'tolower($1)=="docker-content-digest" {gsub(/\r/,"",$2); print $2; exit}')"
|
||||
digest="$(curl -fsSI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" "http://127.0.0.1:5000/v2/agentrun/agentrun-mgr-env/manifests/$env_identity" | awk -F': ' 'tolower($1)=="docker-content-digest" {gsub(/\r/,"",$2); print $2; exit}')"
|
||||
test -n "$digest"
|
||||
curl -fsSI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" "http://127.0.0.1:5000/v2/agentrun/agentrun-mgr/manifests/$digest" >/dev/null
|
||||
curl -fsSI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" "http://127.0.0.1:5000/v2/agentrun/agentrun-mgr-env/manifests/$digest" >/dev/null
|
||||
printf '%s' "$image" > /tekton/results/image
|
||||
printf '%s' "$digest" > /tekton/results/digest
|
||||
printf '%s' "$(params.registry-prefix)/agentrun-mgr@$digest" > /tekton/results/repository-digest
|
||||
printf '%s' "$(params.registry-prefix)/agentrun-mgr-env@$digest" > /tekton/results/repository-digest
|
||||
printf '%s' "$env_identity" > /tekton/results/env-identity
|
||||
printf '%s' built > /tekton/results/status
|
||||
printf '{"event":"agentrun-env-image","status":"built","serviceId":"agentrun-mgr","envIdentity":"%s","image":"%s","digest":"%s"}\n' "$env_identity" "$image" "$digest"
|
||||
volumeMounts:
|
||||
- name: buildkit-bin
|
||||
mountPath: /workspace/buildkit-bin
|
||||
@@ -200,47 +321,85 @@ spec:
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: source
|
||||
- name: git-ssh
|
||||
workspace: git-ssh
|
||||
taskSpec:
|
||||
params:
|
||||
- name: git-url
|
||||
- name: git-write-url
|
||||
- name: gitops-branch
|
||||
- name: revision
|
||||
- name: registry-prefix
|
||||
- name: image
|
||||
- name: digest
|
||||
- name: repository-digest
|
||||
- name: env-identity
|
||||
- name: image-status
|
||||
workspaces:
|
||||
- name: source
|
||||
- name: git-ssh
|
||||
steps:
|
||||
- name: promote
|
||||
image: oven/bun:1.2.15-alpine
|
||||
env:
|
||||
- name: HTTP_PROXY
|
||||
value: http://127.0.0.1:10808
|
||||
- name: HTTPS_PROXY
|
||||
value: http://127.0.0.1:10808
|
||||
- name: NO_PROXY
|
||||
value: hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.42.0.0/16,10.43.0.0/16,.svc,.cluster.local
|
||||
- name: GIT_TERMINAL_PROMPT
|
||||
value: "0"
|
||||
- name: AGENTRUN_IMAGE
|
||||
value: $(params.image)
|
||||
- name: AGENTRUN_DIGEST
|
||||
value: $(params.digest)
|
||||
- name: AGENTRUN_REPOSITORY_DIGEST
|
||||
value: $(params.repository-digest)
|
||||
- name: AGENTRUN_ENV_IDENTITY
|
||||
value: $(params.env-identity)
|
||||
- name: AGENTRUN_IMAGE_STATUS
|
||||
value: $(params.image-status)
|
||||
- name: AGENTRUN_REVISION
|
||||
value: $(params.revision)
|
||||
- name: AGENTRUN_GITOPS_BRANCH
|
||||
value: $(params.gitops-branch)
|
||||
script: |
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
apk add --no-cache git openssh-client
|
||||
mkdir -p /root/.ssh
|
||||
cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa
|
||||
chmod 600 /root/.ssh/id_rsa
|
||||
ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null
|
||||
apk add --no-cache git openssh-client nodejs
|
||||
cd /workspace/source/repo
|
||||
cat > /workspace/source/artifact-catalog.v01.json <<EOF
|
||||
{"lane":"v0.1","sourceBranch":"v0.1","gitopsBranch":"$(params.gitops-branch)","sourceCommitId":"$(params.revision)","services":[{"serviceId":"agentrun-mgr","image":"$(params.image)","digest":"$(params.digest)","repositoryDigest":"$(params.repository-digest)","imageTag":"$(params.revision)"}]}
|
||||
EOF
|
||||
node <<'NODE' > /workspace/source/artifact-catalog.v01.json
|
||||
const { readFileSync } = require("node:fs");
|
||||
const plan = JSON.parse(readFileSync("/workspace/source/ci-plan.json", "utf8"));
|
||||
const service = {
|
||||
serviceId: "agentrun-mgr",
|
||||
artifactKind: "env-reuse",
|
||||
status: process.env.AGENTRUN_IMAGE_STATUS,
|
||||
image: process.env.AGENTRUN_IMAGE,
|
||||
digest: process.env.AGENTRUN_DIGEST,
|
||||
repositoryDigest: process.env.AGENTRUN_REPOSITORY_DIGEST,
|
||||
imageTag: process.env.AGENTRUN_ENV_IDENTITY,
|
||||
envIdentity: process.env.AGENTRUN_ENV_IDENTITY,
|
||||
envImage: process.env.AGENTRUN_IMAGE,
|
||||
envDigest: process.env.AGENTRUN_DIGEST,
|
||||
envRepositoryDigest: process.env.AGENTRUN_REPOSITORY_DIGEST,
|
||||
bootCommit: process.env.AGENTRUN_REVISION,
|
||||
bootScript: "deploy/runtime/boot/agentrun-boot.sh",
|
||||
provenance: {
|
||||
sourceCommitId: process.env.AGENTRUN_REVISION,
|
||||
toolchainInputs: plan.toolchainInputs,
|
||||
buildServices: plan.buildServices,
|
||||
reusedServices: plan.reusedServices,
|
||||
unsafeReuseServices: plan.unsafeReuseServices,
|
||||
previousSourceCommitId: plan.previousService?.bootCommit || null,
|
||||
},
|
||||
};
|
||||
const catalog = {
|
||||
lane: "v0.1",
|
||||
sourceBranch: "v0.1",
|
||||
gitopsBranch: process.env.AGENTRUN_GITOPS_BRANCH,
|
||||
sourceCommitId: process.env.AGENTRUN_REVISION,
|
||||
summary: plan.summary,
|
||||
services: [service],
|
||||
};
|
||||
console.log(JSON.stringify(catalog, null, 2));
|
||||
NODE
|
||||
rm -rf /workspace/source/rendered
|
||||
bun scripts/agentrun-gitops-render.ts --out /workspace/source/rendered --source-commit "$(params.revision)" --registry-prefix "$(params.registry-prefix)" --catalog /workspace/source/artifact-catalog.v01.json --require-catalog
|
||||
rm -rf /workspace/source/gitops
|
||||
git clone --branch "$(params.gitops-branch)" "$(params.git-url)" /workspace/source/gitops || {
|
||||
git clone "$(params.git-url)" /workspace/source/gitops
|
||||
git clone --branch "$(params.gitops-branch)" "$(params.git-write-url)" /workspace/source/gitops || {
|
||||
git clone "$(params.git-write-url)" /workspace/source/gitops
|
||||
cd /workspace/source/gitops
|
||||
git checkout --orphan "$(params.gitops-branch)"
|
||||
git rm -rf . >/dev/null 2>&1 || true
|
||||
@@ -256,9 +415,10 @@ spec:
|
||||
git add deploy
|
||||
git commit -m "gitops: promote agentrun v0.1 $(params.revision)" || true
|
||||
git push origin "$(params.gitops-branch)"
|
||||
printf '{"event":"agentrun-gitops-promote","status":"succeeded","sourceCommitId":"%s","envIdentity":"%s","imageStatus":"%s","summary":%s}\n' "$(params.revision)" "$(params.env-identity)" "$(params.image-status)" "$(node -e 'const p=require("/workspace/source/ci-plan.json"); console.log(JSON.stringify(p.summary))')"
|
||||
params:
|
||||
- name: git-url
|
||||
value: $(params.git-url)
|
||||
- name: git-write-url
|
||||
value: $(params.git-write-url)
|
||||
- name: gitops-branch
|
||||
value: $(params.gitops-branch)
|
||||
- name: revision
|
||||
@@ -271,3 +431,7 @@ spec:
|
||||
value: $(tasks.image-publish.results.digest)
|
||||
- name: repository-digest
|
||||
value: $(tasks.image-publish.results.repository-digest)
|
||||
- name: env-identity
|
||||
value: $(tasks.image-publish.results.env-identity)
|
||||
- name: image-status
|
||||
value: $(tasks.image-publish.results.status)
|
||||
|
||||
@@ -16,6 +16,10 @@ spec:
|
||||
params:
|
||||
- name: revision
|
||||
value: REPLACE_WITH_FULL_SOURCE_COMMIT
|
||||
- name: git-read-url
|
||||
value: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git
|
||||
- name: git-write-url
|
||||
value: http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/agentrun.git
|
||||
workspaces:
|
||||
- name: source
|
||||
volumeClaimTemplate:
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
- `v0.1` 只能部署到 `agentrun-v01` namespace;`v0.2`、`v0.3` 后续使用自己的 namespace 和 GitOps 路径。
|
||||
- CI/CD 必须使用 G14 原生 k3s、纯 Tekton Pipeline/Task/PipelineRun 和 Argo CD;不得引入自定义 runner、CI.json runner、长期自研 poller/reconciler、D601 legacy、临时 clone、手工 Pod patch 或本地镜像作为发布真相。
|
||||
- `v0.1` 的 CD 唯一手写真相源是 source branch 内的 `deploy/deploy.json`;Tekton 生成的 artifact catalog 和 Argo desired state 必须与 source branch 分离,只写入 `v0.1-gitops`。
|
||||
- Source checkout 与 GitOps promotion 必须优先走 `devops-infra` git mirror/relay;Pipeline 不直接依赖 GitHub SSH fetch/push。
|
||||
- Runtime image 采用 env reuse:依赖、Bun/Node、系统包、lockfile、Containerfile 和 boot 脚本构成 env identity;普通 TS/文档/CLI 业务源码变更只改变 boot commit,不重建 env image。
|
||||
- 发布证据以 live runtime、Argo desired state、GitOps branch、Tekton 证据和干净 source worktree 顺序判断。
|
||||
|
||||
## 固定命名
|
||||
@@ -21,12 +23,17 @@
|
||||
| Worktree root | `G14:/root/agentrun-v01/.worktree/{task}` |
|
||||
| Runtime namespace | `agentrun-v01` |
|
||||
| GitOps branch | `v0.1-gitops` |
|
||||
| Git mirror read URL | `http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git` |
|
||||
| Git mirror write URL | `http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/agentrun.git` |
|
||||
| Git mirror cache | `devops-infra:/cache/pikasTech/agentrun.git` |
|
||||
| Artifact catalog | `v0.1-gitops:deploy/artifact-catalog.v01.json` |
|
||||
| Runtime path | `v0.1-gitops:deploy/gitops/g14/runtime-v01` |
|
||||
| Tekton namespace | `agentrun-ci` |
|
||||
| Tekton Pipeline | `agentrun-v01-ci-image-publish` |
|
||||
| Tekton ServiceAccount | `agentrun-v01-tekton-runner` |
|
||||
| PipelineRun prefix | `agentrun-v01-ci-<short12>` |
|
||||
| Env image repository | `127.0.0.1:5000/agentrun/agentrun-mgr-env:<envIdentity>` |
|
||||
| Boot script | `deploy/runtime/boot/agentrun-boot.sh` |
|
||||
| Argo CD AppProject | `argocd/agentrun-v01` |
|
||||
| Argo CD Application | `argocd/agentrun-g14-v01` |
|
||||
|
||||
@@ -45,7 +52,9 @@ CI 的最小检查应覆盖:
|
||||
- Bun/TS 单元自测试,包括 manager schema、adapter mock、Codex fake app-server stdio client 和 CLI JSON 输出。
|
||||
- `deploy/deploy.json` 与 GitOps render 只读校验。
|
||||
|
||||
容器镜像可以直接运行 TS 入口,也可以运行同一 source commit 构建出的 JS artifact;无论选择哪种形式,artifact catalog 必须记录完整 source commit 和 image digest。CI/CD 仍然只允许纯 Tekton + Argo CD,不因 Bun 工具链引入自定义 runner、长期 poller 或源分支生成物提交。
|
||||
容器镜像必须区分 env identity 与 source commit。`agentrun-mgr-env:<envIdentity>` 只包含 Bun runtime、生产依赖、Codex CLI、git/kubectl/node 等系统依赖和 boot 脚本,不 bake `src/`、`scripts/` 或某个业务 source commit。运行时通过 `AGENTRUN_BOOT_COMMIT` 从 git mirror 按完整 SHA 做 `git fetch --depth=1 origin <sha>`,再用 env image 内的 `node_modules` 启动 manager 或 runner。CI/CD 仍然只允许纯 Tekton + Argo CD,不因 Bun 工具链引入自定义 runner、长期 poller 或源分支生成物提交。
|
||||
|
||||
Env identity 的输入至少包含:Bun base image、系统包列表、`deploy/container/Containerfile`、`deploy/runtime/boot/*.sh`、`package.json`、`bun.lock` 和 `tsconfig.json`。只改业务 TS、文档、模板中不影响 runtime env 的内容时,planner 必须输出 `build=0 reuse=1 unsafeReuse=0`,并复用上一版 catalog 中同一 `envIdentity` 的 digest。
|
||||
|
||||
## 真相源
|
||||
|
||||
@@ -54,9 +63,11 @@ CI 的最小检查应覆盖:
|
||||
1. Live runtime:`agentrun-v01` namespace 中 Deployment/Job/Pod ready、事件、日志和 service health。
|
||||
2. Argo desired state:`argocd/agentrun-g14-v01` 的 revision、sync、health、source branch 和 runtime path。
|
||||
3. GitOps branch:`v0.1-gitops` 中的 `deploy/artifact-catalog.v01.json` 与 `deploy/gitops/g14/runtime-v01/**`。
|
||||
4. Tekton 执行证据:PipelineRun、TaskRun result、image digest 和 promotion 终态。
|
||||
4. Tekton 执行证据:PipelineRun、TaskRun result、env identity、build/reuse summary、image digest 和 promotion 终态。
|
||||
5. 干净 source worktree:`G14:/root/agentrun-v01`、`origin/v0.1`、render 脚本、deploy intent 和 `--no-write` 输出。
|
||||
|
||||
`devops-infra` git mirror 是 CI/CD source 与 GitOps relay,不是新的 source truth。source truth 仍是 GitHub `v0.1`,GitOps truth 仍是 `v0.1-gitops`;mirror 只负责降低 Pipeline 中的外网 fetch/push 抖动,并让 exact commit fetch 可在集群内稳定命中。受控入口是 UniDesk CLI:`agentrun v01 git-mirror status|sync|flush`。`status` 必须展示 `localV01`、`githubV01`、`localGitops`、`githubGitops`、`pendingFlush` 和 exact full-SHA fetch 结果;`trigger-current` 在创建 PipelineRun 前必须先检查 `localV01`,必要时同步 mirror,再继续。
|
||||
|
||||
旧 `master` 记忆、`/root/agentrun` 历史固定目录、`agentrun_dev`、`agentrun_prod`、D601 legacy 路径、临时 worktree 或本地容器只能作为线索,不能作为 `v0.1` 发布通过证据。
|
||||
|
||||
## Source 与 GitOps 分层
|
||||
@@ -76,7 +87,7 @@ CI 的最小检查应覆盖:
|
||||
|
||||
`v0.1-gitops` branch 必须包含:
|
||||
|
||||
- `deploy/artifact-catalog.v01.json`,记录 image tag、digest、source commit、service identity、publish/reuse 状态。
|
||||
- `deploy/artifact-catalog.v01.json`,记录 env image tag、digest、source commit、env identity、service identity、build/reuse 状态和 provenance。
|
||||
- `deploy/gitops/g14/runtime-v01/**`,作为 Argo CD 消费的 desired state。
|
||||
- 必要 generated metadata,但不得包含 Secret 值。
|
||||
|
||||
@@ -107,11 +118,11 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st
|
||||
标准链路如下:
|
||||
|
||||
1. 人工或受控 GitHub/UniDesk 入口为某个 `origin/v0.1` source commit 创建 Tekton `PipelineRun`;触发器可以是 Tekton Triggers 或手动 `PipelineRun`,但不能是长期自定义 runner。
|
||||
2. `prepare-source` checkout `v0.1` source,并从 `v0.1-gitops` 读取上一版 `deploy/artifact-catalog.v01.json`。
|
||||
2. `prepare-source` 通过 git mirror read URL 按完整 SHA checkout `v0.1` source,不使用 branch tip 近似 checkout。
|
||||
3. 原语校验 task 只覆盖文档治理、spec 链接、`deploy/deploy.json` schema、轻量语法和必要单元测试;旧 `dev/prod` gate 不进入 lane。
|
||||
4. Plan task 读取 `deploy/deploy.json` 与上一版 artifact catalog,判断 affected/reused services;planner 只能输出 TaskRun result 或临时 workspace 文件,不回写 source。
|
||||
5. Affected service 通过 Tekton Task 内的 BuildKit/kaniko/buildah 之一发布到 G14 本地 registry;reused service 复用 catalog digest。不得使用 Docker daemon、DIND 或仓库外自定义 runner。
|
||||
6. Promotion task 用 publish results 刷新 `deploy/artifact-catalog.v01.json`,并用 `deploy/deploy.json` render `deploy/gitops/g14/runtime-v01/**`,只推送到 `v0.1-gitops`。
|
||||
4. Plan task 通过 git mirror read URL 读取上一版 `v0.1-gitops:deploy/artifact-catalog.v01.json`,计算 `envIdentity` 并判断 affected/reused services;planner 只能输出 TaskRun result 或临时 workspace 文件,不回写 source。
|
||||
5. Affected env image 通过 Tekton Task 内的 BuildKit 发布到 G14 本地 registry;reused service 直接复用 catalog digest。不得使用 Docker daemon、DIND 或仓库外自定义 runner。
|
||||
6. Promotion task 用 publish/reuse results 刷新 `deploy/artifact-catalog.v01.json`,并用 `deploy/deploy.json` render `deploy/gitops/g14/runtime-v01/**`,只通过 git mirror write URL 推送到本地 `v0.1-gitops` relay。发布收口再用 `agentrun v01 git-mirror flush --confirm` 把 GitOps branch 快进回 GitHub。
|
||||
7. Argo CD Application `agentrun-g14-v01` 从 `v0.1-gitops:deploy/gitops/g14/runtime-v01` 同步到 `agentrun-v01`。
|
||||
8. 验收只观察 `agentrun-v01` runtime、Argo revision/sync/health 和对应 service health。
|
||||
|
||||
@@ -119,10 +130,10 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st
|
||||
|
||||
镜像身份同时是 RuntimeAssembly 的 `BackendImageRef` 来源;四要素总模型见 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)。本文只定义 image digest、artifact catalog 和 GitOps promotion 边界,不定义 profile、session 或 resource bundle 字段。
|
||||
|
||||
- `v0.1` 镜像 tag 使用完整 40 位 source commitId。
|
||||
- `v0.1` env image tag 使用 `envIdentity`,而不是 source commitId。source commitId 通过 `AGENTRUN_BOOT_COMMIT` 和 catalog `bootCommit` 进入 runtime。
|
||||
- Runtime manifest 必须使用 digest pin 作为部署身份;G14 本地 registry 对同一 tag 的默认 HEAD 可能返回 Docker schema1 compatibility digest,Tekton 必须用 `Accept: application/vnd.docker.distribution.manifest.v2+json` 采集 containerd 可直接拉取的 schema2 manifest digest,并在写入 catalog 前按 digest HEAD 验证。
|
||||
- Catalog 必须记录 lane、source branch、GitOps branch、source commitId、serviceId、image tag、digest、component identity 和 publish/reuse 状态。
|
||||
- 同一 source commit 对同一 service 应生成同一镜像;lane 差异放在 manifest、env、SecretRef、namespace、RBAC 和 runtime config 中,不 bake 进镜像。
|
||||
- Catalog 必须记录 lane、source branch、GitOps branch、source commitId、serviceId、env image tag、digest、env identity、boot commit、toolchain inputs 和 build/reuse 状态。
|
||||
- 同一 env identity 对同一 service 应生成同一镜像;lane 差异放在 manifest、env、SecretRef、namespace、RBAC 和 runtime config 中,不 bake 进镜像。普通 source commit 差异由 boot checkout 选择。
|
||||
- `deploy/deploy.json` 只承载人写 runtime intent,不承载 digest、publish state 或 reuse evidence。
|
||||
- Source branch 不得因为 promotion 出现自动提交;若发布后 source branch 变化,必须是人工修改源码、测试、文档、模板或 `deploy/deploy.json`。
|
||||
|
||||
@@ -152,7 +163,9 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st
|
||||
- `v0.1-gitops` branch 和 `deploy/gitops/g14/runtime-v01` 成为 Argo desired state 来源。
|
||||
- `deploy/deploy.json` 是 CD 唯一手写真相源;source branch 不跟踪 artifact catalog、runtime generated manifests、digest 或 publish state。
|
||||
- Tekton/Argo 路径不包含自定义 runner、CI.json runner、长期自研 poller 或 control-plane reconciler。
|
||||
- Runtime manifest 使用 digest pin,catalog 记录完整 source commit 与 digest。
|
||||
- Runtime manifest 使用 digest pin,catalog 记录完整 source commit、env identity、build/reuse summary 与 digest。
|
||||
- `agentrun v01 git-mirror status` 中 `sourceInSync=true`、`exactFetch.localV01=true`,并能用 mirror 对当前 full SHA 执行 shallow fetch。
|
||||
- 小的业务源码变更完成后,PipelineRun `planArtifacts.summary` 应为 `build=0 reuse=1 unsafeReuse=0`,`image-publish` task result `status=reused`,且不出现新的 env image build。
|
||||
- 发布完成后可通过 `G14:k3s` 读取 `agentrun-v01` Pod ready、service health 和对应 image digest。
|
||||
|
||||
## 规格的实现情况
|
||||
@@ -164,4 +177,6 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st
|
||||
| `agentrun-v01` namespace | 已实现/已通过主闭环 | GitOps lane 已同步 manager、Postgres、ServiceAccount、SecretRef 和 runner Job 所需对象;发布前仍按 [spec-v01-validation.md](spec-v01-validation.md) 手动复验。 |
|
||||
| `v0.1-gitops` branch | 已实现 | Tekton promotion 生成 artifact catalog 与 runtime desired state,Argo 从 `deploy/gitops/g14/runtime-v01` 同步。 |
|
||||
| 纯 Tekton/Argo lane | 已实现/已通过主闭环 | `agentrun-ci` Pipeline、BuildKit 镜像发布、GitOps promotion 和 Argo Application 已形成闭环;不得回退到自定义 runner 或 dev/prod。 |
|
||||
| git mirror/relay | 已实现 | UniDesk `agentrun v01 git-mirror status|sync|flush` 管理 `devops-infra:/cache/pikasTech/agentrun.git`;Pipeline read/write 默认使用 mirror URL,`trigger-current` 会在创建 PipelineRun 前 pre-sync `v0.1`。 |
|
||||
| env reuse | 已实现/待持续验收 | Pipeline 计算 `envIdentity`,命中上一版 catalog 时输出 `build=0 reuse=1 unsafeReuse=0` 并复用 env image digest;首次 env identity 或依赖/Containerfile/boot 变化才构建 `agentrun-mgr-env:<envIdentity>`。 |
|
||||
| `dev/prod` 废弃口径 | 已定义 | 本文明确 `agentrun_dev` 和 `agentrun_prod` 不作为当前规格目标。 |
|
||||
|
||||
@@ -18,9 +18,29 @@ interface ArtifactCatalog {
|
||||
sourceBranch: string;
|
||||
gitopsBranch: string;
|
||||
sourceCommitId: string;
|
||||
services: Array<{ serviceId: string; image: string; digest: string; repositoryDigest: string; imageTag: string }>;
|
||||
summary?: string;
|
||||
services: CatalogService[];
|
||||
}
|
||||
|
||||
interface CatalogService {
|
||||
serviceId: string;
|
||||
image: string;
|
||||
digest: string;
|
||||
repositoryDigest: string;
|
||||
imageTag: string;
|
||||
artifactKind?: string;
|
||||
status?: string;
|
||||
envIdentity?: string;
|
||||
envImage?: string;
|
||||
envDigest?: string;
|
||||
envRepositoryDigest?: string;
|
||||
bootCommit?: string;
|
||||
bootScript?: string;
|
||||
provenance?: JsonRecord;
|
||||
}
|
||||
|
||||
const defaultBootRepoUrl = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git";
|
||||
|
||||
export async function runGitopsRenderCli(argv: string[]): Promise<void> {
|
||||
try {
|
||||
const options = parseArgs(argv);
|
||||
@@ -52,29 +72,39 @@ export async function renderGitops(options: RenderOptions): Promise<JsonRecord>
|
||||
await writeFile(path.join(options.outDir, "runtime-v01", "postgres.yaml"), postgresYaml(runtimeNamespace));
|
||||
await writeFile(path.join(options.outDir, "runtime-v01", "mgr.yaml"), managerYaml(runtimeNamespace, image, options.sourceCommit));
|
||||
await writeFile(path.join(options.outDir, "runtime-v01", "runner-rbac.yaml"), runnerRbacYaml(runtimeNamespace));
|
||||
return { outDir: options.outDir, runtimeNamespace, gitopsBranch, runtimePath, image: image.repositoryDigest, sourceCommit: options.sourceCommit };
|
||||
return { outDir: options.outDir, runtimeNamespace, gitopsBranch, runtimePath, image: repositoryDigestForService(image), sourceCommit: options.sourceCommit, envIdentity: image.envIdentity ?? null, artifactStatus: image.status ?? null };
|
||||
}
|
||||
|
||||
async function loadCatalog(options: RenderOptions, gitopsBranch: string): Promise<ArtifactCatalog> {
|
||||
if (options.catalogFile) return JSON.parse(await readFile(options.catalogFile, "utf8")) as ArtifactCatalog;
|
||||
if (options.requireCatalog) throw new AgentRunError("schema-invalid", "artifact catalog is required for promotion render", { httpStatus: 2 });
|
||||
const digest = `sha256:${"0".repeat(64)}`;
|
||||
const image = `${options.registryPrefix}/agentrun-mgr:${options.sourceCommit}`;
|
||||
const image = `${options.registryPrefix}/agentrun-mgr-env:${options.sourceCommit}`;
|
||||
return {
|
||||
lane: "v0.1",
|
||||
sourceBranch: "v0.1",
|
||||
gitopsBranch,
|
||||
sourceCommitId: options.sourceCommit,
|
||||
services: [{ serviceId: "agentrun-mgr", image, digest, repositoryDigest: `${options.registryPrefix}/agentrun-mgr@${digest}`, imageTag: options.sourceCommit }],
|
||||
summary: "build=1 reuse=0 unsafeReuse=0",
|
||||
services: [{ serviceId: "agentrun-mgr", artifactKind: "env-reuse", status: "placeholder", image, digest, repositoryDigest: `${options.registryPrefix}/agentrun-mgr-env@${digest}`, imageTag: options.sourceCommit, envIdentity: options.sourceCommit, envImage: image, envDigest: digest, envRepositoryDigest: `${options.registryPrefix}/agentrun-mgr-env@${digest}`, bootCommit: options.sourceCommit, bootScript: "deploy/runtime/boot/agentrun-boot.sh" }],
|
||||
};
|
||||
}
|
||||
|
||||
function imageForService(catalog: ArtifactCatalog, serviceId: string, options: RenderOptions): { repositoryDigest: string } {
|
||||
function imageForService(catalog: ArtifactCatalog, serviceId: string, options: RenderOptions): CatalogService {
|
||||
const service = catalog.services.find((item) => item.serviceId === serviceId);
|
||||
if (!service) throw new AgentRunError("schema-invalid", `catalog missing service ${serviceId}`, { httpStatus: 2 });
|
||||
if (!/^sha256:[a-f0-9]{64}$/u.test(service.digest)) throw new AgentRunError("schema-invalid", `catalog service ${serviceId} has invalid digest`, { httpStatus: 2 });
|
||||
if (options.requireCatalog && service.digest === `sha256:${"0".repeat(64)}`) throw new AgentRunError("schema-invalid", "placeholder digest is not allowed in promotion render", { httpStatus: 2 });
|
||||
return { repositoryDigest: service.repositoryDigest || `${service.image.slice(0, service.image.lastIndexOf(":"))}@${service.digest}` };
|
||||
const digest = service.envDigest ?? service.digest;
|
||||
if (!/^sha256:[a-f0-9]{64}$/u.test(digest)) throw new AgentRunError("schema-invalid", `catalog service ${serviceId} has invalid digest`, { httpStatus: 2 });
|
||||
if (options.requireCatalog && digest === `sha256:${"0".repeat(64)}`) throw new AgentRunError("schema-invalid", "placeholder digest is not allowed in promotion render", { httpStatus: 2 });
|
||||
return service;
|
||||
}
|
||||
|
||||
function repositoryDigestForService(service: CatalogService): string {
|
||||
if (service.envRepositoryDigest) return service.envRepositoryDigest;
|
||||
if (service.repositoryDigest) return service.repositoryDigest;
|
||||
const image = service.envImage ?? service.image;
|
||||
const digest = service.envDigest ?? service.digest;
|
||||
return `${image.slice(0, image.lastIndexOf(":"))}@${digest}`;
|
||||
}
|
||||
|
||||
function projectYaml(namespace: string): string {
|
||||
@@ -209,7 +239,9 @@ spec:
|
||||
`;
|
||||
}
|
||||
|
||||
function managerYaml(namespace: string, image: { repositoryDigest: string }, sourceCommit: string): string {
|
||||
function managerYaml(namespace: string, image: CatalogService, sourceCommit: string): string {
|
||||
const imageRef = repositoryDigestForService(image);
|
||||
const envIdentity = image.envIdentity ?? image.imageTag ?? "unknown";
|
||||
return `apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
@@ -245,11 +277,13 @@ spec:
|
||||
app.kubernetes.io/name: agentrun-mgr
|
||||
annotations:
|
||||
agentrun.pikastech.local/lane: v0.1
|
||||
agentrun.pikastech.local/source-commit: ${JSON.stringify(sourceCommit)}
|
||||
agentrun.pikastech.local/env-identity: ${JSON.stringify(envIdentity)}
|
||||
spec:
|
||||
serviceAccountName: agentrun-v01-mgr
|
||||
containers:
|
||||
- name: mgr
|
||||
image: ${image.repositoryDigest}
|
||||
image: ${imageRef}
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
@@ -264,12 +298,20 @@ spec:
|
||||
key: DATABASE_URL
|
||||
- name: AGENTRUN_SOURCE_COMMIT
|
||||
value: ${JSON.stringify(sourceCommit)}
|
||||
- name: AGENTRUN_BOOT_COMMIT
|
||||
value: ${JSON.stringify(sourceCommit)}
|
||||
- name: AGENTRUN_BOOT_MODE
|
||||
value: mgr
|
||||
- name: AGENTRUN_BOOT_REPO_URL
|
||||
value: ${JSON.stringify(defaultBootRepoUrl)}
|
||||
- name: AGENTRUN_ENV_IDENTITY
|
||||
value: ${JSON.stringify(envIdentity)}
|
||||
- name: AGENTRUN_RUNTIME_NAMESPACE
|
||||
value: ${JSON.stringify(namespace)}
|
||||
- name: AGENTRUN_INTERNAL_MGR_URL
|
||||
value: ${JSON.stringify(`http://agentrun-mgr.${namespace}.svc.cluster.local:8080`)}
|
||||
- name: AGENTRUN_RUNNER_IMAGE
|
||||
value: ${JSON.stringify(image.repositoryDigest)}
|
||||
value: ${JSON.stringify(imageRef)}
|
||||
- name: AGENTRUN_RUNNER_SERVICE_ACCOUNT
|
||||
value: "agentrun-v01-runner"
|
||||
readinessProbe:
|
||||
|
||||
@@ -2,6 +2,8 @@ import { stableHash } from "../common/validation.js";
|
||||
import type { BackendProfile, ExecutionPolicy, JsonRecord, JsonValue, RunRecord, SecretRef } from "../common/types.js";
|
||||
import { backendProfileSpec } from "../common/backend-profiles.js";
|
||||
|
||||
const defaultBootRepoUrl = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git";
|
||||
|
||||
export interface RunnerJobRenderOptions {
|
||||
run: RunRecord;
|
||||
commandId: string;
|
||||
@@ -124,7 +126,7 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani
|
||||
name: "runner",
|
||||
image: options.image,
|
||||
imagePullPolicy: options.imagePullPolicy ?? "IfNotPresent",
|
||||
command: ["bun", "src/runner/main.ts"],
|
||||
command: ["/opt/agentrun/deploy/runtime/boot/agentrun-runner.sh"],
|
||||
env,
|
||||
volumeMounts: [
|
||||
{ name: "runner-home", mountPath: "/home/agentrun" },
|
||||
@@ -164,6 +166,10 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
|
||||
{ name: "AGENTRUN_RESOURCE_BUNDLE_JSON", value: JSON.stringify(options.run.resourceBundleRef ?? null) },
|
||||
{ name: "AGENTRUN_WORKSPACE_ROOT", value: "/home/agentrun/workspaces" },
|
||||
{ name: "AGENTRUN_SOURCE_COMMIT", value: context.sourceCommit },
|
||||
{ name: "AGENTRUN_BOOT_COMMIT", value: context.sourceCommit },
|
||||
{ name: "AGENTRUN_BOOT_REPO_URL", value: defaultBootRepoUrl },
|
||||
{ name: "AGENTRUN_BOOT_MODE", value: "runner" },
|
||||
{ name: "AGENTRUN_APP_ROOT", value: "/home/agentrun/agentrun-source" },
|
||||
{ name: "AGENTRUN_RUNTIME_NAMESPACE", value: context.namespace },
|
||||
{ name: "AGENTRUN_K8S_JOB_NAME", value: context.jobName },
|
||||
{ name: "AGENTRUN_LOG_PATH", value: "/tmp/agentrun-runner.jsonl" },
|
||||
|
||||
@@ -15,6 +15,8 @@ const execFile = promisify(execFileCallback);
|
||||
const selfTest: SelfTestCase = async (context) => {
|
||||
const containerfile = await readFile(path.join(context.root, "deploy/container/Containerfile"), "utf8");
|
||||
assert.ok(containerfile.includes(" git ") && containerfile.includes(" openssh-client"), "runtime image must include git and openssh-client for ResourceBundleRef checkout");
|
||||
assert.ok(containerfile.includes("deploy/runtime/boot") && containerfile.includes("agentrun-mgr.sh"), "runtime image must boot through the env-reuse source checkout script");
|
||||
assert.ok(!containerfile.includes("COPY src ./src"), "runtime env image must not bake source files into every source commit image");
|
||||
const fakeKubectl = path.join(context.tmp, "fake-kubectl-hwlab.js");
|
||||
const createdManifest = path.join(context.tmp, "created-hwlab-runner-job.json");
|
||||
await writeFile(fakeKubectl, `#!/usr/bin/env bun
|
||||
@@ -54,6 +56,8 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
);
|
||||
const manifest = JSON.parse(await readFile(createdManifest, "utf8")) as JsonRecord;
|
||||
assert.ok(JSON.stringify(manifest).includes("AGENTRUN_RESOURCE_BUNDLE_JSON"));
|
||||
assert.ok(JSON.stringify(manifest).includes("/opt/agentrun/deploy/runtime/boot/agentrun-runner.sh"));
|
||||
assert.ok(JSON.stringify(manifest).includes("AGENTRUN_BOOT_COMMIT"));
|
||||
assertNoSecretLeak(created);
|
||||
|
||||
const pendingCancel = await createHwlabRun(client, context, bundle, "hwlab-session-cancel-pending", "cancel pending", "hwlab-command-cancel-pending");
|
||||
|
||||
Reference in New Issue
Block a user