#!/usr/bin/env node import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; import path from "node:path"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; import { readStructuredFile } from "./src/structured-config.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const defaultOutDir = "deploy/gitops/g14"; const defaultRegistryPrefix = process.env.HWLAB_G14_REGISTRY_PREFIX || "127.0.0.1:5000/hwlab"; const defaultSourceRepo = process.env.HWLAB_G14_GIT_URL || "git@github.com:pikasTech/HWLAB.git"; const defaultGitReadUrl = process.env.HWLAB_G14_GIT_READ_URL || null; const defaultV02GitReadUrl = process.env.HWLAB_V02_GIT_READ_URL || "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; const defaultV02GitWriteUrl = process.env.HWLAB_V02_GIT_WRITE_URL || "http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; const defaultBranch = process.env.HWLAB_G14_BRANCH || "G14"; const defaultGitopsBranch = process.env.HWLAB_G14_GITOPS_BRANCH || "G14-gitops"; const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json"; const defaultRuntimeEndpoint = process.env.HWLAB_G14_RUNTIME_ENDPOINT || "http://74.48.78.17:17667"; const defaultWebEndpoint = process.env.HWLAB_G14_WEB_ENDPOINT || "http://74.48.78.17:17666"; const defaultProdRuntimeEndpoint = process.env.HWLAB_G14_PROD_RUNTIME_ENDPOINT || "http://74.48.78.17:18667"; const defaultProdWebEndpoint = process.env.HWLAB_G14_PROD_WEB_ENDPOINT || "http://74.48.78.17:18666"; const defaultV02RuntimeEndpoint = process.env.HWLAB_V02_RUNTIME_ENDPOINT || "https://hwlab.74-48-78-17.nip.io"; const defaultV02WebEndpoint = process.env.HWLAB_V02_WEB_ENDPOINT || "https://hwlab.74-48-78-17.nip.io"; const defaultProxyUrl = process.env.HWLAB_G14_PROXY_URL || "http://127.0.0.1:10808"; const defaultAllProxyUrl = process.env.HWLAB_G14_ALL_PROXY_URL || "socks5h://127.0.0.1:10808"; const defaultNoProxy = process.env.HWLAB_G14_NO_PROXY || "hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc,git-mirror-http,git-mirror-http.devops-infra,git-mirror-http.devops-infra.svc,git-mirror-http.devops-infra.svc.cluster.local,git-mirror-write,git-mirror-write.devops-infra,git-mirror-write.devops-infra.svc,git-mirror-write.devops-infra.svc.cluster.local"; const ciToolsRunnerImage = process.env.HWLAB_G14_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1"; const buildkitRunnerImage = process.env.HWLAB_G14_BUILDKIT_IMAGE || "moby/buildkit:rootless"; const defaultDevBaseImage = process.env.HWLAB_G14_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim"; const defaultServiceIds = Object.freeze([ "hwlab-cloud-api", "hwlab-cloud-web", "hwlab-gateway", "hwlab-edge-proxy", "hwlab-agent-skills" ]); const v02ExtraObservableServices = Object.freeze([ { serviceId: "hwlab-deepseek-proxy", port: 4000, healthPath: "/health/live" } ]); const v02RemovedServiceIds = new Set([ "hwlab-agent-mgr", "hwlab-agent-worker", "hwlab-agent-worker-template", "hwlab-code-agent-workspace", "hwlab-router", "hwlab-tunnel-client", "hwlab-gateway-simu", "hwlab-box-simu", "hwlab-patch-panel" ]); const v02RemovedServiceEnvNames = new Set([ "CODEX_HOME", "OPENAI_API_KEY", "HWLAB_CODE_AGENT_PROVIDER", "HWLAB_CODE_AGENT_MODEL", "HWLAB_CODE_AGENT_OPENAI_BASE_URL", "HWLAB_CODE_AGENT_WORKSPACE", "HWLAB_CODE_AGENT_CODEX_HOME", "HWLAB_CODE_AGENT_CODEX_WORKSPACE", "HWLAB_CODE_AGENT_CODEX_SANDBOX", "HWLAB_CODE_AGENT_CODEX_COMMAND", "HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED", "HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR", "HWLAB_M3_GATEWAY_SIMU_1_URL", "HWLAB_M3_GATEWAY_SIMU_2_URL", "HWLAB_M3_PATCH_PANEL_URL", "HWLAB_GATEWAY_SIMU_URL", "HWLAB_BOX_SIMU_URL", "HWLAB_PATCH_PANEL_URL", "HWLAB_ROUTER_URL", "HWLAB_TUNNEL_CLIENT_URL" ]); const moonBridgeSourceRef = process.env.HWLAB_G14_MOONBRIDGE_REF || "1b99888d3dae889b79ee602cb875c7907f7e76f2"; const moonBridgeImage = process.env.HWLAB_G14_MOONBRIDGE_IMAGE || `${defaultRegistryPrefix}/moonbridge:${moonBridgeSourceRef.slice(0, 12)}`; const deepSeekProfileModel = process.env.HWLAB_G14_DEEPSEEK_PROFILE_MODEL || "deepseek-chat"; const codexApiProfileModel = process.env.HWLAB_G14_CODEX_API_PROFILE_MODEL || "gpt-5.5"; const codexApiProfileBaseUrl = process.env.HWLAB_G14_CODEX_API_PROFILE_BASE_URL || "http://127.0.0.1:49280/responses"; const codexApiForwarderPort = process.env.HWLAB_G14_CODEX_API_FORWARDER_PORT || "49280"; const codexApiForwarderUpstreamBaseUrl = process.env.HWLAB_G14_CODEX_API_UPSTREAM_BASE_URL || "https://hyueapi.com"; const sourceCommitPattern = /^[a-f0-9]{7,40}$/u; const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout"; function k8sLabelHash(value) { return String(value || "").slice(0, 16); } const primitiveValidationTasks = Object.freeze([ { name: "repo-reports-guard", commands: ["node scripts/repo-reports-guard.mjs"] }, { name: "g14-contract-check", commands: [ "node --check scripts/g14-gitops-render.mjs", "render_check_revision=$(git rev-parse HEAD)", "render_check_dir=$(mktemp -d)", "node scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --source-revision \"$render_check_revision\"", "node scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --check --source-revision \"$render_check_revision\"", `node --input-type=module <<'NODE' import { readdirSync, readFileSync, statSync } from "node:fs"; import path from "node:path"; const root = process.cwd(); const ciFileName = "CI" + ".json"; const forbiddenCiFragments = [ "ci" + "-json", "HWLAB_G14_TEKTON" + "_SINGLE_IMAGE_PUBLISH", "single" + "-dind", "docker" + ":29", "DOCKER" + "_HOST", "Docker" + "-in-Docker", "D" + "IND", "docker" + " push", "--build" + "-backend", "HWLAB_ARTIFACT" + "_BUILD_BACKEND", "--legacy" + "-source-images", "HWLAB_G14_USE_DEPLOY_IMAGES" + "=0" ]; const scanTargets = [ "scripts/g14-gitops-render.mjs", "scripts/artifact-publish.mjs", "scripts/g14-artifact-publish.mjs", "scripts/src/g14-ci-plan-lib.mjs", "deploy/gitops/g14/tekton" ]; const skipDirs = new Set([".git", "node_modules", ".worktree"]); function walkFiles(relativePath) { const absolutePath = path.join(root, relativePath); let stat; try { stat = statSync(absolutePath); } catch { return []; } if (stat.isFile()) return [relativePath]; if (!stat.isDirectory()) return []; const files = []; for (const entry of readdirSync(absolutePath, { withFileTypes: true })) { if (entry.isDirectory() && skipDirs.has(entry.name)) continue; files.push(...walkFiles(path.join(relativePath, entry.name))); } return files; } const ciFiles = walkFiles(".").filter((filePath) => path.basename(filePath) === ciFileName); const violations = []; for (const filePath of scanTargets.flatMap(walkFiles)) { const text = readFileSync(path.join(root, filePath), "utf8"); for (const fragment of forbiddenCiFragments) { if (text.includes(fragment)) violations.push({ filePath, fragment }); } } if (ciFiles.length || violations.length) { console.error(JSON.stringify({ status: "failed", ciFiles, violations }, null, 2)); process.exit(1); } NODE`, "node --check scripts/artifact-publish.mjs", "node --check scripts/g14-artifact-publish.mjs" ] }, { name: "codex-api-forwarder-check", commands: [ "node scripts/run-bun.mjs test cmd/hwlab-codex-api-responses-forwarder/main.test.ts" ] } ]); function proxyEnv() { return [ { name: "HTTP_PROXY", value: defaultProxyUrl }, { name: "HTTPS_PROXY", value: defaultProxyUrl }, { name: "ALL_PROXY", value: defaultAllProxyUrl }, { name: "NO_PROXY", value: defaultNoProxy }, { name: "http_proxy", value: defaultProxyUrl }, { name: "https_proxy", value: defaultProxyUrl }, { name: "all_proxy", value: defaultAllProxyUrl }, { name: "no_proxy", value: defaultNoProxy } ]; } function parseArgs(argv) { const args = { lane: process.env.HWLAB_GITOPS_LANE || "g14", outDir: defaultOutDir, catalogPath: defaultCatalogPath, imageTagMode: process.env.HWLAB_ARTIFACT_IMAGE_TAG_MODE || "short", registryPrefix: defaultRegistryPrefix, sourceRevision: null, sourceBranch: defaultBranch, gitopsBranch: defaultGitopsBranch, sourceRepo: defaultSourceRepo, gitReadUrl: defaultGitReadUrl, gitWriteUrl: null, runtimeEndpoint: defaultRuntimeEndpoint, webEndpoint: defaultWebEndpoint, prodRuntimeEndpoint: defaultProdRuntimeEndpoint, prodWebEndpoint: defaultProdWebEndpoint, useDeployImages: true, check: false, write: true, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--lane") args.lane = readOption(argv, ++index, arg); else if (arg === "--out") args.outDir = readOption(argv, ++index, arg); else if (arg === "--catalog-path") args.catalogPath = readOption(argv, ++index, arg); else if (arg === "--image-tag-mode") args.imageTagMode = readOption(argv, ++index, arg); else if (arg === "--registry-prefix") args.registryPrefix = readOption(argv, ++index, arg).replace(/\/+$/u, ""); else if (arg === "--source-revision") args.sourceRevision = readOption(argv, ++index, arg); else if (arg === "--source-branch") args.sourceBranch = readOption(argv, ++index, arg); else if (arg === "--gitops-branch") args.gitopsBranch = readOption(argv, ++index, arg); else if (arg === "--source-repo") args.sourceRepo = readOption(argv, ++index, arg); else if (arg === "--git-read-url") args.gitReadUrl = readOption(argv, ++index, arg); else if (arg === "--git-write-url") args.gitWriteUrl = readOption(argv, ++index, arg); else if (arg === "--runtime-endpoint") args.runtimeEndpoint = readOption(argv, ++index, arg); else if (arg === "--web-endpoint") args.webEndpoint = readOption(argv, ++index, arg); else if (arg === "--prod-runtime-endpoint") args.prodRuntimeEndpoint = readOption(argv, ++index, arg); else if (arg === "--prod-web-endpoint") args.prodWebEndpoint = readOption(argv, ++index, arg); else if (arg === "--use-deploy-images") args.useDeployImages = true; else if (arg === "--check") { args.check = true; args.write = false; } else if (arg === "--no-write") args.write = false; else if (arg === "--help" || arg === "-h") args.help = true; else throw new Error(`unknown argument ${arg}`); } if (args.lane === "v02") { if (args.sourceBranch === defaultBranch) args.sourceBranch = "v0.2"; if (args.gitopsBranch === defaultGitopsBranch) args.gitopsBranch = "v0.2-gitops"; if (args.catalogPath === defaultCatalogPath) args.catalogPath = "deploy/artifact-catalog.v02.json"; if (args.runtimeEndpoint === defaultRuntimeEndpoint) args.runtimeEndpoint = defaultV02RuntimeEndpoint; if (args.webEndpoint === defaultWebEndpoint) args.webEndpoint = defaultV02WebEndpoint; if (args.imageTagMode === "short") args.imageTagMode = "full"; if (!args.gitReadUrl) args.gitReadUrl = defaultV02GitReadUrl; if (!args.gitWriteUrl) args.gitWriteUrl = defaultV02GitWriteUrl; } if (!args.gitReadUrl) args.gitReadUrl = args.sourceRepo; if (!args.gitWriteUrl) args.gitWriteUrl = args.sourceRepo; assert.ok(["g14", "v02"].includes(args.lane), `unknown lane ${args.lane}`); assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`); if (args.lane === "v02") { assert.equal(args.sourceBranch, "v0.2", "v02 source branch must be v0.2"); assert.equal(args.gitopsBranch, "v0.2-gitops", "v02 GitOps branch must be v0.2-gitops"); } return args; } function readOption(argv, index, name) { const value = argv[index]; if (!value || value.startsWith("--")) throw new Error(`${name} requires a value`); return value; } function usage() { return [ "usage: node scripts/g14-gitops-render.mjs [options]", "", "Render G14-only Kubernetes-native CI/CD manifests from built-in Tekton CI primitives, deploy/deploy.yaml, and deploy/k8s/*.", "", "options:", " --lane LANE g14 or v02; default: g14", ` --out DIR default: ${defaultOutDir}`, ` --catalog-path PATH default: ${defaultCatalogPath}`, " --image-tag-mode MODE short or full; v02 uses full source commit IDs", ` --source-repo URL default: ${defaultSourceRepo}`, " --git-read-url URL read-only source URL; v02 defaults to devops-infra git mirror", " --git-write-url URL GitOps write URL; v02 defaults to devops-infra git mirror write relay", ` --source-branch BRANCH default: ${defaultBranch}`, ` --gitops-branch BRANCH default: ${defaultGitopsBranch}`, " --source-revision SHA source commit used for image tags and runtime annotations; default: git rev-parse HEAD", ` --registry-prefix PREFIX default: ${defaultRegistryPrefix}`, ` --runtime-endpoint URL default: ${defaultRuntimeEndpoint}; v02 default: ${defaultV02RuntimeEndpoint}`, ` --web-endpoint URL default: ${defaultWebEndpoint}; v02 default: ${defaultV02WebEndpoint}`, ` --prod-runtime-endpoint URL default: ${defaultProdRuntimeEndpoint}`, ` --prod-web-endpoint URL default: ${defaultProdWebEndpoint}`, " --use-deploy-images default and only supported mode: render workload images from deploy/artifact-catalog.dev.json per service", " --check verify generated files are current without writing", " --no-write plan and print generated file list without writing" ].join("\n"); } function generatedPath(filePath) { return path.isAbsolute(filePath) ? filePath : path.join(repoRoot, filePath); } async function readJson(relativePath) { return readStructuredFile(repoRoot, relativePath); } async function readJsonIfPresent(relativePath, fallback = null) { try { return await readJson(relativePath); } catch (error) { if (error && error.code === "ENOENT") return fallback; throw error; } } async function gitValue(args) { const result = await execFileAsync("git", args, { cwd: repoRoot, timeout: 10000, maxBuffer: 1024 * 1024 }); return result.stdout.trim(); } async function resolveSourceRevision(value) { const revision = value || await gitValue(["rev-parse", "HEAD"]); const full = sourceCommitPattern.test(revision) && revision.length === 40 ? revision : await gitValue(["rev-parse", revision]); assert.match(full, /^[a-f0-9]{40}$/u, "source revision must resolve to a 40-char git commit"); return { full, short: full.slice(0, 7) }; } function jsonManifest(value) { return `${JSON.stringify(value, null, 2)}\n`; } function textFile(value) { return value.endsWith("\n") ? value : `${value}\n`; } function cloneJson(value) { return JSON.parse(JSON.stringify(value)); } function ensureObject(value, name) { assert.ok(value && typeof value === "object" && !Array.isArray(value), `${name} must be an object`); return value; } function asItems(list, name) { assert.equal(list?.kind, "List", `${name} must be a Kubernetes List`); assert.ok(Array.isArray(list.items), `${name}.items must be an array`); return list.items; } function serviceIdForWorkload(item, container) { return ( item?.metadata?.labels?.["hwlab.pikastech.local/service-id"] || item?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/service-id"] || container?.name ); } function upsertEnv(envList, name, value) { if (!Array.isArray(envList)) return; const existing = envList.find((entry) => entry?.name === name); if (existing) { delete existing.valueFrom; existing.value = value; } else { envList.push({ name, value }); } } function upsertEnvEntry(envList, entry) { if (!Array.isArray(envList) || !entry?.name) return; const existing = envList.find((item) => item?.name === entry.name); if (existing) { delete existing.value; delete existing.valueFrom; Object.assign(existing, cloneJson(entry)); } else { envList.push(cloneJson(entry)); } } function syncCodeAgentWorkspaceInitContainer(initContainers, { image, runtimeCommit, runtimeImageTag }) { const initContainer = Array.isArray(initContainers) ? initContainers.find((entry) => entry?.name === "hwlab-code-agent-workspace-init") : null; if (!initContainer) return; initContainer.image = image; initContainer.imagePullPolicy = "IfNotPresent"; initContainer.env ??= []; upsertEnv(initContainer.env, "HWLAB_WORKSPACE_SOURCE_COMMIT", runtimeCommit); upsertEnv(initContainer.env, "HWLAB_IMAGE", image); upsertEnv(initContainer.env, "HWLAB_IMAGE_TAG", runtimeImageTag); } function annotate(metadata, annotations) { metadata.annotations = { ...(metadata.annotations ?? {}), ...annotations }; } function label(metadata, labels) { metadata.labels = { ...(metadata.labels ?? {}), ...labels }; } function imageFor(registryPrefix, serviceId, imageTag) { return `${registryPrefix}/${serviceId}:${imageTag}`; } function imageTagFromReference(image) { const value = String(image ?? ""); const slashIndex = value.lastIndexOf("/"); const colonIndex = value.lastIndexOf(":"); if (colonIndex <= slashIndex) return null; return value.slice(colonIndex + 1).split("@", 1)[0] || null; } function repositoryFromImage(image) { const value = String(image ?? "").split("@", 1)[0]; const slashIndex = value.lastIndexOf("/"); const colonIndex = value.lastIndexOf(":"); return colonIndex > slashIndex ? value.slice(0, colonIndex) : value; } function digestPinnedImage(image, digest) { if (!/^sha256:[a-f0-9]{64}$/u.test(String(digest ?? ""))) return image; const repository = repositoryFromImage(image); return repository ? `${repository}@${digest}` : image; } function catalogServiceById(catalog, serviceId) { return (catalog?.services ?? []).find((service) => service?.serviceId === serviceId) ?? null; } function v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds = null) { if (envReuseServiceIds?.has(serviceId)) return true; const service = catalogServiceById(catalog, serviceId); return service?.runtimeMode === v02EnvReuseRuntimeMode || service?.envReuse === true; } function bootShForService(deployService, serviceId) { assert.ok(deployService?.bootSh, `deploy.lanes.v02.bootScripts.${serviceId} is required`); const value = deployService.bootSh; const text = String(value).replaceAll("\\", "/").replace(/^\.\//u, ""); assert.ok(text && !text.startsWith("/") && !text.split("/").includes(".."), `${serviceId} boot script must be repo-relative`); return text; } function envReuseServiceIdsForLane(deploy, lane) { if (lane !== "v02") return new Set(); const configured = deploy?.lanes?.v02?.envReuseServices; const allowed = new Set(serviceIdsForLane(lane, deploy)); return new Set((Array.isArray(configured) ? configured : []).filter((serviceId) => allowed.has(serviceId))); } function deployServiceForBoot(deploy, serviceId) { return { serviceId, bootSh: deploy?.lanes?.v02?.bootScripts?.[serviceId] }; } function bootMetadataForService({ args, catalog, deployService, serviceId, source }) { const catalogService = catalogServiceById(catalog, serviceId); const environmentImage = catalogService?.environmentImage ?? imageFor(args.registryPrefix, `${serviceId}-env`, `env-${String(catalogService?.environmentInputHash ?? source.full).slice(0, 12)}`); const environmentDigest = catalogService?.environmentDigest ?? catalogService?.digest ?? null; return { runtimeMode: v02EnvReuseRuntimeMode, environmentImage, environmentDigest, environmentInputHash: catalogService?.environmentInputHash ?? null, codeInputHash: catalogService?.codeInputHash ?? null, bootRepo: catalogService?.bootRepo ?? args.sourceRepo, bootCommit: catalogService?.bootCommit ?? source.full, bootSh: catalogService?.bootSh ?? bootShForService(deployService, serviceId) }; } function envReuseBootShForContainer({ serviceId, container, defaultBootSh }) { if (serviceId === "hwlab-cloud-api" && container?.name === "hwlab-codex-api-forwarder") { return "deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh"; } return defaultBootSh; } function applyEnvReuseBootEnv(container, bootMetadata, { sourceBranch, bootSh = bootMetadata?.bootSh } = {}) { if (!container || !bootMetadata) return; delete container.command; delete container.args; container.env ??= []; upsertEnv(container.env, "HWLAB_RUNTIME_MODE", bootMetadata.runtimeMode); upsertEnv(container.env, "HWLAB_BOOT_REPO", bootMetadata.bootRepo); upsertEnv(container.env, "HWLAB_BOOT_COMMIT", bootMetadata.bootCommit); upsertEnv(container.env, "HWLAB_BOOT_REF", sourceBranch); upsertEnv(container.env, "HWLAB_BOOT_SH", bootSh); upsertEnv(container.env, "HWLAB_BOOT_READ_URL", defaultV02GitReadUrl); upsertEnv(container.env, "HWLAB_ENVIRONMENT_IMAGE", bootMetadata.environmentImage ?? ""); upsertEnv(container.env, "HWLAB_ENVIRONMENT_DIGEST", bootMetadata.environmentDigest ?? ""); upsertEnv(container.env, "HWLAB_ENVIRONMENT_INPUT_HASH", bootMetadata.environmentInputHash ?? ""); upsertEnv(container.env, "HWLAB_CODE_INPUT_HASH", bootMetadata.codeInputHash ?? ""); upsertEnv(container.env, "HWLAB_IMAGE_DIGEST", bootMetadata.environmentDigest ?? "unknown"); } function useLocalHealthProbe(container, pathValue = "/health") { if (!container) return; for (const probeName of ["readinessProbe", "livenessProbe"]) { const probe = container[probeName]; if (!probe?.httpGet) continue; probe.httpGet.path = pathValue; } } function annotateEnvReusePodTemplate(podMetadata, bootMetadata) { if (!podMetadata || !bootMetadata) return; annotate(podMetadata, { "hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode, "hwlab.pikastech.local/boot-repo": bootMetadata.bootRepo, "hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit, "hwlab.pikastech.local/boot-sh": bootMetadata.bootSh, "hwlab.pikastech.local/environment-digest": bootMetadata.environmentDigest ?? "not_published", "hwlab.pikastech.local/environment-input-hash": bootMetadata.environmentInputHash ?? "unknown", "hwlab.pikastech.local/code-input-hash": bootMetadata.codeInputHash ?? "unknown" }); label(podMetadata, { "hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode, "hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit }); } function runtimeArtifactForService({ catalog, deploy, serviceId, source, registryPrefix, useDeployImages = false, digestPin = false, envReuseServiceIds = null }) { const catalogService = catalogServiceById(catalog, serviceId); if (v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds)) { const environmentImage = catalogService?.environmentImage ?? imageFor(registryPrefix, `${serviceId}-env`, `env-${String(catalogService?.environmentInputHash ?? source.full).slice(0, 12)}`); const runtimeImage = digestPin && useDeployImages ? digestPinnedImage(environmentImage, catalogService?.environmentDigest ?? catalogService?.digest) : environmentImage; return { image: runtimeImage, imageTag: imageTagFromReference(environmentImage) ?? source.imageTag ?? source.short, commit: catalogService?.bootCommit ?? source.full, runtimeMode: v02EnvReuseRuntimeMode, environmentDigest: catalogService?.environmentDigest ?? catalogService?.digest ?? null }; } const fallbackImageTag = source.imageTag ?? source.short; const image = useDeployImages && catalogService?.image ? catalogService.image : imageFor(registryPrefix, serviceId, fallbackImageTag); const runtimeImage = digestPin && useDeployImages ? digestPinnedImage(image, catalogService?.digest) : image; const imageTag = useDeployImages && catalogService?.imageTag ? catalogService.imageTag : imageTagFromReference(image) ?? fallbackImageTag; const commit = useDeployImages ? catalogService?.sourceCommitId ?? catalogService?.commitId ?? imageTag ?? source.full : source.full; return { image: runtimeImage, imageTag, commit }; } function runtimeImageForService(options) { return runtimeArtifactForService(options).image; } function runtimeCommitForService(options) { return runtimeArtifactForService(options).commit; } function runtimeImageTagForService(options) { return runtimeArtifactForService(options).imageTag; } function artifactEnvironment(args) { return args.lane === "v02" ? "v02" : "dev"; } function artifactEndpoint(args) { return args.lane === "v02" ? defaultV02RuntimeEndpoint : defaultRuntimeEndpoint; } function serviceIdsForLane(lane, deploy = null) { return lane === "v02" ? v02ServiceIdsFromDeploy(deploy) : defaultServiceIds; } function serviceIdsForProfile(profile, deploy = null) { return profile === "v02" ? v02ServiceIdsFromDeploy(deploy) : defaultServiceIds; } function v02ServiceIdsFromDeploy(deploy) { const serviceIds = uniquePreserveOrder((Array.isArray(deploy?.lanes?.v02?.envReuseServices) ? deploy.lanes.v02.envReuseServices : []) .map((item) => String(item ?? "").trim()) .filter(Boolean)); assert.ok(serviceIds.length > 0, "deploy.lanes.v02.envReuseServices must not be empty"); return serviceIds; } function v02ObservableServicesForDeploy(deploy) { const declarations = deploy?.lanes?.v02?.serviceDeclarations ?? {}; const serviceIds = new Set(v02ServiceIdsFromDeploy(deploy)); const declared = Object.entries(declarations) .filter(([serviceId, declaration]) => serviceIds.has(serviceId) && declaration?.observable !== false && Number.isInteger(declaration?.healthPort)) .map(([serviceId, declaration]) => ({ serviceId, port: declaration.healthPort, healthPath: declaration.healthPath || "/health/live" })); return [...declared, ...v02ExtraObservableServices]; } function v02ObservableService(deploy, serviceId) { return v02ObservableServicesForDeploy(deploy).find((item) => item.serviceId === serviceId) ?? null; } function v02ObservableServiceIdsForDeploy(deploy) { return new Set(v02ObservableServicesForDeploy(deploy).map((item) => item.serviceId)); } function servicesParamForLane(lane, deploy = null) { return serviceIdsForLane(lane, deploy).join(","); } function uniquePreserveOrder(values) { const seen = new Set(); const result = []; for (const value of values) { if (seen.has(value)) continue; seen.add(value); result.push(value); } return result; } function isV02RemovedServiceId(serviceId) { return v02RemovedServiceIds.has(serviceId); } function artifactCatalogSkeleton({ args, deploy, source }) { const environment = artifactEnvironment(args); const namespace = args.lane === "v02" ? "hwlab-v02" : "hwlab-dev"; const tag = imageTagForSource(args, source); return { catalogVersion: "v1", kind: "hwlab-artifact-catalog", environment, profile: environment, namespace, endpoint: artifactEndpoint(args), commitId: tag, artifactState: "contract-skeleton", publish: { registryPrefix: args.registryPrefix, sourceCommitId: source.full, imageTag: tag, publishedAt: null }, allowedProfiles: [environment], forbiddenProfiles: args.lane === "v02" ? ["dev", "prod"] : ["prod"], services: serviceIdsForLane(args.lane, deploy).map((serviceId) => artifactCatalogSkeletonService({ args, deploy, source, serviceId, environment, namespace, tag })) }; } function artifactCatalogSkeletonService({ args, deploy, source, serviceId, environment, namespace, tag }) { const base = { serviceId, profile: environment, namespace, commitId: tag, sourceCommitId: source.full, image: imageFor(args.registryPrefix, serviceId, tag), imageTag: tag, digest: null, buildBackend: "contract-skeleton" }; const envReuseServiceIds = envReuseServiceIdsForLane(deploy, args.lane); if (!envReuseServiceIds.has(serviceId)) return base; const bootSh = bootShForService(deployServiceForBoot(deploy, serviceId), serviceId); const environmentImage = imageFor(args.registryPrefix, `${serviceId}-env`, `env-${String(source.full).slice(0, 12)}`); return { ...base, image: environmentImage, imageTag: imageTagFromReference(environmentImage), runtimeMode: v02EnvReuseRuntimeMode, envReuse: true, environmentImage, environmentDigest: null, environmentInputHash: null, bootRepo: args.sourceRepo, bootCommit: source.full, bootSh, codeInputHash: null, bootEnvEvidence: { env: { HWLAB_BOOT_REPO: args.sourceRepo, HWLAB_BOOT_COMMIT: source.full, HWLAB_BOOT_SH: bootSh } } }; } function namespaceNameForProfile(profile) { if (profile === "v02") return "hwlab-v02"; return profile === "prod" ? "hwlab-prod" : "hwlab-dev"; } function runtimePathForProfile(profile) { if (profile === "v02") return "runtime-v02"; return profile === "prod" ? "runtime-prod" : "runtime-dev"; } function runtimeLabelForProfile(profile) { if (profile === "v02") return "v02"; return profile === "prod" ? "prod" : "dev"; } function gitopsTargetForProfile(profile) { return profile === "v02" ? "v02" : "g14"; } function profileEndpoint(args, profile) { if (profile === "v02") return { runtimeEndpoint: args.runtimeEndpoint, webEndpoint: args.webEndpoint }; return profile === "prod" ? { runtimeEndpoint: args.prodRuntimeEndpoint, webEndpoint: args.prodWebEndpoint } : { runtimeEndpoint: args.runtimeEndpoint, webEndpoint: args.webEndpoint }; } function laneRuntimeProfiles(args) { return args.lane === "v02" ? ["v02"] : ["dev", "prod"]; } function laneNames(args) { if (args.lane === "v02") { return { gitopsTarget: "v02", pipeline: "hwlab-v02-ci-image-publish", poller: "hwlab-v02-branch-poller", reconciler: "hwlab-v02-control-plane-reconciler", serviceAccount: "hwlab-v02-tekton-runner", pipelineRunPrefix: "hwlab-v02-ci-poll-", runtimeNamespace: "hwlab-v02", runtimePath: "deploy/gitops/g14/runtime-v02", argoApplications: ["argocd/hwlab-g14-v02"] }; } return { gitopsTarget: "g14", pipeline: "hwlab-g14-ci-image-publish", poller: "hwlab-g14-branch-poller", reconciler: "hwlab-g14-control-plane-reconciler", serviceAccount: "hwlab-tekton-runner", pipelineRunPrefix: "hwlab-g14-ci-poll-", runtimeNamespace: "hwlab-dev", runtimePath: "deploy/gitops/g14/runtime-dev", argoApplications: ["argocd/hwlab-g14-dev", "argocd/hwlab-g14-prod"] }; } function argoApplicationName(profile) { return profile === "v02" ? "hwlab-g14-v02" : `hwlab-g14-${profile}`; } function imageTagForSource(args, source) { return args.imageTagMode === "full" ? source.full : source.short; } function deepSeekBaseUrl(namespace) { return `http://hwlab-deepseek-proxy.${namespace}.svc.cluster.local:4000/v1/responses`; } function codeAgentNoProxy(namespace) { return [ `${namespace}.svc.cluster.local`, ".svc", ".cluster.local", "hyueapi.com", ".hyueapi.com", "hyui.com", ".hyui.com", "127.0.0.1", "localhost", "::1", "10.0.0.0/8", "10.42.0.0/16", "10.43.0.0/16", "api.minimaxi.com", ".minimaxi.com" ].join(","); } function namespaceScopedHost(value, namespace) { if (typeof value !== "string") return value; return value.replace(/\.hwlab-dev\.svc\.cluster\.local/gu, `.${namespace}.svc.cluster.local`); } function secretNameForProfile(secretName, profile) { if (profile !== "v02") return secretName; if (secretName === "hwlab-cloud-api-dev-db") return "hwlab-cloud-api-v02-db"; if (secretName === "hwlab-code-agent-provider") return "hwlab-v02-code-agent-provider"; if (secretName === "hwlab-code-agent-codex-auth") return "hwlab-v02-code-agent-codex-auth"; if (secretName === "hwlab-bootstrap-admin") return "hwlab-v02-bootstrap-admin"; return secretName; } function rewritePodSecretRefs(podSpec, profile) { if (!podSpec || profile !== "v02") return; for (const volume of podSpec.volumes ?? []) { if (volume?.secret?.secretName) volume.secret.secretName = secretNameForProfile(volume.secret.secretName, profile); } } function deployEnvEntry(name, value, namespace, profile = "dev") { if (typeof value === "string" && value.startsWith("secretRef:")) { const ref = value.slice("secretRef:".length); const slashIndex = ref.indexOf("/"); if (slashIndex > 0 && slashIndex < ref.length - 1) { return { name, valueFrom: { secretKeyRef: { name: secretNameForProfile(ref.slice(0, slashIndex), profile), key: ref.slice(slashIndex + 1), optional: true } } }; } } return { name, value: namespaceScopedHost(value, namespace) }; } function applyDeployEnv(envList, deployService, namespace, profile = "dev") { if (!deployService?.env || typeof deployService.env !== "object" || Array.isArray(deployService.env)) return; for (const [name, value] of Object.entries(deployService.env)) { upsertEnvEntry(envList, deployEnvEntry(name, value, namespace, profile)); } } function removeV02LegacySimulatorEnv(envList) { if (!Array.isArray(envList)) return envList; return envList.filter((entry) => !v02RemovedServiceEnvNames.has(entry?.name)); } function removeV02RepoOwnedCodeAgentPodConfig(podSpec) { if (!podSpec) return; podSpec.initContainers = (podSpec.initContainers ?? []).filter((entry) => entry?.name !== "hwlab-code-agent-workspace-init"); const removedVolumeNames = new Set([ "hwlab-code-agent-workspace", "hwlab-code-agent-codex-home", "hwlab-code-agent-codex-config", "hwlab-code-agent-codex-auth" ]); podSpec.volumes = (podSpec.volumes ?? []).filter((volume) => !removedVolumeNames.has(volume?.name)); for (const container of podSpec.containers ?? []) { container.volumeMounts = (container.volumeMounts ?? []).filter((mount) => !removedVolumeNames.has(mount?.name)); } } function deployServicesForProfile(deploy, profile) { const allowed = new Set(serviceIdsForProfile(profile, deploy)); const services = new Map((deploy.services ?? []) .filter((service) => allowed.has(service.serviceId)) .map((service) => [service.serviceId, cloneJson(service)])); const laneServices = profile === "v02" && Array.isArray(deploy.lanes?.v02?.services) ? deploy.lanes.v02.services : []; for (const override of laneServices) { const existing = services.get(override.serviceId) ?? { serviceId: override.serviceId }; services.set(override.serviceId, { ...existing, ...cloneJson(override), env: { ...(existing.env ?? {}), ...(override.env ?? {}) } }); } return services; } function v02MetricsLabels(serviceId) { return { "hwlab.pikastech.local/monitoring": "enabled", "hwlab.pikastech.local/service-id": serviceId }; } function v02MetricsSidecarVolume() { return { name: "hwlab-metrics-sidecar", configMap: { name: "hwlab-v02-metrics-sidecar" } }; } function v02MetricsSidecarAnnotations(metricsSidecarSha256) { return metricsSidecarSha256 ? { "hwlab.pikastech.local/metrics-sidecar-sha256": metricsSidecarSha256 } : {}; } function v02MetricsSidecarContainer({ deploy, serviceId, namespace, gitopsTarget }) { const service = v02ObservableService(deploy, serviceId); assert.ok(service, `unknown v0.2 observable service ${serviceId}`); const extraMetricsEnv = serviceId === "hwlab-cloud-api" ? [ { name: "HWLAB_METRICS_EXTRA_URL", value: `http://127.0.0.1:${service.port}/v1/web-performance/metrics` }, { name: "HWLAB_METRICS_EXTRA_TIMEOUT_MS", value: "2000" } ] : []; return { name: "hwlab-metrics", image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", command: ["node", "/metrics/metrics-sidecar.mjs"], env: [ { name: "HWLAB_METRICS_SERVICE_ID", value: serviceId }, { name: "HWLAB_METRICS_NAMESPACE", value: namespace }, { name: "HWLAB_METRICS_GITOPS_TARGET", value: gitopsTarget }, { name: "HWLAB_METRICS_PORT", value: "9100" }, { name: "HWLAB_METRICS_TARGET_URL", value: `http://127.0.0.1:${service.port}${service.healthPath}` }, { name: "HWLAB_METRICS_TARGET_TIMEOUT_MS", value: "2000" }, ...extraMetricsEnv ], ports: [{ name: "metrics", containerPort: 9100 }], readinessProbe: { httpGet: { path: "/health/live", port: "metrics" }, initialDelaySeconds: 3, periodSeconds: 10 }, livenessProbe: { httpGet: { path: "/health/live", port: "metrics" }, initialDelaySeconds: 10, periodSeconds: 20 }, volumeMounts: [{ name: "hwlab-metrics-sidecar", mountPath: "/metrics", readOnly: true }] }; } function upsertV02MetricsSidecar(podSpec, options) { if (!podSpec || !Array.isArray(podSpec.containers)) return; podSpec.containers = podSpec.containers.filter((container) => container?.name !== "hwlab-metrics"); podSpec.containers.push(v02MetricsSidecarContainer(options)); podSpec.volumes ??= []; podSpec.volumes = podSpec.volumes.filter((volume) => volume?.name !== "hwlab-metrics-sidecar"); podSpec.volumes.push(v02MetricsSidecarVolume()); } function upsertV02MetricsPort(service) { service.spec ??= {}; service.spec.ports ??= []; service.spec.ports = service.spec.ports.filter((port) => port?.name !== "metrics"); service.spec.ports.push({ name: "metrics", port: 9100, targetPort: "metrics" }); } function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = defaultBranch, registryPrefix, runtimeEndpoint, webEndpoint, profile = "dev", useDeployImages = false, metricsSidecarSha256 = null }) { const result = cloneJson(workloads); const deployServices = deployServicesForProfile(deploy, profile); const namespace = namespaceNameForProfile(profile); const profileLabel = runtimeLabelForProfile(profile); const gitopsTarget = gitopsTargetForProfile(profile); const digestPin = profile === "v02"; const envReuseServiceIds = envReuseServiceIdsForLane(deploy, profile); const v02ObservableServiceIds = profile === "v02" ? v02ObservableServiceIdsForDeploy(deploy) : new Set(); const stableRuntimeLabels = { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/environment": profileLabel, "hwlab.pikastech.local/profile": profileLabel }; result.items = asItems(result, "workloads").filter((item) => !(profile === "v02" && isV02RemovedServiceId(serviceIdForWorkload(item, null)))); for (const item of asItems(result, "workloads")) { item.metadata.namespace = namespace; label(item.metadata, { ...stableRuntimeLabels, "hwlab.pikastech.local/gitops-target": gitopsTarget, "hwlab.pikastech.local/source-commit": source.full, "unidesk.ai/g14-staging": profile === "dev" ? "true" : "false" }); annotate(item.metadata, { "hwlab.pikastech.local/gitops-note": profile === "v02" ? "HWLAB v0.2 GitOps target." : profile === "prod" ? "G14 PROD GitOps target." : "G14 DEV GitOps target.", "hwlab.pikastech.local/source-commit": source.full }); if (item.kind === "Job") { annotate(item.metadata, { "argocd.argoproj.io/sync-options": "Force=true,Replace=true" }); if (item.spec?.suspend === true) { annotate(item.metadata, { "argocd.argoproj.io/ignore-healthcheck": "true" }); } } const podTemplate = item?.spec?.template; if (!podTemplate?.spec?.containers) continue; const templateServiceId = serviceIdForWorkload(item, podTemplate.spec.containers[0]); const templateArtifact = runtimeArtifactForService({ catalog, deploy, serviceId: templateServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); const templateSourceCommit = profile === "v02" ? (templateArtifact.commit ?? source.full) : source.full; const templateArtifactCommit = templateArtifact.commit; label(podTemplate.metadata ??= {}, { ...stableRuntimeLabels, "hwlab.pikastech.local/gitops-target": gitopsTarget, "hwlab.pikastech.local/source-commit": templateSourceCommit, "hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit }); if (profile === "v02" && v02ObservableServiceIds.has(templateServiceId)) { label(item.metadata, v02MetricsLabels(templateServiceId)); label(podTemplate.metadata, v02MetricsLabels(templateServiceId)); annotate(podTemplate.metadata, v02MetricsSidecarAnnotations(metricsSidecarSha256)); upsertV02MetricsSidecar(podTemplate.spec, { deploy, serviceId: templateServiceId, namespace, gitopsTarget }); } if ((item.kind === "Deployment" || item.kind === "StatefulSet") && item.spec?.selector?.matchLabels) { item.spec.selector.matchLabels = { ...item.spec.selector.matchLabels, ...stableRuntimeLabels }; } annotate(podTemplate.metadata, { "hwlab.pikastech.local/source-commit": templateSourceCommit, "hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit, "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" }); for (const container of podTemplate.spec.containers) { if (container.name === "hwlab-metrics") continue; const serviceId = serviceIdForWorkload(item, container); const deployService = deployServices.get(serviceId); if (!deployService) continue; const artifact = runtimeArtifactForService({ catalog, deploy, serviceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); const envReuse = profile === "v02" && v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds); const bootMetadata = envReuse ? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.v02?.sourceRepo || defaultSourceRepo, registryPrefix }, catalog, deployService, serviceId, source }) : null; const image = artifact.image; const runtimeCommit = artifact.commit; const runtimeImageTag = artifact.imageTag; container.image = image; container.imagePullPolicy = "IfNotPresent"; container.env ??= []; for (const entry of container.env) { if (Object.hasOwn(entry, "value")) entry.value = namespaceScopedHost(entry.value, namespace); } applyDeployEnv(container.env, deployService, namespace, profile); if (profile === "v02") container.env = removeV02LegacySimulatorEnv(container.env); upsertEnv(container.env, "HWLAB_ENVIRONMENT", profileLabel); upsertEnv(container.env, "HWLAB_COMMIT_ID", runtimeCommit); upsertEnv(container.env, "HWLAB_IMAGE", image); upsertEnv(container.env, "HWLAB_IMAGE_TAG", runtimeImageTag); if (serviceId === "hwlab-agent-skills") { upsertEnv(container.env, "HWLAB_SKILLS_COMMIT_ID", runtimeCommit); } upsertEnv(container.env, "HWLAB_GITOPS_TARGET", gitopsTarget); upsertEnv(container.env, "HWLAB_GITOPS_PROFILE", profileLabel); upsertEnv(container.env, "HWLAB_GITOPS_SOURCE_COMMIT", profile === "v02" ? runtimeCommit : source.full); if (profile === "v02" && serviceId === "hwlab-cloud-api") { upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT", runtimeCommit); } if (bootMetadata) { applyEnvReuseBootEnv(container, bootMetadata, { sourceBranch, bootSh: envReuseBootShForContainer({ serviceId, container, defaultBootSh: bootMetadata.bootSh }) }); if (container.name === serviceId) annotateEnvReusePodTemplate(podTemplate.metadata, bootMetadata); } if (serviceId === "hwlab-cloud-api" || serviceId === "hwlab-edge-proxy") { upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", runtimeEndpoint); } if (profile === "v02" && serviceId === "hwlab-edge-proxy") { useLocalHealthProbe(container, "/health"); } if (serviceId === "hwlab-cloud-api") { if (profile === "v02") { removeV02RepoOwnedCodeAgentPodConfig(podTemplate.spec); upsertEnv(container.env, "HWLAB_M3_IO_CONTROL_ENABLED", "false"); upsertEnv(container.env, "HWLAB_ACCESS_CONTROL_REQUIRED", "1"); upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_ID", "usr_v02_admin"); upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_USERNAME", "admin"); upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_DISPLAY_NAME", "HWLAB v0.2 Admin"); upsertEnvEntry(container.env, deployEnvEntry("HWLAB_BOOTSTRAP_ADMIN_PASSWORD_HASH", "secretRef:hwlab-v02-bootstrap-admin/password-hash", namespace, profile)); upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_API_KEY_ID", "key_master_server_admin"); upsertEnvEntry(container.env, deployEnvEntry("HWLAB_BOOTSTRAP_ADMIN_API_KEY", "secretRef:hwlab-v02-master-server-admin-api-key/api-key", namespace, profile)); upsertEnv(container.env, "HWLAB_OPENFGA_MODE", "enforce"); upsertEnv(container.env, "HWLAB_OPENFGA_API_URL", "http://hwlab-openfga.hwlab-v02.svc.cluster.local:8080"); upsertEnvEntry(container.env, deployEnvEntry("HWLAB_OPENFGA_AUTHN_TOKEN", "secretRef:hwlab-v02-openfga/authn-preshared-key", namespace, profile)); upsertEnv(container.env, "HWLAB_KEYCLOAK_ISSUER", "https://auth.74-48-78-17.nip.io/realms/hwlab"); upsertEnv(container.env, "HWLAB_KEYCLOAK_CLIENT_ID", "hwlab-cloud-web"); upsertEnvEntry(container.env, deployEnvEntry("HWLAB_KEYCLOAK_CLIENT_SECRET", "secretRef:hwlab-cloud-web-client/client-secret", namespace, profile)); upsertEnv(container.env, "HWLAB_CODE_AGENT_ADAPTER", "agentrun-v01"); upsertEnv(container.env, "AGENTRUN_MGR_URL", "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080"); upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID", "G14"); upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE", "agentrun-v01"); upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE", "agentrun-v01"); upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_REPO_URL", "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"); upsertEnv(container.env, "UNIDESK_MAIN_SERVER_IP", "http://74.48.78.17:18081"); } else { syncCodeAgentWorkspaceInitContainer(podTemplate.spec.initContainers, { image, runtimeCommit, runtimeImageTag }); } upsertEnv(container.env, "HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE", "deepseek"); upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_MODEL", deepSeekProfileModel); upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL", deepSeekBaseUrl(namespace)); upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_MODEL", codexApiProfileModel); upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_BASE_URL", codexApiProfileBaseUrl); upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL", codexApiForwarderUpstreamBaseUrl); upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT", codexApiForwarderPort); upsertEnv(container.env, "HWLAB_PREINSTALLED_SKILLS_DIR", "/app/skills"); upsertEnv(container.env, "HWLAB_USER_SKILLS_DIR", "/data/user-skills"); upsertEnv(container.env, "HWLAB_CODE_AGENT_SKILLS_DIRS", "/app/skills:/data/user-skills"); upsertEnv(container.env, "NO_PROXY", codeAgentNoProxy(namespace)); upsertEnv(container.env, "no_proxy", codeAgentNoProxy(namespace)); } if (serviceId === "hwlab-cloud-web") { upsertEnv(container.env, "HWLAB_API_BASE_URL", `http://hwlab-cloud-api.${namespace}.svc.cluster.local:6667`); upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", webEndpoint); } } rewritePodSecretRefs(podTemplate.spec, profile); } return result; } function transformHealthContract(value, namespace, labels, annotations, runtimeEndpoint, webEndpoint, profile = "dev") { const result = transformListNamespace(value, namespace, labels, annotations); if (result.metadata?.name === "hwlab-dev-health-contract") { result.metadata.name = `hwlab-${profile}-health-contract`; } if (result.metadata?.labels && Object.hasOwn(result.metadata.labels, "app.kubernetes.io/name")) { result.metadata.labels["app.kubernetes.io/name"] = `hwlab-${profile}-health-contract`; } if (result.data && typeof result.data === "object") { if (Object.hasOwn(result.data, "endpoint")) result.data.endpoint = runtimeEndpoint; if (Object.hasOwn(result.data, "cloud-web")) { result.data["cloud-web"] = `GET /health/live on ${webEndpoint}; consumes cloud-api only`; } if (Object.hasOwn(result.data, "cloud-api")) { result.data["cloud-api"] = `GET /health/live through ${runtimeEndpoint}`; } if (Object.hasOwn(result.data, "tunnel-client")) { result.data["tunnel-client"] = "disabled for G14 GitOps; no FRP or production traffic tunnel is required"; } } return result; } function transformListNamespace(value, namespace, labels, annotations) { const result = cloneJson(value); if (result.kind === "List") { for (const item of result.items ?? []) { item.metadata ??= {}; item.metadata.namespace = namespace; label(item.metadata, labels); annotate(item.metadata, annotations); } } else { result.metadata ??= {}; result.metadata.namespace = namespace; label(result.metadata, labels); annotate(result.metadata, annotations); } return result; } function transformServices({ services, namespace, labels, annotations, profile = "dev", deploy = null }) { const v02ObservableServiceIds = profile === "v02" ? v02ObservableServiceIdsForDeploy(deploy) : new Set(); const result = transformListNamespace(services, namespace, labels, annotations); if (result.kind === "List") { result.items = (result.items ?? []).filter((item) => !(profile === "v02" && isV02RemovedServiceId(serviceIdForWorkload(item, null)))); for (const item of result.items) { const serviceId = serviceIdForWorkload(item, null); if (profile !== "v02" || !v02ObservableServiceIds.has(serviceId)) continue; label(item.metadata ??= {}, v02MetricsLabels(serviceId)); upsertV02MetricsPort(item); } } return result; } function observabilityManifest({ deploy, namespace, labels, annotations, metricsSidecarScript }) { const baseLabels = { ...labels, "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/monitoring": "enabled" }; const serviceMonitors = v02ObservableServicesForDeploy(deploy).map((service) => ({ apiVersion: "monitoring.coreos.com/v1", kind: "ServiceMonitor", metadata: { name: `hwlab-v02-${service.serviceId}`, namespace, labels: { ...baseLabels, "hwlab.pikastech.local/service-id": service.serviceId }, annotations }, spec: { namespaceSelector: { matchNames: [namespace] }, selector: { matchLabels: { "hwlab.pikastech.local/gitops-target": "v02", "hwlab.pikastech.local/monitoring": "enabled", "hwlab.pikastech.local/service-id": service.serviceId } }, targetLabels: [ "hwlab.pikastech.local/gitops-target", "hwlab.pikastech.local/service-id" ], endpoints: [{ port: "metrics", path: "/metrics", interval: "30s", scrapeTimeout: "10s" }] } })); return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "ConfigMap", metadata: { name: "hwlab-v02-metrics-sidecar", namespace, labels: baseLabels, annotations }, data: { "metrics-sidecar.mjs": metricsSidecarScript } }, ...serviceMonitors, { apiVersion: "monitoring.coreos.com/v1", kind: "PrometheusRule", metadata: { name: "hwlab-v02-observability", namespace, labels: baseLabels, annotations }, spec: { groups: [{ name: "hwlab-v02-observability", rules: [ { alert: "HWLABV02MetricsTargetDown", expr: 'up{namespace="hwlab-v02"} == 0', for: "5m", labels: { severity: "warning", lane: "v02" }, annotations: { summary: "HWLAB v0.2 metrics target is down", description: "Prometheus cannot scrape a HWLAB v0.2 metrics target." } }, { alert: "HWLABV02ServiceHealthProbeFailed", expr: 'hwlab_service_health_probe_success{namespace="hwlab-v02"} == 0', for: "5m", labels: { severity: "warning", lane: "v02" }, annotations: { summary: "HWLAB v0.2 service health probe failed", description: "The metrics sidecar cannot reach the service health endpoint inside the pod network." } } ] }] } } ] }; } function shellSingleQuote(value) { return `'${String(value).replace(/'/g, `'"'"'`)}'`; } function ciTimingShellFunction() { return `ci_now_ms() { node -e 'console.log(Date.now())' } ci_timing_emit() { stage="$1" status="$2" started_ms="$3" finished_ms="$(ci_now_ms)" duration_ms="$((finished_ms - started_ms))" service_id="\${HWLAB_TIMING_SERVICE_ID:-}" node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:"g14-cicd-timing",schemaVersion:"v1",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:"scripts/g14-gitops-render.mjs",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' "$stage" "$status" "$duration_ms" "$service_id" } `; } function dependencyProxyProbeScript(phase, targetUrl) { return `HWLAB_PROXY_PROBE_PHASE=${shellSingleQuote(phase)} HWLAB_PROXY_PROBE_URL=${shellSingleQuote(targetUrl)} node <<'NODE' || echo '{"event":"dependency-proxy-probe-nonblocking","phase":"${phase}","ok":false,"reason":"probe-failed-but-continuing"}' const net = require("node:net"); const phase = process.env.HWLAB_PROXY_PROBE_PHASE || "unknown"; const target = new URL(process.env.HWLAB_PROXY_PROBE_URL || "http://deb.debian.org/debian/dists/bookworm/InRelease"); const proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || ""; function redactProxy(value) { if (!value) return ""; try { const parsed = new URL(value); if (parsed.username || parsed.password) { parsed.username = "***"; parsed.password = ""; } return parsed.toString(); } catch { return ""; } } function emit(payload, exitCode = 0) { console.log(JSON.stringify({ event: "dependency-proxy-probe", phase, target: target.href, proxy: redactProxy(proxyRaw), ...payload })); if (exitCode) process.exit(exitCode); } if (!proxyRaw) emit({ ok: false, reason: "proxy-env-missing" }, 21); let proxy; try { proxy = new URL(proxyRaw); } catch (error) { emit({ ok: false, reason: "proxy-url-invalid", error: error.message }, 22); } if (proxy.protocol !== "http:" && proxy.protocol !== "https:") { emit({ ok: false, reason: "http-proxy-required", protocol: proxy.protocol }, 23); } const startedAt = Date.now(); const socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === "https:" ? 443 : 80)) }); let bytes = 0; let firstByteMs = null; let responseHead = ""; let finished = false; const timeout = setTimeout(() => { if (finished) return; finished = true; socket.destroy(); emit({ ok: false, reason: "timeout", timeoutMs: 15000 }, 24); }, 15000); socket.on("connect", () => { socket.write("GET " + target.href + " HTTP/1.1\\r\\nHost: " + target.host + "\\r\\nUser-Agent: hwlab-g14-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n"); }); socket.on("data", (chunk) => { if (firstByteMs === null) firstByteMs = Date.now() - startedAt; bytes += chunk.length; if (responseHead.length < 240) responseHead += chunk.toString("utf8", 0, Math.min(chunk.length, 240 - responseHead.length)); }); socket.on("end", () => { if (finished) return; finished = true; clearTimeout(timeout); const totalMs = Date.now() - startedAt; const statusLine = responseHead.split("\\r\\n", 1)[0] || ""; const statusCode = Number(statusLine.split(" ")[1] || 0); const ok = statusLine.startsWith("HTTP/") && statusCode >= 200 && statusCode < 400; emit({ ok, reason: ok ? null : "bad-http-status", statusLine, statusCode, firstByteMs, totalMs, bytes, speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0 }, ok ? 0 : 25); }); socket.on("error", (error) => { if (finished) return; finished = true; clearTimeout(timeout); emit({ ok: false, reason: "proxy-connect-failed", error: error.message, totalMs: Date.now() - startedAt }, 26); }); NODE`; } function curlDownloadProbeFunction() { return `redact_proxy_value() { printf '%s' "\${1:-}" | sed -E 's#(https?://)[^/@]+@#\\1***@#; s#(socks5h?://)[^/@]+@#\\1***@#' } curl_dependency_probe() { phase="$1" url="$2" proxy="\${HTTPS_PROXY:-\${https_proxy:-\${HTTP_PROXY:-\${http_proxy:-}}}}" if [ -z "$proxy" ]; then echo '{"event":"dependency-curl-probe","phase":"'"$phase"'","ok":false,"reason":"proxy-env-missing"}' return 21 fi safe_proxy="$(redact_proxy_value "$proxy")" echo '{"event":"dependency-curl-probe-start","phase":"'"$phase"'","proxy":"'"$safe_proxy"'","target":"'"$url"'"}' curl -sS -L --range 0-1048575 -o /dev/null --connect-timeout 15 --max-time 60 --proxy "$proxy" \ -w '{"event":"dependency-curl-probe","phase":"'"$phase"'","http_code":"%{http_code}","time_namelookup":%{time_namelookup},"time_connect":%{time_connect},"time_starttransfer":%{time_starttransfer},"time_total":%{time_total},"speed_download":%{speed_download},"size_download":%{size_download},"remote_ip":"%{remote_ip}"}\n' \ "$url" }`; } function gitSshShellFunction() { return `git_ssh_setup() { mkdir -p /root/.ssh cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa chmod 600 /root/.ssh/id_rsa timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true write_github_proxy_command export GIT_SSH_COMMAND="ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -o ServerAliveInterval=10 -o ServerAliveCountMax=2 -o ProxyCommand='node /tmp/hwlab-github-proxy-connect.mjs 127.0.0.1 10808 %h %p'" git config --global url."ssh://git@ssh.github.com:443/".insteadOf "git@github.com:" git config --global url."ssh://git@ssh.github.com:443/".insteadOf "ssh://git@github.com/" } write_github_proxy_command() { cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY' #!/usr/bin/env node import net from "node:net"; const [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2); const proxyPort = Number.parseInt(proxyPortRaw || "", 10); const targetPort = Number.parseInt(targetPortRaw || "", 10); if (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) { console.error("usage: hwlab-github-proxy-connect "); process.exit(64); } const socket = net.createConnection({ host: proxyHost, port: proxyPort }); let buffer = Buffer.alloc(0); socket.setTimeout(10000, () => { console.error("proxy connect timeout " + proxyHost + ":" + proxyPort + " -> " + targetHost + ":" + targetPort); socket.destroy(); process.exit(65); }); socket.on("connect", () => { socket.write("CONNECT " + targetHost + ":" + targetPort + " HTTP/1.1\\r\\nHost: " + targetHost + ":" + targetPort + "\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n"); }); socket.on("error", (error) => { console.error("proxy connect failed: " + error.message); process.exit(66); }); function onData(chunk) { buffer = Buffer.concat([buffer, chunk]); const headerEnd = buffer.indexOf("\\r\\n\\r\\n"); if (headerEnd === -1 && buffer.length < 8192) return; const head = buffer.slice(0, headerEnd + 4).toString("latin1"); const statusLine = head.split("\\r\\n", 1)[0] || ""; const statusCode = Number.parseInt(statusLine.split(" ")[1] || "", 10); if (!statusLine.startsWith("HTTP/1.") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) { console.error("proxy CONNECT rejected: " + (statusLine || "missing-status")); socket.destroy(); process.exit(67); } socket.off("data", onData); socket.setTimeout(0); const rest = buffer.slice(headerEnd + 4); if (rest.length) process.stdout.write(rest); process.stdin.pipe(socket); socket.pipe(process.stdout); } socket.on("data", onData); socket.on("close", () => process.exit(0)); NODE_PROXY chmod 0700 /tmp/hwlab-github-proxy-connect.mjs } git_now_ms() { node -e 'console.log(Date.now())' 2>/dev/null || date +%s000 } git_timed() { phase="$1" timeout_seconds="$2" shift 2 started_ms="$(git_now_ms)" echo '{"event":"git-operation","phase":"'"$phase"'","status":"started","timeoutSeconds":'"$timeout_seconds"'}' >&2 set +e timeout "$timeout_seconds" "$@" status="$?" set -e finished_ms="$(git_now_ms)" duration_ms="$((finished_ms - started_ms))" if [ "$status" -eq 0 ]; then echo '{"event":"git-operation","phase":"'"$phase"'","status":"succeeded","durationMs":'"$duration_ms"'}' >&2 return 0 fi if [ "$status" -eq 124 ] || [ "$status" -eq 137 ]; then echo '{"event":"git-operation","phase":"'"$phase"'","status":"failed","reason":"timeout","durationMs":'"$duration_ms"',"timeoutSeconds":'"$timeout_seconds"'}' >&2 else echo '{"event":"git-operation","phase":"'"$phase"'","status":"failed","exitCode":'"$status"',"durationMs":'"$duration_ms"'}' >&2 fi return "$status" } `; } function prepareSourceScript() { const lines = [ "#!/bin/sh", "set -eu", ciTimingShellFunction(), "export HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"", "export HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"", "export HWLAB_TEKTON_TASK=\"prepare-source\"", "export HWLAB_SOURCE_REVISION=\"$(params.revision)\"", `echo '{"event":"ci-base-image","phase":"prepare-source","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'`, "for tool in node git timeout; do command -v \"$tool\" >/dev/null 2>&1 || { echo '{\"event\":\"ci-base-image\",\"ok\":false,\"reason\":\"missing-tool\",\"tool\":\"'\"$tool\"'\"}'; exit 31; }; done", "node -e 'console.log(JSON.stringify({event:\"ci-base-image\",phase:\"prepare-source\",ok:true,node:process.version}))'", gitSshShellFunction(), "git_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }", "git_read_url=\"$(params.git-read-url)\"", "if git_url_requires_ssh \"$git_read_url\"; then", " for tool in ssh ssh-keyscan; do command -v \"$tool\" >/dev/null 2>&1 || { echo '{\"event\":\"ci-base-image\",\"ok\":false,\"reason\":\"missing-tool\",\"tool\":\"'\"$tool\"'\"}'; exit 31; }; done", " git_ssh_setup", "else", " echo '{\"event\":\"git-ssh-setup\",\"phase\":\"prepare-source\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-read-url\"}'", "fi", "rm -rf /workspace/source/repo", "source_clone_started_ms=\"$(ci_now_ms)\"", "git_timed source-clone 180 git clone --branch \"$(params.source-branch)\" \"$git_read_url\" /workspace/source/repo", "cd /workspace/source/repo", "git config --global --add safe.directory /workspace/source/repo", "git remote set-url origin \"$git_read_url\"", "git checkout \"$(params.revision)\"", "test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"", "git merge-base --is-ancestor \"$(params.revision)\" \"origin/$(params.source-branch)\" || { echo '{\"event\":\"source-ancestry\",\"status\":\"failed\",\"revision\":\"'\"$(params.revision)\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\"}'; exit 32; }", "ci_timing_emit source-clone succeeded \"$source_clone_started_ms\"", "catalog_path=\"$(params.catalog-path)\"", "mkdir -p \"$(dirname \"$catalog_path\")\"", "catalog_fetch_started_ms=\"$(ci_now_ms)\"", "if git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads \"$git_read_url\" \"$(params.gitops-branch)\" >/dev/null; then", " git remote set-url gitops-catalog \"$git_read_url\" 2>/dev/null || git remote add gitops-catalog \"$git_read_url\"", " git_timed catalog-fetch 90 git fetch --depth 1 gitops-catalog \"$(params.gitops-branch)\" >/dev/null || true", " if git cat-file -e FETCH_HEAD:\"$catalog_path\" 2>/dev/null; then", " git show FETCH_HEAD:\"$catalog_path\" > /tmp/hwlab-gitops-artifact-catalog.json", " if node --input-type=module -e 'import fs from \"node:fs\"; import { readStructuredFile } from \"./scripts/src/structured-config.mjs\"; const ids=(doc)=>(doc.services||[]).map((s)=>s.serviceId); const selected=(process.argv[3]||\"\").split(\",\").filter(Boolean); const gitops=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")); const deploy=await readStructuredFile(process.cwd(),process.argv[2]); const expected=selected.length ? selected : ids(deploy); if (JSON.stringify(ids(gitops)) !== JSON.stringify(expected)) process.exit(42);' /tmp/hwlab-gitops-artifact-catalog.json deploy/deploy.yaml \"$(params.services)\"; then", " cp /tmp/hwlab-gitops-artifact-catalog.json \"$catalog_path\"", " echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"loaded\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'", " else", " echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"ignored-stale\",\"reason\":\"service-ids-mismatch\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'", " fi", " else", " echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"missing-on-gitops-branch\"}'", " fi", "else", " echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"gitops-branch-missing\"}'", "fi", "if [ ! -s \"$catalog_path\" ] && [ \"$(params.lane)\" = \"v02\" ]; then", " node --input-type=module - \"$catalog_path\" \"$(params.revision)\" \"$(params.registry-prefix)\" \"$(params.image-tag-mode)\" \"$(params.services)\" <<'NODE'", "import fs from 'node:fs';", "import { readStructuredFile } from './scripts/src/structured-config.mjs';", "const [catalogPath, revision, registryPrefix, imageTagMode, selectedServices] = process.argv.slice(2);", "const deploy = await readStructuredFile(process.cwd(), 'deploy/deploy.yaml');", "const tag = imageTagMode === 'full' ? revision : revision.slice(0, 7);", "const namespace = 'hwlab-v02';", "const selected = (selectedServices || '').split(',').filter(Boolean);", "const serviceIds = selected.length ? selected : (deploy.services || []).map((service) => service.serviceId);", "const services = serviceIds.map((serviceId) => {", " const service = { serviceId, profile: 'v02', namespace, commitId: tag, sourceCommitId: revision, image: `${registryPrefix}/${serviceId}:${tag}`, imageTag: tag, digest: null, buildBackend: 'contract-skeleton' };", " return service;", "});", "fs.mkdirSync(path.dirname(catalogPath), { recursive: true });", `fs.writeFileSync(catalogPath, JSON.stringify({ catalogVersion: 'v1', kind: 'hwlab-artifact-catalog', environment: 'v02', profile: 'v02', namespace, endpoint: ${JSON.stringify(defaultV02RuntimeEndpoint)}, commitId: tag, artifactState: 'contract-skeleton', publish: { registryPrefix, sourceCommitId: revision, imageTag: tag, publishedAt: null }, allowedProfiles: ['v02'], forbiddenProfiles: ['dev', 'prod'], services }, null, 2) + '\\n');`, "NODE", " echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seeded-v02-skeleton\"}'", "fi", "ci_timing_emit catalog-fetch succeeded \"$catalog_fetch_started_ms\"", "echo '{\"event\":\"prepare-source-dependencies\",\"status\":\"skipped\",\"reason\":\"ci-tools-image-provides-required-node-runtime\"}'", `echo ${shellSingleQuote(`G14 prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}` ]; return `${lines.join("\n")}\n`; } function sourceValidationScript(commands) { return [ "#!/bin/sh", "set -eu", "git config --global --add safe.directory /workspace/source/repo", "cd /workspace/source/repo", "test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"", ...commands ].join("\n") + "\n"; } function primitiveValidationTaskNames() { return primitiveValidationTasks.map((task) => task.name); } function primitiveValidationTask(task) { return { name: task.name, runAfter: ["prepare-source"], workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [ { name: "revision" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "source-branch" }, { name: "gitops-branch" } ], workspaces: [{ name: "source" }], steps: [{ name: task.name, image: ciToolsRunnerImage, env: proxyEnv(), script: sourceValidationScript(task.commands) }] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "source-branch", value: "$(params.source-branch)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" } ] }; } function planArtifactsScript() { return `#!/bin/sh set -eu echo '{"event":"ci-base-image","phase":"plan-artifacts","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' for tool in node git; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"plan-artifacts","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done cd /workspace/source/repo test "$(git rev-parse HEAD)" = "$(params.revision)" node scripts/g14-ci-plan.mjs --lane "$(params.lane)" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog "$(params.catalog-path)" --registry-prefix "$(params.registry-prefix)" --services "$(params.services)" > /workspace/source/ci-plan.json node - <<'NODE' const fs = require("node:fs"); const plan = JSON.parse(fs.readFileSync("/workspace/source/ci-plan.json", "utf8")); const selected = String(process.env.HWLAB_SELECTED_SERVICES || "").split(",").map((item) => item.trim()).filter(Boolean); const allServices = selected.length > 0 ? selected : ${JSON.stringify(defaultServiceIds)}; const affected = new Set(plan.affectedServices || []); const plannedBuildServices = Array.isArray(plan.buildServices) ? new Set(plan.buildServices) : null; const selectedSet = new Set(selected); const byService = new Map((plan.services || []).map((service) => [service.serviceId, service])); const entries = allServices.map((serviceId) => { const service = byService.get(serviceId) || {}; const serviceSelected = selectedSet.has(serviceId); const rolloutAffected = serviceSelected && affected.has(serviceId); const envReuse = service.runtimeMode === "env-reuse-git-mirror-checkout" || service.envReuse === true; const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected)); return { serviceId, selected: serviceSelected, affected: buildRequired, buildRequired, rolloutAffected, runtimeMode: service.runtimeMode || "service-image", envChanged: service.envChanged ?? null, codeChanged: service.codeChanged ?? null }; }); for (const entry of entries) { fs.writeFileSync("/tekton/results/affected-" + entry.serviceId, entry.affected ? "true" : "false"); } fs.writeFileSync("/workspace/source/affected-services.json", JSON.stringify({ sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId), reusedServices: plan.reusedServices || [], buildSkippedCount: plan.buildSkippedCount || 0, changedPathSummary: plan.changedPathSummary || null, ciCdPlan: plan.ciCdPlan || null, services: plan.services || [], entries }, null, 2) + String.fromCharCode(10)); console.log(JSON.stringify({ event: "g14-ci-plan", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], reusedServices: plan.reusedServices || [], buildSkippedCount: plan.buildSkippedCount || 0 })); NODE `; } function perServicePublishScript() { return `#!/bin/sh set -eu ${ciTimingShellFunction()} export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)" export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)" export HWLAB_TEKTON_TASK="build-$(params.service-id)" export HWLAB_SOURCE_REVISION="$(params.revision)" export HWLAB_TIMING_SERVICE_ID="$(params.service-id)" ${dependencyProxyProbeScript("service-image-publish-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")} echo '{"event":"ci-base-image","phase":"service-image-publish","serviceId":"'"$(params.service-id)"'","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' for tool in node npm git python3 ssh curl; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"service-image-publish","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done git config --global --add safe.directory /workspace/source/repo cd /workspace/source/repo test "$(git rev-parse HEAD)" = "$(params.revision)" export HWLAB_ARTIFACT_LANE="$(params.lane)" export HWLAB_ARTIFACT_CATALOG_PATH="$(params.catalog-path)" export HWLAB_DEPLOY_MANIFEST_PATH="deploy/deploy.yaml" export HWLAB_ARTIFACT_IMAGE_TAG_MODE="$(params.image-tag-mode)" mkdir -p /workspace/source/service-results if [ -s /workspace/source/affected-services.json ] && ! node -e 'const fs=require("node:fs"); const p=JSON.parse(fs.readFileSync("/workspace/source/affected-services.json","utf8")); const service=process.argv[1]; const item=(p.entries||[]).find((entry)=>entry.serviceId===service); process.exit(item && item.affected ? 0 : 1);' "$(params.service-id)"; then node - "$(params.service-id)" <<'NODE' const fs = require("node:fs"); const serviceId = process.argv[2]; const catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, "utf8")); const plan = fs.existsSync("/workspace/source/affected-services.json") ? JSON.parse(fs.readFileSync("/workspace/source/affected-services.json", "utf8")) : {}; const service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {}; const planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {}; const envReuse = planned.runtimeMode === "env-reuse-git-mirror-checkout" || service.runtimeMode === "env-reuse-git-mirror-checkout" || planned.envReuse === true || service.envReuse === true; const image = envReuse ? (service.environmentImage || service.image || "") : (service.image || ""); const digest = envReuse ? (service.environmentDigest || service.digest || "not_published") : (service.digest || "not_published"); const imageTag = service.imageTag || (image.includes(":") ? image.slice(image.lastIndexOf(":") + 1) : ""); const repository = image.includes(":") ? image.slice(0, image.lastIndexOf(":")) : image; const revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag; const componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || "") : (service.componentInputHash || ""); const environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || "") : (service.environmentInputHash || ""); const codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || "") : (service.codeInputHash || ""); const values = { "service-id": serviceId, status: /^sha256:[a-f0-9]{64}$/.test(digest) ? "reused" : "blocked_reuse_unavailable", image, "image-tag": imageTag, digest, "repository-digest": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + "@" + digest : "", "source-commit-id": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag), "component-input-hash": componentInputHash, "environment-input-hash": environmentInputHash, "code-input-hash": codeInputHash, "runtime-mode": envReuse ? "env-reuse-git-mirror-checkout" : "service-image", "boot-repo": planned.bootRepo || service.bootRepo || "", "boot-commit": envReuse ? revision : (service.bootCommit || ""), "boot-sh": planned.bootSh || service.bootSh || "", "build-created-at": service.buildCreatedAt || "", "build-backend": envReuse ? "reused-env-catalog" : "reused-catalog", "reused-from": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || "catalog" }; for (const [name, value] of Object.entries(values)) fs.writeFileSync("/tekton/results/" + name, String(value || "")); NODE echo '{"event":"service-build-skip","serviceId":"'"$(params.service-id)"'","reason":"component-inputs-unchanged"}' exit 0 fi rm -rf /workspace/service-work/repo mkdir -p /workspace/service-work/repo tar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f - cd /workspace/service-work/repo export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)-$(params.service-id)" export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton" export HWLAB_DEV_REGISTRY_PREFIX="$(params.registry-prefix)" export HWLAB_DEV_REGISTRY_K8S_NATIVE=1 export HWLAB_G14_CICD_TIMING=1 export HWLAB_TEKTON_PIPELINERUN export HWLAB_TEKTON_TASKRUN export HWLAB_TEKTON_TASK export HWLAB_SOURCE_REVISION export PATH="/workspace/buildkit-bin:$PATH" export HWLAB_BUILDKIT_ADDR="unix:///workspace/buildkit-run/buildkitd.sock" if [ -n "$(params.base-image)" ]; then export HWLAB_DEV_BASE_IMAGE="$(params.base-image)"; fi if [ -n "\${HWLAB_DEV_BASE_IMAGE:-}" ]; then echo '{"event":"dependency-local-registry-probe-start","phase":"service-image-publish-pre-base-image","serviceId":"'"$(params.service-id)"'","target":"http://127.0.0.1:5000/v2/"}' registry_started_at="$(date +%s)" curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null registry_finished_at="$(date +%s)" echo '{"event":"dependency-local-registry-probe","phase":"service-image-publish-pre-base-image","serviceId":"'"$(params.service-id)"'","ok":true,"durationSeconds":'"$((registry_finished_at - registry_started_at))"'}' fi buildkit_ready=0 for attempt in $(seq 1 60); do if /workspace/buildkit-bin/buildctl --addr "$HWLAB_BUILDKIT_ADDR" debug workers >/dev/null 2>&1; then buildkit_ready=1 break fi sleep 1 done if [ "$buildkit_ready" != "1" ]; then echo '{"event":"buildkit-sidecar-not-ready","serviceId":"'"$(params.service-id)"'","addr":"'"$HWLAB_BUILDKIT_ADDR"'"}' >&2 exit 1 fi node scripts/g14-artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report "/workspace/source/service-results/$(params.service-id).json" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR" `; } function collectArtifactsScript() { return `#!/bin/sh set -eu echo '{"event":"ci-base-image","phase":"collect-artifacts","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' for tool in node git; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"collect-artifacts","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done cd /workspace/source/repo test "$(git rev-parse HEAD)" = "$(params.revision)" export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)-collect" export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton" export HWLAB_DEV_REGISTRY_PREFIX="$(params.registry-prefix)" export HWLAB_DEV_REGISTRY_K8S_NATIVE=1 if [ -n "$(params.base-image)" ]; then export HWLAB_DEV_BASE_IMAGE="$(params.base-image)"; fi export HWLAB_ARTIFACT_LANE="$(params.lane)" export HWLAB_ARTIFACT_CATALOG_PATH="$(params.catalog-path)" export HWLAB_DEPLOY_MANIFEST_PATH="deploy/deploy.yaml" export HWLAB_ARTIFACT_IMAGE_TAG_MODE="$(params.image-tag-mode)" node scripts/g14-artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report /workspace/source/dev-artifacts.json --quiet-build --concurrency 1 --services "$(params.services)" --external-service-report-dir /workspace/source/service-results node scripts/refresh-artifact-catalog.mjs --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --target-ref HEAD --publish-report /workspace/source/dev-artifacts.json --no-write `; } function gitopsPromoteScript() { return `#!/bin/sh set -eu ${ciTimingShellFunction()} export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)" export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)" export HWLAB_TEKTON_TASK="gitops-promote" export HWLAB_SOURCE_REVISION="$(params.revision)" printf 'true' > /tekton/results/runtime-ready-required echo '{"event":"ci-base-image","phase":"gitops-promote","image":"${ciToolsRunnerImage}","policy":"no-runtime-apt"}' for tool in node git timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"gitops-promote","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done ${gitSshShellFunction()} git_url_requires_ssh() { case "$1" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; } lane="$(params.lane)" source_head_url_for_setup="$(params.git-url)" gitops_read_url_for_setup="$(params.git-url)" if [ "$lane" = "v02" ]; then source_head_url_for_setup="$(params.git-read-url)" gitops_read_url_for_setup="$(params.git-read-url)" fi gitops_write_url_for_setup="$(params.git-write-url)" if git_url_requires_ssh "$source_head_url_for_setup" || git_url_requires_ssh "$gitops_read_url_for_setup" || git_url_requires_ssh "$gitops_write_url_for_setup"; then for tool in ssh ssh-keyscan; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"gitops-promote","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done git_ssh_setup else echo '{"event":"git-ssh-setup","phase":"gitops-promote","status":"skipped","reason":"non-ssh-git-url"}' fi cd /workspace/source/repo test "$(git rev-parse HEAD)" = "$(params.revision)" check_source_head() { phase="$1" expected="\${2:-$(params.revision)}" source_head_url="$(params.git-url)" if [ "$(params.lane)" = "v02" ]; then source_head_url="$(params.git-read-url)" fi latest_file="$(mktemp)" if ! git_timed "source-head-$phase" 45 git ls-remote "$source_head_url" "refs/heads/$(params.source-branch)" > "$latest_file"; then echo '{"status":"failed","reason":"source-head-check-failed","phase":"'"$phase"'","sourceBranch":"'"$(params.source-branch)"'","sourceRevision":"'"$(params.revision)"'"}' exit 1 fi latest="$(cut -f1 "$latest_file" | head -n 1)" if [ -z "$latest" ]; then echo '{"status":"failed","reason":"source-head-unresolved","phase":"'"$phase"'","sourceBranch":"'"$(params.source-branch)"'","sourceRevision":"'"$(params.revision)"'"}' exit 1 fi if [ "$latest" != "$expected" ]; then printf 'false' > /tekton/results/runtime-ready-required || true echo '{"status":"skipped-stale-source","verdict":"superseded","phase":"'"$phase"'","sourceBranch":"'"$(params.source-branch)"'","sourceRevision":"'"$(params.revision)"'","expectedRevision":"'"$expected"'","latestRevision":"'"$latest"'"}' exit 0 fi } check_source_head before-render git config --global user.name "HWLAB G14 GitOps Bot" git config --global user.email "hwlab-g14-gitops-bot@users.noreply.github.com" catalog_path="$(params.catalog-path)" runtime_path="$(params.runtime-path)" argo_hard_refresh_v02() { if [ "$lane" != "v02" ]; then return 0; fi node <<'NODE' const fs = require("node:fs"); const https = require("node:https"); const host = process.env.KUBERNETES_SERVICE_HOST; const port = process.env.KUBERNETES_SERVICE_PORT || "443"; const startedAt = Date.now(); const application = "hwlab-g14-v02"; const pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null; const taskRun = process.env.HWLAB_TEKTON_TASKRUN || null; const task = process.env.HWLAB_TEKTON_TASK || null; const revision = process.env.HWLAB_SOURCE_REVISION || null; function emit(payload) { console.log(JSON.stringify({ event: "g14-cicd-timing", schemaVersion: "v1", stage: "argo-hard-refresh", pipelineRun, taskRun, task, revision, application, source: "scripts/g14-gitops-render.mjs", at: new Date().toISOString(), ...payload })); } function safeJson(text) { try { return JSON.parse(text); } catch { return null; } } function request(method, path, body, contentType = "application/merge-patch+json") { const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8"); const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"); const payload = body ? JSON.stringify(body) : ""; const headers = { Authorization: "Bearer " + token }; if (payload) { headers["Content-Type"] = contentType; headers["Content-Length"] = Buffer.byteLength(payload); } return new Promise((resolve, reject) => { const req = https.request({ host, port, method, path, ca, headers }, (res) => { let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) })); }); req.on("error", reject); if (payload) req.write(payload); req.end(); }); } (async () => { if (!host) { emit({ status: "skipped", reason: "kubernetes-service-host-missing", durationMs: Date.now() - startedAt }); return; } const response = await request( "PATCH", "/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/" + encodeURIComponent(application) + "?fieldManager=hwlab-v02-gitops-promote", { metadata: { annotations: { "argocd.argoproj.io/refresh": "hard" } } } ); if (response.statusCode >= 200 && response.statusCode < 300) { emit({ status: "succeeded", durationMs: Date.now() - startedAt, statusCode: response.statusCode }); return; } emit({ status: "degraded", reason: "argo-refresh-patch-failed", durationMs: Date.now() - startedAt, statusCode: response.statusCode, body: response.body.slice(0, 1000) }); })().catch((error) => { emit({ status: "degraded", reason: "argo-refresh-patch-error", durationMs: Date.now() - startedAt, error: error.message }); }); NODE } if [ -s /workspace/source/dev-artifacts.json ]; then node scripts/refresh-artifact-catalog.mjs --lane "$lane" --catalog-path "$catalog_path" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --target-ref "$(params.revision)" --publish-report /workspace/source/dev-artifacts.json --write fi gitops_render_started_ms="$(ci_now_ms)" node scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images node scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images --check ci_timing_emit gitops-render succeeded "$gitops_render_started_ms" check_source_head before-push workdir="$(mktemp -d)" gitops_read_url="$(params.git-url)" gitops_write_url="$(params.git-write-url)" if [ "$lane" = "v02" ]; then gitops_read_url="$(params.git-read-url)" fi gitops_clone_started_ms="$(ci_now_ms)" git_timed gitops-clone 180 git clone --no-checkout "$gitops_read_url" "$workdir/gitops" cd "$workdir/gitops" git remote set-url origin "$gitops_write_url" old_runtime_snapshot="$workdir/old-runtime-snapshot" if git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin "$(params.gitops-branch)" >/dev/null; then git_timed gitops-fetch 90 git fetch origin "$(params.gitops-branch)" git checkout -B "$(params.gitops-branch)" "origin/$(params.gitops-branch)" if [ "$lane" = "v02" ] && [ -d "$runtime_path" ]; then mkdir -p "$old_runtime_snapshot" cp -a "$runtime_path"/. "$old_runtime_snapshot"/ fi if [ "$lane" = "v02" ]; then rm -rf "$runtime_path" "$catalog_path"; else rm -rf deploy/gitops/g14 "$catalog_path"; fi else git checkout --orphan "$(params.gitops-branch)" git rm -rf . >/dev/null 2>&1 || true fi ci_timing_emit gitops-clone succeeded "$gitops_clone_started_ms" mkdir -p "$(dirname "$runtime_path")" "$(dirname "$catalog_path")" if [ "$lane" = "v02" ]; then cp -a "/workspace/source/repo/$runtime_path" "$runtime_path" cp "/workspace/source/repo/$catalog_path" "$catalog_path" git add "$catalog_path" "$runtime_path" else mkdir -p deploy/gitops cp -a /workspace/source/repo/deploy/gitops/g14 deploy/gitops/g14 cp "/workspace/source/repo/$catalog_path" "$catalog_path" git add "$catalog_path" deploy/gitops/g14 fi if [ "$lane" = "v02" ] && [ -s /workspace/source/affected-services.json ]; then runtime_noop_decision="$(node - "$runtime_path" "$old_runtime_snapshot" /workspace/source/affected-services.json <<'NODE' const fs = require("node:fs"); const path = require("node:path"); const [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2); function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, "utf8")); } function stable(value) { if (Array.isArray(value)) return "[" + value.map(stable).join(",") + "]"; if (value && typeof value === "object") { return "{" + Object.keys(value).sort().map((key) => JSON.stringify(key) + ":" + stable(value[key])).join(",") + "}"; } return JSON.stringify(value); } function scrub(value) { if (Array.isArray(value)) return value.map(scrub); if (!value || typeof value !== "object") return value; const result = {}; for (const [key, child] of Object.entries(value)) { if (key === "hwlab.pikastech.local/source-commit" || key === "hwlab.pikastech.local/artifact-source-commit" || key === "hwlab.pikastech.local/boot-commit" || key === "sourceCommitId" || key === "bootCommit") { result[key] = ""; continue; } const envName = String(value.name || ""); if (key === "value" && /^HWLAB_.*COMMIT/.test(envName)) { result[key] = ""; continue; } result[key] = scrub(child); } return result; } function listFiles(rootDir) { if (!rootDir || !fs.existsSync(rootDir)) return null; const files = []; function walk(current) { for (const entry of fs.readdirSync(current, { withFileTypes: true })) { const fullPath = path.join(current, entry.name); if (entry.isDirectory()) walk(fullPath); else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, "/")); } } walk(rootDir); return files.sort(); } function normalizedTree(rootDir) { const files = listFiles(rootDir); if (!files) return null; const entries = {}; for (const relativePath of files) { const filePath = path.join(rootDir, relativePath); const text = fs.readFileSync(filePath, "utf8"); try { entries[relativePath] = stable(scrub(JSON.parse(text))); } catch { entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, ""); } } return stable(entries); } const plan = readJson(planPath); const buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : []; const rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : []; if (buildServices.length > 0 || rolloutServices.length > 0) { process.stdout.write("runtime-required"); process.exit(0); } const oldTree = normalizedTree(oldRuntimePath); const newTree = normalizedTree(runtimePath); process.stdout.write(oldTree && newTree && oldTree === newTree ? "runtime-identity-only" : "runtime-required"); NODE )" if [ "$runtime_noop_decision" = "runtime-identity-only" ]; then printf 'false' > /tekton/results/runtime-ready-required echo '{"status":"skipped-runtime-unchanged","reason":"runtime-identity-only","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}' ci_timing_emit gitops-commit skipped "$(ci_now_ms)" ci_timing_emit gitops-push skipped "$(ci_now_ms)" exit 0 fi fi if git diff --cached --quiet; then printf 'false' > /tekton/results/runtime-ready-required echo '{"status":"unchanged","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}' ci_timing_emit gitops-commit unchanged "$(ci_now_ms)" ci_timing_emit gitops-push unchanged "$(ci_now_ms)" exit 0 fi short="$(printf '%.7s' "$(params.revision)")" gitops_commit_started_ms="$(ci_now_ms)" git commit -m "chore: promote G14 GitOps source $short" ci_timing_emit gitops-commit succeeded "$gitops_commit_started_ms" gitops_push_started_ms="$(ci_now_ms)" git_timed gitops-push 120 git push origin "HEAD:$(params.gitops-branch)" ci_timing_emit gitops-push succeeded "$gitops_push_started_ms" argo_hard_refresh_v02 echo '{"status":"pushed","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'","gitopsWriteUrl":"'"$gitops_write_url"'"}' `; } function controlPlaneReconcileScript() { return `#!/bin/sh set -eu ${dependencyProxyProbeScript("control-plane-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")} echo '{"event":"ci-base-image","phase":"control-plane-reconciler","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' for tool in node npm git python3 ssh curl timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"control-plane-reconciler","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done ${gitSshShellFunction()} git_ssh_setup workdir="$(mktemp -d)" git_timed control-plane-clone 180 git clone --depth 1 --branch "$SOURCE_BRANCH" "$GIT_READ_URL" "$workdir/repo" cd "$workdir/repo" git remote set-url origin "$GIT_READ_URL" revision="$(git rev-parse HEAD)" : "\${LANE:?LANE is required}" : "\${CATALOG_PATH:?CATALOG_PATH is required}" : "\${IMAGE_TAG_MODE:?IMAGE_TAG_MODE is required}" node scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" node scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" --check node <<'NODE' const fs = require("node:fs"); const https = require("node:https"); function requiredEnv(name) { const value = process.env[name]; if (!value) throw new Error(name + " is required"); return value; } const namespace = requiredEnv("POD_NAMESPACE"); const host = process.env.KUBERNETES_SERVICE_HOST; const port = process.env.KUBERNETES_SERVICE_PORT || "443"; if (!host) throw new Error("KUBERNETES_SERVICE_HOST is not available"); const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8"); const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"); const tektonDir = requiredEnv("TEKTON_DIR"); const argoFiles = String(process.env.ARGO_FILES || "").split(",").map((item) => item.trim()).filter(Boolean); const fieldManager = requiredEnv("FIELD_MANAGER"); const eventName = requiredEnv("RECONCILE_EVENT"); const manifestFiles = [ "deploy/gitops/g14/" + tektonDir + "/rbac.yaml", "deploy/gitops/g14/" + tektonDir + "/pipeline.yaml", ...(process.env.LANE === "v02" ? [] : [ "deploy/gitops/g14/" + tektonDir + "/poller.yaml", "deploy/gitops/g14/" + tektonDir + "/control-plane-reconciler.yaml" ]), ...argoFiles ]; const plurals = new Map([ ["ServiceAccount", "serviceaccounts"], ["Role", "roles"], ["RoleBinding", "rolebindings"], ["Pipeline", "pipelines"], ["CronJob", "cronjobs"], ["Application", "applications"], ["AppProject", "appprojects"] ]); function encode(value) { return encodeURIComponent(String(value)); } function itemsFrom(file) { const parsed = JSON.parse(fs.readFileSync(file, "utf8")); return parsed.kind === "List" ? parsed.items || [] : [parsed]; } function apiPath(item) { if (item.kind === "Namespace") return null; const name = item.metadata?.name; if (!name) throw new Error("manifest item is missing metadata.name"); const ns = item.metadata?.namespace || namespace; const plural = plurals.get(item.kind); if (!plural) throw new Error("unsupported reconciler manifest kind " + item.kind); if (item.apiVersion === "v1") return "/api/v1/namespaces/" + encode(ns) + "/" + plural + "/" + encode(name); const parts = String(item.apiVersion || "").split("/"); if (parts.length !== 2) throw new Error("unsupported apiVersion " + item.apiVersion + " for " + item.kind); return "/apis/" + encode(parts[0]) + "/" + encode(parts[1]) + "/namespaces/" + encode(ns) + "/" + plural + "/" + encode(name); } function reconcilePolicy(item) { const ns = item.metadata?.namespace || namespace; const crossNamespaceRbac = (item.kind === "Role" || item.kind === "RoleBinding") && ns !== namespace; if (crossNamespaceRbac && process.env.HWLAB_RECONCILE_CROSS_NAMESPACE_RBAC !== "1") { return { action: "skip", reason: "cross-namespace-rbac-bootstrap-managed", namespace: ns }; } return { action: "apply", namespace: ns }; } function request(method, path, body) { const payload = body ? JSON.stringify(body) : ""; const headers = { Authorization: "Bearer " + token }; if (payload) { headers["Content-Type"] = "application/apply-patch+yaml"; headers["Content-Length"] = Buffer.byteLength(payload); } return new Promise((resolve, reject) => { const req = https.request({ host, port, method, path, ca, headers }, (res) => { let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data })); }); req.on("error", reject); if (payload) req.write(payload); req.end(); }); } (async () => { const results = []; for (const file of manifestFiles) { for (const item of itemsFrom(file)) { const policy = reconcilePolicy(item); if (policy.action === "skip") { results.push({ file, kind: item.kind, name: item.metadata?.name || null, namespace: policy.namespace, status: "skipped", reason: policy.reason }); continue; } const path = apiPath(item); if (!path) { results.push({ file, kind: item.kind, name: item.metadata?.name || null, status: "skipped-cluster-scoped" }); continue; } const response = await request("PATCH", path + "?fieldManager=" + encode(fieldManager) + "&force=true", item); if (response.statusCode < 200 || response.statusCode > 299) { throw new Error("apply failed for " + item.kind + "/" + item.metadata.name + " status=" + response.statusCode + " body=" + response.body.slice(0, 1000)); } results.push({ file, kind: item.kind, name: item.metadata.name, status: "applied", statusCode: response.statusCode }); } } console.log(JSON.stringify({ event: eventName, sourceRevision: process.env.RECONCILE_REVISION || null, results }, null, 2)); })().catch((error) => { console.error(error.stack || error.message); process.exitCode = 1; }); NODE `; } function pollerScript() { return `#!/bin/sh set -eu ${dependencyProxyProbeScript("poller-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")} echo '{"event":"ci-base-image","phase":"poller","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}' for tool in node git ssh curl timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"poller","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done ${gitSshShellFunction()} git_ssh_setup workdir="$(mktemp -d)" git_timed poller-clone 180 git clone --depth 1 --branch "$SOURCE_BRANCH" "$GIT_READ_URL" "$workdir/repo" cd "$workdir/repo" git remote set-url origin "$GIT_READ_URL" revision="$(git rev-parse HEAD)" subject="$(git log -1 --pretty=%s)" case "$subject" in "chore: promote G14 GitOps source "*) echo "Skipping generated GitOps promotion commit $revision" exit 0 ;; esac short="$(printf '%.12s' "$revision")" : "\${PIPELINERUN_PREFIX:?PIPELINERUN_PREFIX is required}" : "\${GITOPS_TARGET:?GITOPS_TARGET is required}" prefix="$PIPELINERUN_PREFIX" name="$prefix-$short" export POLLER_REVISION="$revision" export POLLER_PIPELINERUN="$name" export POLLER_SOURCE_SUBJECT="$subject" echo "Checking $GITOPS_TARGET source $revision with PipelineRun $name" node <<'NODE' const fs = require("node:fs"); const https = require("node:https"); function requiredEnv(name) { const value = process.env[name]; if (!value) throw new Error(name + " is required"); return value; } function optionalEnv(name) { return process.env[name] || ""; } const namespace = requiredEnv("POD_NAMESPACE"); const name = requiredEnv("POLLER_PIPELINERUN"); const revision = requiredEnv("POLLER_REVISION"); const host = process.env.KUBERNETES_SERVICE_HOST; const port = process.env.KUBERNETES_SERVICE_PORT || "443"; if (!host) throw new Error("KUBERNETES_SERVICE_HOST is not available"); const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8"); const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"); function request(method, path, body) { const payload = body ? JSON.stringify(body) : ""; const headers = { Authorization: "Bearer " + token }; if (payload) { headers["Content-Type"] = "application/json"; headers["Content-Length"] = Buffer.byteLength(payload); } return new Promise((resolve, reject) => { const req = https.request({ host, port, method, path, ca, headers }, (res) => { let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data })); }); req.on("error", reject); if (payload) req.write(payload); req.end(); }); } const labels = { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": requiredEnv("GITOPS_TARGET"), "hwlab.pikastech.local/source-commit": revision, "hwlab.pikastech.local/trigger": "polling" }; const pipelineRun = { apiVersion: "tekton.dev/v1", kind: "PipelineRun", metadata: { name, namespace, labels, annotations: { "hwlab.pikastech.local/source-subject": optionalEnv("POLLER_SOURCE_SUBJECT"), "hwlab.pikastech.local/source-branch": requiredEnv("SOURCE_BRANCH"), "hwlab.pikastech.local/gitops-branch": requiredEnv("GITOPS_BRANCH") } }, spec: { pipelineRef: { name: requiredEnv("PIPELINE_NAME") }, taskRunTemplate: { serviceAccountName: requiredEnv("SERVICE_ACCOUNT_NAME"), podTemplate: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", securityContext: { fsGroup: 1000 } } }, params: [ { name: "git-url", value: requiredEnv("GIT_URL") }, { name: "git-read-url", value: requiredEnv("GIT_READ_URL") }, { name: "source-branch", value: requiredEnv("SOURCE_BRANCH") }, { name: "gitops-branch", value: requiredEnv("GITOPS_BRANCH") }, { name: "lane", value: requiredEnv("LANE") }, { name: "catalog-path", value: requiredEnv("CATALOG_PATH") }, { name: "image-tag-mode", value: requiredEnv("IMAGE_TAG_MODE") }, { name: "runtime-path", value: requiredEnv("RUNTIME_PATH") }, { name: "revision", value: revision }, { name: "registry-prefix", value: requiredEnv("REGISTRY_PREFIX") }, { name: "services", value: requiredEnv("SERVICES") }, { name: "base-image", value: requiredEnv("BASE_IMAGE") } ], workspaces: [ { name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } }, { name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } } ] } }; const base = "/apis/tekton.dev/v1/namespaces/" + encodeURIComponent(namespace) + "/pipelineruns"; const item = base + "/" + encodeURIComponent(name); (async () => { const existing = await request("GET", item); if (existing.statusCode === 200) { console.log(JSON.stringify({ status: "exists", pipelineRun: name, revision })); return; } if (existing.statusCode !== 404) { throw new Error("unexpected PipelineRun lookup status " + existing.statusCode + ": " + existing.body.slice(0, 500)); } const created = await request("POST", base, pipelineRun); if (created.statusCode < 200 || created.statusCode > 299) { throw new Error("PipelineRun create failed with status " + created.statusCode + ": " + created.body.slice(0, 1000)); } console.log(JSON.stringify({ status: "created", pipelineRun: name, revision })); })().catch((error) => { console.error(error.stack || error.message); process.exitCode = 1; }); NODE `; } function ciLaneSettings(argsOrLane = "g14") { const lane = typeof argsOrLane === "string" ? argsOrLane : argsOrLane.lane; if (lane === "v02") { return { lane: "v02", profile: "v02", gitopsTarget: "v02", pipelineName: "hwlab-v02-ci-image-publish", pipelineRunPrefix: "hwlab-v02-ci-poll", serviceAccountName: "hwlab-v02-tekton-runner", pollerName: "hwlab-v02-branch-poller", controlPlaneReconcilerName: "hwlab-v02-control-plane-reconciler", tektonDir: "tekton-v02", catalogPath: "deploy/artifact-catalog.v02.json", imageTagMode: "full", runtimeNamespace: "hwlab-v02" }; } return { lane: "g14", profile: "dev", gitopsTarget: "g14", pipelineName: "hwlab-g14-ci-image-publish", pipelineRunPrefix: "hwlab-g14-ci-poll", serviceAccountName: "hwlab-tekton-runner", pollerName: "hwlab-g14-branch-poller", controlPlaneReconcilerName: "hwlab-g14-control-plane-reconciler", tektonDir: "tekton", catalogPath: "deploy/artifact-catalog.dev.json", imageTagMode: "short", runtimeNamespace: "hwlab-dev" }; } function tektonRbac(args = { lane: "g14" }) { const settings = ciLaneSettings(args); if (settings.lane === "v02") { return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } }, { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: settings.serviceAccountName, namespace: "hwlab-ci" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-v02-runtime-observer", namespace: "hwlab-v02" }, rules: [ { apiGroups: [""], resources: ["pods", "services", "configmaps"], verbs: ["get", "list", "watch"] }, { apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"] }, { apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-v02-runtime-observer", namespace: "hwlab-v02" }, subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-v02-runtime-observer" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-v02-pipelinerun-writer", namespace: "hwlab-ci" }, rules: [ { apiGroups: ["tekton.dev"], resources: ["pipelineruns"], verbs: ["get", "list", "create"] }, { apiGroups: ["tekton.dev"], resources: ["pipelines"], verbs: ["get", "patch", "create"] }, { apiGroups: ["batch"], resources: ["cronjobs"], verbs: ["get", "patch", "create"] }, { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] }, { apiGroups: [""], resources: ["serviceaccounts"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-v02-pipelinerun-writer", namespace: "hwlab-ci" }, subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-v02-pipelinerun-writer" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-v02-argocd-reconciler", namespace: "argocd" }, rules: [ { apiGroups: ["argoproj.io"], resources: ["applications", "appprojects"], verbs: ["get", "patch", "create"] }, { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-v02-argocd-reconciler", namespace: "argocd" }, subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-v02-argocd-reconciler" } } ] }; } return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } }, { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: "hwlab-tekton-runner", namespace: "hwlab-ci" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" }, rules: [ { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" }, subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-g14-control-plane-reconciler" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-tekton-runtime-observer", namespace: "hwlab-dev" }, rules: [ { apiGroups: [""], resources: ["pods", "services", "configmaps"], verbs: ["get", "list", "watch"] }, { apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"] }, { apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-tekton-runtime-observer", namespace: "hwlab-dev" }, subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-tekton-runtime-observer" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-tekton-pipelinerun-writer", namespace: "hwlab-ci" }, rules: [ { apiGroups: ["tekton.dev"], resources: ["pipelineruns"], verbs: ["get", "list", "create"] }, { apiGroups: ["tekton.dev"], resources: ["pipelines"], verbs: ["get", "patch", "create"] }, { apiGroups: ["batch"], resources: ["cronjobs"], verbs: ["get", "patch", "create"] }, { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] }, { apiGroups: [""], resources: ["serviceaccounts"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-tekton-pipelinerun-writer", namespace: "hwlab-ci" }, subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-tekton-pipelinerun-writer" } } ] }; } function buildTaskName(serviceId) { return `build-${serviceId}`; } function affectedResultName(serviceId) { return `affected-${serviceId}`; } const serviceResultFields = Object.freeze([ "status", "service-id", "image", "image-tag", "digest", "repository-digest", "source-commit-id", "component-input-hash", "environment-input-hash", "code-input-hash", "runtime-mode", "boot-repo", "boot-commit", "boot-sh", "build-created-at", "build-backend", "reused-from" ]); function serviceResultParamName(serviceId, field) { return `${serviceId}-${field}`; } function serviceResultEnvName(serviceId, field) { return `HWLAB_SERVICE_RESULT_${serviceId.toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}_${field.toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}`; } function serviceResultParams(serviceIds = defaultServiceIds) { return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({ name: serviceResultParamName(serviceId, field), default: "" }))); } function serviceResultParamValues(serviceIds = defaultServiceIds) { return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({ name: serviceResultParamName(serviceId, field), value: `$(tasks.${buildTaskName(serviceId)}.results.${field})` }))); } function serviceResultEnv(serviceIds = defaultServiceIds) { return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({ name: serviceResultEnvName(serviceId, field), value: `$(params.${serviceResultParamName(serviceId, field)})` }))); } function serviceWorkVolume() { return { name: "service-work", emptyDir: {} }; } function buildkitBinVolume() { return { name: "buildkit-bin", emptyDir: {} }; } function buildkitRunVolume() { return { name: "buildkit-run", emptyDir: {} }; } function prepareBuildkitClientStep() { return { name: "prepare-buildkit-client", image: buildkitRunnerImage, securityContext: { runAsUser: 0, runAsGroup: 0 }, volumeMounts: [{ name: "buildkit-bin", mountPath: "/workspace/buildkit-bin" }], script: `#!/bin/sh set -eu mkdir -p /workspace/buildkit-bin cp /usr/bin/buildctl /workspace/buildkit-bin/ chmod +x /workspace/buildkit-bin/* ` }; } function buildkitSidecar() { return { name: "buildkitd", image: buildkitRunnerImage, args: ["--addr", "unix:///workspace/buildkit-run/buildkitd.sock", "--oci-worker-no-process-sandbox"], env: proxyEnv(), volumeMounts: [{ name: "buildkit-run", mountPath: "/workspace/buildkit-run" }], securityContext: { runAsUser: 1000, runAsGroup: 1000, allowPrivilegeEscalation: true, appArmorProfile: { type: "Unconfined" }, seccompProfile: { type: "Unconfined" } } }; } function planArtifactsTask({ serviceIds = defaultServiceIds } = {}) { return { name: "plan-artifacts", runAfter: primitiveValidationTaskNames(), workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [ { name: "revision" }, { name: "catalog-path" }, { name: "registry-prefix" }, { name: "services" } ], results: serviceIds.map((serviceId) => ({ name: affectedResultName(serviceId), description: `${serviceId} changed according to g14-ci-plan` })), workspaces: [{ name: "source" }], steps: [{ name: "plan", image: ciToolsRunnerImage, env: [{ name: "HWLAB_SELECTED_SERVICES", value: "$(params.services)" }, ...proxyEnv()], script: planArtifactsScript() }] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "services", value: "$(params.services)" } ] }; } function perServiceBuildTask(serviceId, { runAfter = ["plan-artifacts"] } = {}) { return { name: buildTaskName(serviceId), runAfter, workspaces: [{ name: "source", workspace: "source" }], when: [{ input: `$(tasks.plan-artifacts.results.${affectedResultName(serviceId)})`, operator: "in", values: ["true"] }], taskSpec: { params: [ { name: "revision" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "registry-prefix" }, { name: "service-id" }, { name: "base-image" } ], results: serviceResultFields.map((field) => ({ name: field, description: `${serviceId} ${field}` })), workspaces: [{ name: "source" }], volumes: [serviceWorkVolume(), buildkitBinVolume(), buildkitRunVolume()], sidecars: [buildkitSidecar()], steps: [ prepareBuildkitClientStep(), { name: "publish", image: ciToolsRunnerImage, securityContext: { runAsUser: 1000, runAsGroup: 1000 }, env: proxyEnv(), volumeMounts: [{ name: "service-work", mountPath: "/workspace/service-work" }, { name: "buildkit-bin", mountPath: "/workspace/buildkit-bin" }, { name: "buildkit-run", mountPath: "/workspace/buildkit-run" }], script: perServicePublishScript() } ] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "service-id", value: serviceId }, { name: "base-image", value: "$(params.base-image)" } ] }; } function collectArtifactsTask({ serviceIds = defaultServiceIds } = {}) { return { name: "collect-artifacts", runAfter: serviceIds.map(buildTaskName), workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [ { name: "revision" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "registry-prefix" }, { name: "services" }, { name: "base-image" } ], workspaces: [{ name: "source" }], steps: [{ name: "collect", image: ciToolsRunnerImage, env: proxyEnv(), script: collectArtifactsScript() }] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "services", value: "$(params.services)" }, { name: "base-image", value: "$(params.base-image)" } ] }; } function runtimeReadyScript() { return `#!/bin/sh set -eu ${ciTimingShellFunction()} export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)" export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)" export HWLAB_TEKTON_TASK="runtime-ready" export HWLAB_SOURCE_REVISION="$(params.revision)" node <<'NODE' const fs = require("node:fs"); const https = require("node:https"); const host = process.env.KUBERNETES_SERVICE_HOST; const port = process.env.KUBERNETES_SERVICE_PORT || "443"; function requiredEnv(name) { const value = process.env[name]; if (!value) throw new Error(name + " is required"); return value; } const revision = requiredEnv("HWLAB_SOURCE_REVISION"); const runtimeNamespace = requiredEnv("HWLAB_RUNTIME_NAMESPACE"); const gitopsTarget = requiredEnv("HWLAB_GITOPS_TARGET"); const argoApplication = requiredEnv("HWLAB_ARGO_APPLICATION"); const pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null; const taskRun = process.env.HWLAB_TEKTON_TASKRUN || null; const task = process.env.HWLAB_TEKTON_TASK || null; const timeoutMs = 240000; const pollMs = 1000; const progressEveryMs = 10000; function emit(payload) { console.log(JSON.stringify({ event: "g14-cicd-timing", schemaVersion: "v1", pipelineRun, taskRun, task, revision, source: "scripts/g14-gitops-render.mjs", at: new Date().toISOString(), ...payload })); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function safeJson(text) { try { return JSON.parse(text); } catch { return null; } } if (!host) { emit({ stage: "argo-refresh", status: "failed", reason: "kubernetes-service-host-missing", durationMs: 0 }); process.exit(1); } const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8"); const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"); function request(method, path, body, contentType = "application/json") { const payload = body ? JSON.stringify(body) : ""; const headers = { Authorization: "Bearer " + token }; if (payload) { headers["Content-Type"] = contentType; headers["Content-Length"] = Buffer.byteLength(payload); } return new Promise((resolve, reject) => { const req = https.request({ host, port, method, path, ca, headers }, (res) => { let data = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { data += chunk; }); res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) })); }); req.on("error", reject); if (payload) req.write(payload); req.end(); }); } function listPath(group, version, namespace, resource) { if (group) return "/apis/" + group + "/" + version + "/namespaces/" + encodeURIComponent(namespace) + "/" + resource; return "/api/" + version + "/namespaces/" + encodeURIComponent(namespace) + "/" + resource; } async function runtimeItems() { const [deployments, statefulsets] = await Promise.all([ request("GET", listPath("apps", "v1", runtimeNamespace, "deployments")), request("GET", listPath("apps", "v1", runtimeNamespace, "statefulsets")) ]); if (deployments.statusCode === 403 || statefulsets.statusCode === 403) return { status: "rbac-denied", items: [] }; const items = [...(deployments.json?.items || []), ...(statefulsets.json?.items || [])] .filter((item) => item.metadata?.labels?.["hwlab.pikastech.local/gitops-target"] === gitopsTarget); return { status: "ok", items }; } function runtimeSourceCommit(item) { return item.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/source-commit"] || item.spec?.template?.metadata?.annotations?.["hwlab.pikastech.local/source-commit"] || item.metadata?.labels?.["hwlab.pikastech.local/source-commit"] || item.metadata?.annotations?.["hwlab.pikastech.local/source-commit"] || null; } function serviceIdForRuntimeItem(item) { return item.metadata?.labels?.["hwlab.pikastech.local/service-id"] || item.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/service-id"] || item.metadata?.labels?.["app.kubernetes.io/name"] || item.metadata?.name || null; } function readObservedServiceIds() { try { const plan = JSON.parse(fs.readFileSync("/workspace/source/affected-services.json", "utf8")); const rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices.filter(Boolean) : []; if (rolloutServices.length === 0) return null; const ids = new Set(rolloutServices); if (ids.has("hwlab-cloud-api")) ids.add("hwlab-deepseek-proxy"); return ids; } catch { return null; } } const observedServiceIds = readObservedServiceIds(); const readinessMode = observedServiceIds ? "service-rollout" : "infra-runtime-change"; function observedRuntimeItems(items) { if (!observedServiceIds) return items; return items.filter((item) => observedServiceIds.has(serviceIdForRuntimeItem(item))); } function readyWorkload(item) { const specReplicas = item.spec?.replicas ?? 1; if (specReplicas === 0) return { ready: true, skipped: true }; const status = item.status || {}; const desired = specReplicas; const ready = status.readyReplicas ?? 0; const updated = status.updatedReplicas ?? 0; const observedGeneration = status.observedGeneration ?? 0; const generation = item.metadata?.generation ?? 0; return { ready: observedGeneration >= generation && ready >= desired && updated >= desired, desired, readyReplicas: ready, updatedReplicas: updated }; } function maybeEmitProgress(state, stage, startedAt, payload) { const now = Date.now(); if (now - state.lastProgressAt < progressEveryMs) return; state.lastProgressAt = now; emit({ stage, status: "progress", durationMs: now - startedAt, readinessMode, ...payload }); } async function argoApplicationState() { const path = "/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/" + encodeURIComponent(argoApplication); const response = await request("GET", path); if (response.statusCode === 403) return { status: "rbac-denied", item: null, statusCode: response.statusCode }; if (response.statusCode < 200 || response.statusCode > 299) return { status: "error", item: null, statusCode: response.statusCode, body: response.body.slice(0, 500) }; return { status: "ok", item: response.json, statusCode: response.statusCode }; } async function waitForArgoSyncedHealthy() { const startedAt = Date.now(); const progressState = { lastProgressAt: 0 }; for (;;) { const application = await argoApplicationState(); if (application.status === "rbac-denied") { emit({ stage: "argo-sync-health", status: "failed", reason: "argocd-application-rbac-denied", durationMs: Date.now() - startedAt, readinessMode }); process.exitCode = 1; return { observed: false }; } if (application.status !== "ok") { emit({ stage: "argo-sync-health", status: "failed", reason: "argocd-application-read-failed", statusCode: application.statusCode, body: application.body || null, durationMs: Date.now() - startedAt, readinessMode }); process.exitCode = 1; return { observed: false }; } const syncStatus = application.item?.status?.sync?.status || null; const healthStatus = application.item?.status?.health?.status || null; const operationPhase = application.item?.status?.operationState?.phase || null; const operationMessage = application.item?.status?.operationState?.message || null; if (syncStatus === "Synced" && healthStatus === "Healthy") { emit({ stage: "argo-sync-health", status: "succeeded", durationMs: Date.now() - startedAt, readinessMode, syncStatus, healthStatus, operationPhase }); return { observed: true }; } if (Date.now() - startedAt >= timeoutMs) { emit({ stage: "argo-sync-health", status: "timeout", durationMs: Date.now() - startedAt, readinessMode, syncStatus, healthStatus, operationPhase, operationMessage }); process.exitCode = 1; return { observed: false }; } maybeEmitProgress(progressState, "argo-sync-health", startedAt, { syncStatus, healthStatus, operationPhase, operationMessage }); await sleep(pollMs); } } async function waitForArgoRefresh() { const startedAt = Date.now(); const progressState = { lastProgressAt: 0 }; for (;;) { const runtime = await runtimeItems(); if (runtime.status === "rbac-denied") { emit({ stage: "argo-refresh", status: "failed", reason: "runtime-observer-rbac-denied", durationMs: Date.now() - startedAt }); process.exitCode = 1; return { observed: false, skipped: true }; } const observed = observedRuntimeItems(runtime.items); const pending = observed .map((item) => ({ kind: item.kind, name: item.metadata?.name, sourceCommit: runtimeSourceCommit(item) })) .filter((item) => item.sourceCommit !== revision); if (observed.length > 0 && pending.length === 0) { emit({ stage: "argo-refresh", status: "succeeded", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length, observedCount: observed.length }); return { observed: true, skipped: false }; } if (Date.now() - startedAt >= timeoutMs) { emit({ stage: "argo-refresh", status: "timeout", reason: "source-commit-refresh-timeout", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length, observedCount: observed.length, pending: pending.slice(0, 8) }); process.exitCode = 1; return { observed: false, skipped: false }; } maybeEmitProgress(progressState, "argo-refresh", startedAt, { workloadCount: runtime.items.length, observedCount: observed.length, pending: pending.slice(0, 8) }); await sleep(pollMs); } } async function waitForWorkloads() { const startedAt = Date.now(); const progressState = { lastProgressAt: 0 }; for (;;) { const runtime = await runtimeItems(); if (runtime.status === "rbac-denied") { emit({ stage: "workload-ready", status: "failed", reason: "runtime-observer-rbac-denied", durationMs: Date.now() - startedAt }); process.exitCode = 1; return; } const observed = observedRuntimeItems(runtime.items); const blocked = observed.map((item) => ({ kind: item.kind, name: item.metadata?.name, ...readyWorkload(item) })).filter((item) => !item.ready); if (observed.length > 0 && blocked.length === 0) { emit({ stage: "workload-ready", status: "succeeded", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length, observedCount: observed.length }); return; } if (Date.now() - startedAt >= timeoutMs) { emit({ stage: "workload-ready", status: "timeout", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length, observedCount: observed.length, blocked: blocked.slice(0, 8) }); process.exitCode = 1; return; } maybeEmitProgress(progressState, "workload-ready", startedAt, { workloadCount: runtime.items.length, observedCount: observed.length, blocked: blocked.slice(0, 8) }); await sleep(pollMs); } } (async () => { emit({ stage: "runtime-ready", status: "started", readinessMode, observedServiceCount: observedServiceIds ? observedServiceIds.size : 0 }); if (!observedServiceIds) { const argo = await waitForArgoSyncedHealthy(); if (!argo.observed) return; await waitForWorkloads(); return; } const refresh = await waitForArgoRefresh(); if (!refresh.observed) return; await waitForWorkloads(); })().catch((error) => { emit({ stage: "runtime-ready", status: "failed", reason: error.message, durationMs: 0 }); process.exitCode = 1; }); NODE `; } function runtimeReadyTask({ profile = "dev" } = {}) { return { name: "runtime-ready", runAfter: ["gitops-promote"], workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [{ name: "revision" }, { name: "runtime-namespace" }, { name: "gitops-target" }, { name: "argo-application" }], workspaces: [{ name: "source" }], steps: [{ name: "runtime-ready", image: ciToolsRunnerImage, env: [ ...proxyEnv(), { name: "HWLAB_RUNTIME_NAMESPACE", value: "$(params.runtime-namespace)" }, { name: "HWLAB_GITOPS_TARGET", value: "$(params.gitops-target)" }, { name: "HWLAB_ARGO_APPLICATION", value: "$(params.argo-application)" } ], script: runtimeReadyScript() }] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "runtime-namespace", value: namespaceNameForProfile(profile) }, { name: "gitops-target", value: gitopsTargetForProfile(profile) }, { name: "argo-application", value: argoApplicationName(profile) } ] }; } function imagePublishTaskSet(args = { lane: "g14" }, deploy = null) { const serviceIds = serviceIdsForLane(args.lane, deploy); const buildTasks = serviceIds.map((serviceId) => perServiceBuildTask(serviceId, { runAfter: ["plan-artifacts"] })); return [ planArtifactsTask({ serviceIds }), ...buildTasks, collectArtifactsTask({ serviceIds }) ]; } function tektonPipeline(args = { lane: "g14" }, deploy = null) { const settings = ciLaneSettings(args); const runtimePath = `deploy/gitops/g14/${runtimePathForProfile(settings.profile)}`; return { apiVersion: "tekton.dev/v1", kind: "Pipeline", metadata: { name: settings.pipelineName, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget }, annotations: { "hwlab.pikastech.local/source-config": "scripts/g14-gitops-render.mjs#tekton-native-primitive-ci", "hwlab.pikastech.local/ci-contract": "tekton-native-primitive-tasks", "hwlab.pikastech.local/policy": "native-per-service-taskrun-image-publish" } }, spec: { params: [ { name: "git-url", type: "string", default: defaultSourceRepo }, { name: "git-read-url", type: "string", default: args.gitReadUrl }, { name: "git-write-url", type: "string", default: args.gitWriteUrl }, { name: "source-branch", type: "string", default: args.sourceBranch }, { name: "gitops-branch", type: "string", default: args.gitopsBranch }, { name: "lane", type: "string", default: settings.lane }, { name: "catalog-path", type: "string", default: args.catalogPath || settings.catalogPath }, { name: "image-tag-mode", type: "string", default: args.imageTagMode || settings.imageTagMode }, { name: "runtime-path", type: "string", default: runtimePath }, { name: "revision", type: "string", description: "Full git commit SHA; the only source truth for image tags." }, { name: "registry-prefix", type: "string", default: defaultRegistryPrefix }, { name: "services", type: "string", default: servicesParamForLane(args.lane, deploy) }, { name: "base-image", type: "string", default: defaultDevBaseImage } ], workspaces: [ { name: "source" }, { name: "git-ssh" } ], tasks: [ { name: "prepare-source", workspaces: [ { name: "source", workspace: "source" }, { name: "git-ssh", workspace: "git-ssh" } ], taskSpec: { params: [ { name: "git-url" }, { name: "git-read-url" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "registry-prefix" }, { name: "services" }, { name: "revision" } ], workspaces: [{ name: "source" }, { name: "git-ssh" }], steps: [{ name: "prepare-source", image: ciToolsRunnerImage, env: proxyEnv(), script: prepareSourceScript() }] }, params: [ { name: "git-url", value: "$(params.git-url)" }, { name: "git-read-url", value: "$(params.git-read-url)" }, { name: "source-branch", value: "$(params.source-branch)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" }, { name: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "services", value: "$(params.services)" }, { name: "revision", value: "$(params.revision)" } ] }, ...primitiveValidationTasks.map(primitiveValidationTask), ...imagePublishTaskSet(args, deploy), { name: "gitops-promote", runAfter: ["collect-artifacts"], workspaces: [ { name: "source", workspace: "source" }, { name: "git-ssh", workspace: "git-ssh" } ], taskSpec: { params: [ { name: "git-url" }, { name: "git-read-url" }, { name: "git-write-url" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "runtime-path" }, { name: "revision" }, { name: "registry-prefix" } ], results: [{ name: "runtime-ready-required", description: "true when GitOps promotion changed runtime desired state and runtime readiness must be observed" }], workspaces: [{ name: "source" }, { name: "git-ssh" }], steps: [{ name: "promote", image: ciToolsRunnerImage, env: proxyEnv(), script: gitopsPromoteScript() }] }, params: [ { name: "git-url", value: "$(params.git-url)" }, { 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: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "runtime-path", value: "$(params.runtime-path)" }, { name: "revision", value: "$(params.revision)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" } ] }, { ...runtimeReadyTask({ profile: settings.profile }), when: [{ input: "$(tasks.gitops-promote.results.runtime-ready-required)", operator: "in", values: ["true"] }] } ] } }; } function tektonPipelineRunTemplate({ source, args }) { const settings = ciLaneSettings(args); const runtimePath = `deploy/gitops/g14/${runtimePathForProfile(settings.profile)}`; return { apiVersion: "tekton.dev/v1", kind: "PipelineRun", metadata: { generateName: `${settings.pipelineRunPrefix}-manual-`, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget, "hwlab.pikastech.local/source-commit": source.full } }, spec: { pipelineRef: { name: settings.pipelineName }, taskRunTemplate: { serviceAccountName: settings.serviceAccountName, podTemplate: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", securityContext: { fsGroup: 1000 } } }, params: [ { name: "git-url", value: args.sourceRepo }, { name: "git-read-url", value: args.gitReadUrl }, { name: "git-write-url", value: args.gitWriteUrl }, { name: "source-branch", value: args.sourceBranch }, { name: "gitops-branch", value: args.gitopsBranch }, { name: "lane", value: settings.lane }, { name: "catalog-path", value: args.catalogPath || settings.catalogPath }, { name: "image-tag-mode", value: args.imageTagMode || settings.imageTagMode }, { name: "runtime-path", value: runtimePath }, { name: "revision", value: source.full }, { name: "registry-prefix", value: args.registryPrefix }, { name: "base-image", value: defaultDevBaseImage } ], workspaces: [ { name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } }, { name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } } ] } }; } function tektonPollerCronJob(args, deploy) { const settings = ciLaneSettings(args); if (settings.lane === "v02") throw new Error("v02 CI/CD is manually triggered by UniDesk CLI and must not render a branch poller CronJob"); return { apiVersion: "batch/v1", kind: "CronJob", metadata: { name: settings.pollerName, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget }, annotations: { "hwlab.pikastech.local/source-branch": args.sourceBranch, "hwlab.pikastech.local/gitops-branch": args.gitopsBranch } }, spec: { schedule: settings.lane === "v02" ? "*/10 * * * *" : "* * * * *", concurrencyPolicy: "Forbid", successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 3, jobTemplate: { spec: { backoffLimit: 1, template: { metadata: { labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget } }, spec: { serviceAccountName: settings.serviceAccountName, restartPolicy: "Never", hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "poll", image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", env: [ ...proxyEnv(), { name: "POD_NAMESPACE", valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } }, { name: "PIPELINE_NAME", value: settings.pipelineName }, { name: "PIPELINERUN_PREFIX", value: settings.pipelineRunPrefix }, { name: "SERVICE_ACCOUNT_NAME", value: settings.serviceAccountName }, { name: "GITOPS_TARGET", value: settings.gitopsTarget }, { name: "LANE", value: settings.lane }, { name: "CATALOG_PATH", value: args.catalogPath || settings.catalogPath }, { name: "IMAGE_TAG_MODE", value: args.imageTagMode || settings.imageTagMode }, { name: "RUNTIME_PATH", value: `deploy/gitops/g14/${runtimePathForProfile(settings.profile)}` }, { name: "GIT_URL", value: args.sourceRepo }, { name: "GIT_READ_URL", value: args.gitReadUrl }, { name: "SOURCE_BRANCH", value: args.sourceBranch }, { name: "GITOPS_BRANCH", value: args.gitopsBranch }, { name: "REGISTRY_PREFIX", value: args.registryPrefix }, { name: "SERVICES", value: servicesParamForLane(args.lane, deploy) }, { name: "BASE_IMAGE", value: defaultDevBaseImage } ], volumeMounts: [{ name: "git-ssh", mountPath: "/workspace/git-ssh", readOnly: true }], command: ["/bin/sh", "-c"], args: [pollerScript()] }], volumes: [{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }] } } } } } }; } function tektonControlPlaneReconcilerCronJob(args) { const settings = ciLaneSettings(args); if (settings.lane === "v02") throw new Error("v02 CI/CD is manually triggered by UniDesk CLI and must not render a control-plane reconciler CronJob"); return { apiVersion: "batch/v1", kind: "CronJob", metadata: { name: settings.controlPlaneReconcilerName, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget }, annotations: { "hwlab.pikastech.local/source-branch": args.sourceBranch, "hwlab.pikastech.local/gitops-branch": args.gitopsBranch, "hwlab.pikastech.local/purpose": "render-and-apply-g14-ci-control-plane" } }, spec: { schedule: "*/10 * * * *", concurrencyPolicy: "Forbid", successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 3, jobTemplate: { spec: { backoffLimit: 1, template: { metadata: { labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget } }, spec: { serviceAccountName: settings.serviceAccountName, restartPolicy: "Never", hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "reconcile", image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", env: [ ...proxyEnv(), { name: "POD_NAMESPACE", valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } }, { name: "LANE", value: settings.lane }, { name: "CATALOG_PATH", value: args.catalogPath || settings.catalogPath }, { name: "IMAGE_TAG_MODE", value: args.imageTagMode || settings.imageTagMode }, { name: "TEKTON_DIR", value: settings.tektonDir }, { name: "ARGO_FILES", value: settings.lane === "v02" ? "deploy/gitops/g14/argocd/project.yaml,deploy/gitops/g14/argocd/application-v02.yaml" : "" }, { name: "FIELD_MANAGER", value: settings.controlPlaneReconcilerName }, { name: "RECONCILE_EVENT", value: `${settings.gitopsTarget}-control-plane-reconciled` }, { name: "GIT_URL", value: args.sourceRepo }, { name: "GIT_READ_URL", value: args.gitReadUrl }, { name: "SOURCE_BRANCH", value: args.sourceBranch }, { name: "GITOPS_BRANCH", value: args.gitopsBranch }, { name: "REGISTRY_PREFIX", value: args.registryPrefix } ], volumeMounts: [{ name: "git-ssh", mountPath: "/workspace/git-ssh", readOnly: true }], command: ["/bin/sh", "-c"], args: [controlPlaneReconcileScript()] }], volumes: [{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }] } } } } } }; } function registryManifest() { return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } }, spec: { replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-registry" } }, template: { metadata: { labels: { "app.kubernetes.io/name": "hwlab-registry" } }, spec: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "registry", image: "registry:2.8.3", imagePullPolicy: "IfNotPresent", ports: [{ name: "registry", containerPort: 5000, hostPort: 5000 }], env: [{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" }], volumeMounts: [{ name: "storage", mountPath: "/var/lib/registry" }], readinessProbe: { httpGet: { path: "/v2/", port: "registry" } }, livenessProbe: { httpGet: { path: "/v2/", port: "registry" } } }], volumes: [{ name: "storage", hostPath: { path: "/var/lib/hwlab/registry", type: "DirectoryOrCreate" } }] } } } }, { apiVersion: "v1", kind: "Service", metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } }, spec: { selector: { "app.kubernetes.io/name": "hwlab-registry" }, ports: [{ name: "registry", port: 5000, targetPort: "registry" }] } } ] }; } function devopsInfraGitMirrorManifest() { const labels = { "app.kubernetes.io/name": "git-mirror", "app.kubernetes.io/part-of": "devops-infra" }; return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "Namespace", metadata: { name: "devops-infra" } }, { apiVersion: "v1", kind: "PersistentVolumeClaim", metadata: { name: "git-mirror-cache", namespace: "devops-infra", labels }, spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "20Gi" } } } }, { apiVersion: "v1", kind: "ConfigMap", metadata: { name: "git-mirror-sync-script", namespace: "devops-infra", labels }, data: { "sync.sh": `#!/bin/sh set -eu repo_url="ssh://git@ssh.github.com:443/pikasTech/HWLAB.git" repo_path="/cache/pikasTech/HWLAB.git" lock_dir="/cache/.hwlab-sync.lock" sync_started_ms=$(node -e 'console.log(Date.now())') now_ms() { node -e 'console.log(Date.now())'; } emit_timing() { phase="$1" status="$2" started_ms="$3" finished_ms=$(now_ms) duration_ms=$((finished_ms - started_ms)) printf '{"event":"git-mirror-sync-timing","phase":"%s","status":"%s","durationMs":%s,"repository":"pikasTech/HWLAB"}\n' "$phase" "$status" "$duration_ms" } mkdir -p /cache/pikasTech /root/.ssh if ! mkdir "$lock_dir" 2>/dev/null; then echo "git mirror sync already running" exit 0 fi trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT INT TERM cp /git-ssh/ssh-privatekey /root/.ssh/id_rsa chmod 0400 /root/.ssh/id_rsa cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY' #!/usr/bin/env node import net from "node:net"; const [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2); const proxyPort = Number.parseInt(proxyPortRaw || "", 10); const targetPort = Number.parseInt(targetPortRaw || "", 10); if (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) process.exit(64); const socket = net.createConnection({ host: proxyHost, port: proxyPort }); let buffer = Buffer.alloc(0); socket.setTimeout(10000, () => { socket.destroy(); process.exit(65); }); socket.on("connect", () => socket.write("CONNECT " + targetHost + ":" + targetPort + " HTTP/1.1\\r\\nHost: " + targetHost + ":" + targetPort + "\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n")); socket.on("error", () => process.exit(66)); function onData(chunk) { buffer = Buffer.concat([buffer, chunk]); const headerEnd = buffer.indexOf("\\r\\n\\r\\n"); if (headerEnd === -1 && buffer.length < 8192) return; const head = buffer.slice(0, headerEnd + 4).toString("latin1"); const statusLine = head.split("\\r\\n", 1)[0] || ""; const statusCode = Number.parseInt(statusLine.split(" ")[1] || "", 10); if (!statusLine.startsWith("HTTP/1.") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) { socket.destroy(); process.exit(67); } socket.off("data", onData); socket.setTimeout(0); const rest = buffer.slice(headerEnd + 4); if (rest.length) process.stdout.write(rest); process.stdin.pipe(socket); socket.pipe(process.stdout); } socket.on("data", onData); socket.on("close", () => process.exit(0)); NODE_PROXY chmod 0700 /tmp/hwlab-github-proxy-connect.mjs export GIT_SSH_COMMAND="ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 -o ProxyCommand='node /tmp/hwlab-github-proxy-connect.mjs 127.0.0.1 10808 %h %p'" if [ -d "$repo_path/objects" ] && [ -f "$repo_path/HEAD" ]; then git -C "$repo_path" remote set-url origin "$repo_url" || git -C "$repo_path" remote add origin "$repo_url" else rm -rf "$repo_path" git init --bare "$repo_path" git -C "$repo_path" remote add origin "$repo_url" fi /script/install-hooks.sh git -C "$repo_path" config uploadpack.hideRefs refs/mirror-stage git -C "$repo_path" config uploadpack.allowReachableSHA1InWant true git -C "$repo_path" config uploadpack.allowAnySHA1InWant true git -C "$repo_path" config --unset-all remote.origin.fetch 2>/dev/null || true git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/v0.2:refs/mirror-stage/heads/v0.2' git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/v0.2-gitops:refs/mirror-stage/heads/v0.2-gitops' git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/G14:refs/mirror-stage/heads/G14' git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/G14-gitops:refs/mirror-stage/heads/G14-gitops' fetch_started_ms=$(now_ms) timeout 180 git -C "$repo_path" fetch --quiet --prune origin emit_timing fetch succeeded "$fetch_started_ms" git -C "$repo_path" for-each-ref --format='%(objectname) %(refname)' refs/mirror-stage/heads > /tmp/hwlab-stage-heads git -C "$repo_path" for-each-ref --format='%(refname)' refs/mirror-stage/heads > /tmp/hwlab-stage-head-refs git -C "$repo_path" for-each-ref --format='%(objectname) %(refname)' refs/mirror-stage/tags > /tmp/hwlab-stage-tags git -C "$repo_path" for-each-ref --format='%(refname)' refs/mirror-stage/tags > /tmp/hwlab-stage-tag-refs if [ ! -s /tmp/hwlab-stage-heads ]; then echo "git mirror sync fetched no branch refs" >&2 exit 41 fi for required_ref in refs/mirror-stage/heads/v0.2 refs/mirror-stage/heads/v0.2-gitops refs/mirror-stage/heads/G14 refs/mirror-stage/heads/G14-gitops; do git -C "$repo_path" show-ref --verify --quiet "$required_ref" || { echo "git mirror sync missing required ref $required_ref" >&2; exit 43; } done validate_started_ms=$(now_ms) while read -r sha ref; do [ -n "$sha" ] || continue git -C "$repo_path" cat-file -e "$sha^{commit}" git -C "$repo_path" cat-file -e "$sha^{tree}" if git -C "$repo_path" rev-list --objects --missing=print "$sha" | grep -q '^?'; then echo "git mirror sync found missing objects for $ref $sha" >&2 exit 42 fi done < /tmp/hwlab-stage-heads emit_timing validate succeeded "$validate_started_ms" publish_started_ms=$(now_ms) while read -r sha ref; do [ -n "$sha" ] || continue name=$(printf '%s\n' "$ref" | sed 's#^refs/mirror-stage/heads/##') if [ "$name" = "v0.2-gitops" ]; then local_sha=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true) if [ -n "$local_sha" ] && [ "$local_sha" != "$sha" ]; then if git -C "$repo_path" merge-base --is-ancestor "$sha" "$local_sha"; then echo "git mirror sync keeps local v0.2-gitops ahead of GitHub" continue fi if ! git -C "$repo_path" merge-base --is-ancestor "$local_sha" "$sha"; then echo "git mirror sync found divergent v0.2-gitops local=$local_sha github=$sha" >&2 exit 44 fi fi fi git -C "$repo_path" update-ref "refs/heads/$name" "$sha" done < /tmp/hwlab-stage-heads while read -r sha ref; do [ -n "$sha" ] || continue name=$(printf '%s\n' "$ref" | sed 's#^refs/mirror-stage/tags/##') git -C "$repo_path" update-ref "refs/tags/$name" "$sha" done < /tmp/hwlab-stage-tags git -C "$repo_path" for-each-ref --format='%(refname)' refs/heads > /tmp/hwlab-public-heads while read -r ref; do [ -n "$ref" ] || continue name=$(printf '%s\n' "$ref" | sed 's#^refs/heads/##') if ! grep -Fxq "refs/mirror-stage/heads/$name" /tmp/hwlab-stage-head-refs; then git -C "$repo_path" update-ref -d "$ref" fi done < /tmp/hwlab-public-heads git -C "$repo_path" for-each-ref --format='%(refname)' refs/tags > /tmp/hwlab-public-tags while read -r ref; do [ -n "$ref" ] || continue name=$(printf '%s\n' "$ref" | sed 's#^refs/tags/##') if ! grep -Fxq "refs/mirror-stage/tags/$name" /tmp/hwlab-stage-tag-refs; then git -C "$repo_path" update-ref -d "$ref" fi done < /tmp/hwlab-public-tags emit_timing publish succeeded "$publish_started_ms" if git -C "$repo_path" show-ref --verify --quiet refs/heads/v0.2; then git -C "$repo_path" symbolic-ref HEAD refs/heads/v0.2 elif git -C "$repo_path" show-ref --verify --quiet refs/heads/G14; then git -C "$repo_path" symbolic-ref HEAD refs/heads/G14 fi fsck_started_ms=$(now_ms) git -C "$repo_path" fsck --connectivity-only --no-dangling >/tmp/hwlab-git-fsck.out emit_timing fsck succeeded "$fsck_started_ms" git -C "$repo_path" update-server-info v02_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2 2>/dev/null || true) v02_github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/v0.2 2>/dev/null || true) g14_head=$(git -C "$repo_path" rev-parse refs/heads/G14 2>/dev/null || true) g14_github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/G14 2>/dev/null || true) v02_gitops_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true) v02_gitops_github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/v0.2-gitops 2>/dev/null || true) published_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) printf '%s\n' "$v02_head" | tee /cache/HWLAB-v0.2.head printf '%s\n' "$published_at" > /cache/HWLAB.last-sync export v02_head v02_github_head g14_head g14_github_head v02_gitops_head v02_gitops_github_head published_at node - <<'NODE' | tee /cache/HWLAB.last-sync.json const v02Head = process.env.v02_head || null; const v02GithubHead = process.env.v02_github_head || null; const gitopsHead = process.env.v02_gitops_head || null; const gitopsGithubHead = process.env.v02_gitops_github_head || null; const payload = { event: "git-mirror-sync", status: "published", repository: "pikasTech/HWLAB", publishedAt: process.env.published_at, pendingFlush: Boolean(gitopsHead && gitopsGithubHead && gitopsHead !== gitopsGithubHead), sourceInSync: Boolean(v02Head && v02GithubHead && v02Head === v02GithubHead), gitopsInSync: Boolean(gitopsHead && gitopsGithubHead && gitopsHead === gitopsGithubHead), refs: { "refs/heads/v0.2": v02Head, "refs/mirror-stage/heads/v0.2": v02GithubHead, "refs/heads/G14": process.env.g14_head || null, "refs/mirror-stage/heads/G14": process.env.g14_github_head || null, "refs/heads/v0.2-gitops": gitopsHead, "refs/mirror-stage/heads/v0.2-gitops": gitopsGithubHead } }; console.log(JSON.stringify(payload)); NODE emit_timing total succeeded "$sync_started_ms" `, "install-hooks.sh": `#!/bin/sh set -eu repo_path="/cache/pikasTech/HWLAB.git" mkdir -p "$repo_path/hooks" cat > "$repo_path/hooks/pre-receive" <<'HOOK' #!/bin/sh set -eu zero="0000000000000000000000000000000000000000" status=0 while read -r old new ref; do if [ "$ref" != "refs/heads/v0.2-gitops" ]; then echo "git mirror write rejected: ref $ref is not allowlisted" >&2 status=1 continue fi if [ "$new" = "$zero" ]; then echo "git mirror write rejected: deleting $ref is forbidden" >&2 status=1 continue fi git cat-file -e "$new^{commit}" || status=1 git cat-file -e "$new^{tree}" || status=1 if git rev-list --objects --missing=print "$new" | grep -q '^?'; then echo "git mirror write rejected: missing objects for $new" >&2 status=1 continue fi if [ "$old" != "$zero" ] && ! git merge-base --is-ancestor "$old" "$new"; then echo "git mirror write rejected: non-fast-forward $ref" >&2 status=1 continue fi if [ "$old" = "$zero" ]; then git diff-tree --no-commit-id --name-only -r "$new" else git diff --name-only "$old" "$new" fi | while read -r path; do case "$path" in deploy/artifact-catalog.v02.json|deploy/gitops/g14/runtime-v02/*) ;; "") ;; *) echo "git mirror write rejected: path $path is outside v0.2 GitOps outputs" >&2; exit 64 ;; esac done || status=1 done exit "$status" HOOK cat > "$repo_path/hooks/post-receive" <<'HOOK' #!/bin/sh set -eu repo_path=$(git rev-parse --git-dir) git -C "$repo_path" update-server-info local_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true) github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/v0.2-gitops 2>/dev/null || true) written_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) export local_head github_head written_at node - <<'NODE' > /cache/HWLAB.last-write.json const localHead = process.env.local_head || null; const githubHead = process.env.github_head || null; console.log(JSON.stringify({ event: "git-mirror-write", status: "received", repository: "pikasTech/HWLAB", writtenAt: process.env.written_at, pendingFlush: Boolean(localHead && localHead !== githubHead), refs: { "refs/heads/v0.2-gitops": localHead, "refs/mirror-stage/heads/v0.2-gitops": githubHead } })); NODE HOOK chmod 0755 "$repo_path/hooks/pre-receive" "$repo_path/hooks/post-receive" git -C "$repo_path" config http.receivepack true git -C "$repo_path" config receive.denyDeletes true git -C "$repo_path" config receive.denyNonFastForwards true `, "flush.sh": `#!/bin/sh set -eu repo_url="ssh://git@ssh.github.com:443/pikasTech/HWLAB.git" repo_path="/cache/pikasTech/HWLAB.git" lock_dir="/cache/.hwlab-flush.lock" started_ms=$(node -e 'console.log(Date.now())') now_ms() { node -e 'console.log(Date.now())'; } emit_timing() { phase="$1" status="$2" started="$3" finished=$(now_ms) duration=$((finished - started)) printf '{"event":"git-mirror-flush-timing","phase":"%s","status":"%s","durationMs":%s,"repository":"pikasTech/HWLAB"}\n' "$phase" "$status" "$duration" } if ! mkdir "$lock_dir" 2>/dev/null; then echo "git mirror flush already running" exit 0 fi trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT INT TERM mkdir -p /root/.ssh cp /git-ssh/ssh-privatekey /root/.ssh/id_rsa chmod 0400 /root/.ssh/id_rsa cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY' #!/usr/bin/env node import net from "node:net"; const [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2); const proxyPort = Number.parseInt(proxyPortRaw || "", 10); const targetPort = Number.parseInt(targetPortRaw || "", 10); if (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) process.exit(64); const socket = net.createConnection({ host: proxyHost, port: proxyPort }); let buffer = Buffer.alloc(0); socket.setTimeout(10000, () => { socket.destroy(); process.exit(65); }); socket.on("connect", () => socket.write("CONNECT " + targetHost + ":" + targetPort + " HTTP/1.1\\r\\nHost: " + targetHost + ":" + targetPort + "\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n")); socket.on("error", () => process.exit(66)); function onData(chunk) { buffer = Buffer.concat([buffer, chunk]); const headerEnd = buffer.indexOf("\\r\\n\\r\\n"); if (headerEnd === -1 && buffer.length < 8192) return; const head = buffer.slice(0, headerEnd + 4).toString("latin1"); const statusLine = head.split("\\r\\n", 1)[0] || ""; const statusCode = Number.parseInt(statusLine.split(" ")[1] || "", 10); if (!statusLine.startsWith("HTTP/1.") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) { socket.destroy(); process.exit(67); } socket.off("data", onData); socket.setTimeout(0); const rest = buffer.slice(headerEnd + 4); if (rest.length) process.stdout.write(rest); process.stdin.pipe(socket); socket.pipe(process.stdout); } socket.on("data", onData); socket.on("close", () => process.exit(0)); NODE_PROXY chmod 0700 /tmp/hwlab-github-proxy-connect.mjs export GIT_SSH_COMMAND="ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 -o ProxyCommand='node /tmp/hwlab-github-proxy-connect.mjs 127.0.0.1 10808 %h %p'" /script/install-hooks.sh local_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true) if [ -z "$local_head" ]; then echo "git mirror flush missing local refs/heads/v0.2-gitops" >&2 exit 51 fi fetch_started=$(now_ms) timeout 90 git -C "$repo_path" fetch --quiet "$repo_url" refs/heads/v0.2-gitops:refs/mirror-stage/heads/v0.2-gitops emit_timing fetch succeeded "$fetch_started" github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/v0.2-gitops 2>/dev/null || true) if [ -n "$github_head" ] && [ "$github_head" != "$local_head" ] && ! git -C "$repo_path" merge-base --is-ancestor "$github_head" "$local_head"; then echo "git mirror flush rejected divergent v0.2-gitops local=$local_head github=$github_head" >&2 exit 52 fi push_started=$(now_ms) timeout 120 git -C "$repo_path" push "$repo_url" "$local_head:refs/heads/v0.2-gitops" emit_timing push succeeded "$push_started" git -C "$repo_path" update-ref refs/mirror-stage/heads/v0.2-gitops "$local_head" git -C "$repo_path" update-server-info flushed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) export local_head flushed_at node - <<'NODE' | tee /cache/HWLAB.last-flush.json console.log(JSON.stringify({ event: "git-mirror-flush", status: "flushed", repository: "pikasTech/HWLAB", flushedAt: process.env.flushed_at, pendingFlush: false, refs: { "refs/heads/v0.2-gitops": process.env.local_head || null } })); NODE emit_timing total succeeded "$started_ms" `, "http.mjs": `import { createServer } from "node:http"; import { spawn } from "node:child_process"; import { createReadStream, statSync } from "node:fs"; import path from "node:path"; import { createGunzip, createInflate } from "node:zlib"; const cacheRoot = process.env.GIT_MIRROR_CACHE_ROOT || "/cache"; const port = Number(process.env.PORT || 8080); createServer((req, res) => { handle(req, res).catch((error) => { if (!res.headersSent) res.writeHead(500, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: false, error: error.message })); }); }).listen(port, "0.0.0.0", () => { console.log(JSON.stringify({ event: "git-mirror-smart-http-listening", port, cacheRoot })); }); async function handle(req, res) { const url = new URL(req.url || "/", "http://git-mirror-http"); const writeHost = isWriteHost(req.headers.host || ""); if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/health")) { res.writeHead(200, { "content-type": "application/json", "cache-control": "no-cache" }); res.end(JSON.stringify({ ok: true, service: "git-mirror-smart-http", writeHost })); return; } if (req.method === "GET" && url.pathname.endsWith("/info/refs") && url.searchParams.get("service") === "git-upload-pack") { await runUploadPackAdvertisement(repoPathFromGitServicePath(url.pathname, "/info/refs"), res); return; } if (req.method === "POST" && url.pathname.endsWith("/git-upload-pack")) { await runUploadPackRpc(repoPathFromGitServicePath(url.pathname, "/git-upload-pack"), req, res); return; } if (req.method === "GET" && url.pathname.endsWith("/info/refs") && url.searchParams.get("service") === "git-receive-pack") { if (!writeHost) return rejectReadOnly(res); await runReceivePackAdvertisement(repoPathFromGitServicePath(url.pathname, "/info/refs"), res); return; } if (req.method === "POST" && url.pathname.endsWith("/git-receive-pack")) { if (!writeHost) return rejectReadOnly(res); await runReceivePackRpc(repoPathFromGitServicePath(url.pathname, "/git-receive-pack"), req, res); return; } if (req.method === "GET" || req.method === "HEAD") { serveStaticFile(url.pathname, req, res); return; } res.writeHead(405, { "content-type": "text/plain" }); res.end("method not allowed\\n"); } function isWriteHost(hostHeader) { const host = String(hostHeader || "").split(":", 1)[0].toLowerCase(); return host === "git-mirror-write" || host.startsWith("git-mirror-write."); } function rejectReadOnly(res) { res.writeHead(403, { "content-type": "application/json", "cache-control": "no-cache" }); res.end(JSON.stringify({ ok: false, error: "git mirror receive-pack is only available through git-mirror-write" })); } function repoPathFromGitServicePath(urlPath, suffix) { const repoUrlPath = urlPath.slice(0, -suffix.length); if (!repoUrlPath.endsWith(".git")) throw new Error(` + "`" + `unsupported git repo path: \${urlPath}` + "`" + `); return safePath(repoUrlPath); } function safePath(urlPath) { const decoded = decodeURIComponent(urlPath.replace(/^[/]+/u, "")); const fullPath = path.resolve(cacheRoot, decoded); const normalizedRoot = path.resolve(cacheRoot); if (fullPath !== normalizedRoot && !fullPath.startsWith(` + "`" + `\${normalizedRoot}\${path.sep}` + "`" + `)) throw new Error("path escapes cache root"); return fullPath; } function smartHeaders(contentType) { return { "content-type": contentType, "cache-control": "no-cache, max-age=0, must-revalidate", expires: "Fri, 01 Jan 1980 00:00:00 GMT", pragma: "no-cache" }; } function pktLine(text) { const length = Buffer.byteLength(text) + 4; return ` + "`" + `\${length.toString(16).padStart(4, "0")}\${text}` + "`" + `; } function requestBodyStream(req) { const encoding = String(req.headers["content-encoding"] || "identity").toLowerCase(); if (encoding === "identity" || encoding === "") return req; if (encoding === "gzip" || encoding === "x-gzip") return req.pipe(createGunzip()); if (encoding === "deflate") return req.pipe(createInflate()); throw new Error(` + "`" + `unsupported git upload-pack content-encoding: \${encoding}` + "`" + `); } function runUploadPackAdvertisement(repoPath, res) { return new Promise((resolve, reject) => { const child = spawn("git-upload-pack", ["--stateless-rpc", "--advertise-refs", repoPath], { stdio: ["ignore", "pipe", "pipe"] }); let stderr = ""; child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); }); child.on("error", reject); child.on("close", (code) => { if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-upload-pack advertise failed: \${stderr.trim()}` + "`" + `)); else resolve(); }); res.writeHead(200, smartHeaders("application/x-git-upload-pack-advertisement")); res.write(pktLine("# service=git-upload-pack\\n")); res.write("0000"); child.stdout.pipe(res, { end: false }); child.stdout.on("end", () => res.end()); }); } function runUploadPackRpc(repoPath, req, res) { return new Promise((resolve, reject) => { const child = spawn("git-upload-pack", ["--stateless-rpc", repoPath], { stdio: ["pipe", "pipe", "pipe"] }); let stderr = ""; child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); }); child.on("error", reject); child.on("close", (code) => { if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-upload-pack rpc failed: \${stderr.trim()}` + "`" + `)); else resolve(); }); res.writeHead(200, smartHeaders("application/x-git-upload-pack-result")); const body = requestBodyStream(req); body.on("error", (error) => child.stdin.destroy(error)); body.pipe(child.stdin); child.stdout.pipe(res); }); } function runReceivePackAdvertisement(repoPath, res) { return new Promise((resolve, reject) => { const child = spawn("git-receive-pack", ["--stateless-rpc", "--advertise-refs", repoPath], { stdio: ["ignore", "pipe", "pipe"] }); let stderr = ""; child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); }); child.on("error", reject); child.on("close", (code) => { if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-receive-pack advertise failed: \${stderr.trim()}` + "`" + `)); else resolve(); }); res.writeHead(200, smartHeaders("application/x-git-receive-pack-advertisement")); res.write(pktLine("# service=git-receive-pack\\n")); res.write("0000"); child.stdout.pipe(res, { end: false }); child.stdout.on("end", () => res.end()); }); } function runReceivePackRpc(repoPath, req, res) { return new Promise((resolve, reject) => { const child = spawn("git-receive-pack", ["--stateless-rpc", repoPath], { stdio: ["pipe", "pipe", "pipe"] }); let stderr = ""; child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); }); child.on("error", reject); child.on("close", (code) => { if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-receive-pack rpc failed: \${stderr.trim()}` + "`" + `)); else resolve(); }); res.writeHead(200, smartHeaders("application/x-git-receive-pack-result")); const body = requestBodyStream(req); body.on("error", (error) => child.stdin.destroy(error)); body.pipe(child.stdin); child.stdout.pipe(res); }); } function serveStaticFile(urlPath, req, res) { const fullPath = safePath(urlPath); let stat; try { stat = statSync(fullPath); } catch { res.writeHead(404, { "content-type": "text/plain" }); res.end("not found\\n"); return; } if (!stat.isFile()) { res.writeHead(404, { "content-type": "text/plain" }); res.end("not found\\n"); return; } res.writeHead(200, { "content-length": stat.size, "content-type": contentType(fullPath), "cache-control": "no-cache" }); if (req.method === "HEAD") { res.end(); return; } createReadStream(fullPath).pipe(res); } function contentType(filePath) { if (filePath.endsWith(".pack")) return "application/x-git-packed-objects"; if (filePath.endsWith(".idx")) return "application/x-git-packed-objects-toc"; return "text/plain"; } ` } }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "git-mirror-http", namespace: "devops-infra", labels: { ...labels, "app.kubernetes.io/component": "read-only-http" } }, spec: { replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": "git-mirror", "app.kubernetes.io/component": "read-only-http" } }, template: { metadata: { labels: { ...labels, "app.kubernetes.io/component": "read-only-http" } }, spec: { containers: [{ name: "http", image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", command: ["node"], args: ["/script/http.mjs"], ports: [{ name: "http", containerPort: 8080 }], readinessProbe: { httpGet: { path: "/pikasTech/HWLAB.git/HEAD", port: "http" }, initialDelaySeconds: 5, periodSeconds: 10 }, livenessProbe: { httpGet: { path: "/", port: "http" }, initialDelaySeconds: 10, periodSeconds: 30 }, volumeMounts: [ { name: "cache", mountPath: "/cache" }, { name: "script", mountPath: "/script", readOnly: true } ] }], volumes: [ { name: "cache", persistentVolumeClaim: { claimName: "git-mirror-cache" } }, { name: "script", configMap: { name: "git-mirror-sync-script", defaultMode: 0o555 } } ] } } } }, { apiVersion: "v1", kind: "Service", metadata: { name: "git-mirror-http", namespace: "devops-infra", labels: { ...labels, "app.kubernetes.io/component": "read-only-http" } }, spec: { selector: { "app.kubernetes.io/name": "git-mirror", "app.kubernetes.io/component": "read-only-http" }, ports: [{ name: "http", port: 80, targetPort: "http" }] } }, { apiVersion: "v1", kind: "Service", metadata: { name: "git-mirror-write", namespace: "devops-infra", labels: { ...labels, "app.kubernetes.io/component": "write-relay" } }, spec: { selector: { "app.kubernetes.io/name": "git-mirror", "app.kubernetes.io/component": "read-only-http" }, ports: [{ name: "http", port: 80, targetPort: "http" }] } } ] }; } function argoProject(args = { lane: "g14" }) { if (args.lane === "v02") { return { apiVersion: "argoproj.io/v1alpha1", kind: "AppProject", metadata: { name: "hwlab-v02", namespace: "argocd" }, spec: { description: "HWLAB v0.2 additive GitOps project on G14; DEV/PROD remain outside this lane.", sourceRepos: [args.gitReadUrl], destinations: [{ server: "https://kubernetes.default.svc", namespace: "hwlab-v02" }], clusterResourceWhitelist: [{ group: "", kind: "Namespace" }], namespaceResourceWhitelist: [{ group: "*", kind: "*" }] } }; } return { apiVersion: "argoproj.io/v1alpha1", kind: "AppProject", metadata: { name: "hwlab-g14", namespace: "argocd" }, spec: { description: "HWLAB G14 GitOps project; D601 remains outside this project.", sourceRepos: [defaultSourceRepo], destinations: [ { server: "https://kubernetes.default.svc", namespace: "hwlab-dev" }, { server: "https://kubernetes.default.svc", namespace: "hwlab-prod" } ], clusterResourceWhitelist: [{ group: "", kind: "Namespace" }], namespaceResourceWhitelist: [{ group: "*", kind: "*" }] } }; } function argoApplication(args, profile = "dev") { const namespace = namespaceNameForProfile(profile); const runtimePath = runtimePathForProfile(profile); const gitopsTarget = gitopsTargetForProfile(profile); const project = profile === "v02" ? "hwlab-v02" : "hwlab-g14"; const repoURL = profile === "v02" ? args.gitReadUrl : args.sourceRepo; return { apiVersion: "argoproj.io/v1alpha1", kind: "Application", metadata: { name: argoApplicationName(profile), namespace: "argocd", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": gitopsTarget } }, spec: { project, source: { repoURL, targetRevision: args.gitopsBranch, path: `deploy/gitops/g14/${runtimePath}` }, destination: { server: "https://kubernetes.default.svc", namespace }, syncPolicy: { automated: { prune: profile === "v02", selfHeal: true }, syncOptions: ["CreateNamespace=true", "ApplyOutOfSyncOnly=true"] } } }; } function g14FrpcManifest({ profile = "dev", source = null } = {}) { const namespace = namespaceNameForProfile(profile); const profileLabel = runtimeLabelForProfile(profile); const deploymentName = profile === "v02" ? "hwlab-v02-frpc" : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc"; const configName = `${deploymentName}-config`; const proxyPrefix = profile === "v02" ? "hwlab-v02" : profile === "prod" ? "hwlab-g14-prod" : "hwlab-g14"; const webRemotePort = profile === "v02" ? 19666 : profile === "prod" ? 18666 : 17666; const edgeRemotePort = profile === "v02" ? 19667 : profile === "prod" ? 18667 : 17667; const labels = { "app.kubernetes.io/name": deploymentName, "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/environment": profileLabel, "hwlab.pikastech.local/gitops-target": gitopsTargetForProfile(profile), "hwlab.pikastech.local/profile": profileLabel }; const sourceCommitLabel = source?.full ? { "hwlab.pikastech.local/source-commit": source.full } : {}; const renderedLabels = { ...labels, ...sourceCommitLabel }; const podNetwork = profile === "v02" ? { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet" } : {}; const configText = `serverAddr = "74.48.78.17" serverPort = 7000 loginFailExit = true [[proxies]] name = "${proxyPrefix}-cloud-web" type = "tcp" localIP = "hwlab-cloud-web.${namespace}.svc.cluster.local" localPort = 8080 remotePort = ${webRemotePort} [[proxies]] name = "${proxyPrefix}-edge-proxy" type = "tcp" localIP = "hwlab-edge-proxy.${namespace}.svc.cluster.local" localPort = 6667 remotePort = ${edgeRemotePort} `; const configSha256 = createHash("sha256").update(configText).digest("hex"); const templateLabels = { ...labels, "hwlab.pikastech.local/config-sha256": k8sLabelHash(configSha256) }; const templateAnnotations = { "hwlab.pikastech.local/config-sha256": configSha256 }; return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "ConfigMap", metadata: { name: configName, namespace, labels: renderedLabels }, data: { "frpc.toml": configText } }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: deploymentName, namespace, labels: renderedLabels }, spec: { replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": deploymentName } }, template: { metadata: { labels: templateLabels, annotations: templateAnnotations }, spec: { ...podNetwork, containers: [{ name: "frpc", image: "fatedier/frpc:v0.68.1", imagePullPolicy: "IfNotPresent", args: ["-c", "/etc/frp/frpc.toml"], volumeMounts: [{ name: "config", mountPath: "/etc/frp", readOnly: true }] }], volumes: [{ name: "config", configMap: { name: configName } }] } } } } ] }; } function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", sourceRepo = defaultSourceRepo, deploy = null, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) { const namespace = namespaceNameForProfile(profile); const bridgeServiceId = "hwlab-cloud-api"; const digestPin = profile === "v02"; const envReuseServiceIds = profile === "v02" ? new Set([bridgeServiceId]) : null; const bridgeImage = runtimeImageForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); const bridgeCommit = runtimeCommitForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); const bridgeImageTag = runtimeImageTagForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds }); const bridgeBootMetadata = profile === "v02" && v02EnvReuseEnabled(catalog, bridgeServiceId, envReuseServiceIds) ? bootMetadataForService({ args: { sourceRepo, registryPrefix }, catalog, deployService: deployServiceForBoot(deploy, bridgeServiceId), serviceId: bridgeServiceId, source }) : null; const labels = { "app.kubernetes.io/name": "hwlab-deepseek-proxy", "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/gitops-target": gitopsTargetForProfile(profile), "hwlab.pikastech.local/service-id": "hwlab-deepseek-proxy", "hwlab.pikastech.local/source-commit": source.full }; if (profile === "v02") Object.assign(labels, v02MetricsLabels("hwlab-deepseek-proxy")); const templateLabels = { ...labels, "hwlab.pikastech.local/source-commit": bridgeCommit, "hwlab.pikastech.local/bridge-source-commit": bridgeCommit }; const templateAnnotations = { "hwlab.pikastech.local/bridge-service-id": bridgeServiceId, "hwlab.pikastech.local/source-commit": bridgeCommit, "hwlab.pikastech.local/bridge-source-commit": bridgeCommit, "hwlab.pikastech.local/bridge-image-tag": bridgeImageTag, "hwlab.pikastech.local/bridge-image": bridgeImage, ...v02MetricsSidecarAnnotations(profile === "v02" ? metricsSidecarSha256 : null) }; const responsesBridgeContainer = { name: "responses-bridge", image: bridgeImage, imagePullPolicy: "IfNotPresent", command: ["/usr/local/bin/bun", "run", "/app/cmd/hwlab-deepseek-responses-bridge/main.ts"], env: [ { name: "PORT", value: "4000" }, { name: "HWLAB_COMMIT_ID", value: bridgeCommit }, { name: "HWLAB_IMAGE", value: bridgeImage }, { name: "HWLAB_IMAGE_TAG", value: bridgeImageTag }, { name: "HWLAB_DEEPSEEK_BRIDGE_UPSTREAM", value: "http://127.0.0.1:4001" }, { name: "HWLAB_DEEPSEEK_BRIDGE_MODEL", value: deepSeekProfileModel } ], ports: [{ name: "http", containerPort: 4000 }], readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 }, livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 } }; if (bridgeBootMetadata) { applyEnvReuseBootEnv(responsesBridgeContainer, bridgeBootMetadata, { sourceBranch, bootSh: "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh" }); Object.assign(templateAnnotations, { "hwlab.pikastech.local/bridge-runtime-mode": bridgeBootMetadata.runtimeMode, "hwlab.pikastech.local/bridge-boot-repo": bridgeBootMetadata.bootRepo, "hwlab.pikastech.local/bridge-boot-commit": bridgeBootMetadata.bootCommit, "hwlab.pikastech.local/bridge-boot-sh": "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh", "hwlab.pikastech.local/bridge-environment-digest": bridgeBootMetadata.environmentDigest ?? "not_published" }); Object.assign(templateLabels, { "hwlab.pikastech.local/bridge-runtime-mode": bridgeBootMetadata.runtimeMode, "hwlab.pikastech.local/bridge-boot-commit": bridgeBootMetadata.bootCommit }); } return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "ConfigMap", metadata: { name: "hwlab-deepseek-proxy-config", namespace, labels }, data: { "render-config.sh": moonBridgeConfigRenderScript() } }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "hwlab-deepseek-proxy", namespace, labels, annotations: { "hwlab.pikastech.local/moonbridge-source-ref": moonBridgeSourceRef, ...templateAnnotations } }, spec: { replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" } }, template: { metadata: { labels: templateLabels, annotations: templateAnnotations }, spec: { initContainers: [{ name: "moonbridge-config", image: bridgeImage, imagePullPolicy: "IfNotPresent", command: ["/bin/sh", "-eu", "/scripts/render-config.sh"], env: [ { name: "DEEPSEEK_API_KEY", valueFrom: { secretKeyRef: { name: secretNameForProfile("hwlab-code-agent-provider", profile), key: "openai-api-key", optional: true } } }, { name: "OPENCODE_API_KEY", valueFrom: { secretKeyRef: { name: secretNameForProfile("hwlab-code-agent-provider", profile), key: "opencode-api-key", optional: true } } } ], volumeMounts: [ { name: "moonbridge-scripts", mountPath: "/scripts", readOnly: true }, { name: "moonbridge-config", mountPath: "/config" } ] }], containers: [responsesBridgeContainer, { name: "moonbridge", image: moonBridgeImage, imagePullPolicy: "IfNotPresent", args: ["-config", "/config/config.yml"], ports: [{ name: "moonbridge-http", containerPort: 4001 }], readinessProbe: { httpGet: { path: "/v1/models", port: "moonbridge-http" }, initialDelaySeconds: 10, periodSeconds: 10 }, livenessProbe: { httpGet: { path: "/v1/models", port: "moonbridge-http" }, initialDelaySeconds: 20, periodSeconds: 20 }, volumeMounts: [ { name: "moonbridge-config", mountPath: "/config", readOnly: true }, { name: "moonbridge-data", mountPath: "/data" } ] }, ...(profile === "v02" ? [v02MetricsSidecarContainer({ deploy, serviceId: "hwlab-deepseek-proxy", namespace, gitopsTarget: gitopsTargetForProfile(profile) })] : [])], volumes: [ { name: "moonbridge-scripts", configMap: { name: "hwlab-deepseek-proxy-config" } }, { name: "moonbridge-config", emptyDir: {} }, { name: "moonbridge-data", emptyDir: {} }, ...(profile === "v02" ? [v02MetricsSidecarVolume()] : []) ] } } } }, { apiVersion: "v1", kind: "Service", metadata: { name: "hwlab-deepseek-proxy", namespace, labels }, spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }, ...(profile === "v02" ? [{ name: "metrics", port: 9100, targetPort: "metrics" }] : [])] } } ] }; } function moonBridgeConfigRenderScript() { return `#!/bin/sh if [ -z "\${DEEPSEEK_API_KEY:-}" ] && [ -z "\${OPENCODE_API_KEY:-}" ]; then echo "at least one of DEEPSEEK_API_KEY or OPENCODE_API_KEY is required" >&2 exit 1 fi cat > /config/config.yml < {", " let body = \"\";", " req.setEncoding(\"utf8\");", " req.on(\"data\", (chunk) => {", " body += chunk;", " if (body.length > 1024 * 1024) req.destroy(new Error(\"request body too large\"));", " });", " req.on(\"end\", () => resolve(body ? JSON.parse(body) : {}));", " req.on(\"error\", reject);", " });", "}", "", "function postJson(url, payload, timeoutMs) {", " return new Promise((resolve, reject) => {", " const parsed = new URL(url);", " const body = JSON.stringify(payload);", " const request = http.request({", " hostname: parsed.hostname,", " port: parsed.port || 80,", " path: parsed.pathname + parsed.search,", " method: \"POST\",", " headers: { \"content-type\": \"application/json\", \"content-length\": Buffer.byteLength(body) },", " timeout: timeoutMs", " }, (response) => {", " let responseBody = \"\";", " response.setEncoding(\"utf8\");", " response.on(\"data\", (chunk) => { responseBody += chunk; });", " response.on(\"end\", () => {", " try {", " const parsedBody = responseBody ? JSON.parse(responseBody) : null;", " resolve({ statusCode: response.statusCode, body: parsedBody });", " } catch (error) {", " reject(error);", " }", " });", " });", " request.on(\"timeout\", () => request.destroy(new Error(\"request timeout after \" + timeoutMs + \"ms\")));", " request.on(\"error\", reject);", " request.end(body);", " });", "}", "", "function gatewayPayload(input) {", " const traceId = input.traceId || \"trc_device_agent_71_freq_\" + Date.now();", " return {", " jsonrpc: \"2.0\",", " id: input.id || \"req_device_agent_71_freq_\" + Date.now(),", " method: \"hardware.invoke.shell\",", " meta: { serviceId: \"hwlab-cloud-api\", actorId: \"device-agent-71-freq\", environment: \"dev\", traceId, deviceId, workspaceRoot },", " params: {", " gatewaySessionId,", " resourceId,", " capabilityId,", " input: {", " command: input.command,", " cwd: input.cwd || workspaceRoot,", " timeoutMs: input.timeoutMs || 30000", " }", " }", " };", "}", "", "const server = http.createServer(async (req, res) => {", " try {", " const url = new URL(req.url || \"/\", \"http://device-agent-71-freq\");", " if (req.method === \"GET\" && url.pathname === \"/health\") {", " return sendJson(res, 200, { ok: true, deviceId, workspaceRoot, gatewaySessionId, resourceId, capabilityId, cloudApiUrl });", " }", " if (req.method === \"GET\" && url.pathname === \"/skills\") {", " return sendJson(res, 200, { ok: true, deviceId, skills: [{ name: \"cmd\", description: \"Run a bounded Windows cmd command through hwlab-gateway.\" }] });", " }", " if (req.method === \"POST\" && url.pathname === \"/run\") {", " const input = await readBody(req);", " if (!input.command || typeof input.command !== \"string\") return sendJson(res, 400, { ok: false, error: \"command is required\" });", " const timeoutMs = Number(input.timeoutMs || 30000);", " const upstream = await postJson(cloudApiUrl + \"/json-rpc\", gatewayPayload({ ...input, timeoutMs }), timeoutMs + 5000);", " return sendJson(res, upstream.statusCode || 502, { ok: upstream.statusCode >= 200 && upstream.statusCode < 300, deviceId, gatewaySessionId, upstream: upstream.body });", " }", " return sendJson(res, 404, { ok: false, error: \"not found\" });", " } catch (error) {", " return sendJson(res, 500, { ok: false, error: error.message });", " }", "});", "", "server.listen(port, \"0.0.0.0\", () => {", " console.log(JSON.stringify({ ok: true, service: \"device-agent-71-freq\", port, deviceId, workspaceRoot, cloudApiUrl }));", "});" ].join("\n") + "\n"; } function deviceAgent71FreqManifest({ profile = "dev", source, registryPrefix, catalog = null, useDeployImages = false }) { assert.equal(profile, "dev", "71-freq device-agent is dev-only"); const namespace = namespaceNameForProfile(profile); const name = "device-agent-71-freq"; const bridgeServiceId = "hwlab-cloud-api"; const bridgeImage = runtimeImageForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages }); const bridgeCommit = runtimeCommitForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages }); const bridgeImageTag = runtimeImageTagForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages }); const labels = { "app.kubernetes.io/name": name, "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/component": "device-agent", "hwlab.pikastech.local/device-id": "71-freq", "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/gitops-target": "g14", "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile), "hwlab.pikastech.local/service-id": name, "hwlab.pikastech.local/source-commit": source.full }; const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" }; const selector = { "app.kubernetes.io/name": name }; const script = deviceAgent71FreqServerScript(); const scriptSha256 = createHash("sha256").update(script).digest("hex"); const templateLabels = { ...labels, ...selector, "hwlab.pikastech.local/source-commit": bridgeCommit }; const templateAnnotations = { ...annotations, "hwlab.pikastech.local/script-sha256": scriptSha256, "hwlab.pikastech.local/bridge-service-id": bridgeServiceId, "hwlab.pikastech.local/bridge-source-commit": bridgeCommit, "hwlab.pikastech.local/bridge-image-tag": bridgeImageTag, "hwlab.pikastech.local/bridge-image": bridgeImage }; return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "ConfigMap", metadata: { name: `${name}-script`, namespace, labels, annotations: templateAnnotations }, data: { "server.mjs": script } }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name, namespace, labels, annotations }, spec: { replicas: 1, selector: { matchLabels: selector }, template: { metadata: { labels: templateLabels, annotations: templateAnnotations }, spec: { containers: [{ name: "device-agent", image: bridgeImage, imagePullPolicy: "IfNotPresent", command: ["node", "/opt/device-agent/server.mjs"], env: [ { name: "PORT", value: "7601" }, { name: "HWLAB_COMMIT_ID", value: bridgeCommit }, { name: "HWLAB_IMAGE", value: bridgeImage }, { name: "HWLAB_IMAGE_TAG", value: bridgeImageTag }, { name: "DEVICE_ID", value: "71-freq" }, { name: "DEVICE_WORKSPACE_ROOT", value: "F:\\Work\\ConStart" }, { name: "HWLAB_CLOUD_API_URL", value: `http://hwlab-cloud-api.${namespace}.svc.cluster.local:6667` }, { name: "HWLAB_GATEWAY_SESSION_ID", value: "gws_d601_win_71_freq" }, { name: "HWLAB_GATEWAY_RESOURCE_ID", value: "res_d601_windows_host" }, { name: "HWLAB_GATEWAY_CAPABILITY_ID", value: "cap_d601_windows_cmd_exec" } ], ports: [{ name: "http", containerPort: 7601 }], readinessProbe: { httpGet: { path: "/health", port: "http" }, initialDelaySeconds: 3, periodSeconds: 10 }, livenessProbe: { httpGet: { path: "/health", port: "http" }, initialDelaySeconds: 10, periodSeconds: 20 }, resources: { requests: { cpu: "10m", memory: "64Mi" }, limits: { cpu: "200m", memory: "256Mi" } }, volumeMounts: [ { name: "script", mountPath: "/opt/device-agent", readOnly: true }, { name: "hwlab-code-agent-workspace", mountPath: "/workspace" } ] }], volumes: [ { name: "script", configMap: { name: `${name}-script` } }, { name: "hwlab-code-agent-workspace", persistentVolumeClaim: { claimName: "hwlab-code-agent-workspace" } } ] } } } }, { apiVersion: "v1", kind: "Service", metadata: { name, namespace, labels, annotations }, spec: { type: "ClusterIP", selector, ports: [{ name: "http", port: 7601, targetPort: "http" }] } } ] }; } function v02PostgresManifest({ migrationSql, source }) { const namespace = "hwlab-v02"; const name = "hwlab-v02-postgres"; const labels = { "app.kubernetes.io/name": name, "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/environment": "v02", "hwlab.pikastech.local/gitops-target": "v02", "hwlab.pikastech.local/profile": "v02", "hwlab.pikastech.local/source-commit": source.full }; const migrationSha256 = createHash("sha256").update(migrationSql).digest("hex"); const templateLabels = { "app.kubernetes.io/name": name, "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/environment": "v02", "hwlab.pikastech.local/gitops-target": "v02", "hwlab.pikastech.local/profile": "v02", "hwlab.pikastech.local/migration-sha256": k8sLabelHash(migrationSha256) }; const templateAnnotations = { "hwlab.pikastech.local/migration-sha256": migrationSha256 }; return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "ConfigMap", metadata: { name: `${name}-init`, namespace, labels }, data: { "0001_cloud_core_skeleton.sql": migrationSql } }, { apiVersion: "v1", kind: "Service", metadata: { name, namespace, labels }, spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": name }, ports: [{ name: "postgres", port: 5432, targetPort: "postgres" }] } }, { apiVersion: "apps/v1", kind: "StatefulSet", metadata: { name, namespace, labels }, spec: { serviceName: name, replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": name } }, template: { metadata: { labels: templateLabels, annotations: templateAnnotations }, spec: { containers: [{ name: "postgres", image: "127.0.0.1:5000/hwlab/postgres:16-alpine", imagePullPolicy: "IfNotPresent", env: [ { name: "POSTGRES_DB", value: "hwlab_v02" }, { name: "POSTGRES_USER", value: "hwlab_v02" }, { name: "POSTGRES_PASSWORD", valueFrom: { secretKeyRef: { name: "hwlab-v02-postgres", key: "POSTGRES_PASSWORD" } } } ], ports: [{ name: "postgres", containerPort: 5432 }], readinessProbe: { tcpSocket: { port: "postgres" }, initialDelaySeconds: 5, periodSeconds: 10 }, livenessProbe: { tcpSocket: { port: "postgres" }, initialDelaySeconds: 30, periodSeconds: 20 }, volumeMounts: [ { name: "data", mountPath: "/var/lib/postgresql/data" }, { name: "init", mountPath: "/docker-entrypoint-initdb.d", readOnly: true } ] }], volumes: [{ name: "init", configMap: { name: `${name}-init` } }] } }, volumeClaimTemplates: [{ metadata: { name: "data" }, spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } }] } } ] }; } function v02OpenFgaManifest({ source }) { const namespace = "hwlab-v02"; const name = "hwlab-openfga"; const image = "127.0.0.1:5000/hwlab/openfga:v1.17.0"; const labels = { "app.kubernetes.io/name": name, "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/component": "authorization", "hwlab.pikastech.local/environment": "v02", "hwlab.pikastech.local/gitops-target": "v02", "hwlab.pikastech.local/profile": "v02", "hwlab.pikastech.local/service-id": name, "hwlab.pikastech.local/source-commit": source.full }; const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" }; const selector = { "app.kubernetes.io/name": name }; const env = [ { name: "OPENFGA_DATASTORE_ENGINE", value: "postgres" }, { name: "OPENFGA_DATASTORE_URI", valueFrom: { secretKeyRef: { name: "hwlab-v02-openfga", key: "datastore-uri" } } }, { name: "OPENFGA_AUTHN_METHOD", value: "preshared" }, { name: "OPENFGA_AUTHN_PRESHARED_KEYS", valueFrom: { secretKeyRef: { name: "hwlab-v02-openfga", key: "authn-preshared-key" } } }, { name: "OPENFGA_PLAYGROUND_ENABLED", value: "false" }, { name: "OPENFGA_HTTP_ADDR", value: "0.0.0.0:8080" }, { name: "OPENFGA_GRPC_ADDR", value: "0.0.0.0:8081" } ]; const templateLabels = { ...labels, ...selector }; return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "batch/v1", kind: "Job", metadata: { name: `${name}-migrate`, namespace, labels, annotations: { ...annotations, "argocd.argoproj.io/hook": "PreSync", "argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded" } }, spec: { backoffLimit: 3, template: { metadata: { labels: templateLabels, annotations }, spec: { restartPolicy: "OnFailure", containers: [{ name: "openfga-migrate", image, imagePullPolicy: "IfNotPresent", args: ["migrate"], env }] } } } }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name, namespace, labels, annotations }, spec: { replicas: 1, selector: { matchLabels: selector }, template: { metadata: { labels: templateLabels, annotations }, spec: { containers: [{ name: "openfga", image, imagePullPolicy: "IfNotPresent", args: ["run"], env, ports: [{ name: "http", containerPort: 8080 }, { name: "grpc", containerPort: 8081 }], readinessProbe: { httpGet: { path: "/healthz", port: "http" }, initialDelaySeconds: 3, periodSeconds: 10 }, livenessProbe: { httpGet: { path: "/healthz", port: "http" }, initialDelaySeconds: 15, periodSeconds: 20 }, resources: { requests: { cpu: "50m", memory: "128Mi" }, limits: { cpu: "500m", memory: "512Mi" } } }] } } } }, { apiVersion: "v1", kind: "Service", metadata: { name, namespace, labels, annotations }, spec: { type: "ClusterIP", selector, ports: [{ name: "http", port: 8080, targetPort: "http" }, { name: "grpc", port: 8081, targetPort: "grpc" }] } } ] }; } function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev" } = {}) { const resources = ["namespace.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"]; if (profile !== "v02") resources.splice(1, 0, "code-agent-codex-config.yaml"); if (profile === "v02") resources.push("postgres.yaml", "openfga.yaml", "observability.yaml"); if (includeDeviceAgent) resources.push("device-agent-71-freq.yaml"); resources.push("g14-frpc.yaml"); return { apiVersion: "kustomize.config.k8s.io/v1beta1", kind: "Kustomization", namespace: namespaceNameForProfile(profile), resources }; } function readme({ source, args }) { if (args.lane === "v02") { return `# HWLAB v0.2 GitOps CI/CD This directory is generated by \`scripts/g14-gitops-render.mjs --lane v02\` from source branch \`${args.sourceBranch}\`. - Target: additive G14 v0.2 lane only. - CI: Tekton Pipeline \`hwlab-ci/hwlab-v02-ci-image-publish\`. - Git mirror/relay: v0.2 CI reads source/catalog from \`${args.gitReadUrl}\` and writes GitOps promotion to \`${args.gitWriteUrl}\`; canonical repo \`${args.sourceRepo}\` remains the source identity and manual flush upstream. - Artifact contract: runtime images come from \`${args.catalogPath}\`; source branch \`v0.2\` does not track this generated catalog. - Manual trigger: run \`bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm\` from UniDesk to create commit-pinned PipelineRun \`hwlab-v02-ci-poll-\` for the current \`origin/v0.2\` commit. - Branch split: source branch \`${args.sourceBranch}\` remains human-authored source; generated desired state is promoted to local mirror branch \`${args.gitopsBranch}\`. - CD: Argo CD Application \`argocd/hwlab-g14-v02\` consumes \`${args.gitReadUrl}:${args.gitopsBranch}:deploy/gitops/g14/runtime-v02\` and deploys only to namespace \`hwlab-v02\`. - Public preview: FRP exposes v0.2 web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-v02/hwlab-v02-frpc\`. - G14 DEV/PROD boundary: this lane does not modify \`G14\`, \`G14-gitops\`, \`hwlab-dev\`, \`hwlab-prod\`, 17666/17667 or 18666/18667. - CronJob policy: v0.2 does not generate \`hwlab-v02-branch-poller\` or \`hwlab-v02-control-plane-reconciler\`; obsolete live CronJobs should be deleted by the UniDesk control-plane apply command. - Gate policy: old DEV/D601/main gates do not enter this lane; new checks stay limited to the fixed branch, namespace, catalog, runtime path and Argo boundaries. `; } return `# HWLAB G14 GitOps CI/CD This directory is generated from built-in Tekton-native CI primitive declarations plus human-authored \`deploy/deploy.yaml\`, CI-authored \`deploy/artifact-catalog.dev.json\`, and \`deploy/k8s/*\` by \`scripts/g14-gitops-render.mjs\`. - Target: G14 k3s only. - CI: Tekton Pipeline \`hwlab-ci/hwlab-g14-ci-image-publish\`. - Artifact contract: runtime images come from \`deploy/artifact-catalog.dev.json\`; \`deploy/deploy.yaml\` only carries human-authored config such as env and topology. - Branch split: source branch \`${args.sourceBranch}\` remains the watched code branch; generated desired state is promoted to \`${args.gitopsBranch}\`. - CD: Argo CD Applications \`argocd/hwlab-g14-dev\` and \`argocd/hwlab-g14-prod\` consume \`${args.gitopsBranch}:deploy/gitops/g14/runtime-dev\` and \`runtime-prod\`. - Public preview: FRP exposes G14 DEV web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-dev/hwlab-g14-frpc\`, and G14 PROD web \`${args.prodWebEndpoint}\` / edge \`${args.prodRuntimeEndpoint}\` through \`hwlab-prod/hwlab-g14-prod-frpc\`; D601 keeps \`:16666/:16667\`. - Manual polling: run \`kubectl -n hwlab-ci create job --from=cronjob/hwlab-g14-branch-poller hwlab-g14-branch-poller-manual-$(date -u +%Y%m%d%H%M%S)\`; this reuses the same poller path and creates a commit-pinned Tekton PipelineRun only when needed. - Control-plane reconcile: CronJob \`hwlab-ci/hwlab-g14-control-plane-reconciler\` polls \`${args.sourceBranch}\`, runs \`scripts/g14-gitops-render.mjs\`, and server-side-applies the generated Tekton RBAC/Pipeline/Poller/Reconciler manifests so CI control-plane changes do not require a manual render/apply step. - D601 boundary: this path does not target D601, does not use UniDesk Code Queue, and does not change D601 production traffic. - Concurrency: CI builds are per source commit and do not acquire the legacy DEV CD Lease; Argo CD reconciles the Git desired state. `; } async function plannedFiles(args) { const [deploy, namespaceTemplate, config, services, health, workloads] = await Promise.all([ readJson("deploy/deploy.yaml"), readJson("deploy/k8s/base/namespace.yaml"), readJson("deploy/k8s/base/code-agent-codex-config.yaml"), readJson("deploy/k8s/base/services.yaml"), readJson("deploy/k8s/dev/health-contract.yaml"), readJson("deploy/k8s/base/workloads.yaml") ]); const source = await resolveSourceRevision(args.sourceRevision); source.imageTag = imageTagForSource(args, source); let artifactCatalog = await readJsonIfPresent(args.catalogPath, null); if (!artifactCatalog && args.lane === "v02") artifactCatalog = artifactCatalogSkeleton({ args, deploy, source }); if (!artifactCatalog) artifactCatalog = await readJson(args.catalogPath); const settings = ciLaneSettings(args); const profiles = laneRuntimeProfiles(args); const migrationSql = args.lane === "v02" ? await readFile(path.join(repoRoot, "internal/db/migrations/0001_cloud_core_skeleton.sql"), "utf8") : null; const metricsSidecarScript = args.lane === "v02" ? await readFile(path.join(repoRoot, "internal/dev-entrypoint/metrics-sidecar.mjs"), "utf8") : null; const metricsSidecarSha256 = metricsSidecarScript ? createHash("sha256").update(metricsSidecarScript).digest("hex") : null; const baseLabels = { "hwlab.pikastech.local/gitops-target": settings.gitopsTarget, "hwlab.pikastech.local/source-commit": source.full }; const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" }; const files = new Map(); const putJson = (relative, value) => files.set(path.join(args.outDir, relative), jsonManifest(value)); const putText = (relative, value) => files.set(path.join(args.outDir, relative), textFile(value)); const publicEndpoints = Object.fromEntries(profiles.map((profile) => { const endpoints = profileEndpoint(args, profile); return [profile, { web: endpoints.webEndpoint, runtime: endpoints.runtimeEndpoint }]; })); const sourceDescriptor = { kind: args.lane === "v02" ? "hwlab-v02-gitops-source" : "hwlab-g14-gitops-source", sourceCommit: source.full, sourceShortCommit: source.short, sourceImageTag: source.imageTag, sourceBranch: args.sourceBranch, gitopsBranch: args.gitopsBranch, lane: settings.lane, sourceRepo: args.sourceRepo, gitReadUrl: args.gitReadUrl, gitWriteUrl: args.gitWriteUrl, registryPrefix: args.registryPrefix, runtimePaths: profiles.map((profile) => `deploy/gitops/g14/${runtimePathForProfile(profile)}`), publicEndpoints, frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile)}/${profile === "v02" ? "hwlab-v02-frpc" : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc"}`])), tektonPipeline: `hwlab-ci/${settings.pipelineName}`, argoApplications: profiles.map((profile) => `argocd/${argoApplicationName(profile)}`) }; if (args.useDeployImages) { sourceDescriptor.renderMode = { imageSource: args.catalogPath, configSource: "deploy/deploy.yaml", mixedDesiredState: true }; } putJson("source.json", sourceDescriptor); putText("README.md", readme({ source, args })); putJson("registry/registry.yaml", registryManifest()); if (args.lane === "v02") putJson("devops-infra/git-mirror.yaml", devopsInfraGitMirrorManifest()); putJson(`${settings.tektonDir}/rbac.yaml`, tektonRbac(args)); putJson(`${settings.tektonDir}/pipeline.yaml`, tektonPipeline(args, deploy)); if (args.lane !== "v02") { putJson(`${settings.tektonDir}/poller.yaml`, tektonPollerCronJob(args, deploy)); putJson(`${settings.tektonDir}/control-plane-reconciler.yaml`, tektonControlPlaneReconcilerCronJob(args)); } putJson(`${settings.tektonDir}/pipelinerun.sample.yaml`, tektonPipelineRunTemplate({ source, args })); putJson("argocd/project.yaml", argoProject(args)); for (const profile of profiles) putJson(`argocd/application-${profile}.yaml`, argoApplication(args, profile)); for (const profile of profiles) { const namespace = namespaceNameForProfile(profile); const includeDeviceAgent = profile === "dev"; const profileLabels = { ...baseLabels, "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile) }; const runtimeNamespace = cloneJson(namespaceTemplate); runtimeNamespace.metadata.name = namespace; label(runtimeNamespace.metadata, profileLabels); annotate(runtimeNamespace.metadata, annotations); const runtimePath = runtimePathForProfile(profile); const endpoints = profileEndpoint(args, profile); putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent })); putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace); if (profile !== "v02") putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations)); putJson(`${runtimePath}/services.yaml`, transformServices({ services, namespace, labels: profileLabels, annotations, profile, deploy })); putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile)); putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, catalog: artifactCatalog, source, sourceBranch: args.sourceBranch, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile, useDeployImages: args.useDeployImages, metricsSidecarSha256 })); putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, sourceBranch: args.sourceBranch, sourceRepo: args.sourceRepo, deploy, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages, metricsSidecarSha256 })); if (profile === "v02") putJson(`${runtimePath}/postgres.yaml`, v02PostgresManifest({ migrationSql, source })); if (profile === "v02") putJson(`${runtimePath}/openfga.yaml`, v02OpenFgaManifest({ source })); if (profile === "v02") putJson(`${runtimePath}/observability.yaml`, observabilityManifest({ deploy, namespace, labels: profileLabels, annotations, metricsSidecarScript })); if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages })); putJson(`${runtimePath}/g14-frpc.yaml`, g14FrpcManifest({ profile, source })); } return { files, source }; } async function writeFiles(files) { for (const [filePath, content] of files) { const absolutePath = generatedPath(filePath); await mkdir(path.dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, content); } } async function cleanStaleGeneratedOutput(args) { if (args.lane !== "v02") return; for (const relativePath of ["runtime-v02", "tekton-v02"]) { await rm(generatedPath(path.join(args.outDir, relativePath)), { recursive: true, force: true }); } } async function checkFiles(files) { const mismatches = []; for (const [filePath, expected] of files) { let actual = null; try { actual = await readFile(generatedPath(filePath), "utf8"); } catch (error) { if (error?.code !== "ENOENT") throw error; } if (actual !== expected) mismatches.push(filePath); } return mismatches; } async function main() { let args = parseArgs(process.argv.slice(2)); if (args.help) { console.log(usage()); return; } ensureObject(args, "args"); const { files, source } = await plannedFiles(args); if (args.check) { const mismatches = await checkFiles(files); console.log(JSON.stringify({ ok: mismatches.length === 0, sourceCommit: source.full, outDir: args.outDir, checked: files.size, mismatches }, null, 2)); process.exitCode = mismatches.length === 0 ? 0 : 1; return; } if (args.write) { await cleanStaleGeneratedOutput(args); await writeFiles(files); } console.log(JSON.stringify({ ok: true, sourceCommit: source.full, sourceShort: source.short, outDir: args.outDir, files: [...files.keys()] }, null, 2)); } main().catch((error) => { console.error(error.stack || error.message); process.exitCode = 1; });