diff --git a/.tekton/hwlab-jd01-v03-pac.yaml b/.tekton/hwlab-jd01-v03-pac.yaml deleted file mode 100644 index 8124eba3..00000000 --- a/.tekton/hwlab-jd01-v03-pac.yaml +++ /dev/null @@ -1,83 +0,0 @@ -apiVersion: tekton.dev/v1 -kind: PipelineRun -metadata: - generateName: hwlab-jd01-v03-ci-poll- - annotations: - pipelinesascode.tekton.dev/on-event: "[push]" - pipelinesascode.tekton.dev/on-target-branch: "[v0.3]" - pipelinesascode.tekton.dev/on-cel-expression: "event == 'push' && target_branch == 'v0.3' && node == 'JD01'" - pipelinesascode.tekton.dev/max-keep-runs: "8" - pipelinesascode.tekton.dev/pipeline: "ci/pipelines/hwlab-jd01-v03-ci-image-publish.yaml" - hwlab.pikastech.local/ci-contract: tekton-native-primitive-tasks - hwlab.pikastech.local/download-profile: jd01-node-default - hwlab.pikastech.local/gitops-branch: v0.3-gitops - hwlab.pikastech.local/network-profile: jd01-node-ci-egress - hwlab.pikastech.local/node: JD01 - hwlab.pikastech.local/policy: native-per-service-taskrun-image-publish - hwlab.pikastech.local/runtime-path: deploy/gitops/node/jd01/runtime-v03 - hwlab.pikastech.local/source-branch: v0.3 - hwlab.pikastech.local/source-config: .tekton/hwlab-jd01-v03-pac.yaml - labels: - app.kubernetes.io/name: hwlab-jd01-v03-pac - app.kubernetes.io/part-of: hwlab - unidesk.ai/source-commit: "{{revision}}" - hwlab.pikastech.local/gitops-target: v03 - hwlab.pikastech.local/source-commit: "{{revision}}" - hwlab.pikastech.local/trigger: pipelines-as-code -spec: - timeouts: - pipeline: 1h0m0s - taskRunTemplate: - serviceAccountName: hwlab-jd01-v03-tekton-runner - podTemplate: - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet - securityContext: - fsGroup: 1000 - pipelineRef: - name: hwlab-jd01-v03-ci-image-publish - workspaces: - - name: source - volumeClaimTemplate: - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 8Gi - - name: git-ssh - secret: - secretName: hwlab-git-ssh - params: - - name: git-url - value: git@github.com:pikasTech/HWLAB.git - - name: git-read-url - value: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git - - name: git-write-url - value: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git - - name: source-branch - value: v0.3 - - name: gitops-branch - value: v0.3-gitops - - name: lane - value: v03 - - name: catalog-path - value: deploy/artifact-catalog.jd01-v03.json - - name: image-tag-mode - value: full - - name: runtime-path - value: deploy/gitops/node/jd01/runtime-v03 - - name: source-commit - value: "{{revision}}" - - name: revision - value: "{{revision}}" - - name: registry-prefix - value: 127.0.0.1:5000/hwlab - - name: services - value: hwlab-cloud-api,hwlab-workbench-runtime,hwlab-user-billing,hwlab-project-management,hwlab-cloud-web,hwlab-gateway,hwlab-edge-proxy,hwlab-agent-skills - - name: base-image - value: 127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim - - name: build-cache-mode - value: registry - - name: runtime-ready-timeout-ms - value: "60000" diff --git a/.tekton/hwlab-nc01-v03-pac.yaml b/.tekton/hwlab-nc01-v03-pac.yaml index 0f884c0f..dc4b3261 100644 --- a/.tekton/hwlab-nc01-v03-pac.yaml +++ b/.tekton/hwlab-nc01-v03-pac.yaml @@ -9,7 +9,7 @@ metadata: pipelinesascode.tekton.dev/on-cel-expression: "event == 'push' && target_branch == 'v0.3' && node == 'NC01'" pipelinesascode.tekton.dev/max-keep-runs: "8" unidesk.ai/owning-config-ref: "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01" - unidesk.ai/effective-config-sha256: sha256:01b954f394174302b5ebce2de98a24d091be1c5922fbc188ef5af72ff1bb569c + unidesk.ai/effective-config-sha256: sha256:f42181ba9c079806968bb8374967a2bc710c7a87b56030d512cc00c004be126b unidesk.ai/source-artifact-renderer: hwlab-runtime-lane unidesk.ai/source-artifact-mode: remote-pipeline-annotation pipelinesascode.tekton.dev/pipeline: ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml @@ -52,16 +52,23 @@ spec: resources: requests: storage: 8Gi - - name: git-ssh + - name: gitea-auth secret: - secretName: hwlab-git-ssh + secretName: pac-gitea-hwlab-nc01-v03 + items: + - key: token + path: token params: - name: git-url value: "{{ repo_url }}" - name: git-read-url value: "{{ git_read_url }}" + - name: gitops-read-url + value: "{{ gitops_read_url }}" - name: git-write-url value: "{{ git_write_url }}" + - name: gitops-username + value: "{{ gitops_username }}" - name: source-branch value: "{{ source_branch }}" - name: gitops-branch diff --git a/ci/pipelines/hwlab-jd01-v03-ci-image-publish.yaml b/ci/pipelines/hwlab-jd01-v03-ci-image-publish.yaml deleted file mode 100644 index f141d69f..00000000 --- a/ci/pipelines/hwlab-jd01-v03-ci-image-publish.yaml +++ /dev/null @@ -1,3500 +0,0 @@ -{ - "apiVersion": "tekton.dev/v1", - "kind": "Pipeline", - "metadata": { - "name": "hwlab-jd01-v03-ci-image-publish", - "namespace": "hwlab-ci", - "labels": { - "app.kubernetes.io/part-of": "hwlab", - "hwlab.pikastech.local/gitops-target": "v03" - }, - "annotations": { - "hwlab.pikastech.local/source-config": "scripts/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": "git@github.com:pikasTech/HWLAB.git" - }, - { - "name": "git-read-url", - "type": "string", - "default": "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git" - }, - { - "name": "git-write-url", - "type": "string", - "default": "http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/HWLAB.git" - }, - { - "name": "source-branch", - "type": "string", - "default": "v0.3" - }, - { - "name": "gitops-branch", - "type": "string", - "default": "v0.3-gitops" - }, - { - "name": "lane", - "type": "string", - "default": "v03" - }, - { - "name": "catalog-path", - "type": "string", - "default": "deploy/artifact-catalog.v03.json" - }, - { - "name": "image-tag-mode", - "type": "string", - "default": "full" - }, - { - "name": "runtime-path", - "type": "string", - "default": "deploy/gitops/node/runtime-v03" - }, - { - "name": "revision", - "type": "string", - "description": "Full git commit SHA; the only source truth for image tags." - }, - { - "name": "registry-prefix", - "type": "string", - "default": "127.0.0.1:5000/hwlab" - }, - { - "name": "services", - "type": "string", - "default": "hwlab-cloud-api,hwlab-workbench-runtime,hwlab-user-billing,hwlab-project-management,hwlab-cloud-web,hwlab-gateway,hwlab-edge-proxy,hwlab-agent-skills" - }, - { - "name": "base-image", - "type": "string", - "default": "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim" - }, - { - "name": "build-cache-mode", - "type": "string", - "default": "registry" - }, - { - "name": "runtime-ready-timeout-ms", - "type": "string", - "default": "60000" - } - ], - "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": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"prepare-source\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"prepare-source\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nnode -e 'console.log(JSON.stringify({event:\"ci-base-image\",phase:\"prepare-source\",ok:true,node:process.version}))'\ngit_ssh_setup() {\n mkdir -p /root/.ssh\n cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa\n chmod 600 /root/.ssh/id_rsa\n timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n write_github_proxy_command\n 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'\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"git@github.com:\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"ssh://git@github.com/\"\n}\n\nwrite_github_proxy_command() {\n cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY'\n#!/usr/bin/env node\nimport net from \"node:net\";\n\nconst [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);\nconst proxyPort = Number.parseInt(proxyPortRaw || \"\", 10);\nconst targetPort = Number.parseInt(targetPortRaw || \"\", 10);\nif (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {\n console.error(\"usage: hwlab-github-proxy-connect \");\n process.exit(64);\n}\n\nconst socket = net.createConnection({ host: proxyHost, port: proxyPort });\nlet buffer = Buffer.alloc(0);\n\nsocket.setTimeout(10000, () => {\n console.error(\"proxy connect timeout \" + proxyHost + \":\" + proxyPort + \" -> \" + targetHost + \":\" + targetPort);\n socket.destroy();\n process.exit(65);\n});\n\nsocket.on(\"connect\", () => {\n socket.write(\"CONNECT \" + targetHost + \":\" + targetPort + \" HTTP/1.1\\r\\nHost: \" + targetHost + \":\" + targetPort + \"\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n\");\n});\n\nsocket.on(\"error\", (error) => {\n console.error(\"proxy connect failed: \" + error.message);\n process.exit(66);\n});\n\nfunction onData(chunk) {\n buffer = Buffer.concat([buffer, chunk]);\n const headerEnd = buffer.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1 && buffer.length < 8192) return;\n const head = buffer.slice(0, headerEnd + 4).toString(\"latin1\");\n const statusLine = head.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number.parseInt(statusLine.split(\" \")[1] || \"\", 10);\n if (!statusLine.startsWith(\"HTTP/1.\") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {\n console.error(\"proxy CONNECT rejected: \" + (statusLine || \"missing-status\"));\n socket.destroy();\n process.exit(67);\n }\n socket.off(\"data\", onData);\n socket.setTimeout(0);\n const rest = buffer.slice(headerEnd + 4);\n if (rest.length) process.stdout.write(rest);\n process.stdin.pipe(socket);\n socket.pipe(process.stdout);\n}\n\nsocket.on(\"data\", onData);\nsocket.on(\"close\", () => process.exit(0));\nNODE_PROXY\n chmod 0700 /tmp/hwlab-github-proxy-connect.mjs\n}\n\ngit_now_ms() {\n node -e 'console.log(Date.now())' 2>/dev/null || date +%s000\n}\n\ngit_timed() {\n phase=\"$1\"\n timeout_seconds=\"$2\"\n shift 2\n started_ms=\"$(git_now_ms)\"\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"started\",\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n set +e\n timeout \"$timeout_seconds\" \"$@\"\n status=\"$?\"\n set -e\n finished_ms=\"$(git_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n if [ \"$status\" -eq 0 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"succeeded\",\"durationMs\":'\"$duration_ms\"'}' >&2\n return 0\n fi\n if [ \"$status\" -eq 124 ] || [ \"$status\" -eq 137 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"reason\":\"timeout\",\"durationMs\":'\"$duration_ms\"',\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n else\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"exitCode\":'\"$status\"',\"durationMs\":'\"$duration_ms\"'}' >&2\n fi\n return \"$status\"\n}\n\ngit_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }\ngit_read_url=\"$(params.git-read-url)\"\nif git_url_requires_ssh \"$git_read_url\"; then\n 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\n git_ssh_setup\nelse\n echo '{\"event\":\"git-ssh-setup\",\"phase\":\"prepare-source\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-read-url\"}'\nfi\nrm -rf /workspace/source/repo\nsource_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed source-clone 180 git clone --branch \"$(params.source-branch)\" \"$git_read_url\" /workspace/source/repo\ncd /workspace/source/repo\ngit config --global --add safe.directory /workspace/source/repo\ngit remote set-url origin \"$git_read_url\"\ngit checkout \"$(params.revision)\"\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\ngit 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; }\nci_timing_emit source-clone succeeded \"$source_clone_started_ms\"\nprepare_source_dependencies_started_ms=\"$(ci_now_ms)\"\necho '{\"event\":\"prepare-source-dependencies\",\"status\":\"skipped\",\"reason\":\"renderer-dependency-install-disabled\",\"dependency\":\"yaml\"}'\nci_timing_emit prepare-source-dependencies succeeded \"$prepare_source_dependencies_started_ms\"\ncatalog_path=\"$(params.catalog-path)\"\nmkdir -p \"$(dirname \"$catalog_path\")\"\ncatalog_fetch_started_ms=\"$(ci_now_ms)\"\nif git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads \"$git_read_url\" \"$(params.gitops-branch)\" >/dev/null; then\n git remote set-url gitops-catalog \"$git_read_url\" 2>/dev/null || git remote add gitops-catalog \"$git_read_url\"\n git_timed catalog-fetch 90 git fetch --depth 1 gitops-catalog \"$(params.gitops-branch)\" >/dev/null || true\n if git cat-file -e FETCH_HEAD:\"$catalog_path\" 2>/dev/null; then\n git show FETCH_HEAD:\"$catalog_path\" > /tmp/hwlab-gitops-artifact-catalog.json\n if node --input-type=module - /tmp/hwlab-gitops-artifact-catalog.json deploy/deploy.yaml \"$(params.services)\" <<'NODE'\nimport fs from \"node:fs\";\nconst [catalogPath, deployPath, selectedServices] = process.argv.slice(2);\nconst ids = (doc) => (doc.services || []).map((service) => service.serviceId).filter(Boolean);\nconst topLevelServiceIds = (text) => {\n const values = [];\n let inServices = false;\n for (const line of text.split(/\\r?\\n/u)) {\n if (/^services:\\s*$/u.test(line)) { inServices = true; continue; }\n if (!inServices) continue;\n if (/^\\S/u.test(line)) break;\n const match = line.match(/^\\s*-\\s+serviceId:\\s*['\"]?([^'\"#\\s]+)['\"]?/u);\n if (match) values.push(match[1]);\n }\n return values;\n};\nconst selected = (selectedServices || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst uniqueSorted = (items) => [...new Set(items)].sort();\nconst gitops = JSON.parse(fs.readFileSync(catalogPath, \"utf8\"));\nconst expected = selected.length ? selected : topLevelServiceIds(fs.readFileSync(deployPath, \"utf8\"));\nconst actual = ids(gitops);\nconst expectedSet = uniqueSorted(expected);\nconst actualSet = uniqueSorted(actual);\nconst sameServices = expectedSet.length === actualSet.length && expectedSet.every((item, index) => item === actualSet[index]);\nif (!sameServices) {\n const missing = expectedSet.filter((item) => !actualSet.includes(item));\n const extra = actualSet.filter((item) => !expectedSet.includes(item));\n console.error(JSON.stringify({ event: \"gitops-artifact-catalog\", phase: \"prepare-source\", status: \"ignored-stale\", reason: \"service-ids-mismatch\", expected, actual, missing, extra }));\n process.exit(42);\n}\nNODE\n then\n cp /tmp/hwlab-gitops-artifact-catalog.json \"$catalog_path\"\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"loaded\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'\n else\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"ignored-stale\",\"reason\":\"service-ids-mismatch\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'\n fi\n else\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"missing-on-gitops-branch\"}'\n fi\nelse\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"gitops-branch-missing\"}'\nfi\nif [ ! -s \"$catalog_path\" ] && [ \"$(params.lane)\" = \"v02\" ]; then\n node --input-type=module - \"$catalog_path\" \"$(params.revision)\" \"$(params.registry-prefix)\" \"$(params.image-tag-mode)\" \"$(params.services)\" <<'NODE'\nimport fs from 'node:fs';\nimport path from 'node:path';\nconst [catalogPath, revision, registryPrefix, imageTagMode, selectedServices] = process.argv.slice(2);\nconst topLevelServiceIds = (text) => {\n const values = [];\n let inServices = false;\n for (const line of text.split(/\\r?\\n/u)) {\n if (/^services:\\s*$/u.test(line)) { inServices = true; continue; }\n if (!inServices) continue;\n if (/^\\S/u.test(line)) break;\n const match = line.match(/^\\s*-\\s+serviceId:\\s*['\"]?([^'\"#\\s]+)['\"]?/u);\n if (match) values.push(match[1]);\n }\n return values;\n};\nconst tag = imageTagMode === 'full' ? revision : revision.slice(0, 7);\nconst namespace = 'hwlab-v02';\nconst selected = (selectedServices || '').split(',').filter(Boolean);\nconst serviceIds = selected.length ? selected : topLevelServiceIds(fs.readFileSync('deploy/deploy.yaml', 'utf8'));\nconst services = serviceIds.map((serviceId) => {\n const service = { serviceId, profile: 'v02', namespace, commitId: tag, sourceCommitId: revision, image: `${registryPrefix}/${serviceId}:${tag}`, imageTag: tag, digest: null, buildBackend: 'contract-skeleton' };\n return service;\n});\nfs.mkdirSync(path.dirname(catalogPath), { recursive: true });\nfs.writeFileSync(catalogPath, JSON.stringify({ catalogVersion: 'v1', kind: 'hwlab-artifact-catalog', environment: 'v02', profile: 'v02', namespace, endpoint: \"https://hwlab.74-48-78-17.nip.io\", commitId: tag, artifactState: 'contract-skeleton', publish: { registryPrefix, sourceCommitId: revision, imageTag: tag, publishedAt: null }, allowedProfiles: ['v02'], forbiddenProfiles: ['dev', 'prod'], services }, null, 2) + '\\n');\nNODE\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seeded-v02-skeleton\"}'\nfi\nci_timing_emit catalog-fetch succeeded \"$catalog_fetch_started_ms\"\necho 'node prepare-source complete; validation task count=3'\n" - } - ] - }, - "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)" - } - ] - }, - { - "name": "repo-reports-guard", - "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": "repo-reports-guard", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "script": "#!/bin/sh\nset -eu\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nnode scripts/repo-reports-guard.mjs\n" - } - ] - }, - "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)" - } - ] - }, - { - "name": "node-contract-check", - "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": "node-contract-check", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "script": "#!/bin/sh\nset -eu\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nnode --check scripts/gitops-render.mjs\nnode --input-type=module <<'NODE'\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\n\nconst root = process.cwd();\nconst ciFileName = \"CI\" + \".json\";\nconst forbiddenCiFragments = [\n \"ci\" + \"-json\",\n \"HWLAB_NODE_TEKTON\" + \"_SINGLE_IMAGE_PUBLISH\",\n \"single\" + \"-dind\",\n \"docker\" + \":29\",\n \"DOCKER\" + \"_HOST\",\n \"Docker\" + \"-in-Docker\",\n \"D\" + \"IND\",\n \"docker\" + \" push\",\n \"--build\" + \"-backend\",\n \"HWLAB_ARTIFACT\" + \"_BUILD_BACKEND\",\n \"--legacy\" + \"-source-images\",\n \"HWLAB_NODE_USE_DEPLOY_IMAGES\" + \"=0\"\n];\nconst scanTargets = [\n \"scripts/gitops-render.mjs\",\n \"scripts/src/runtime-lane.ts\",\n \"scripts/artifact-publish.mjs\",\n \"scripts/src/ci-plan-lib.mjs\",\n \"deploy/gitops/node/tekton\"\n];\nconst skipDirs = new Set([\".git\", \"node_modules\", \".worktree\"]);\n\nfunction walkFiles(relativePath) {\n const absolutePath = path.join(root, relativePath);\n let stat;\n try {\n stat = statSync(absolutePath);\n } catch {\n return [];\n }\n if (stat.isFile()) return [relativePath];\n if (!stat.isDirectory()) return [];\n const files = [];\n for (const entry of readdirSync(absolutePath, { withFileTypes: true })) {\n if (entry.isDirectory() && skipDirs.has(entry.name)) continue;\n files.push(...walkFiles(path.join(relativePath, entry.name)));\n }\n return files;\n}\n\nconst ciFiles = walkFiles(\".\").filter((filePath) => path.basename(filePath) === ciFileName);\nconst violations = [];\nfor (const filePath of scanTargets.flatMap(walkFiles)) {\n const text = readFileSync(path.join(root, filePath), \"utf8\");\n for (const fragment of forbiddenCiFragments) {\n if (text.includes(fragment)) violations.push({ filePath, fragment });\n }\n}\nif (ciFiles.length || violations.length) {\n console.error(JSON.stringify({ status: \"failed\", ciFiles, violations }, null, 2));\n process.exit(1);\n}\nNODE\nnode --check scripts/artifact-publish.mjs\n" - } - ] - }, - "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)" - } - ] - }, - { - "name": "codex-api-forwarder-check", - "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": "codex-api-forwarder-check", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "script": "#!/bin/sh\nset -eu\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nnode scripts/run-bun.mjs test cmd/hwlab-codex-api-responses-forwarder/main.test.ts\n" - } - ] - }, - "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)" - } - ] - }, - { - "name": "plan-artifacts", - "runAfter": [ - "repo-reports-guard", - "node-contract-check", - "codex-api-forwarder-check" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "taskSpec": { - "params": [ - { - "name": "git-url" - }, - { - "name": "git-read-url" - }, - { - "name": "gitops-branch" - }, - { - "name": "lane" - }, - { - "name": "revision" - }, - { - "name": "catalog-path" - }, - { - "name": "registry-prefix" - }, - { - "name": "services" - } - ], - "results": [ - { - "name": "affected-hwlab-cloud-api", - "description": "hwlab-cloud-api rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-cloud-api", - "description": "hwlab-cloud-api image build required according to ci-plan" - }, - { - "name": "affected-hwlab-workbench-runtime", - "description": "hwlab-workbench-runtime rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-workbench-runtime", - "description": "hwlab-workbench-runtime image build required according to ci-plan" - }, - { - "name": "affected-hwlab-user-billing", - "description": "hwlab-user-billing rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-user-billing", - "description": "hwlab-user-billing image build required according to ci-plan" - }, - { - "name": "affected-hwlab-project-management", - "description": "hwlab-project-management rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-project-management", - "description": "hwlab-project-management image build required according to ci-plan" - }, - { - "name": "affected-hwlab-cloud-web", - "description": "hwlab-cloud-web rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-cloud-web", - "description": "hwlab-cloud-web image build required according to ci-plan" - }, - { - "name": "affected-hwlab-gateway", - "description": "hwlab-gateway rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-gateway", - "description": "hwlab-gateway image build required according to ci-plan" - }, - { - "name": "affected-hwlab-edge-proxy", - "description": "hwlab-edge-proxy rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-edge-proxy", - "description": "hwlab-edge-proxy image build required according to ci-plan" - }, - { - "name": "affected-hwlab-agent-skills", - "description": "hwlab-agent-skills rollout affected according to ci-plan" - }, - { - "name": "build-hwlab-agent-skills", - "description": "hwlab-agent-skills image build required according to ci-plan" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "steps": [ - { - "name": "plan", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "env": [ - { - "name": "HWLAB_SELECTED_SERVICES", - "value": "$(params.services)" - }, - { - "name": "HWLAB_DEV_REGISTRY_K8S_NATIVE", - "value": "1" - }, - { - "name": "HWLAB_TEKTON_PIPELINERUN", - "value": "$(context.pipelineRun.name)" - }, - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "script": "#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"plan-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps/yaml\" ] && [ ! -e node_modules/yaml ]; then\n mkdir -p node_modules\n ln -s \"$ci_node_deps/yaml\" node_modules/yaml\nfi\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nHWLAB_CATALOG_PATH=\"$(params.catalog-path)\" HWLAB_GIT_URL=\"$(params.git-url)\" HWLAB_GIT_READ_URL=\"$(params.git-read-url)\" HWLAB_GITOPS_BRANCH=\"$(params.gitops-branch)\" HWLAB_SERVICES=\"$(params.services)\" node scripts/ci/restore-artifact-catalog.mjs\nnode scripts/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)\" --verify-reuse-registry > /workspace/source/ci-plan.json\nnode - <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(\"/workspace/source/ci-plan.json\", \"utf8\"));\nconst selected = String(process.env.HWLAB_SELECTED_SERVICES || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst allServices = selected.length > 0 ? selected : [\"hwlab-cloud-api\",\"hwlab-cloud-web\",\"hwlab-gateway\",\"hwlab-edge-proxy\",\"hwlab-agent-skills\"];\nconst affected = new Set(plan.affectedServices || []);\nconst plannedBuildServices = Array.isArray(plan.buildServices) ? new Set(plan.buildServices) : null;\nconst selectedSet = new Set(selected);\nconst byService = new Map((plan.services || []).map((service) => [service.serviceId, service]));\nconst entries = allServices.map((serviceId) => {\n const service = byService.get(serviceId) || {};\n const serviceSelected = selectedSet.has(serviceId);\n const rolloutAffected = serviceSelected && affected.has(serviceId);\n const envReuse = service.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.envReuse === true;\n const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected));\n return {\n serviceId,\n selected: serviceSelected,\n affected: rolloutAffected,\n buildRequired,\n rolloutAffected,\n runtimeMode: service.runtimeMode || \"service-image\",\n envChanged: service.envChanged ?? null,\n codeChanged: service.codeChanged ?? null\n };\n});\nfor (const entry of entries) {\n fs.writeFileSync(\"/tekton/results/affected-\" + entry.serviceId, entry.affected ? \"true\" : \"false\");\n fs.writeFileSync(\"/tekton/results/build-\" + entry.serviceId, entry.buildRequired ? \"true\" : \"false\");\n}\nfs.writeFileSync(\"/workspace/source/affected-services.json\", JSON.stringify({\n sourceCommitId: plan.sourceCommitId,\n affectedServices: plan.affectedServices || [],\n rolloutServices: plan.rolloutServices || plan.affectedServices || [],\n buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId),\n rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [],\n reusedServices: plan.reusedServices || [],\n serviceReusedCount: plan.serviceReusedCount || 0,\n imageBuildSkippedServices: plan.imageBuildSkippedServices || [],\n buildSkippedCount: plan.buildSkippedCount || 0,\n artifactCatalog: plan.artifactCatalog || null,\n envArtifactGroups: plan.envArtifactGroups || [],\n changedPathSummary: plan.changedPathSummary || null,\n ciCdPlan: plan.ciCdPlan || null,\n services: plan.services || [],\n entries\n}, null, 2) + String.fromCharCode(10));\nconsole.log(JSON.stringify({ event: \"g14-ci-plan\", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [], reusedServices: plan.reusedServices || [], serviceReusedCount: plan.serviceReusedCount || 0, imageBuildSkippedServices: plan.imageBuildSkippedServices || [], buildSkippedCount: plan.buildSkippedCount || 0, artifactCatalog: plan.artifactCatalog || null, noImageBuildReason: plan.ciCdPlan?.noImageBuildReason || null, envArtifactGroups: plan.envArtifactGroups || [] }));\nNODE\n" - } - ] - }, - "params": [ - { - "name": "git-url", - "value": "$(params.git-url)" - }, - { - "name": "git-read-url", - "value": "$(params.git-read-url)" - }, - { - "name": "gitops-branch", - "value": "$(params.gitops-branch)" - }, - { - "name": "lane", - "value": "$(params.lane)" - }, - { - "name": "revision", - "value": "$(params.revision)" - }, - { - "name": "catalog-path", - "value": "$(params.catalog-path)" - }, - { - "name": "registry-prefix", - "value": "$(params.registry-prefix)" - }, - { - "name": "services", - "value": "$(params.services)" - } - ] - }, - { - "name": "build-hwlab-cloud-api", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-cloud-api)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-cloud-api status" - }, - { - "name": "service-id", - "description": "hwlab-cloud-api service-id" - }, - { - "name": "image", - "description": "hwlab-cloud-api image" - }, - { - "name": "image-tag", - "description": "hwlab-cloud-api image-tag" - }, - { - "name": "digest", - "description": "hwlab-cloud-api digest" - }, - { - "name": "repository-digest", - "description": "hwlab-cloud-api repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-cloud-api source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-cloud-api component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-cloud-api environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-cloud-api code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-cloud-api runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-cloud-api boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-cloud-api boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-cloud-api boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-cloud-api build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-cloud-api build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-cloud-api reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-cloud-api" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "build-hwlab-workbench-runtime", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-workbench-runtime)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-workbench-runtime status" - }, - { - "name": "service-id", - "description": "hwlab-workbench-runtime service-id" - }, - { - "name": "image", - "description": "hwlab-workbench-runtime image" - }, - { - "name": "image-tag", - "description": "hwlab-workbench-runtime image-tag" - }, - { - "name": "digest", - "description": "hwlab-workbench-runtime digest" - }, - { - "name": "repository-digest", - "description": "hwlab-workbench-runtime repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-workbench-runtime source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-workbench-runtime component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-workbench-runtime environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-workbench-runtime code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-workbench-runtime runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-workbench-runtime boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-workbench-runtime boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-workbench-runtime boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-workbench-runtime build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-workbench-runtime build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-workbench-runtime reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-workbench-runtime" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "build-hwlab-user-billing", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-user-billing)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-user-billing status" - }, - { - "name": "service-id", - "description": "hwlab-user-billing service-id" - }, - { - "name": "image", - "description": "hwlab-user-billing image" - }, - { - "name": "image-tag", - "description": "hwlab-user-billing image-tag" - }, - { - "name": "digest", - "description": "hwlab-user-billing digest" - }, - { - "name": "repository-digest", - "description": "hwlab-user-billing repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-user-billing source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-user-billing component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-user-billing environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-user-billing code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-user-billing runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-user-billing boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-user-billing boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-user-billing boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-user-billing build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-user-billing build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-user-billing reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-user-billing" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "build-hwlab-project-management", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-project-management)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-project-management status" - }, - { - "name": "service-id", - "description": "hwlab-project-management service-id" - }, - { - "name": "image", - "description": "hwlab-project-management image" - }, - { - "name": "image-tag", - "description": "hwlab-project-management image-tag" - }, - { - "name": "digest", - "description": "hwlab-project-management digest" - }, - { - "name": "repository-digest", - "description": "hwlab-project-management repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-project-management source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-project-management component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-project-management environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-project-management code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-project-management runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-project-management boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-project-management boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-project-management boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-project-management build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-project-management build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-project-management reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-project-management" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "build-hwlab-cloud-web", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-cloud-web)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-cloud-web status" - }, - { - "name": "service-id", - "description": "hwlab-cloud-web service-id" - }, - { - "name": "image", - "description": "hwlab-cloud-web image" - }, - { - "name": "image-tag", - "description": "hwlab-cloud-web image-tag" - }, - { - "name": "digest", - "description": "hwlab-cloud-web digest" - }, - { - "name": "repository-digest", - "description": "hwlab-cloud-web repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-cloud-web source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-cloud-web component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-cloud-web environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-cloud-web code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-cloud-web runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-cloud-web boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-cloud-web boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-cloud-web boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-cloud-web build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-cloud-web build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-cloud-web reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-cloud-web" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "build-hwlab-gateway", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-gateway)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-gateway status" - }, - { - "name": "service-id", - "description": "hwlab-gateway service-id" - }, - { - "name": "image", - "description": "hwlab-gateway image" - }, - { - "name": "image-tag", - "description": "hwlab-gateway image-tag" - }, - { - "name": "digest", - "description": "hwlab-gateway digest" - }, - { - "name": "repository-digest", - "description": "hwlab-gateway repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-gateway source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-gateway component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-gateway environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-gateway code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-gateway runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-gateway boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-gateway boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-gateway boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-gateway build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-gateway build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-gateway reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-gateway" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "build-hwlab-edge-proxy", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-edge-proxy)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-edge-proxy status" - }, - { - "name": "service-id", - "description": "hwlab-edge-proxy service-id" - }, - { - "name": "image", - "description": "hwlab-edge-proxy image" - }, - { - "name": "image-tag", - "description": "hwlab-edge-proxy image-tag" - }, - { - "name": "digest", - "description": "hwlab-edge-proxy digest" - }, - { - "name": "repository-digest", - "description": "hwlab-edge-proxy repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-edge-proxy source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-edge-proxy component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-edge-proxy environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-edge-proxy code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-edge-proxy runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-edge-proxy boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-edge-proxy boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-edge-proxy boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-edge-proxy build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-edge-proxy build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-edge-proxy reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-edge-proxy" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "build-hwlab-agent-skills", - "runAfter": [ - "plan-artifacts" - ], - "workspaces": [ - { - "name": "source", - "workspace": "source" - } - ], - "when": [ - { - "input": "$(tasks.plan-artifacts.results.build-hwlab-agent-skills)", - "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" - }, - { - "name": "build-cache-mode" - } - ], - "results": [ - { - "name": "status", - "description": "hwlab-agent-skills status" - }, - { - "name": "service-id", - "description": "hwlab-agent-skills service-id" - }, - { - "name": "image", - "description": "hwlab-agent-skills image" - }, - { - "name": "image-tag", - "description": "hwlab-agent-skills image-tag" - }, - { - "name": "digest", - "description": "hwlab-agent-skills digest" - }, - { - "name": "repository-digest", - "description": "hwlab-agent-skills repository-digest" - }, - { - "name": "source-commit-id", - "description": "hwlab-agent-skills source-commit-id" - }, - { - "name": "component-input-hash", - "description": "hwlab-agent-skills component-input-hash" - }, - { - "name": "environment-input-hash", - "description": "hwlab-agent-skills environment-input-hash" - }, - { - "name": "code-input-hash", - "description": "hwlab-agent-skills code-input-hash" - }, - { - "name": "runtime-mode", - "description": "hwlab-agent-skills runtime-mode" - }, - { - "name": "boot-repo", - "description": "hwlab-agent-skills boot-repo" - }, - { - "name": "boot-commit", - "description": "hwlab-agent-skills boot-commit" - }, - { - "name": "boot-sh", - "description": "hwlab-agent-skills boot-sh" - }, - { - "name": "build-created-at", - "description": "hwlab-agent-skills build-created-at" - }, - { - "name": "build-backend", - "description": "hwlab-agent-skills build-backend" - }, - { - "name": "reused-from", - "description": "hwlab-agent-skills reused-from" - } - ], - "workspaces": [ - { - "name": "source" - } - ], - "volumes": [ - { - "name": "service-work", - "emptyDir": {} - }, - { - "name": "buildkit-bin", - "emptyDir": {} - }, - { - "name": "buildkit-run", - "emptyDir": {} - } - ], - "sidecars": [ - { - "name": "buildkitd", - "image": "moby/buildkit:rootless", - "args": [ - "--addr", - "unix:///workspace/buildkit-run/buildkitd.sock", - "--oci-worker-no-process-sandbox" - ], - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000, - "allowPrivilegeEscalation": true, - "appArmorProfile": { - "type": "Unconfined" - }, - "seccompProfile": { - "type": "Unconfined" - } - } - } - ], - "steps": [ - { - "name": "prepare-buildkit-client", - "image": "moby/buildkit:rootless", - "securityContext": { - "runAsUser": 0, - "runAsGroup": 0 - }, - "volumeMounts": [ - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - } - ], - "script": "#!/bin/sh\nset -eu\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" - }, - { - "name": "publish", - "image": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "securityContext": { - "runAsUser": 1000, - "runAsGroup": 1000 - }, - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "volumeMounts": [ - { - "name": "service-work", - "mountPath": "/workspace/service-work" - }, - { - "name": "buildkit-bin", - "mountPath": "/workspace/buildkit-bin" - }, - { - "name": "buildkit-run", - "mountPath": "/workspace/buildkit-run" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" - } - ] - }, - "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": "hwlab-agent-skills" - }, - { - "name": "base-image", - "value": "$(params.base-image)" - }, - { - "name": "build-cache-mode", - "value": "$(params.build-cache-mode)" - } - ] - }, - { - "name": "collect-artifacts", - "runAfter": [ - "build-hwlab-cloud-api", - "build-hwlab-workbench-runtime", - "build-hwlab-user-billing", - "build-hwlab-project-management", - "build-hwlab-cloud-web", - "build-hwlab-gateway", - "build-hwlab-edge-proxy", - "build-hwlab-agent-skills" - ], - "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": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "script": "#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"collect-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-collect\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nconst willRunGitopsPromote = plan.ciCdPlan && plan.ciCdPlan.willRunGitopsPromote === true;\nprocess.exit(!willRunGitopsPromote && buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n rm -f /workspace/source/dev-artifacts.json\n echo '{\"event\":\"collect-artifacts\",\"status\":\"skipped\",\"reason\":\"no-build-no-rollout-plan\"}'\n exit 0\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/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 --ci-plan-path /workspace/source/affected-services.json\nnode 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\n" - } - ] - }, - "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)" - } - ] - }, - { - "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": "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1", - "env": [ - { - "name": "HTTP_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "HTTPS_PROXY", - "value": "http://127.0.0.1:10808" - }, - { - "name": "ALL_PROXY", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "NO_PROXY", - "value": "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" - }, - { - "name": "http_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "https_proxy", - "value": "http://127.0.0.1:10808" - }, - { - "name": "all_proxy", - "value": "socks5h://127.0.0.1:10808" - }, - { - "name": "no_proxy", - "value": "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" - } - ], - "script": "#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"gitops-promote\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"gitops-promote\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apt\"}'\nfor 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\ngit_ssh_setup() {\n mkdir -p /root/.ssh\n cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa\n chmod 600 /root/.ssh/id_rsa\n timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n write_github_proxy_command\n 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'\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"git@github.com:\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"ssh://git@github.com/\"\n}\n\nwrite_github_proxy_command() {\n cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY'\n#!/usr/bin/env node\nimport net from \"node:net\";\n\nconst [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);\nconst proxyPort = Number.parseInt(proxyPortRaw || \"\", 10);\nconst targetPort = Number.parseInt(targetPortRaw || \"\", 10);\nif (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {\n console.error(\"usage: hwlab-github-proxy-connect \");\n process.exit(64);\n}\n\nconst socket = net.createConnection({ host: proxyHost, port: proxyPort });\nlet buffer = Buffer.alloc(0);\n\nsocket.setTimeout(10000, () => {\n console.error(\"proxy connect timeout \" + proxyHost + \":\" + proxyPort + \" -> \" + targetHost + \":\" + targetPort);\n socket.destroy();\n process.exit(65);\n});\n\nsocket.on(\"connect\", () => {\n socket.write(\"CONNECT \" + targetHost + \":\" + targetPort + \" HTTP/1.1\\r\\nHost: \" + targetHost + \":\" + targetPort + \"\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n\");\n});\n\nsocket.on(\"error\", (error) => {\n console.error(\"proxy connect failed: \" + error.message);\n process.exit(66);\n});\n\nfunction onData(chunk) {\n buffer = Buffer.concat([buffer, chunk]);\n const headerEnd = buffer.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1 && buffer.length < 8192) return;\n const head = buffer.slice(0, headerEnd + 4).toString(\"latin1\");\n const statusLine = head.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number.parseInt(statusLine.split(\" \")[1] || \"\", 10);\n if (!statusLine.startsWith(\"HTTP/1.\") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {\n console.error(\"proxy CONNECT rejected: \" + (statusLine || \"missing-status\"));\n socket.destroy();\n process.exit(67);\n }\n socket.off(\"data\", onData);\n socket.setTimeout(0);\n const rest = buffer.slice(headerEnd + 4);\n if (rest.length) process.stdout.write(rest);\n process.stdin.pipe(socket);\n socket.pipe(process.stdout);\n}\n\nsocket.on(\"data\", onData);\nsocket.on(\"close\", () => process.exit(0));\nNODE_PROXY\n chmod 0700 /tmp/hwlab-github-proxy-connect.mjs\n}\n\ngit_now_ms() {\n node -e 'console.log(Date.now())' 2>/dev/null || date +%s000\n}\n\ngit_timed() {\n phase=\"$1\"\n timeout_seconds=\"$2\"\n shift 2\n started_ms=\"$(git_now_ms)\"\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"started\",\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n set +e\n timeout \"$timeout_seconds\" \"$@\"\n status=\"$?\"\n set -e\n finished_ms=\"$(git_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n if [ \"$status\" -eq 0 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"succeeded\",\"durationMs\":'\"$duration_ms\"'}' >&2\n return 0\n fi\n if [ \"$status\" -eq 124 ] || [ \"$status\" -eq 137 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"reason\":\"timeout\",\"durationMs\":'\"$duration_ms\"',\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n else\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"exitCode\":'\"$status\"',\"durationMs\":'\"$duration_ms\"'}' >&2\n fi\n return \"$status\"\n}\n\ngit_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }\nlane=\"$(params.lane)\"\ncase \"$lane\" in\n v[0-9][0-9]*) runtime_lane=true ;;\n *) runtime_lane=false ;;\nesac\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\nelse\n printf 'true' > /tekton/results/runtime-ready-required\nfi\nsource_head_url_for_setup=\"$(params.git-url)\"\ngitops_read_url_for_setup=\"$(params.git-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url_for_setup=\"$(params.git-read-url)\"\n gitops_read_url_for_setup=\"$(params.git-read-url)\"\nfi\ngitops_write_url_for_setup=\"$(params.git-write-url)\"\nif 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\n 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\n git_ssh_setup\nelse\n echo '{\"event\":\"git-ssh-setup\",\"phase\":\"gitops-promote\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-url\"}'\nfi\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\n\ncheck_source_head() {\n phase=\"$1\"\n expected=\"${2:-$(params.revision)}\"\n source_head_url=\"$(params.git-url)\"\n if [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url=\"$(params.git-read-url)\"\n fi\n latest_file=\"$(mktemp)\"\n if ! git_timed \"source-head-$phase\" 45 git ls-remote \"$source_head_url\" \"refs/heads/$(params.source-branch)\" > \"$latest_file\"; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-check-failed\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n latest=\"$(cut -f1 \"$latest_file\" | head -n 1)\"\n if [ -z \"$latest\" ]; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-unresolved\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n if [ \"$latest\" != \"$expected\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"status\":\"skipped-stale-source\",\"verdict\":\"superseded\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"expectedRevision\":\"'\"$expected\"'\",\"latestRevision\":\"'\"$latest\"'\"}'\n exit 0\n fi\n}\n\ncheck_source_head before-render\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nconst willRunGitopsPromote = plan.ciCdPlan && plan.ciCdPlan.willRunGitopsPromote === true;\nprocess.exit(!willRunGitopsPromote && buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"event\":\"gitops-promote\",\"status\":\"skipped\",\"reason\":\"no-build-no-rollout-plan\"}'\n exit 0\nfi\ngit config --global user.name \"HWLAB node GitOps Bot\"\ngit config --global user.email \"hwlab-node-gitops-bot@users.noreply.github.com\"\ncatalog_path=\"$(params.catalog-path)\"\nruntime_path=\"$(params.runtime-path)\"\ngitops_root_from_runtime_path() {\n path=\"$1\"\n case \"$path\" in\n */*) printf '%s\n' \"${path%/*}\" ;;\n *) printf '.\n' ;;\n esac\n}\ngitops_root=\"$(gitops_root_from_runtime_path \"$runtime_path\")\"\n\nargo_hard_refresh_runtime_lane() {\n if [ \"$runtime_lane\" != \"true\" ]; then return 0; fi\n ARGO_APPLICATION=\"hwlab-node-$lane\" ARGO_FIELD_MANAGER=\"hwlab-$lane-gitops-promote\" node <<'NODE'\nconst fs = require(\"node:fs\");\nconst https = require(\"node:https\");\n\nconst host = process.env.KUBERNETES_SERVICE_HOST;\nconst port = process.env.KUBERNETES_SERVICE_PORT || \"443\";\nconst startedAt = Date.now();\nconst application = process.env.ARGO_APPLICATION || \"hwlab-node-v02\";\nconst fieldManager = process.env.ARGO_FIELD_MANAGER || \"hwlab-v02-gitops-promote\";\nconst pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;\nconst taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;\nconst task = process.env.HWLAB_TEKTON_TASK || null;\nconst revision = process.env.HWLAB_SOURCE_REVISION || null;\n\nfunction emit(payload) {\n console.log(JSON.stringify({\n event: \"node-cicd-timing\",\n schemaVersion: \"v1\",\n stage: \"argo-hard-refresh\",\n pipelineRun,\n taskRun,\n task,\n revision,\n application,\n source: \"scripts/gitops-render.mjs\",\n at: new Date().toISOString(),\n ...payload\n }));\n}\n\nfunction safeJson(text) {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n\nfunction request(method, path, body, contentType = \"application/merge-patch+json\") {\n const token = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/token\", \"utf8\");\n const ca = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\");\n const payload = body ? JSON.stringify(body) : \"\";\n const headers = { Authorization: \"Bearer \" + token };\n if (payload) {\n headers[\"Content-Type\"] = contentType;\n headers[\"Content-Length\"] = Buffer.byteLength(payload);\n }\n return new Promise((resolve, reject) => {\n const req = https.request({ host, port, method, path, ca, headers }, (res) => {\n let data = \"\";\n res.setEncoding(\"utf8\");\n res.on(\"data\", (chunk) => { data += chunk; });\n res.on(\"end\", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) }));\n });\n req.on(\"error\", reject);\n if (payload) req.write(payload);\n req.end();\n });\n}\n\n(async () => {\n if (!host) {\n emit({ status: \"skipped\", reason: \"kubernetes-service-host-missing\", durationMs: Date.now() - startedAt });\n return;\n }\n const response = await request(\n \"PATCH\",\n \"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/\" + encodeURIComponent(application) + \"?fieldManager=\" + encodeURIComponent(fieldManager),\n { metadata: { annotations: { \"argocd.argoproj.io/refresh\": \"hard\" } } }\n );\n if (response.statusCode >= 200 && response.statusCode < 300) {\n emit({ status: \"succeeded\", durationMs: Date.now() - startedAt, statusCode: response.statusCode });\n return;\n }\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-failed\", durationMs: Date.now() - startedAt, statusCode: response.statusCode, body: response.body.slice(0, 1000) });\n})().catch((error) => {\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-error\", durationMs: Date.now() - startedAt, error: error.message });\n});\nNODE\n}\n\nif [ -s /workspace/source/dev-artifacts.json ]; then\n 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\nfi\ngitops_render_started_ms=\"$(ci_now_ms)\"\nnode scripts/run-bun.mjs scripts/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)\" --gitops-root \"$gitops_root\" --out \"$gitops_root\" --registry-prefix \"$(params.registry-prefix)\" --use-deploy-images\nnode scripts/run-bun.mjs scripts/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)\" --gitops-root \"$gitops_root\" --out \"$gitops_root\" --registry-prefix \"$(params.registry-prefix)\" --use-deploy-images --check\nci_timing_emit gitops-render succeeded \"$gitops_render_started_ms\"\ncheck_source_head before-push\nworkdir=\"$(mktemp -d)\"\ngitops_read_url=\"$(params.git-url)\"\ngitops_write_url=\"$(params.git-write-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n gitops_read_url=\"$(params.git-read-url)\"\nfi\ngitops_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-clone 180 git clone --no-checkout \"$gitops_read_url\" \"$workdir/gitops\"\ncd \"$workdir/gitops\"\ngit remote set-url origin \"$gitops_write_url\"\nold_runtime_snapshot=\"$workdir/old-runtime-snapshot\"\nif git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin \"$(params.gitops-branch)\" >/dev/null; then\n git_timed gitops-fetch 90 git fetch origin \"$(params.gitops-branch)\"\n git checkout -B \"$(params.gitops-branch)\" \"origin/$(params.gitops-branch)\"\n if [ \"$runtime_lane\" = \"true\" ] && [ -d \"$runtime_path\" ]; then\n mkdir -p \"$old_runtime_snapshot\"\n cp -a \"$runtime_path\"/. \"$old_runtime_snapshot\"/\n fi\n if [ \"$runtime_lane\" = \"true\" ]; then rm -rf \"$runtime_path\" \"$catalog_path\"; else rm -rf deploy/gitops/node \"$catalog_path\"; fi\nelse\n git checkout --orphan \"$(params.gitops-branch)\"\n git rm -rf . >/dev/null 2>&1 || true\nfi\nci_timing_emit gitops-clone succeeded \"$gitops_clone_started_ms\"\nmkdir -p \"$(dirname \"$runtime_path\")\" \"$(dirname \"$catalog_path\")\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n cp -a \"/workspace/source/repo/$runtime_path\" \"$runtime_path\"\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" \"$runtime_path\"\nelse\n mkdir -p deploy/gitops\n cp -a /workspace/source/repo/deploy/gitops/node deploy/gitops/node\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" deploy/gitops/node\nfi\nif [ \"$runtime_lane\" = \"true\" ] && [ -s /workspace/source/affected-services.json ]; then\n runtime_noop_decision=\"$(node - \"$runtime_path\" \"$old_runtime_snapshot\" /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2);\n\nfunction readJson(filePath) {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n}\n\nfunction stable(value) {\n if (Array.isArray(value)) return \"[\" + value.map(stable).join(\",\") + \"]\";\n if (value && typeof value === \"object\") {\n return \"{\" + Object.keys(value).sort().map((key) => JSON.stringify(key) + \":\" + stable(value[key])).join(\",\") + \"}\";\n }\n return JSON.stringify(value);\n}\n\nfunction scrub(value) {\n if (Array.isArray(value)) return value.map(scrub);\n if (!value || typeof value !== \"object\") return value;\n const result = {};\n for (const [key, child] of Object.entries(value)) {\n if (key === \"hwlab.pikastech.local/source-commit\" ||\n key === \"hwlab.pikastech.local/artifact-source-commit\" ||\n key === \"hwlab.pikastech.local/boot-commit\" ||\n key === \"sourceCommitId\" ||\n key === \"bootCommit\") {\n result[key] = \"\";\n continue;\n }\n const envName = String(value.name || \"\");\n if (key === \"value\" && /^HWLAB_.*COMMIT/.test(envName)) {\n result[key] = \"\";\n continue;\n }\n result[key] = scrub(child);\n }\n return result;\n}\n\nfunction listFiles(rootDir) {\n if (!rootDir || !fs.existsSync(rootDir)) return null;\n const files = [];\n function walk(current) {\n for (const entry of fs.readdirSync(current, { withFileTypes: true })) {\n const fullPath = path.join(current, entry.name);\n if (entry.isDirectory()) walk(fullPath);\n else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, \"/\"));\n }\n }\n walk(rootDir);\n return files.sort();\n}\n\nfunction normalizedTree(rootDir) {\n const files = listFiles(rootDir);\n if (!files) return null;\n const entries = {};\n for (const relativePath of files) {\n const filePath = path.join(rootDir, relativePath);\n const text = fs.readFileSync(filePath, \"utf8\");\n try {\n entries[relativePath] = stable(scrub(JSON.parse(text)));\n } catch {\n entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, \"\");\n }\n }\n return stable(entries);\n}\n\nconst plan = readJson(planPath);\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : [];\nif (buildServices.length > 0 || rolloutServices.length > 0) {\n process.stdout.write(\"runtime-required\");\n process.exit(0);\n}\n\nconst oldTree = normalizedTree(oldRuntimePath);\nconst newTree = normalizedTree(runtimePath);\nprocess.stdout.write(oldTree && newTree && oldTree === newTree ? \"runtime-identity-only\" : \"runtime-required\");\nNODE\n)\"\n if [ \"$runtime_noop_decision\" = \"runtime-identity-only\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"skipped-runtime-unchanged\",\"reason\":\"runtime-identity-only\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit skipped \"$(ci_now_ms)\"\n ci_timing_emit gitops-push skipped \"$(ci_now_ms)\"\n exit 0\n fi\nfi\nif git diff --cached --quiet; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"unchanged\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit unchanged \"$(ci_now_ms)\"\n ci_timing_emit gitops-push unchanged \"$(ci_now_ms)\"\n exit 0\nfi\nshort=\"$(printf '%.7s' \"$(params.revision)\")\"\ngitops_commit_started_ms=\"$(ci_now_ms)\"\ngit commit -m \"chore: promote node GitOps source $short\"\nci_timing_emit gitops-commit succeeded \"$gitops_commit_started_ms\"\ngitops_push_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-push 120 git push origin \"HEAD:$(params.gitops-branch)\"\nci_timing_emit gitops-push succeeded \"$gitops_push_started_ms\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"event\":\"runtime-ready\",\"phase\":\"gitops-promote\",\"status\":\"delegated-post-flush-closeout\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\nfi\nargo_hard_refresh_runtime_lane\necho '{\"status\":\"pushed\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"gitopsWriteUrl\":\"'\"$gitops_write_url\"'\"}'\n" - } - ] - }, - "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)" - } - ] - } - ] - } -} diff --git a/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml b/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml index b74bf972..e3ac7a27 100644 --- a/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml +++ b/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml @@ -437,7 +437,7 @@ spec: value: /tekton/home - name: XDG_CONFIG_HOME value: /tekton/home/.config - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"plan-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps/yaml\" ] && [ ! -e node_modules/yaml ]; then\n mkdir -p node_modules\n ln -s \"$ci_node_deps/yaml\" node_modules/yaml\nfi\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nHWLAB_CATALOG_PATH=\"$(params.catalog-path)\" HWLAB_GIT_URL=\"$(params.git-url)\" HWLAB_GIT_READ_URL=\"$(params.git-read-url)\" HWLAB_GITOPS_BRANCH=\"$(params.gitops-branch)\" HWLAB_SERVICES=\"$(params.services)\" node scripts/ci/restore-artifact-catalog.mjs\nnode scripts/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)\" --verify-reuse-registry > /workspace/source/ci-plan.json\nnode - <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(\"/workspace/source/ci-plan.json\", \"utf8\"));\nconst selected = String(process.env.HWLAB_SELECTED_SERVICES || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst allServices = selected.length > 0 ? selected : [\"hwlab-cloud-api\",\"hwlab-cloud-web\",\"hwlab-gateway\",\"hwlab-edge-proxy\",\"hwlab-agent-skills\"];\nconst affected = new Set(plan.affectedServices || []);\nconst plannedBuildServices = Array.isArray(plan.buildServices) ? new Set(plan.buildServices) : null;\nconst selectedSet = new Set(selected);\nconst byService = new Map((plan.services || []).map((service) => [service.serviceId, service]));\nconst entries = allServices.map((serviceId) => {\n const service = byService.get(serviceId) || {};\n const serviceSelected = selectedSet.has(serviceId);\n const rolloutAffected = serviceSelected && affected.has(serviceId);\n const envReuse = service.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.envReuse === true;\n const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected));\n return {\n serviceId,\n selected: serviceSelected,\n affected: rolloutAffected,\n buildRequired,\n rolloutAffected,\n runtimeMode: service.runtimeMode || \"service-image\",\n envChanged: service.envChanged ?? null,\n codeChanged: service.codeChanged ?? null\n };\n});\nfor (const entry of entries) {\n fs.writeFileSync(\"/tekton/results/affected-\" + entry.serviceId, entry.affected ? \"true\" : \"false\");\n fs.writeFileSync(\"/tekton/results/build-\" + entry.serviceId, entry.buildRequired ? \"true\" : \"false\");\n}\nfs.writeFileSync(\"/workspace/source/affected-services.json\", JSON.stringify({\n sourceCommitId: plan.sourceCommitId,\n affectedServices: plan.affectedServices || [],\n rolloutServices: plan.rolloutServices || plan.affectedServices || [],\n buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId),\n rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [],\n reusedServices: plan.reusedServices || [],\n serviceReusedCount: plan.serviceReusedCount || 0,\n imageBuildSkippedServices: plan.imageBuildSkippedServices || [],\n buildSkippedCount: plan.buildSkippedCount || 0,\n artifactCatalog: plan.artifactCatalog || null,\n envArtifactGroups: plan.envArtifactGroups || [],\n changedPathSummary: plan.changedPathSummary || null,\n ciCdPlan: plan.ciCdPlan || null,\n services: plan.services || [],\n entries\n}, null, 2) + String.fromCharCode(10));\nconsole.log(JSON.stringify({ event: \"g14-ci-plan\", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [], reusedServices: plan.reusedServices || [], serviceReusedCount: plan.serviceReusedCount || 0, imageBuildSkippedServices: plan.imageBuildSkippedServices || [], buildSkippedCount: plan.buildSkippedCount || 0, artifactCatalog: plan.artifactCatalog || null, noImageBuildReason: plan.ciCdPlan?.noImageBuildReason || null, envArtifactGroups: plan.envArtifactGroups || [] }));\nNODE\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"plan-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps/yaml\" ] && [ ! -e node_modules/yaml ]; then\n mkdir -p node_modules\n ln -s \"$ci_node_deps/yaml\" node_modules/yaml\nfi\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nHWLAB_CATALOG_PATH=\"$(params.catalog-path)\" HWLAB_GIT_URL=\"$(params.git-url)\" HWLAB_GIT_READ_URL=\"$(params.git-read-url)\" HWLAB_GITOPS_BRANCH=\"$(params.gitops-branch)\" HWLAB_SERVICES=\"$(params.services)\" node scripts/ci/restore-artifact-catalog.mjs\nnode scripts/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)\" --verify-reuse-registry > /workspace/source/ci-plan.json\nnode - <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(\"/workspace/source/ci-plan.json\", \"utf8\"));\nconst selected = String(process.env.HWLAB_SELECTED_SERVICES || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst allServices = selected.length > 0 ? selected : [\"hwlab-cloud-api\",\"hwlab-cloud-web\",\"hwlab-gateway\",\"hwlab-edge-proxy\",\"hwlab-agent-skills\"];\nconst affected = new Set(plan.affectedServices || []);\nconst plannedBuildServices = Array.isArray(plan.buildServices) ? new Set(plan.buildServices) : null;\nconst selectedSet = new Set(selected);\nconst byService = new Map((plan.services || []).map((service) => [service.serviceId, service]));\nconst entries = allServices.map((serviceId) => {\n const service = byService.get(serviceId) || {};\n const serviceSelected = selectedSet.has(serviceId);\n const rolloutAffected = serviceSelected && affected.has(serviceId);\n const envReuse = service.runtimeMode === \"env-reuse-gitea-checkout\" || service.envReuse === true;\n const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected));\n return {\n serviceId,\n selected: serviceSelected,\n affected: rolloutAffected,\n buildRequired,\n rolloutAffected,\n runtimeMode: service.runtimeMode || \"service-image\",\n envChanged: service.envChanged ?? null,\n codeChanged: service.codeChanged ?? null\n };\n});\nfor (const entry of entries) {\n fs.writeFileSync(\"/tekton/results/affected-\" + entry.serviceId, entry.affected ? \"true\" : \"false\");\n fs.writeFileSync(\"/tekton/results/build-\" + entry.serviceId, entry.buildRequired ? \"true\" : \"false\");\n}\nfs.writeFileSync(\"/workspace/source/affected-services.json\", JSON.stringify({\n sourceCommitId: plan.sourceCommitId,\n affectedServices: plan.affectedServices || [],\n rolloutServices: plan.rolloutServices || plan.affectedServices || [],\n buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId),\n rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [],\n reusedServices: plan.reusedServices || [],\n serviceReusedCount: plan.serviceReusedCount || 0,\n imageBuildSkippedServices: plan.imageBuildSkippedServices || [],\n buildSkippedCount: plan.buildSkippedCount || 0,\n artifactCatalog: plan.artifactCatalog || null,\n envArtifactGroups: plan.envArtifactGroups || [],\n changedPathSummary: plan.changedPathSummary || null,\n ciCdPlan: plan.ciCdPlan || null,\n services: plan.services || [],\n entries\n}, null, 2) + String.fromCharCode(10));\nconsole.log(JSON.stringify({ event: \"g14-ci-plan\", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [], reusedServices: plan.reusedServices || [], serviceReusedCount: plan.serviceReusedCount || 0, imageBuildSkippedServices: plan.imageBuildSkippedServices || [], buildSkippedCount: plan.buildSkippedCount || 0, artifactCatalog: plan.artifactCatalog || null, noImageBuildReason: plan.ciCdPlan?.noImageBuildReason || null, envArtifactGroups: plan.envArtifactGroups || [] }));\nNODE\n" imagePullPolicy: Always params: - name: git-url @@ -640,7 +640,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-release-tool-github-pr\",\"unideskSsh\":\"agentrun-release-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-gitea-checkout\" || service.runtimeMode === \"env-reuse-gitea-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-gitea-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-release-tool-github-pr\",\"unideskSsh\":\"agentrun-release-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision diff --git a/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml b/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml index 09656c6c..d0b7bd6a 100644 --- a/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml +++ b/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml @@ -19,7 +19,7 @@ metadata: unidesk.ai/ci-build-batch-count: "9" unidesk.ai/ci-build-max-parallel: "2" unidesk.ai/owning-config-ref: "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01" - unidesk.ai/effective-config-sha256: sha256:01b954f394174302b5ebce2de98a24d091be1c5922fbc188ef5af72ff1bb569c + unidesk.ai/effective-config-sha256: sha256:f42181ba9c079806968bb8374967a2bc710c7a87b56030d512cc00c004be126b unidesk.ai/source-artifact-renderer: hwlab-runtime-lane unidesk.ai/source-artifact-mode: remote-pipeline-annotation spec: @@ -30,9 +30,15 @@ spec: - name: git-read-url type: string default: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git + - name: gitops-read-url + type: string + default: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git - name: git-write-url type: string - default: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git + default: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git + - name: gitops-username + type: string + default: unidesk-admin - name: source-branch type: string default: v0.3 @@ -83,18 +89,17 @@ spec: description: Reviewed release plan envelope supplied by the manual webhook. workspaces: - name: source - - name: git-ssh + - name: gitea-auth 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: gitops-read-url - name: source-branch - name: gitops-branch - name: lane @@ -105,7 +110,6 @@ spec: - name: revision workspaces: - name: source - - name: git-ssh steps: - name: prepare-source image: 127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1 @@ -136,13 +140,15 @@ spec: value: /tekton/home - name: XDG_CONFIG_HOME value: /tekton/home/.config - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"prepare-source\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"prepare-source\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nnode -e 'console.log(JSON.stringify({event:\"ci-base-image\",phase:\"prepare-source\",ok:true,node:process.version}))'\ngit_ssh_setup() {\n mkdir -p /root/.ssh\n cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa\n chmod 600 /root/.ssh/id_rsa\n timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n write_github_proxy_command\n 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'\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"git@github.com:\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"ssh://git@github.com/\"\n}\n\nwrite_github_proxy_command() {\n cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY'\n#!/usr/bin/env node\nimport net from \"node:net\";\n\nconst [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);\nconst proxyPort = Number.parseInt(proxyPortRaw || \"\", 10);\nconst targetPort = Number.parseInt(targetPortRaw || \"\", 10);\nif (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {\n console.error(\"usage: hwlab-github-proxy-connect \");\n process.exit(64);\n}\n\nconst socket = net.createConnection({ host: proxyHost, port: proxyPort });\nlet buffer = Buffer.alloc(0);\n\nsocket.setTimeout(10000, () => {\n console.error(\"proxy connect timeout \" + proxyHost + \":\" + proxyPort + \" -> \" + targetHost + \":\" + targetPort);\n socket.destroy();\n process.exit(65);\n});\n\nsocket.on(\"connect\", () => {\n socket.write(\"CONNECT \" + targetHost + \":\" + targetPort + \" HTTP/1.1\\r\\nHost: \" + targetHost + \":\" + targetPort + \"\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n\");\n});\n\nsocket.on(\"error\", (error) => {\n console.error(\"proxy connect failed: \" + error.message);\n process.exit(66);\n});\n\nfunction onData(chunk) {\n buffer = Buffer.concat([buffer, chunk]);\n const headerEnd = buffer.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1 && buffer.length < 8192) return;\n const head = buffer.slice(0, headerEnd + 4).toString(\"latin1\");\n const statusLine = head.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number.parseInt(statusLine.split(\" \")[1] || \"\", 10);\n if (!statusLine.startsWith(\"HTTP/1.\") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {\n console.error(\"proxy CONNECT rejected: \" + (statusLine || \"missing-status\"));\n socket.destroy();\n process.exit(67);\n }\n socket.off(\"data\", onData);\n socket.setTimeout(0);\n const rest = buffer.slice(headerEnd + 4);\n if (rest.length) process.stdout.write(rest);\n process.stdin.pipe(socket);\n socket.pipe(process.stdout);\n}\n\nsocket.on(\"data\", onData);\nsocket.on(\"close\", () => process.exit(0));\nNODE_PROXY\n chmod 0700 /tmp/hwlab-github-proxy-connect.mjs\n}\n\ngit_now_ms() {\n node -e 'console.log(Date.now())' 2>/dev/null || date +%s000\n}\n\ngit_timed() {\n phase=\"$1\"\n timeout_seconds=\"$2\"\n shift 2\n started_ms=\"$(git_now_ms)\"\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"started\",\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n set +e\n timeout \"$timeout_seconds\" \"$@\"\n status=\"$?\"\n set -e\n finished_ms=\"$(git_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n if [ \"$status\" -eq 0 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"succeeded\",\"durationMs\":'\"$duration_ms\"'}' >&2\n return 0\n fi\n if [ \"$status\" -eq 124 ] || [ \"$status\" -eq 137 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"reason\":\"timeout\",\"durationMs\":'\"$duration_ms\"',\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n else\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"exitCode\":'\"$status\"',\"durationMs\":'\"$duration_ms\"'}' >&2\n fi\n return \"$status\"\n}\n\ngit_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }\ngit_read_url=\"$(params.git-read-url)\"\nif git_url_requires_ssh \"$git_read_url\"; then\n 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\n git_ssh_setup\nelse\n echo '{\"event\":\"git-ssh-setup\",\"phase\":\"prepare-source\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-read-url\"}'\nfi\nrm -rf /workspace/source/repo\nsource_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed source-clone 180 git clone --branch \"$(params.source-branch)\" \"$git_read_url\" /workspace/source/repo\ncd /workspace/source/repo\ngit config --global --add safe.directory /workspace/source/repo\ngit remote set-url origin \"$git_read_url\"\ngit checkout \"$(params.revision)\"\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\ngit 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; }\nci_timing_emit source-clone succeeded \"$source_clone_started_ms\"\nprepare_source_dependencies_started_ms=\"$(ci_now_ms)\"\necho '{\"event\":\"prepare-source-dependencies\",\"status\":\"skipped\",\"reason\":\"renderer-dependency-install-disabled\",\"dependency\":\"yaml\"}'\nci_timing_emit prepare-source-dependencies succeeded \"$prepare_source_dependencies_started_ms\"\ncatalog_path=\"$(params.catalog-path)\"\nmkdir -p \"$(dirname \"$catalog_path\")\"\ncatalog_fetch_started_ms=\"$(ci_now_ms)\"\nif git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads \"$git_read_url\" \"$(params.gitops-branch)\" >/dev/null; then\n git remote set-url gitops-catalog \"$git_read_url\" 2>/dev/null || git remote add gitops-catalog \"$git_read_url\"\n git_timed catalog-fetch 90 git fetch --depth 1 gitops-catalog \"$(params.gitops-branch)\" >/dev/null || true\n if git cat-file -e FETCH_HEAD:\"$catalog_path\" 2>/dev/null; then\n git show FETCH_HEAD:\"$catalog_path\" > /tmp/hwlab-gitops-artifact-catalog.json\n if node --input-type=module - /tmp/hwlab-gitops-artifact-catalog.json deploy/deploy.yaml \"$(params.services)\" <<'NODE'\nimport fs from \"node:fs\";\nconst [catalogPath, deployPath, selectedServices] = process.argv.slice(2);\nconst ids = (doc) => (doc.services || []).map((service) => service.serviceId).filter(Boolean);\nconst topLevelServiceIds = (text) => {\n const values = [];\n let inServices = false;\n for (const line of text.split(/\\r?\\n/u)) {\n if (/^services:\\s*$/u.test(line)) { inServices = true; continue; }\n if (!inServices) continue;\n if (/^\\S/u.test(line)) break;\n const match = line.match(/^\\s*-\\s+serviceId:\\s*['\"]?([^'\"#\\s]+)['\"]?/u);\n if (match) values.push(match[1]);\n }\n return values;\n};\nconst selected = (selectedServices || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst uniqueSorted = (items) => [...new Set(items)].sort();\nconst gitops = JSON.parse(fs.readFileSync(catalogPath, \"utf8\"));\nconst expected = selected.length ? selected : topLevelServiceIds(fs.readFileSync(deployPath, \"utf8\"));\nconst actual = ids(gitops);\nconst expectedSet = uniqueSorted(expected);\nconst actualSet = uniqueSorted(actual);\nconst sameServices = expectedSet.length === actualSet.length && expectedSet.every((item, index) => item === actualSet[index]);\nif (!sameServices) {\n const missing = expectedSet.filter((item) => !actualSet.includes(item));\n const extra = actualSet.filter((item) => !expectedSet.includes(item));\n console.error(JSON.stringify({ event: \"gitops-artifact-catalog\", phase: \"prepare-source\", status: \"ignored-stale\", reason: \"service-ids-mismatch\", expected, actual, missing, extra }));\n process.exit(42);\n}\nNODE\n then\n cp /tmp/hwlab-gitops-artifact-catalog.json \"$catalog_path\"\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"loaded\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'\n else\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"ignored-stale\",\"reason\":\"service-ids-mismatch\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'\n fi\n else\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"missing-on-gitops-branch\"}'\n fi\nelse\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"gitops-branch-missing\"}'\nfi\nif [ ! -s \"$catalog_path\" ] && [ \"$(params.lane)\" = \"v02\" ]; then\n node --input-type=module - \"$catalog_path\" \"$(params.revision)\" \"$(params.registry-prefix)\" \"$(params.image-tag-mode)\" \"$(params.services)\" <<'NODE'\nimport fs from 'node:fs';\nimport path from 'node:path';\nconst [catalogPath, revision, registryPrefix, imageTagMode, selectedServices] = process.argv.slice(2);\nconst topLevelServiceIds = (text) => {\n const values = [];\n let inServices = false;\n for (const line of text.split(/\\r?\\n/u)) {\n if (/^services:\\s*$/u.test(line)) { inServices = true; continue; }\n if (!inServices) continue;\n if (/^\\S/u.test(line)) break;\n const match = line.match(/^\\s*-\\s+serviceId:\\s*['\"]?([^'\"#\\s]+)['\"]?/u);\n if (match) values.push(match[1]);\n }\n return values;\n};\nconst tag = imageTagMode === 'full' ? revision : revision.slice(0, 7);\nconst namespace = 'hwlab-v02';\nconst selected = (selectedServices || '').split(',').filter(Boolean);\nconst serviceIds = selected.length ? selected : topLevelServiceIds(fs.readFileSync('deploy/deploy.yaml', 'utf8'));\nconst services = serviceIds.map((serviceId) => {\n const service = { serviceId, profile: 'v02', namespace, commitId: tag, sourceCommitId: revision, image: `${registryPrefix}/${serviceId}:${tag}`, imageTag: tag, digest: null, buildBackend: 'contract-skeleton' };\n return service;\n});\nfs.mkdirSync(path.dirname(catalogPath), { recursive: true });\nfs.writeFileSync(catalogPath, JSON.stringify({ catalogVersion: 'v1', kind: 'hwlab-artifact-catalog', environment: 'v02', profile: 'v02', namespace, endpoint: \"https://hwlab.74-48-78-17.nip.io\", commitId: tag, artifactState: 'contract-skeleton', publish: { registryPrefix, sourceCommitId: revision, imageTag: tag, publishedAt: null }, allowedProfiles: ['v02'], forbiddenProfiles: ['dev', 'prod'], services }, null, 2) + '\\n');\nNODE\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seeded-v02-skeleton\"}'\nfi\nci_timing_emit catalog-fetch succeeded \"$catalog_fetch_started_ms\"\necho 'node prepare-source complete; validation task count=3'\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"prepare-source\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"prepare-source\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nnode -e 'console.log(JSON.stringify({event:\"ci-base-image\",phase:\"prepare-source\",ok:true,node:process.version}))'\ngit_read_url=\"$(params.git-read-url)\"\ngitops_read_url=\"$(params.gitops-read-url)\"\nrm -rf /workspace/source/repo\nsource_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed source-clone 180 git clone --branch \"$(params.source-branch)\" \"$git_read_url\" /workspace/source/repo\ncd /workspace/source/repo\ngit config --global --add safe.directory /workspace/source/repo\ngit remote set-url origin \"$git_read_url\"\ngit checkout \"$(params.revision)\"\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\ngit 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; }\nci_timing_emit source-clone succeeded \"$source_clone_started_ms\"\nprepare_source_dependencies_started_ms=\"$(ci_now_ms)\"\necho '{\"event\":\"prepare-source-dependencies\",\"status\":\"skipped\",\"reason\":\"renderer-dependency-install-disabled\",\"dependency\":\"yaml\"}'\nci_timing_emit prepare-source-dependencies succeeded \"$prepare_source_dependencies_started_ms\"\ncatalog_path=\"$(params.catalog-path)\"\nmkdir -p \"$(dirname \"$catalog_path\")\"\ncatalog_fetch_started_ms=\"$(ci_now_ms)\"\nif git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads \"$gitops_read_url\" \"$(params.gitops-branch)\" >/dev/null; then\n git remote set-url gitops-catalog \"$gitops_read_url\" 2>/dev/null || git remote add gitops-catalog \"$gitops_read_url\"\n git_timed catalog-fetch 90 git fetch --depth 1 gitops-catalog \"$(params.gitops-branch)\" >/dev/null || true\n if git cat-file -e FETCH_HEAD:\"$catalog_path\" 2>/dev/null; then\n git show FETCH_HEAD:\"$catalog_path\" > /tmp/hwlab-gitops-artifact-catalog.json\n if node --input-type=module - /tmp/hwlab-gitops-artifact-catalog.json deploy/deploy.yaml \"$(params.services)\" <<'NODE'\nimport fs from \"node:fs\";\nconst [catalogPath, deployPath, selectedServices] = process.argv.slice(2);\nconst ids = (doc) => (doc.services || []).map((service) => service.serviceId).filter(Boolean);\nconst topLevelServiceIds = (text) => {\n const values = [];\n let inServices = false;\n for (const line of text.split(/\\r?\\n/u)) {\n if (/^services:\\s*$/u.test(line)) { inServices = true; continue; }\n if (!inServices) continue;\n if (/^\\S/u.test(line)) break;\n const match = line.match(/^\\s*-\\s+serviceId:\\s*['\"]?([^'\"#\\s]+)['\"]?/u);\n if (match) values.push(match[1]);\n }\n return values;\n};\nconst selected = (selectedServices || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst uniqueSorted = (items) => [...new Set(items)].sort();\nconst gitops = JSON.parse(fs.readFileSync(catalogPath, \"utf8\"));\nconst expected = selected.length ? selected : topLevelServiceIds(fs.readFileSync(deployPath, \"utf8\"));\nconst actual = ids(gitops);\nconst expectedSet = uniqueSorted(expected);\nconst actualSet = uniqueSorted(actual);\nconst sameServices = expectedSet.length === actualSet.length && expectedSet.every((item, index) => item === actualSet[index]);\nif (!sameServices) {\n const missing = expectedSet.filter((item) => !actualSet.includes(item));\n const extra = actualSet.filter((item) => !expectedSet.includes(item));\n console.error(JSON.stringify({ event: \"gitops-artifact-catalog\", phase: \"prepare-source\", status: \"ignored-stale\", reason: \"service-ids-mismatch\", expected, actual, missing, extra }));\n process.exit(42);\n}\nNODE\n then\n cp /tmp/hwlab-gitops-artifact-catalog.json \"$catalog_path\"\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"loaded\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'\n else\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"ignored-stale\",\"reason\":\"service-ids-mismatch\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'\n fi\n else\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"missing-on-gitops-branch\"}'\n fi\nelse\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"gitops-branch-missing\"}'\nfi\nif [ ! -s \"$catalog_path\" ] && [ \"$(params.lane)\" = \"v02\" ]; then\n node --input-type=module - \"$catalog_path\" \"$(params.revision)\" \"$(params.registry-prefix)\" \"$(params.image-tag-mode)\" \"$(params.services)\" <<'NODE'\nimport fs from 'node:fs';\nimport path from 'node:path';\nconst [catalogPath, revision, registryPrefix, imageTagMode, selectedServices] = process.argv.slice(2);\nconst topLevelServiceIds = (text) => {\n const values = [];\n let inServices = false;\n for (const line of text.split(/\\r?\\n/u)) {\n if (/^services:\\s*$/u.test(line)) { inServices = true; continue; }\n if (!inServices) continue;\n if (/^\\S/u.test(line)) break;\n const match = line.match(/^\\s*-\\s+serviceId:\\s*['\"]?([^'\"#\\s]+)['\"]?/u);\n if (match) values.push(match[1]);\n }\n return values;\n};\nconst tag = imageTagMode === 'full' ? revision : revision.slice(0, 7);\nconst namespace = 'hwlab-v02';\nconst selected = (selectedServices || '').split(',').filter(Boolean);\nconst serviceIds = selected.length ? selected : topLevelServiceIds(fs.readFileSync('deploy/deploy.yaml', 'utf8'));\nconst services = serviceIds.map((serviceId) => {\n const service = { serviceId, profile: 'v02', namespace, commitId: tag, sourceCommitId: revision, image: `${registryPrefix}/${serviceId}:${tag}`, imageTag: tag, digest: null, buildBackend: 'contract-skeleton' };\n return service;\n});\nfs.mkdirSync(path.dirname(catalogPath), { recursive: true });\nfs.writeFileSync(catalogPath, JSON.stringify({ catalogVersion: 'v1', kind: 'hwlab-artifact-catalog', environment: 'v02', profile: 'v02', namespace, endpoint: \"https://hwlab.74-48-78-17.nip.io\", commitId: tag, artifactState: 'contract-skeleton', publish: { registryPrefix, sourceCommitId: revision, imageTag: tag, publishedAt: null }, allowedProfiles: ['v02'], forbiddenProfiles: ['dev', 'prod'], services }, null, 2) + '\\n');\nNODE\n echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seeded-v02-skeleton\"}'\nfi\nci_timing_emit catalog-fetch succeeded \"$catalog_fetch_started_ms\"\necho 'node prepare-source complete; validation task count=3'\n" imagePullPolicy: Always params: - name: git-url value: $(params.git-url) - name: git-read-url value: $(params.git-read-url) + - name: gitops-read-url + value: $(params.gitops-read-url) - name: source-branch value: $(params.source-branch) - name: gitops-branch @@ -353,7 +359,7 @@ spec: taskSpec: params: - name: git-url - - name: git-read-url + - name: gitops-read-url - name: gitops-branch - name: lane - name: revision @@ -471,13 +477,13 @@ spec: value: /tekton/home - name: XDG_CONFIG_HOME value: /tekton/home/.config - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"plan-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps/yaml\" ] && [ ! -e node_modules/yaml ]; then\n mkdir -p node_modules\n ln -s \"$ci_node_deps/yaml\" node_modules/yaml\nfi\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nHWLAB_CATALOG_PATH=\"$(params.catalog-path)\" HWLAB_GIT_URL=\"$(params.git-url)\" HWLAB_GIT_READ_URL=\"$(params.git-read-url)\" HWLAB_GITOPS_BRANCH=\"$(params.gitops-branch)\" HWLAB_SERVICES=\"$(params.services)\" node scripts/ci/restore-artifact-catalog.mjs\nnode scripts/ci-plan.mjs --lane \"$(params.lane)\" --base-ref \"$(params.release-base-commit)\" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog \"$(params.catalog-path)\" --registry-prefix \"$(params.registry-prefix)\" --services \"$(params.services)\" --release-target \"$(params.release-target)\" --release-consumer \"$(params.release-consumer)\" --verify-reuse-registry > /workspace/source/ci-plan.json\nnode scripts/ci/verify-reviewed-plan.mjs --actual-plan /workspace/source/ci-plan.json --reviewed-plan \"$(params.reviewed-plan)\"\nnode - <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(\"/workspace/source/ci-plan.json\", \"utf8\"));\nconst selected = String(process.env.HWLAB_SELECTED_SERVICES || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst allServices = selected.length > 0 ? selected : [\"hwlab-cloud-api\",\"hwlab-cloud-web\",\"hwlab-gateway\",\"hwlab-edge-proxy\",\"hwlab-agent-skills\"];\nconst affected = new Set(plan.affectedServices || []);\nconst plannedBuildServices = Array.isArray(plan.buildServices) ? new Set(plan.buildServices) : null;\nconst selectedSet = new Set(selected);\nconst byService = new Map((plan.services || []).map((service) => [service.serviceId, service]));\nconst entries = allServices.map((serviceId) => {\n const service = byService.get(serviceId) || {};\n const serviceSelected = selectedSet.has(serviceId);\n const rolloutAffected = serviceSelected && affected.has(serviceId);\n const envReuse = service.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.envReuse === true;\n const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected));\n return {\n serviceId,\n selected: serviceSelected,\n affected: rolloutAffected,\n buildRequired,\n rolloutAffected,\n runtimeMode: service.runtimeMode || \"service-image\",\n envChanged: service.envChanged ?? null,\n codeChanged: service.codeChanged ?? null\n };\n});\nfor (const entry of entries) {\n fs.writeFileSync(\"/tekton/results/affected-\" + entry.serviceId, entry.affected ? \"true\" : \"false\");\n fs.writeFileSync(\"/tekton/results/build-\" + entry.serviceId, entry.buildRequired ? \"true\" : \"false\");\n}\nfs.writeFileSync(\"/workspace/source/affected-services.json\", JSON.stringify({\n sourceCommitId: plan.sourceCommitId,\n planIdentity: plan.planIdentity || null,\n affectedServices: plan.affectedServices || [],\n rolloutServices: plan.rolloutServices || plan.affectedServices || [],\n buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId),\n rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [],\n reusedServices: plan.reusedServices || [],\n serviceReusedCount: plan.serviceReusedCount || 0,\n imageBuildSkippedServices: plan.imageBuildSkippedServices || [],\n buildSkippedCount: plan.buildSkippedCount || 0,\n artifactCatalog: plan.artifactCatalog || null,\n envArtifactGroups: plan.envArtifactGroups || [],\n changedPathSummary: plan.changedPathSummary || null,\n ciCdPlan: plan.ciCdPlan || null,\n services: plan.services || [],\n entries\n}, null, 2) + String.fromCharCode(10));\nconsole.log(JSON.stringify({ event: \"g14-ci-plan\", sourceCommitId: plan.sourceCommitId, planIdentity: plan.planIdentity || null, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [], reusedServices: plan.reusedServices || [], serviceReusedCount: plan.serviceReusedCount || 0, imageBuildSkippedServices: plan.imageBuildSkippedServices || [], buildSkippedCount: plan.buildSkippedCount || 0, artifactCatalog: plan.artifactCatalog || null, noImageBuildReason: plan.ciCdPlan?.noImageBuildReason || null, envArtifactGroups: plan.envArtifactGroups || [] }));\nNODE\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"plan-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps/yaml\" ] && [ ! -e node_modules/yaml ]; then\n mkdir -p node_modules\n ln -s \"$ci_node_deps/yaml\" node_modules/yaml\nfi\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nHWLAB_CATALOG_PATH=\"$(params.catalog-path)\" HWLAB_GIT_URL=\"$(params.git-url)\" HWLAB_GIT_READ_URL=\"$(params.gitops-read-url)\" HWLAB_GITOPS_BRANCH=\"$(params.gitops-branch)\" HWLAB_SERVICES=\"$(params.services)\" node scripts/ci/restore-artifact-catalog.mjs\nnode scripts/ci-plan.mjs --lane \"$(params.lane)\" --base-ref \"$(params.release-base-commit)\" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog \"$(params.catalog-path)\" --registry-prefix \"$(params.registry-prefix)\" --services \"$(params.services)\" --release-target \"$(params.release-target)\" --release-consumer \"$(params.release-consumer)\" --verify-reuse-registry > /workspace/source/ci-plan.json\nnode scripts/ci/verify-reviewed-plan.mjs --actual-plan /workspace/source/ci-plan.json --reviewed-plan \"$(params.reviewed-plan)\"\nnode - <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(\"/workspace/source/ci-plan.json\", \"utf8\"));\nconst selected = String(process.env.HWLAB_SELECTED_SERVICES || \"\").split(\",\").map((item) => item.trim()).filter(Boolean);\nconst allServices = selected.length > 0 ? selected : [\"hwlab-cloud-api\",\"hwlab-cloud-web\",\"hwlab-gateway\",\"hwlab-edge-proxy\",\"hwlab-agent-skills\"];\nconst affected = new Set(plan.affectedServices || []);\nconst plannedBuildServices = Array.isArray(plan.buildServices) ? new Set(plan.buildServices) : null;\nconst selectedSet = new Set(selected);\nconst byService = new Map((plan.services || []).map((service) => [service.serviceId, service]));\nconst entries = allServices.map((serviceId) => {\n const service = byService.get(serviceId) || {};\n const serviceSelected = selectedSet.has(serviceId);\n const rolloutAffected = serviceSelected && affected.has(serviceId);\n const envReuse = service.runtimeMode === \"env-reuse-gitea-checkout\" || service.envReuse === true;\n const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected));\n return {\n serviceId,\n selected: serviceSelected,\n affected: rolloutAffected,\n buildRequired,\n rolloutAffected,\n runtimeMode: service.runtimeMode || \"service-image\",\n envChanged: service.envChanged ?? null,\n codeChanged: service.codeChanged ?? null\n };\n});\nfor (const entry of entries) {\n fs.writeFileSync(\"/tekton/results/affected-\" + entry.serviceId, entry.affected ? \"true\" : \"false\");\n fs.writeFileSync(\"/tekton/results/build-\" + entry.serviceId, entry.buildRequired ? \"true\" : \"false\");\n}\nfs.writeFileSync(\"/workspace/source/affected-services.json\", JSON.stringify({\n sourceCommitId: plan.sourceCommitId,\n planIdentity: plan.planIdentity || null,\n affectedServices: plan.affectedServices || [],\n rolloutServices: plan.rolloutServices || plan.affectedServices || [],\n buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId),\n rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [],\n reusedServices: plan.reusedServices || [],\n serviceReusedCount: plan.serviceReusedCount || 0,\n imageBuildSkippedServices: plan.imageBuildSkippedServices || [],\n buildSkippedCount: plan.buildSkippedCount || 0,\n artifactCatalog: plan.artifactCatalog || null,\n envArtifactGroups: plan.envArtifactGroups || [],\n changedPathSummary: plan.changedPathSummary || null,\n ciCdPlan: plan.ciCdPlan || null,\n services: plan.services || [],\n entries\n}, null, 2) + String.fromCharCode(10));\nconsole.log(JSON.stringify({ event: \"g14-ci-plan\", sourceCommitId: plan.sourceCommitId, planIdentity: plan.planIdentity || null, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], rolloutWithoutImageBuildServices: plan.rolloutWithoutImageBuildServices || [], reusedServices: plan.reusedServices || [], serviceReusedCount: plan.serviceReusedCount || 0, imageBuildSkippedServices: plan.imageBuildSkippedServices || [], buildSkippedCount: plan.buildSkippedCount || 0, artifactCatalog: plan.artifactCatalog || null, noImageBuildReason: plan.ciCdPlan?.noImageBuildReason || null, envArtifactGroups: plan.envArtifactGroups || [] }));\nNODE\n" imagePullPolicy: Always params: - name: git-url value: $(params.git-url) - - name: git-read-url - value: $(params.git-read-url) + - name: gitops-read-url + value: $(params.gitops-read-url) - name: gitops-branch value: $(params.gitops-branch) - name: lane @@ -653,7 +659,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "set -eu\nbuild_service() {\n service_id=\"$1\"\n mkdir -p \"/workspace/service-work/$service_id\" \"/workspace/service-results-tekton/$service_id\"\n export HWLAB_TEKTON_RESULTS_DIR=\"/workspace/service-results-tekton/$service_id\"\n# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$service_id\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$service_id\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$service_id\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/$service_id/home\nexport HOME=/workspace/service-work/$service_id/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$service_id\"; then\n node - \"$service_id\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(process.env.HWLAB_TEKTON_RESULTS_DIR + \"/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$service_id\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/$service_id/repo\nmkdir -p /workspace/service-work/$service_id/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/$service_id/repo -xo -f -\ncd /workspace/service-work/$service_id/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$service_id\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n echo '{\"event\":\"dependency-local-registry-probe-start\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$service_id\"'\",\"target\":\"http://127.0.0.1:5000/v2/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n echo '{\"event\":\"dependency-local-registry-probe\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$service_id\"'\",\"ok\":true,\"durationSeconds\":'\"$((registry_finished_at - registry_started_at))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$service_id\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-v03\",\"node\":\"NC01\",\"lane\":\"v03\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/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/$service_id.json\" --tekton-results-dir \"$HWLAB_TEKTON_RESULTS_DIR\" --quiet-build --concurrency 1 --services \"$service_id\" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr \"$HWLAB_BUILDKIT_ADDR\" --build-cache-mode \"$(params.build-cache-mode)\"\n\n}\nmax_parallel=2\nservice_csv=\"$(params.services)\"\nold_ifs=\"$IFS\"\nIFS=','\nset -- $service_csv\nIFS=\"$old_ifs\"\nfailed=0\nwhile [ \"$#\" -gt 0 ]; do\n pids=\"\"\n launched=0\n while [ \"$#\" -gt 0 ] && [ \"$launched\" -lt \"$max_parallel\" ]; do\n service_id=\"$1\"\n shift\n (build_service \"$service_id\") &\n pids=\"$pids $!\"\n launched=$((launched + 1))\n done\n for pid in $pids; do if ! wait \"$pid\"; then failed=1; fi; done\n if [ \"$failed\" -ne 0 ]; then exit \"$failed\"; fi\ndone" + script: "set -eu\nbuild_service() {\n service_id=\"$1\"\n mkdir -p \"/workspace/service-work/$service_id\" \"/workspace/service-results-tekton/$service_id\"\n export HWLAB_TEKTON_RESULTS_DIR=\"/workspace/service-results-tekton/$service_id\"\n# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$service_id\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$service_id\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$service_id\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/$service_id/home\nexport HOME=/workspace/service-work/$service_id/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$service_id\"; then\n node - \"$service_id\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-gitea-checkout\" || service.runtimeMode === \"env-reuse-gitea-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-gitea-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(process.env.HWLAB_TEKTON_RESULTS_DIR + \"/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$service_id\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/$service_id/repo\nmkdir -p /workspace/service-work/$service_id/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/$service_id/repo -xo -f -\ncd /workspace/service-work/$service_id/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$service_id\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n echo '{\"event\":\"dependency-local-registry-probe-start\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$service_id\"'\",\"target\":\"http://127.0.0.1:5000/v2/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n echo '{\"event\":\"dependency-local-registry-probe\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$service_id\"'\",\"ok\":true,\"durationSeconds\":'\"$((registry_finished_at - registry_started_at))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$service_id\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/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/$service_id.json\" --tekton-results-dir \"$HWLAB_TEKTON_RESULTS_DIR\" --quiet-build --concurrency 1 --services \"$service_id\" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr \"$HWLAB_BUILDKIT_ADDR\" --build-cache-mode \"$(params.build-cache-mode)\"\n\n}\nmax_parallel=2\nservice_csv=\"$(params.services)\"\nold_ifs=\"$IFS\"\nIFS=','\nset -- $service_csv\nIFS=\"$old_ifs\"\nfailed=0\nwhile [ \"$#\" -gt 0 ]; do\n pids=\"\"\n launched=0\n while [ \"$#\" -gt 0 ] && [ \"$launched\" -lt \"$max_parallel\" ]; do\n service_id=\"$1\"\n shift\n (build_service \"$service_id\") &\n pids=\"$pids $!\"\n launched=$((launched + 1))\n done\n for pid in $pids; do if ! wait \"$pid\"; then failed=1; fi; done\n if [ \"$failed\" -ne 0 ]; then exit \"$failed\"; fi\ndone" imagePullPolicy: Always computeResources: requests: @@ -727,7 +733,7 @@ spec: value: /tekton/home - name: XDG_CONFIG_HOME value: /tekton/home/.config - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"collect-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-collect\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nconst willRunGitopsPromote = plan.ciCdPlan && plan.ciCdPlan.willRunGitopsPromote === true;\nprocess.exit(!willRunGitopsPromote && buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n rm -f /workspace/source/dev-artifacts.json\n echo '{\"event\":\"collect-artifacts\",\"status\":\"skipped\",\"reason\":\"no-build-no-rollout-plan\"}'\n exit 0\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-v03\",\"node\":\"NC01\",\"lane\":\"v03\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/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 --ci-plan-path /workspace/source/affected-services.json\nnode 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\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"collect-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-collect\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nconst willRunGitopsPromote = plan.ciCdPlan && plan.ciCdPlan.willRunGitopsPromote === true;\nprocess.exit(!willRunGitopsPromote && buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n rm -f /workspace/source/dev-artifacts.json\n echo '{\"event\":\"collect-artifacts\",\"status\":\"skipped\",\"reason\":\"no-build-no-rollout-plan\"}'\n exit 0\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/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 --ci-plan-path /workspace/source/affected-services.json\nnode 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\n" imagePullPolicy: Always params: - name: revision @@ -750,13 +756,15 @@ spec: workspaces: - name: source workspace: source - - name: git-ssh - workspace: git-ssh + - name: gitea-auth + workspace: gitea-auth taskSpec: params: - name: git-url - name: git-read-url + - name: gitops-read-url - name: git-write-url + - name: gitops-username - name: source-branch - name: gitops-branch - name: lane @@ -770,7 +778,7 @@ spec: description: true when GitOps promotion changed runtime desired state and runtime readiness must be observed workspaces: - name: source - - name: git-ssh + - name: gitea-auth steps: - name: promote image: 127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1 @@ -801,7 +809,7 @@ spec: value: /tekton/home - name: XDG_CONFIG_HOME value: /tekton/home/.config - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"gitops-promote\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"gitops-promote\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apt\"}'\nfor 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\ngit_ssh_setup() {\n mkdir -p /root/.ssh\n cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa\n chmod 600 /root/.ssh/id_rsa\n timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n write_github_proxy_command\n 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'\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"git@github.com:\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"ssh://git@github.com/\"\n}\n\nwrite_github_proxy_command() {\n cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY'\n#!/usr/bin/env node\nimport net from \"node:net\";\n\nconst [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);\nconst proxyPort = Number.parseInt(proxyPortRaw || \"\", 10);\nconst targetPort = Number.parseInt(targetPortRaw || \"\", 10);\nif (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {\n console.error(\"usage: hwlab-github-proxy-connect \");\n process.exit(64);\n}\n\nconst socket = net.createConnection({ host: proxyHost, port: proxyPort });\nlet buffer = Buffer.alloc(0);\n\nsocket.setTimeout(10000, () => {\n console.error(\"proxy connect timeout \" + proxyHost + \":\" + proxyPort + \" -> \" + targetHost + \":\" + targetPort);\n socket.destroy();\n process.exit(65);\n});\n\nsocket.on(\"connect\", () => {\n socket.write(\"CONNECT \" + targetHost + \":\" + targetPort + \" HTTP/1.1\\r\\nHost: \" + targetHost + \":\" + targetPort + \"\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n\");\n});\n\nsocket.on(\"error\", (error) => {\n console.error(\"proxy connect failed: \" + error.message);\n process.exit(66);\n});\n\nfunction onData(chunk) {\n buffer = Buffer.concat([buffer, chunk]);\n const headerEnd = buffer.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1 && buffer.length < 8192) return;\n const head = buffer.slice(0, headerEnd + 4).toString(\"latin1\");\n const statusLine = head.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number.parseInt(statusLine.split(\" \")[1] || \"\", 10);\n if (!statusLine.startsWith(\"HTTP/1.\") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {\n console.error(\"proxy CONNECT rejected: \" + (statusLine || \"missing-status\"));\n socket.destroy();\n process.exit(67);\n }\n socket.off(\"data\", onData);\n socket.setTimeout(0);\n const rest = buffer.slice(headerEnd + 4);\n if (rest.length) process.stdout.write(rest);\n process.stdin.pipe(socket);\n socket.pipe(process.stdout);\n}\n\nsocket.on(\"data\", onData);\nsocket.on(\"close\", () => process.exit(0));\nNODE_PROXY\n chmod 0700 /tmp/hwlab-github-proxy-connect.mjs\n}\n\ngit_now_ms() {\n node -e 'console.log(Date.now())' 2>/dev/null || date +%s000\n}\n\ngit_timed() {\n phase=\"$1\"\n timeout_seconds=\"$2\"\n shift 2\n started_ms=\"$(git_now_ms)\"\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"started\",\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n set +e\n timeout \"$timeout_seconds\" \"$@\"\n status=\"$?\"\n set -e\n finished_ms=\"$(git_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n if [ \"$status\" -eq 0 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"succeeded\",\"durationMs\":'\"$duration_ms\"'}' >&2\n return 0\n fi\n if [ \"$status\" -eq 124 ] || [ \"$status\" -eq 137 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"reason\":\"timeout\",\"durationMs\":'\"$duration_ms\"',\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n else\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"exitCode\":'\"$status\"',\"durationMs\":'\"$duration_ms\"'}' >&2\n fi\n return \"$status\"\n}\n\ngit_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }\nlane=\"$(params.lane)\"\ncase \"$lane\" in\n node) runtime_lane=false ;;\n *) runtime_lane=true ;;\nesac\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\nelse\n printf 'true' > /tekton/results/runtime-ready-required\nfi\nsource_head_url_for_setup=\"$(params.git-url)\"\ngitops_read_url_for_setup=\"$(params.git-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url_for_setup=\"$(params.git-read-url)\"\n gitops_read_url_for_setup=\"$(params.git-read-url)\"\nfi\ngitops_write_url_for_setup=\"$(params.git-write-url)\"\nif 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\n 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\n git_ssh_setup\nelse\n echo '{\"event\":\"git-ssh-setup\",\"phase\":\"gitops-promote\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-url\"}'\nfi\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\n\ncheck_source_head() {\n phase=\"$1\"\n expected=\"${2:-$(params.revision)}\"\n source_head_url=\"$(params.git-url)\"\n if [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url=\"$(params.git-read-url)\"\n fi\n latest_file=\"$(mktemp)\"\n if ! git_timed \"source-head-$phase\" 45 git ls-remote \"$source_head_url\" \"refs/heads/$(params.source-branch)\" > \"$latest_file\"; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-check-failed\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n latest=\"$(cut -f1 \"$latest_file\" | head -n 1)\"\n if [ -z \"$latest\" ]; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-unresolved\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n if [ \"$latest\" != \"$expected\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"status\":\"skipped-stale-source\",\"verdict\":\"superseded\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"expectedRevision\":\"'\"$expected\"'\",\"latestRevision\":\"'\"$latest\"'\"}'\n exit 0\n fi\n}\n\ncheck_source_head before-render\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nprocess.exit(buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"event\":\"gitops-promote\",\"status\":\"continuing\",\"reason\":\"no-build-no-rollout-plan-gitops-verify\"}' >&2\nfi\ngit config --global user.name \"HWLAB node GitOps Bot\"\ngit config --global user.email \"hwlab-node-gitops-bot@users.noreply.github.com\"\ncatalog_path=\"$(params.catalog-path)\"\nruntime_path=\"$(params.runtime-path)\"\nunidesk_legacy_runtime_path='deploy/gitops/node/runtime-v03'\ngitops_root_from_runtime_path() {\n path=\"$1\"\n case \"$path\" in\n */*) printf '%s\n' \"${path%/*}\" ;;\n *) printf '.\n' ;;\n esac\n}\ngitops_root=\"$(gitops_root_from_runtime_path \"$runtime_path\")\"\n\nargo_hard_refresh_runtime_lane() {\n if [ \"$runtime_lane\" != \"true\" ]; then return 0; fi\n ARGO_APPLICATION=\"hwlab-node-$lane\" ARGO_FIELD_MANAGER=\"hwlab-$lane-gitops-promote\" node <<'NODE'\nconst fs = require(\"node:fs\");\nconst https = require(\"node:https\");\n\nconst host = process.env.KUBERNETES_SERVICE_HOST;\nconst port = process.env.KUBERNETES_SERVICE_PORT || \"443\";\nconst startedAt = Date.now();\nconst application = process.env.ARGO_APPLICATION || \"hwlab-node-v02\";\nconst fieldManager = process.env.ARGO_FIELD_MANAGER || \"hwlab-v02-gitops-promote\";\nconst pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;\nconst taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;\nconst task = process.env.HWLAB_TEKTON_TASK || null;\nconst revision = process.env.HWLAB_SOURCE_REVISION || null;\n\nfunction emit(payload) {\n console.log(JSON.stringify({\n event: \"node-cicd-timing\",\n schemaVersion: \"v1\",\n stage: \"argo-hard-refresh\",\n pipelineRun,\n taskRun,\n task,\n revision,\n application,\n source: \"scripts/gitops-render.mjs\",\n at: new Date().toISOString(),\n ...payload\n }));\n}\n\nfunction safeJson(text) {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n\nfunction request(method, path, body, contentType = \"application/merge-patch+json\") {\n const token = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/token\", \"utf8\");\n const ca = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\");\n const payload = body ? JSON.stringify(body) : \"\";\n const headers = { Authorization: \"Bearer \" + token };\n if (payload) {\n headers[\"Content-Type\"] = contentType;\n headers[\"Content-Length\"] = Buffer.byteLength(payload);\n }\n return new Promise((resolve, reject) => {\n const req = https.request({ host, port, method, path, ca, headers }, (res) => {\n let data = \"\";\n res.setEncoding(\"utf8\");\n res.on(\"data\", (chunk) => { data += chunk; });\n res.on(\"end\", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) }));\n });\n req.on(\"error\", reject);\n if (payload) req.write(payload);\n req.end();\n });\n}\n\n(async () => {\n if (!host) {\n emit({ status: \"skipped\", reason: \"kubernetes-service-host-missing\", durationMs: Date.now() - startedAt });\n return;\n }\n const response = await request(\n \"PATCH\",\n \"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/\" + encodeURIComponent(application) + \"?fieldManager=\" + encodeURIComponent(fieldManager),\n { metadata: { annotations: { \"argocd.argoproj.io/refresh\": \"hard\" } } }\n );\n if (response.statusCode >= 200 && response.statusCode < 300) {\n emit({ status: \"succeeded\", durationMs: Date.now() - startedAt, statusCode: response.statusCode });\n return;\n }\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-failed\", durationMs: Date.now() - startedAt, statusCode: response.statusCode, body: response.body.slice(0, 1000) });\n})().catch((error) => {\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-error\", durationMs: Date.now() - startedAt, error: error.message });\n});\nNODE\n}\n\nif [ -s /workspace/source/dev-artifacts.json ]; then\n 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\nfi\ngitops_render_started_ms=\"$(ci_now_ms)\"\nnode - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-v03\",\"node\":\"NC01\",\"lane\":\"v03\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/run-bun.mjs scripts/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)\" --gitops-root \"deploy/gitops/node/nc01\" --out \"deploy/gitops/node/nc01\" --registry-prefix \"$(params.registry-prefix)\" --use-deploy-images\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-postprocess.mjs\nfeature_config_schema_started_ms=\"$(date +%s%3N 2>/dev/null || date +%s000)\"\nexport UNIDESK_FEATURE_CONFIG_REPO_DIR=\"$PWD\"\nexport UNIDESK_FEATURE_CONFIG_RENDERED_ROOT=\"deploy/gitops/node/nc01/runtime-v03\"\nexport UNIDESK_PAC_CONSUMER='hwlab-nc01-v03'\nexport OTEL_EXPORTER_OTLP_TRACES_ENDPOINT='http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces'\nexport OTEL_SERVICE_NAME='unidesk-cicd'\nexport OTEL_TRACES_SAMPLER_ARG='1'\nexport OTEL_EXPORTER_TIMEOUT_MS='300'\nfeature_config_validator_base=''\nfeature_config_validator=''\nif feature_config_validator_base=\"$(mktemp \"${TMPDIR:-/tmp}/unidesk-feature-config-validator.XXXXXX\" 2>/dev/null)\"; then\n feature_config_validator=\"${feature_config_validator_base}.mjs\"\n if ! printf '%s' 'H4sIAAAAAAACA80ba3PbyO17fgXDZjpkT6bttNNp5fo0iq3c+c6vkeS0HdeVaWplM6ZIlQ/bGp/+ewHsm6Rk5a4fmpkk2hcWr8ViAfB373erIt+9i9Ndlj45aTZl7+L5IstL59XJw3SazT8tS1Y4K2eWZ3PHxRndKF8uysw90FPZS1yUxWiZRh0nKcqw5D9zFk4/xwnTrWmcY8OGNys0rEVYPphj2DZ3igBKyYbsP1WcMxvMPJtWCTMn4+JxhhhcDU/tyVWewMx3UZYWJWDGwR3a4D0OKJizMgxggX8g5kfP0+GaJbhl8DWLU2+RZxErigAme37HcRdh9Bjes+BrkaWur2D9s392ClDuk+wuTMYPcRFUaTxlxeM/w3ni9HpOtijjLA0TuYO7hAHXXz/UMfBT23zpn54c98cXw8nVef9L/+S0/+l0APu6nwf98dVwMDm6OP988sOkdR5yir0QUzm02qLR0Y+Ds/7ksj/+EUHCnFl8vzsDvlQ52+HNoIge2Dzk5Gt4syqNkAjnKUziKTDyM191RItGtMbjdBYA+3XlO6/vHEfKbZEdxzn0E9tzVmTJE5PTAzkMnLKEAUyREDhSl6h0hxuo0gvCO9ijKtnIXKiFLrbsGIBpt3jmeO/1KfGaYHwfyAHKU/ivqJLSc23+7XCIO/O4KOL0HqTs8h5nBvrtxIWjR/Y7TlolqK+wdcIkmYhImS+JgY7oA+x/Gl2cB4swL5hnHtgWHGHPqpz9xeUcXDlRWEYPjsfyPMt9AXYrIuKUxA2o3go0EAmHkHBmIWAw7TofXgnwGcgNzo3YZXVrkgdIvFOiEVsNcB7qimgfsygJ8xBVgg95fEslF2tdIFALEpbeg3C/d/b8b6WpFeD13s2aEbGVQVQTq2m1SGJgNyu+AbGnMI/Du4TtqNUN5DTcFvwam9bVSp5ZW7G4NPpfnz7ufdwDOYT8l0ekqXP09QmGUvYsJ3qvTpgkfOcuQKsYHKIyj6NStpSFyPJ5WMKkWZiAuqwEXIAYwAXzM1s+Z/kUwD3yX13HfdkRNnVHEOjKAzpeLhhMwI3o5NAefEMFWO7LSQmibL4ABTW1aJuTQF2OQzN6AGQK8A4P19jlntOUJSGR5UBK+AQnBAXrOl3nLW0097WPkhzalz9QuPy3Ploo5ggcgTY5y25gjDS5ug+Mbs7SKcvZ9O9Z/phk4fRIDnqvYkc5Y5hlIGZtt3WvxA0M+ILl5fI8nDMQ/cXdVxaVAYhYHudAzIhZIShb/WorpZmNtojrSxsDa5aIH1yDLSBfHHZ++cV5HxdDFqFiqvEAtqkAWxw26WlMkEce4e1tR0GcLqrSvCskT51nIQxDfEUaLoqHrGxcImpKLyiq+TwE0YNYbdtLN5s8Ik3cfcsoMGmb+3keLoO4oP89uTzg4+DbOLUuUPTrm4PtRFcxJHyOUgcy7rIKKJ+ClSHbwkWH1s4XMlUWFcRg0ixJVsLd6haQdwCdJWevHd6q6f6weVy2uD5fOBtgxlon6ElNgaEtvCiiR/pDRQlakQfPeQzCu/3wSr4At4bxTAkGlvmrf6W3tFSwQQ+Z9IQFeveKqrxKv5Uo414h2KbFsUjdmmMbzIAF8H9pDH4Th1ct1ykc3wU4mKVha7OSJQPZbfi48JILLsaD08ngH5cXw/FgOLkYn15OxsP+0WA0GZwfX16cnI9xheuaV3IZz1lWlWcojUVWxGX8xE7Skt2z3DP3HKt5Gzcdn5wNLq7Gk7ORdfEX4Ry8ivR+iETDTlb7C55ea7ORNb1tQ0HXqH92eQrb9oc/iP3QMkm2tZpQxVvS3b+HeQobIYNhX9HyXMmvydnJaHRy/oM62+R9yIbindh7a/lbe2sVMAzdq6GlHcLtMlyiBe+KvQ10uzVaVhzWSvHDkLG4nL6JGUqmm3hhHIL/RzZwMg0A+qnA7ciFHvLMPSWfCpY/xRFDT6RrHcaRHmjV1NFg+OXkaDA5758N6PhJrzSKI+2pIX7VnOUatuypA706PzkejH6GF+oRvllHV2eDobygJTTreMnOMg8jBo8tlhpel9FZ34iO2GV/OOBmwxyqrXIVGWn2fFZo6NS09j+Z1vY+mSqkF2FqDvO26dWt09bwOYxl7+e6UJU1sPTHOLwdteOMwWVxAqxDoqhhGhVr0/fqIP1WXX9L09/W8fZbbhsrR1Z7AnZuMDzvn04+w0sEQiH1A05gO//j2+HbLsxfzb1t7cSqzT3bykCs89DE8Tjk8Y2xPjFey9kzglPiVBhXvuxB1tJss8eI13r7f/aDMhsR1zz3gb24ZtCLjpMBVnTUYPxlAwg6zgYE3gYAx+jpQssz9yvDvES9uErjl/MwxWtfwP0U34MSebTcd/7g7E/29vbwb2r6mvLFU2RVHrERYAuW5Vo/Inl/F2QflgD3DoJWaHpATH3VVm9OB5773FAHKVhq1zAztv1uN8+O6oeYWaRss9turk1TbKxE1AJpmicUnDLxqPs8a6DwJ9YEXnVhVLKpKwIlYiKo4EoZ1AjexnW+iW5kW0p3mQKMBAe2G4yxEZYXgB/Me9p3NWxurm3AysR3jC7TivM/QRB4Qo+lTsoncw/OkNOlGD6Oj8SNYM9e+SY0QYSJ/MSKPbvm7Mc4BXj7FoJ1PTUH4e4wh7qbZ2+lhyTMGqbP3BSBLLVlkZ2djSvvkix65Eu5td44GwNQ9ibYs3kN5+KEUjLWUh3s3gyArg9oVGlpA6CBI+w3AdjiZU8g+bqaOXR5bykUpSIbnu6Ej2uv2lqYv0Wcv05EzVWzOC/KCTHUXkwDFAOxQdhcXt3UTkRZFXgMcWs4Luax11PlL/7/igICtUjA2y7ZosUdU26YeZ3Cv2WeJQnLZQT5DkAfqV7z6hGw8J3JSuGaeJ7vHH5vgAlCBIBJOvsZV3+Dw0WzgB9M+ZkKPYMMqRGQNHzIMPh8eTEaK4V6gAQLy4mjmCYrQaV3SohBg6jccMHD7cCuXcqSaWbfZdNl16l5QoJhSnxFfA+5wK5JGO+yHWf0YCUlQfaokghodi1PynQQb38cjy8nH17VQq4ZmI2pdW18GG8Mxpr70bgTA9fDNGLZzCHFdX7/e+6C0s1Nl4VLwqdRF8Pmwq+kyLhwatGXHRwrV/ZXgVaub52eWQwMTpSeJCzMpZqJqb5yKuve5NrMFEGjwKqKGssRCFKKeDeqDT0+3IyCx64dZm5GxWvsfnVEjgBMqkwl5hBxd+YV6Podc0IQCUGWqcLnGCIoGqALCSOdIsIArXSg5YERG8B54cFbGbDkaani01LYA3GOz8IFP7szkIfHJ1+bgf+OSgPwWN+NA+IToXM4STkgtYFum6E2JF+HQWRWblEVD96tnAbZSBOTleRJg1k6cIHnME4r1gw+zBTZNhbXLZmqG31qUeJAsFxNsheZK5S96IcnQTz3/DURry2pI7KmpJ/MaSC1DY1KyhSVrks8uGelZyNMFQ0yyK9WcERN5PzaDA2zaMDs6Gm+rZpacVE7wRFtgSdVyr+hHeEGTUq4XrxrA+wN3SUaXZWd3ff5ojkotXct0GosvJXC/PAqfgHvIRvjlBnIICnjBeT2tS5jUlzvRRUHbseFpLj1WpJCNo+nuJV1ZH59bs7KynXszJuz0sZJqKM5u6GT5qClkWSsjIIIcyJZOV4Xo2IrtUuKC/E5hBt0KqzHCKRv4eqbJgdfSvQwPxA5BqyaII8Tevdkp0yONQYom8SmFJ+2Bg1TRXUYwJAEiILnbRWhOKmcorDJk6dRo/DdobPP1dqAN80ieEbCKMCkyMGx6Cg8XGmYLGMRBE3muOCxumN5ykDyJ9BTeBKYsUqFJ7LpaMEioAgXQ6oPGr0Afi4SnvqD9oFaU7OifC1AtS0B/rGZqUmsYXyfZ9UCUb4mjyiMU3CQMGUZpzF5daLnxsTcgqCWIRQ7syjwu6ZNbjCxaPdQXtEGbIHGqrgGULVfDwNbCFT1YEcrTME4rrHBQ1iAy/jUI2eD1F0cJuokbbWOUht7+R+u2dcYYENYNzLKNBQKxzMqOEwz/fr6pmLbgtJmvf5b/lop07+qR2w4ctz9FNnPrg6ji6CNSk3vSIXZAXSVx2yXCIg6L1BMCD/WS+ysIrDaiQMWu4ECqk6e7LB0VXY2eFMvRqDOrm0hZanMO1M+lyDFEmuajJf5Sr+VlFVuNR1kEfXzZx6+0ABI+iNGyg7MEag9Ax8kw2urOT4Ty0xnbAFs4vHga5t9uOuNNH9TAXZp2b7nB7R4ngBhFSaZz6aoymX8U8xcZAtZDVSDrJUPj0tt8Psahb5TPuTZM1l/nty/bZY5KBjA3DnlBiLGpry+zAa3km6Ncf7pMUCXnKpd9QQ9fj1zRvbfLAYUEzsExXSZeEwUOaKKZblJl3OQeBwBkzNazu8yuMRP4/TRa7GzxlRJyxLnKV6j/2QBZ1g1hct2/xV4ve4y7M2TX/DB6X/YjSsw/AUEZBFj04aR7rQAE8VqseX7SBXdSkDEtlbZEIyVdjZbLQ3fuqAHvH2Y2u5M4xyV7KWkrL9R8ohzVJGjrMQDNiF3NHOs25eCBBKZa6OWEuHDCVJvXj0JO800vxxAl4cv7ieJRpwAcT9S3eLoO8oGROl/GoHIlX/aOkvnqdaiZDJvrf9AZItlsrMXYDSVP5pPwYS5+Jq270yFLPoYVOFj9+ClKXtuWuRo32f8LiNULA4a7BfXXZNWGqiT21ol1PYO5wPKH3V1FdU0A+OaZsJXFO9D98B4DPOIg6gZlhc+xRzsMdOH7rXN6IrKDQ5Y1DnWYaruNnByEIP5OuxpXCW8tqUOU3W3wZSDGGClEl7XeJRAIs8iAe/jXXcFB12ggj8FhNVt/a1ChTkU/XSMMKZjhI11nZV5xp9VtlNPxQtKEyoEz2NAilq9SZ1UPYLl5QkkOcA+FN/t3ldgNxzXl6/vAkw286D+6+Of4DrsqoeL5R9RSHv7eLQVN5bhfsun0AFiHZLvbqhr53Pr0XdNY9fiT8cu++TNeVUSjhYe670eDPIZPiDkcQaCCVvkbcxAvJHOWxtzbwTa14TY7bj65rTHW8x8O+/RluzYNpmzWu871j8GSSlghlcsBcrFNy4tRpNP4Vd+i70kza2ZS1XSrSxk8/sVUdetTOXaGQd2PKGtwqX/0xecOfl0dX6Mn62YZxKumreXtDinAq070O+ELL/3NpiW8jxVFSo+wQHW7EzhDtylfwSjAr4L+BGuFQcXLgQ5SarO8EoXeHsu4OVQHb0ahrAc+nYsjZZYsGuWg7eEmg3ytANU/xpFODTfiAcHbRYNt2ZN+GdZGHsRSSA8vCtZk/VZIOq54vMtMKNimtvZ4Jz5Hp/fEfADsailOogiRPasYMpmIdwpGHK0RxrRVgWD3uOSr67p2uJHBNxtcAVPRPlIzB2CkKODzzpIJdjlKhL8+uwIVRxt89WARIkmH7ylWbcbJQq+Tl4tSscTH+E08yU9M12CyRbKk6z82xYlbEVAXPbmdc3ELazfC3ISwuT7cR60c8C4YAUTTCyUD2d4j2K2kDUPvByaORWwLjocQ5UJ0PO+Ub5OkRVzs5YKYXPDN5iqXSlRJ8Mh1PziWhWWQZaqOypI+c+r+Z2aYDCJ9wMdEgRf4ZNN5YvBYDo92ZBujIlFS/3ub0DkMwb9WBseGDPWzb9BnGAzXo1aqwZW3E8/dHb/vbe3413v7fw13JndvP7x48o3mvt/hqZqfVzRM5C9sMgTouFw7QC8KnCkPXrB9f4NRFSz0+yZ5UchvE58Vdajih3l1I/rpjYufLsUQX5NYmhZLTUmPyex8hjQJXMYdTXXPegDQqYTRCLyGeC0W4tFIQR2awew23Ku7rIMcqSpK/wZKPNxsOsLn88nyshi2/qUNMVVjlOP0h2lWG+JxIhVYr6d+wvtE8md8v0mf2U+OgqrAmKA+AB5KMvFSKS6McU7vajKlvSwKQfb2ydHlyrAuPG3vXuziKvdx1eovJM1VBqllgIqY1CWeii0tfvPP4TbztbrcjRdqmG4/UNRj6Yp4YqLF1nti2pC1/pKWzlhYX7/BMeGFxX7wUPOZvanvzq8ewI/X3gyVS2FBzN0Xsw8d2dHBZgxqMnvX16/8ebnKa+18HPLrkDADpoiYnrXwuG6Of07B0haWZ/ggB9WHvFLDVzh1X8BgBIs15M/AAA=' | base64 -d | gzip -d >\"$feature_config_validator\"; then\n rm -f \"$feature_config_validator_base\" \"$feature_config_validator\" || true\n feature_config_validator=''\n fi\n rm -f \"$feature_config_validator_base\" || true\nfi\nexport UNIDESK_AJV2020_BUNDLE='/etc/unidesk-cicd-runtime-gitops/ajv2020.min.js'\nexport UNIDESK_FEATURE_CONFIG_VALIDATOR=\"${feature_config_validator:-${TMPDIR:-/tmp}/unidesk-feature-config-validator-unavailable.mjs}\"\nif ! node --input-type=module <<'NODE_UNIDESK_FEATURE_CONFIG_SCHEMA'\nimport { pathToFileURL } from 'node:url';\nconst validator = await import(pathToFileURL(process.env.UNIDESK_FEATURE_CONFIG_VALIDATOR).href);\nawait validator.runFeatureConfigSchemaValidation({ repoDir: process.env.UNIDESK_FEATURE_CONFIG_REPO_DIR, renderedRoot: process.env.UNIDESK_FEATURE_CONFIG_RENDERED_ROOT, consumer: process.env.UNIDESK_PAC_CONSUMER });\nNODE_UNIDESK_FEATURE_CONFIG_SCHEMA\nthen\n printf '{\"event\":\"feature-config-schema-validation\",\"warning\":true,\"blocking\":false,\"code\":\"feature-config-validator-process-failure\",\"mutation\":false,\"valuesPrinted\":false}\\n' >&2\nfi\nif [ -n \"$feature_config_validator\" ]; then rm -f \"$feature_config_validator\" || true; fi\nif command -v ci_timing_emit >/dev/null 2>&1; then\n ci_timing_emit feature-config-schema-validation succeeded \"$feature_config_schema_started_ms\"\nelse\n printf '{\"event\":\"ci.stage.timing\",\"stage\":\"feature-config-schema-validation\",\"status\":\"succeeded\",\"blocking\":false}\\n' >&2\nfi\n\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-verify.mjs\nci_timing_emit gitops-render succeeded \"$gitops_render_started_ms\"\ncheck_source_head before-push\nworkdir=\"$(mktemp -d)\"\ngitops_read_url=\"$(params.git-url)\"\ngitops_write_url=\"$(params.git-write-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n gitops_read_url=\"$(params.git-read-url)\"\nfi\ngitops_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-clone 180 git clone --no-checkout \"$gitops_read_url\" \"$workdir/gitops\"\ncd \"$workdir/gitops\"\ngit remote set-url origin \"$gitops_write_url\"\nold_runtime_snapshot=\"$workdir/old-runtime-snapshot\"\nif git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin \"$(params.gitops-branch)\" >/dev/null; then\n git_timed gitops-fetch 90 git fetch origin \"$(params.gitops-branch)\"\n git checkout -B \"$(params.gitops-branch)\" \"origin/$(params.gitops-branch)\"\n if [ \"$runtime_lane\" = \"true\" ] && [ -d \"$runtime_path\" ]; then\n mkdir -p \"$old_runtime_snapshot\"\n cp -a \"$runtime_path\"/. \"$old_runtime_snapshot\"/\n fi\n if [ \"$runtime_lane\" = \"true\" ]; then rm -rf \"$runtime_path\" \"$catalog_path\" \"$unidesk_legacy_runtime_path\"; else rm -rf deploy/gitops/node \"$catalog_path\"; fi\nelse\n git checkout --orphan \"$(params.gitops-branch)\"\n git rm -rf . >/dev/null 2>&1 || true\nfi\nci_timing_emit gitops-clone succeeded \"$gitops_clone_started_ms\"\nmkdir -p \"$(dirname \"$runtime_path\")\" \"$(dirname \"$catalog_path\")\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n cp -a \"/workspace/source/repo/$runtime_path\" \"$runtime_path\"\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" \"$runtime_path\"\n if [ -n \"${unidesk_legacy_runtime_path:-}\" ]; then git add -A \"$unidesk_legacy_runtime_path\" || true; fi\nelse\n mkdir -p deploy/gitops\n cp -a /workspace/source/repo/deploy/gitops/node deploy/gitops/node\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" deploy/gitops/node\nfi\nif [ \"$runtime_lane\" = \"true\" ] && [ -s /workspace/source/affected-services.json ]; then\n runtime_noop_decision=\"$(node - \"$runtime_path\" \"$old_runtime_snapshot\" /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2);\n\nfunction readJson(filePath) {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n}\n\nfunction stable(value) {\n if (Array.isArray(value)) return \"[\" + value.map(stable).join(\",\") + \"]\";\n if (value && typeof value === \"object\") {\n return \"{\" + Object.keys(value).sort().map((key) => JSON.stringify(key) + \":\" + stable(value[key])).join(\",\") + \"}\";\n }\n return JSON.stringify(value);\n}\n\nfunction scrub(value) {\n if (Array.isArray(value)) return value.map(scrub);\n if (!value || typeof value !== \"object\") return value;\n const result = {};\n for (const [key, child] of Object.entries(value)) {\n if (key === \"hwlab.pikastech.local/source-commit\" ||\n key === \"hwlab.pikastech.local/artifact-source-commit\" ||\n key === \"hwlab.pikastech.local/boot-commit\" ||\n key === \"sourceCommitId\" ||\n key === \"bootCommit\") {\n result[key] = \"\";\n continue;\n }\n const envName = String(value.name || \"\");\n if (key === \"value\" && /^HWLAB_.*COMMIT/.test(envName)) {\n result[key] = \"\";\n continue;\n }\n result[key] = scrub(child);\n }\n return result;\n}\n\nfunction listFiles(rootDir) {\n if (!rootDir || !fs.existsSync(rootDir)) return null;\n const files = [];\n function walk(current) {\n for (const entry of fs.readdirSync(current, { withFileTypes: true })) {\n const fullPath = path.join(current, entry.name);\n if (entry.isDirectory()) walk(fullPath);\n else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, \"/\"));\n }\n }\n walk(rootDir);\n return files.sort();\n}\n\nfunction normalizedTree(rootDir) {\n const files = listFiles(rootDir);\n if (!files) return null;\n const entries = {};\n for (const relativePath of files) {\n const filePath = path.join(rootDir, relativePath);\n const text = fs.readFileSync(filePath, \"utf8\");\n try {\n entries[relativePath] = stable(scrub(JSON.parse(text)));\n } catch {\n entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, \"\");\n }\n }\n return stable(entries);\n}\n\nconst plan = readJson(planPath);\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : [];\nif (buildServices.length > 0 || rolloutServices.length > 0) {\n process.stdout.write(\"runtime-required\");\n process.exit(0);\n}\n\nconst oldTree = normalizedTree(oldRuntimePath);\nconst newTree = normalizedTree(runtimePath);\nprocess.stdout.write(oldTree && newTree && oldTree === newTree ? \"runtime-identity-only\" : \"runtime-required\");\nNODE\n)\"\n if [ \"$runtime_noop_decision\" = \"runtime-identity-only\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"skipped-runtime-unchanged\",\"reason\":\"runtime-identity-only\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit skipped \"$(ci_now_ms)\"\n ci_timing_emit gitops-push skipped \"$(ci_now_ms)\"\n exit 0\n fi\nfi\nif git diff --cached --quiet; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"unchanged\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit unchanged \"$(ci_now_ms)\"\n ci_timing_emit gitops-push unchanged \"$(ci_now_ms)\"\n exit 0\nfi\nshort=\"$(printf '%.7s' \"$(params.revision)\")\"\ngitops_commit_started_ms=\"$(ci_now_ms)\"\ngit commit -m \"chore: promote node GitOps source $short\"\nci_timing_emit gitops-commit succeeded \"$gitops_commit_started_ms\"\ngitops_push_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-push 120 git push origin \"HEAD:$(params.gitops-branch)\"\nci_timing_emit gitops-push succeeded \"$gitops_push_started_ms\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"event\":\"runtime-ready\",\"phase\":\"gitops-promote\",\"status\":\"delegated-post-flush-closeout\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\nfi\nargo_hard_refresh_runtime_lane\necho '{\"status\":\"pushed\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"gitopsWriteUrl\":\"'\"$gitops_write_url\"'\"}'\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"gitops-promote\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"gitops-promote\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apt\"}'\nfor 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\nlane=\"$(params.lane)\"\ncase \"$lane\" in\n node) runtime_lane=false ;;\n *) runtime_lane=true ;;\nesac\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\nelse\n printf 'true' > /tekton/results/runtime-ready-required\nfi\ntest -s /workspace/gitea-auth/token || { echo '{\"event\":\"gitea-auth\",\"phase\":\"gitops-promote\",\"status\":\"failed\",\"reason\":\"token-missing\"}'; exit 31; }\naskpass_dir=\"$(mktemp -d)\"\ncat > \"$askpass_dir/askpass.sh\" <<'SH'\n#!/bin/sh\ncase \"$1\" in\n *sername*) printf '%s\\n' \"$GITEA_USERNAME\" ;;\n *) cat /workspace/gitea-auth/token ;;\nesac\nSH\nchmod 0700 \"$askpass_dir/askpass.sh\"\nexport GITEA_USERNAME=\"$(params.gitops-username)\"\nexport GIT_ASKPASS=\"$askpass_dir/askpass.sh\"\nexport GIT_ASKPASS_REQUIRE=force\nexport GIT_TERMINAL_PROMPT=0\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\n\ncheck_source_head() {\n phase=\"$1\"\n expected=\"${2:-$(params.revision)}\"\n source_head_url=\"$(params.git-read-url)\"\n latest_file=\"$(mktemp)\"\n if ! git_timed \"source-head-$phase\" 45 git ls-remote \"$source_head_url\" \"refs/heads/$(params.source-branch)\" > \"$latest_file\"; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-check-failed\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n latest=\"$(cut -f1 \"$latest_file\" | head -n 1)\"\n if [ -z \"$latest\" ]; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-unresolved\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n if [ \"$latest\" != \"$expected\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"status\":\"skipped-stale-source\",\"verdict\":\"superseded\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"expectedRevision\":\"'\"$expected\"'\",\"latestRevision\":\"'\"$latest\"'\"}'\n exit 0\n fi\n}\n\ncheck_source_head before-render\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nprocess.exit(buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"event\":\"gitops-promote\",\"status\":\"continuing\",\"reason\":\"no-build-no-rollout-plan-gitops-verify\"}' >&2\nfi\ngit config --global user.name \"HWLAB node GitOps Bot\"\ngit config --global user.email \"hwlab-node-gitops-bot@users.noreply.github.com\"\ncatalog_path=\"$(params.catalog-path)\"\nruntime_path=\"$(params.runtime-path)\"\nunidesk_legacy_runtime_path='deploy/gitops/node/runtime-v03'\ngitops_root_from_runtime_path() {\n path=\"$1\"\n case \"$path\" in\n */*) printf '%s\n' \"${path%/*}\" ;;\n *) printf '.\n' ;;\n esac\n}\ngitops_root=\"$(gitops_root_from_runtime_path \"$runtime_path\")\"\n\nargo_hard_refresh_runtime_lane() {\n if [ \"$runtime_lane\" != \"true\" ]; then return 0; fi\n ARGO_APPLICATION=\"hwlab-node-$lane\" ARGO_FIELD_MANAGER=\"hwlab-$lane-gitops-promote\" node <<'NODE'\nconst fs = require(\"node:fs\");\nconst https = require(\"node:https\");\n\nconst host = process.env.KUBERNETES_SERVICE_HOST;\nconst port = process.env.KUBERNETES_SERVICE_PORT || \"443\";\nconst startedAt = Date.now();\nconst application = process.env.ARGO_APPLICATION || \"hwlab-node-v02\";\nconst fieldManager = process.env.ARGO_FIELD_MANAGER || \"hwlab-v02-gitops-promote\";\nconst pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;\nconst taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;\nconst task = process.env.HWLAB_TEKTON_TASK || null;\nconst revision = process.env.HWLAB_SOURCE_REVISION || null;\n\nfunction emit(payload) {\n console.log(JSON.stringify({\n event: \"node-cicd-timing\",\n schemaVersion: \"v1\",\n stage: \"argo-hard-refresh\",\n pipelineRun,\n taskRun,\n task,\n revision,\n application,\n source: \"scripts/gitops-render.mjs\",\n at: new Date().toISOString(),\n ...payload\n }));\n}\n\nfunction safeJson(text) {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n\nfunction request(method, path, body, contentType = \"application/merge-patch+json\") {\n const token = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/token\", \"utf8\");\n const ca = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\");\n const payload = body ? JSON.stringify(body) : \"\";\n const headers = { Authorization: \"Bearer \" + token };\n if (payload) {\n headers[\"Content-Type\"] = contentType;\n headers[\"Content-Length\"] = Buffer.byteLength(payload);\n }\n return new Promise((resolve, reject) => {\n const req = https.request({ host, port, method, path, ca, headers }, (res) => {\n let data = \"\";\n res.setEncoding(\"utf8\");\n res.on(\"data\", (chunk) => { data += chunk; });\n res.on(\"end\", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) }));\n });\n req.on(\"error\", reject);\n if (payload) req.write(payload);\n req.end();\n });\n}\n\n(async () => {\n if (!host) {\n emit({ status: \"skipped\", reason: \"kubernetes-service-host-missing\", durationMs: Date.now() - startedAt });\n return;\n }\n const response = await request(\n \"PATCH\",\n \"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/\" + encodeURIComponent(application) + \"?fieldManager=\" + encodeURIComponent(fieldManager),\n { metadata: { annotations: { \"argocd.argoproj.io/refresh\": \"hard\" } } }\n );\n if (response.statusCode >= 200 && response.statusCode < 300) {\n emit({ status: \"succeeded\", durationMs: Date.now() - startedAt, statusCode: response.statusCode });\n return;\n }\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-failed\", durationMs: Date.now() - startedAt, statusCode: response.statusCode, body: response.body.slice(0, 1000) });\n})().catch((error) => {\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-error\", durationMs: Date.now() - startedAt, error: error.message });\n});\nNODE\n}\n\nif [ -s /workspace/source/dev-artifacts.json ]; then\n 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\nfi\ngitops_render_started_ms=\"$(ci_now_ms)\"\nnode - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/run-bun.mjs scripts/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)\" --gitops-root \"deploy/gitops/node/nc01\" --out \"deploy/gitops/node/nc01\" --registry-prefix \"$(params.registry-prefix)\" --use-deploy-images\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-postprocess.mjs\nfeature_config_schema_started_ms=\"$(date +%s%3N 2>/dev/null || date +%s000)\"\nexport UNIDESK_FEATURE_CONFIG_REPO_DIR=\"$PWD\"\nexport UNIDESK_FEATURE_CONFIG_RENDERED_ROOT=\"deploy/gitops/node/nc01/runtime-v03\"\nexport UNIDESK_PAC_CONSUMER='hwlab-nc01-v03'\nexport OTEL_EXPORTER_OTLP_TRACES_ENDPOINT='http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces'\nexport OTEL_SERVICE_NAME='unidesk-cicd'\nexport OTEL_TRACES_SAMPLER_ARG='1'\nexport OTEL_EXPORTER_TIMEOUT_MS='300'\nfeature_config_validator_base=''\nfeature_config_validator=''\nif feature_config_validator_base=\"$(mktemp \"${TMPDIR:-/tmp}/unidesk-feature-config-validator.XXXXXX\" 2>/dev/null)\"; then\n feature_config_validator=\"${feature_config_validator_base}.mjs\"\n if ! printf '%s' 'H4sIAAAAAAACA80ba3PbyO17fgXDZjpkT6bttNNp5fo0iq3c+c6vkeS0HdeVaWplM6ZIlQ/bGp/+ewHsm6Rk5a4fmpkk2hcWr8ViAfB373erIt+9i9Ndlj45aTZl7+L5IstL59XJw3SazT8tS1Y4K2eWZ3PHxRndKF8uysw90FPZS1yUxWiZRh0nKcqw5D9zFk4/xwnTrWmcY8OGNys0rEVYPphj2DZ3igBKyYbsP1WcMxvMPJtWCTMn4+JxhhhcDU/tyVWewMx3UZYWJWDGwR3a4D0OKJizMgxggX8g5kfP0+GaJbhl8DWLU2+RZxErigAme37HcRdh9Bjes+BrkaWur2D9s392ClDuk+wuTMYPcRFUaTxlxeM/w3ni9HpOtijjLA0TuYO7hAHXXz/UMfBT23zpn54c98cXw8nVef9L/+S0/+l0APu6nwf98dVwMDm6OP988sOkdR5yir0QUzm02qLR0Y+Ds/7ksj/+EUHCnFl8vzsDvlQ52+HNoIge2Dzk5Gt4syqNkAjnKUziKTDyM191RItGtMbjdBYA+3XlO6/vHEfKbZEdxzn0E9tzVmTJE5PTAzkMnLKEAUyREDhSl6h0hxuo0gvCO9ijKtnIXKiFLrbsGIBpt3jmeO/1KfGaYHwfyAHKU/ivqJLSc23+7XCIO/O4KOL0HqTs8h5nBvrtxIWjR/Y7TlolqK+wdcIkmYhImS+JgY7oA+x/Gl2cB4swL5hnHtgWHGHPqpz9xeUcXDlRWEYPjsfyPMt9AXYrIuKUxA2o3go0EAmHkHBmIWAw7TofXgnwGcgNzo3YZXVrkgdIvFOiEVsNcB7qimgfsygJ8xBVgg95fEslF2tdIFALEpbeg3C/d/b8b6WpFeD13s2aEbGVQVQTq2m1SGJgNyu+AbGnMI/Du4TtqNUN5DTcFvwam9bVSp5ZW7G4NPpfnz7ufdwDOYT8l0ekqXP09QmGUvYsJ3qvTpgkfOcuQKsYHKIyj6NStpSFyPJ5WMKkWZiAuqwEXIAYwAXzM1s+Z/kUwD3yX13HfdkRNnVHEOjKAzpeLhhMwI3o5NAefEMFWO7LSQmibL4ABTW1aJuTQF2OQzN6AGQK8A4P19jlntOUJSGR5UBK+AQnBAXrOl3nLW0097WPkhzalz9QuPy3Ploo5ggcgTY5y25gjDS5ug+Mbs7SKcvZ9O9Z/phk4fRIDnqvYkc5Y5hlIGZtt3WvxA0M+ILl5fI8nDMQ/cXdVxaVAYhYHudAzIhZIShb/WorpZmNtojrSxsDa5aIH1yDLSBfHHZ++cV5HxdDFqFiqvEAtqkAWxw26WlMkEce4e1tR0GcLqrSvCskT51nIQxDfEUaLoqHrGxcImpKLyiq+TwE0YNYbdtLN5s8Ik3cfcsoMGmb+3keLoO4oP89uTzg4+DbOLUuUPTrm4PtRFcxJHyOUgcy7rIKKJ+ClSHbwkWH1s4XMlUWFcRg0ixJVsLd6haQdwCdJWevHd6q6f6weVy2uD5fOBtgxlon6ElNgaEtvCiiR/pDRQlakQfPeQzCu/3wSr4At4bxTAkGlvmrf6W3tFSwQQ+Z9IQFeveKqrxKv5Uo414h2KbFsUjdmmMbzIAF8H9pDH4Th1ct1ykc3wU4mKVha7OSJQPZbfi48JILLsaD08ngH5cXw/FgOLkYn15OxsP+0WA0GZwfX16cnI9xheuaV3IZz1lWlWcojUVWxGX8xE7Skt2z3DP3HKt5Gzcdn5wNLq7Gk7ORdfEX4Ry8ivR+iETDTlb7C55ea7ORNb1tQ0HXqH92eQrb9oc/iP3QMkm2tZpQxVvS3b+HeQobIYNhX9HyXMmvydnJaHRy/oM62+R9yIbindh7a/lbe2sVMAzdq6GlHcLtMlyiBe+KvQ10uzVaVhzWSvHDkLG4nL6JGUqmm3hhHIL/RzZwMg0A+qnA7ciFHvLMPSWfCpY/xRFDT6RrHcaRHmjV1NFg+OXkaDA5758N6PhJrzSKI+2pIX7VnOUatuypA706PzkejH6GF+oRvllHV2eDobygJTTreMnOMg8jBo8tlhpel9FZ34iO2GV/OOBmwxyqrXIVGWn2fFZo6NS09j+Z1vY+mSqkF2FqDvO26dWt09bwOYxl7+e6UJU1sPTHOLwdteOMwWVxAqxDoqhhGhVr0/fqIP1WXX9L09/W8fZbbhsrR1Z7AnZuMDzvn04+w0sEQiH1A05gO//j2+HbLsxfzb1t7cSqzT3bykCs89DE8Tjk8Y2xPjFey9kzglPiVBhXvuxB1tJss8eI13r7f/aDMhsR1zz3gb24ZtCLjpMBVnTUYPxlAwg6zgYE3gYAx+jpQssz9yvDvES9uErjl/MwxWtfwP0U34MSebTcd/7g7E/29vbwb2r6mvLFU2RVHrERYAuW5Vo/Inl/F2QflgD3DoJWaHpATH3VVm9OB5773FAHKVhq1zAztv1uN8+O6oeYWaRss9turk1TbKxE1AJpmicUnDLxqPs8a6DwJ9YEXnVhVLKpKwIlYiKo4EoZ1AjexnW+iW5kW0p3mQKMBAe2G4yxEZYXgB/Me9p3NWxurm3AysR3jC7TivM/QRB4Qo+lTsoncw/OkNOlGD6Oj8SNYM9e+SY0QYSJ/MSKPbvm7Mc4BXj7FoJ1PTUH4e4wh7qbZ2+lhyTMGqbP3BSBLLVlkZ2djSvvkix65Eu5td44GwNQ9ibYs3kN5+KEUjLWUh3s3gyArg9oVGlpA6CBI+w3AdjiZU8g+bqaOXR5bykUpSIbnu6Ej2uv2lqYv0Wcv05EzVWzOC/KCTHUXkwDFAOxQdhcXt3UTkRZFXgMcWs4Luax11PlL/7/igICtUjA2y7ZosUdU26YeZ3Cv2WeJQnLZQT5DkAfqV7z6hGw8J3JSuGaeJ7vHH5vgAlCBIBJOvsZV3+Dw0WzgB9M+ZkKPYMMqRGQNHzIMPh8eTEaK4V6gAQLy4mjmCYrQaV3SohBg6jccMHD7cCuXcqSaWbfZdNl16l5QoJhSnxFfA+5wK5JGO+yHWf0YCUlQfaokghodi1PynQQb38cjy8nH17VQq4ZmI2pdW18GG8Mxpr70bgTA9fDNGLZzCHFdX7/e+6C0s1Nl4VLwqdRF8Pmwq+kyLhwatGXHRwrV/ZXgVaub52eWQwMTpSeJCzMpZqJqb5yKuve5NrMFEGjwKqKGssRCFKKeDeqDT0+3IyCx64dZm5GxWvsfnVEjgBMqkwl5hBxd+YV6Podc0IQCUGWqcLnGCIoGqALCSOdIsIArXSg5YERG8B54cFbGbDkaani01LYA3GOz8IFP7szkIfHJ1+bgf+OSgPwWN+NA+IToXM4STkgtYFum6E2JF+HQWRWblEVD96tnAbZSBOTleRJg1k6cIHnME4r1gw+zBTZNhbXLZmqG31qUeJAsFxNsheZK5S96IcnQTz3/DURry2pI7KmpJ/MaSC1DY1KyhSVrks8uGelZyNMFQ0yyK9WcERN5PzaDA2zaMDs6Gm+rZpacVE7wRFtgSdVyr+hHeEGTUq4XrxrA+wN3SUaXZWd3ff5ojkotXct0GosvJXC/PAqfgHvIRvjlBnIICnjBeT2tS5jUlzvRRUHbseFpLj1WpJCNo+nuJV1ZH59bs7KynXszJuz0sZJqKM5u6GT5qClkWSsjIIIcyJZOV4Xo2IrtUuKC/E5hBt0KqzHCKRv4eqbJgdfSvQwPxA5BqyaII8Tevdkp0yONQYom8SmFJ+2Bg1TRXUYwJAEiILnbRWhOKmcorDJk6dRo/DdobPP1dqAN80ieEbCKMCkyMGx6Cg8XGmYLGMRBE3muOCxumN5ykDyJ9BTeBKYsUqFJ7LpaMEioAgXQ6oPGr0Afi4SnvqD9oFaU7OifC1AtS0B/rGZqUmsYXyfZ9UCUb4mjyiMU3CQMGUZpzF5daLnxsTcgqCWIRQ7syjwu6ZNbjCxaPdQXtEGbIHGqrgGULVfDwNbCFT1YEcrTME4rrHBQ1iAy/jUI2eD1F0cJuokbbWOUht7+R+u2dcYYENYNzLKNBQKxzMqOEwz/fr6pmLbgtJmvf5b/lop07+qR2w4ctz9FNnPrg6ji6CNSk3vSIXZAXSVx2yXCIg6L1BMCD/WS+ysIrDaiQMWu4ECqk6e7LB0VXY2eFMvRqDOrm0hZanMO1M+lyDFEmuajJf5Sr+VlFVuNR1kEfXzZx6+0ABI+iNGyg7MEag9Ax8kw2urOT4Ty0xnbAFs4vHga5t9uOuNNH9TAXZp2b7nB7R4ngBhFSaZz6aoymX8U8xcZAtZDVSDrJUPj0tt8Psahb5TPuTZM1l/nty/bZY5KBjA3DnlBiLGpry+zAa3km6Ncf7pMUCXnKpd9QQ9fj1zRvbfLAYUEzsExXSZeEwUOaKKZblJl3OQeBwBkzNazu8yuMRP4/TRa7GzxlRJyxLnKV6j/2QBZ1g1hct2/xV4ve4y7M2TX/DB6X/YjSsw/AUEZBFj04aR7rQAE8VqseX7SBXdSkDEtlbZEIyVdjZbLQ3fuqAHvH2Y2u5M4xyV7KWkrL9R8ohzVJGjrMQDNiF3NHOs25eCBBKZa6OWEuHDCVJvXj0JO800vxxAl4cv7ieJRpwAcT9S3eLoO8oGROl/GoHIlX/aOkvnqdaiZDJvrf9AZItlsrMXYDSVP5pPwYS5+Jq270yFLPoYVOFj9+ClKXtuWuRo32f8LiNULA4a7BfXXZNWGqiT21ol1PYO5wPKH3V1FdU0A+OaZsJXFO9D98B4DPOIg6gZlhc+xRzsMdOH7rXN6IrKDQ5Y1DnWYaruNnByEIP5OuxpXCW8tqUOU3W3wZSDGGClEl7XeJRAIs8iAe/jXXcFB12ggj8FhNVt/a1ChTkU/XSMMKZjhI11nZV5xp9VtlNPxQtKEyoEz2NAilq9SZ1UPYLl5QkkOcA+FN/t3ldgNxzXl6/vAkw286D+6+Of4DrsqoeL5R9RSHv7eLQVN5bhfsun0AFiHZLvbqhr53Pr0XdNY9fiT8cu++TNeVUSjhYe670eDPIZPiDkcQaCCVvkbcxAvJHOWxtzbwTa14TY7bj65rTHW8x8O+/RluzYNpmzWu871j8GSSlghlcsBcrFNy4tRpNP4Vd+i70kza2ZS1XSrSxk8/sVUdetTOXaGQd2PKGtwqX/0xecOfl0dX6Mn62YZxKumreXtDinAq070O+ELL/3NpiW8jxVFSo+wQHW7EzhDtylfwSjAr4L+BGuFQcXLgQ5SarO8EoXeHsu4OVQHb0ahrAc+nYsjZZYsGuWg7eEmg3ytANU/xpFODTfiAcHbRYNt2ZN+GdZGHsRSSA8vCtZk/VZIOq54vMtMKNimtvZ4Jz5Hp/fEfADsailOogiRPasYMpmIdwpGHK0RxrRVgWD3uOSr67p2uJHBNxtcAVPRPlIzB2CkKODzzpIJdjlKhL8+uwIVRxt89WARIkmH7ylWbcbJQq+Tl4tSscTH+E08yU9M12CyRbKk6z82xYlbEVAXPbmdc3ELazfC3ISwuT7cR60c8C4YAUTTCyUD2d4j2K2kDUPvByaORWwLjocQ5UJ0PO+Ub5OkRVzs5YKYXPDN5iqXSlRJ8Mh1PziWhWWQZaqOypI+c+r+Z2aYDCJ9wMdEgRf4ZNN5YvBYDo92ZBujIlFS/3ub0DkMwb9WBseGDPWzb9BnGAzXo1aqwZW3E8/dHb/vbe3413v7fw13JndvP7x48o3mvt/hqZqfVzRM5C9sMgTouFw7QC8KnCkPXrB9f4NRFSz0+yZ5UchvE58Vdajih3l1I/rpjYufLsUQX5NYmhZLTUmPyex8hjQJXMYdTXXPegDQqYTRCLyGeC0W4tFIQR2awew23Ku7rIMcqSpK/wZKPNxsOsLn88nyshi2/qUNMVVjlOP0h2lWG+JxIhVYr6d+wvtE8md8v0mf2U+OgqrAmKA+AB5KMvFSKS6McU7vajKlvSwKQfb2ydHlyrAuPG3vXuziKvdx1eovJM1VBqllgIqY1CWeii0tfvPP4TbztbrcjRdqmG4/UNRj6Yp4YqLF1nti2pC1/pKWzlhYX7/BMeGFxX7wUPOZvanvzq8ewI/X3gyVS2FBzN0Xsw8d2dHBZgxqMnvX16/8ebnKa+18HPLrkDADpoiYnrXwuG6Of07B0haWZ/ggB9WHvFLDVzh1X8BgBIs15M/AAA=' | base64 -d | gzip -d >\"$feature_config_validator\"; then\n rm -f \"$feature_config_validator_base\" \"$feature_config_validator\" || true\n feature_config_validator=''\n fi\n rm -f \"$feature_config_validator_base\" || true\nfi\nexport UNIDESK_AJV2020_BUNDLE='/etc/unidesk-cicd-runtime-gitops/ajv2020.min.js'\nexport UNIDESK_FEATURE_CONFIG_VALIDATOR=\"${feature_config_validator:-${TMPDIR:-/tmp}/unidesk-feature-config-validator-unavailable.mjs}\"\nif ! node --input-type=module <<'NODE_UNIDESK_FEATURE_CONFIG_SCHEMA'\nimport { pathToFileURL } from 'node:url';\nconst validator = await import(pathToFileURL(process.env.UNIDESK_FEATURE_CONFIG_VALIDATOR).href);\nawait validator.runFeatureConfigSchemaValidation({ repoDir: process.env.UNIDESK_FEATURE_CONFIG_REPO_DIR, renderedRoot: process.env.UNIDESK_FEATURE_CONFIG_RENDERED_ROOT, consumer: process.env.UNIDESK_PAC_CONSUMER });\nNODE_UNIDESK_FEATURE_CONFIG_SCHEMA\nthen\n printf '{\"event\":\"feature-config-schema-validation\",\"warning\":true,\"blocking\":false,\"code\":\"feature-config-validator-process-failure\",\"mutation\":false,\"valuesPrinted\":false}\\n' >&2\nfi\nif [ -n \"$feature_config_validator\" ]; then rm -f \"$feature_config_validator\" || true; fi\nif command -v ci_timing_emit >/dev/null 2>&1; then\n ci_timing_emit feature-config-schema-validation succeeded \"$feature_config_schema_started_ms\"\nelse\n printf '{\"event\":\"ci.stage.timing\",\"stage\":\"feature-config-schema-validation\",\"status\":\"succeeded\",\"blocking\":false}\\n' >&2\nfi\n\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-verify.mjs\nci_timing_emit gitops-render succeeded \"$gitops_render_started_ms\"\ncheck_source_head before-push\nworkdir=\"$(mktemp -d)\"\ngitops_read_url=\"$(params.gitops-read-url)\"\ngitops_write_url=\"$(params.git-write-url)\"\ngitops_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-clone 180 git clone --no-checkout \"$gitops_read_url\" \"$workdir/gitops\"\ncd \"$workdir/gitops\"\ngit remote set-url origin \"$gitops_write_url\"\nold_runtime_snapshot=\"$workdir/old-runtime-snapshot\"\nif git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin \"$(params.gitops-branch)\" >/dev/null; then\n git_timed gitops-fetch 90 git fetch origin \"$(params.gitops-branch)\"\n git checkout -B \"$(params.gitops-branch)\" \"origin/$(params.gitops-branch)\"\n if [ \"$runtime_lane\" = \"true\" ] && [ -d \"$runtime_path\" ]; then\n mkdir -p \"$old_runtime_snapshot\"\n cp -a \"$runtime_path\"/. \"$old_runtime_snapshot\"/\n fi\n if [ \"$runtime_lane\" = \"true\" ]; then rm -rf \"$runtime_path\" \"$catalog_path\" \"$unidesk_legacy_runtime_path\"; else rm -rf deploy/gitops/node \"$catalog_path\"; fi\nelse\n git checkout --orphan \"$(params.gitops-branch)\"\n git rm -rf . >/dev/null 2>&1 || true\nfi\nci_timing_emit gitops-clone succeeded \"$gitops_clone_started_ms\"\nmkdir -p \"$(dirname \"$runtime_path\")\" \"$(dirname \"$catalog_path\")\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n cp -a \"/workspace/source/repo/$runtime_path\" \"$runtime_path\"\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" \"$runtime_path\"\n if [ -n \"${unidesk_legacy_runtime_path:-}\" ]; then git add -A \"$unidesk_legacy_runtime_path\" || true; fi\nelse\n mkdir -p deploy/gitops\n cp -a /workspace/source/repo/deploy/gitops/node deploy/gitops/node\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" deploy/gitops/node\nfi\nif [ \"$runtime_lane\" = \"true\" ] && [ -s /workspace/source/affected-services.json ]; then\n runtime_noop_decision=\"$(node - \"$runtime_path\" \"$old_runtime_snapshot\" /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2);\n\nfunction readJson(filePath) {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n}\n\nfunction stable(value) {\n if (Array.isArray(value)) return \"[\" + value.map(stable).join(\",\") + \"]\";\n if (value && typeof value === \"object\") {\n return \"{\" + Object.keys(value).sort().map((key) => JSON.stringify(key) + \":\" + stable(value[key])).join(\",\") + \"}\";\n }\n return JSON.stringify(value);\n}\n\nfunction scrub(value) {\n if (Array.isArray(value)) return value.map(scrub);\n if (!value || typeof value !== \"object\") return value;\n const result = {};\n for (const [key, child] of Object.entries(value)) {\n if (key === \"hwlab.pikastech.local/source-commit\" ||\n key === \"hwlab.pikastech.local/artifact-source-commit\" ||\n key === \"hwlab.pikastech.local/boot-commit\" ||\n key === \"sourceCommitId\" ||\n key === \"bootCommit\") {\n result[key] = \"\";\n continue;\n }\n const envName = String(value.name || \"\");\n if (key === \"value\" && /^HWLAB_.*COMMIT/.test(envName)) {\n result[key] = \"\";\n continue;\n }\n result[key] = scrub(child);\n }\n return result;\n}\n\nfunction listFiles(rootDir) {\n if (!rootDir || !fs.existsSync(rootDir)) return null;\n const files = [];\n function walk(current) {\n for (const entry of fs.readdirSync(current, { withFileTypes: true })) {\n const fullPath = path.join(current, entry.name);\n if (entry.isDirectory()) walk(fullPath);\n else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, \"/\"));\n }\n }\n walk(rootDir);\n return files.sort();\n}\n\nfunction normalizedTree(rootDir) {\n const files = listFiles(rootDir);\n if (!files) return null;\n const entries = {};\n for (const relativePath of files) {\n const filePath = path.join(rootDir, relativePath);\n const text = fs.readFileSync(filePath, \"utf8\");\n try {\n entries[relativePath] = stable(scrub(JSON.parse(text)));\n } catch {\n entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, \"\");\n }\n }\n return stable(entries);\n}\n\nconst plan = readJson(planPath);\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : [];\nif (buildServices.length > 0 || rolloutServices.length > 0) {\n process.stdout.write(\"runtime-required\");\n process.exit(0);\n}\n\nconst oldTree = normalizedTree(oldRuntimePath);\nconst newTree = normalizedTree(runtimePath);\nprocess.stdout.write(oldTree && newTree && oldTree === newTree ? \"runtime-identity-only\" : \"runtime-required\");\nNODE\n)\"\n if [ \"$runtime_noop_decision\" = \"runtime-identity-only\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"skipped-runtime-unchanged\",\"reason\":\"runtime-identity-only\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit skipped \"$(ci_now_ms)\"\n ci_timing_emit gitops-push skipped \"$(ci_now_ms)\"\n exit 0\n fi\nfi\nif git diff --cached --quiet; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"unchanged\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit unchanged \"$(ci_now_ms)\"\n ci_timing_emit gitops-push unchanged \"$(ci_now_ms)\"\n exit 0\nfi\nshort=\"$(printf '%.7s' \"$(params.revision)\")\"\ngitops_commit_started_ms=\"$(ci_now_ms)\"\ngit commit -m \"chore: promote node GitOps source $short\"\nci_timing_emit gitops-commit succeeded \"$gitops_commit_started_ms\"\ngitops_push_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-push 120 git push origin \"HEAD:$(params.gitops-branch)\"\nci_timing_emit gitops-push succeeded \"$gitops_push_started_ms\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"event\":\"runtime-ready\",\"phase\":\"gitops-promote\",\"status\":\"delegated-post-flush-closeout\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\nfi\nargo_hard_refresh_runtime_lane\necho '{\"status\":\"pushed\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"gitopsWriteUrl\":\"'\"$gitops_write_url\"'\"}'\n" imagePullPolicy: Always volumeMounts: - name: unidesk-runtime-gitops-scripts @@ -817,8 +825,12 @@ spec: value: $(params.git-url) - name: git-read-url value: $(params.git-read-url) + - name: gitops-read-url + value: $(params.gitops-read-url) - name: git-write-url value: $(params.git-write-url) + - name: gitops-username + value: $(params.gitops-username) - name: source-branch value: $(params.source-branch) - name: gitops-branch diff --git a/deploy/deploy.yaml b/deploy/deploy.yaml index 67594908..559d0610 100644 --- a/deploy/deploy.yaml +++ b/deploy/deploy.yaml @@ -94,43 +94,20 @@ lanes: node: G14 sourceBranch: v0.2 gitopsBranch: v0.2-gitops - gitMirror: - namespace: devops-infra - serviceReadName: git-mirror-http - serviceWriteName: git-mirror-write - cachePvcName: hwlab-git-mirror-cache - cachePvcStorage: 20Gi - cacheHostPath: /var/lib/rancher/k3s/storage/hwlab-d601-v02-git-mirror-cache - servicePort: 8080 - syncConfigMapName: git-mirror-sync-script - egressProxy: - mode: node-global - required: true - namespace: platform-infra - serviceName: sub2api-egress-proxy - port: 10808 - clientName: d601-global-proxy - noProxy: - - localhost - - 127.0.0.1 - - ::1 - - 127.0.0.1:5000 - - localhost:5000 - - .svc - - .svc.cluster.local - - .cluster.local - - kubernetes - - kubernetes.default - - kubernetes.default.svc - - 10.0.0.0/8 - - 10.42.0.0/16 - - 10.43.0.0/16 - - 172.16.0.0/12 - - 192.168.0.0/16 - - 82.156.23.220 - - 74.48.78.17 - - hyueapi.com - - .hyueapi.com + egressProxy: + required: true + namespace: platform-infra + serviceName: sub2api-egress-proxy + port: 10808 + noProxy: + - localhost + - 127.0.0.1 + - ::1 + - .svc + - .svc.cluster.local + - .cluster.local + - hyueapi.com + - .hyueapi.com namespace: hwlab-v02 endpoint: https://hwlab.74-48-78-17.nip.io publicEndpoints: @@ -277,49 +254,26 @@ lanes: AGENTRUN_API_KEY: secretRef:hwlab-v02-master-server-admin-api-key/api-key HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: agentrun-v01 HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: agentrun-v01 - HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git + HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git v03: name: v0.3 node: G14 sourceBranch: v0.3 gitopsBranch: v0.3-gitops - gitMirror: - namespace: devops-infra - serviceReadName: git-mirror-http - serviceWriteName: git-mirror-write - cachePvcName: hwlab-git-mirror-cache - cachePvcStorage: 20Gi - cacheHostPath: /var/lib/rancher/k3s/storage/hwlab-d601-v03-git-mirror-cache - servicePort: 8080 - syncConfigMapName: git-mirror-sync-script - egressProxy: - mode: node-global - required: true - namespace: platform-infra - serviceName: sub2api-egress-proxy - port: 10808 - clientName: d601-global-proxy - noProxy: - - localhost - - 127.0.0.1 - - ::1 - - 127.0.0.1:5000 - - localhost:5000 - - .svc - - .svc.cluster.local - - .cluster.local - - kubernetes - - kubernetes.default - - kubernetes.default.svc - - 10.0.0.0/8 - - 10.42.0.0/16 - - 10.43.0.0/16 - - 172.16.0.0/12 - - 192.168.0.0/16 - - 82.156.23.220 - - 74.48.78.17 - - hyueapi.com - - .hyueapi.com + egressProxy: + required: true + namespace: platform-infra + serviceName: sub2api-egress-proxy + port: 10808 + noProxy: + - localhost + - 127.0.0.1 + - ::1 + - .svc + - .svc.cluster.local + - .cluster.local + - hyueapi.com + - .hyueapi.com namespace: hwlab-v03 endpoint: https://hwlab.pikapython.com publicEndpoints: @@ -1319,7 +1273,7 @@ lanes: sourceBranch: release gitopsBranch: release-gitops gitReadUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git - gitWriteUrl: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git + gitWriteUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git artifactCatalog: deploy/artifact-catalog.nc01-production.json runtimePath: runtime-production namespace: hwlab-production diff --git a/deploy/runtime/boot/hwlab-agent-skills.sh b/deploy/runtime/boot/hwlab-agent-skills.sh index 710c1f96..c20d66ef 100644 --- a/deploy/runtime/boot/hwlab-agent-skills.sh +++ b/deploy/runtime/boot/hwlab-agent-skills.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-agent-skills" export HWLAB_ARTIFACT_KIND="skills-bundle" export HWLAB_SERVICE_ENTRYPOINT="skills/hwlab-agent-runtime/SKILL.md" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_SKILLS_COMMIT_ID="${HWLAB_SKILLS_COMMIT_ID:-${HWLAB_BOOT_COMMIT}}" @@ -81,11 +81,11 @@ function healthPayload() { environment, status: "ok", artifactKind: "skills-bundle", - runtimeMode: process.env.HWLAB_RUNTIME_MODE || "env-reuse-git-mirror-checkout", + runtimeMode: process.env.HWLAB_RUNTIME_MODE || "env-reuse-gitea-checkout", revision: process.env.HWLAB_REVISION || commitId, commit: { id: commitId, - source: process.env.HWLAB_BUILD_SOURCE || "git-mirror-runtime-checkout" + source: process.env.HWLAB_BUILD_SOURCE || "gitea-runtime-checkout" }, image: { reference: imageReference, diff --git a/deploy/runtime/boot/hwlab-cloud-api.sh b/deploy/runtime/boot/hwlab-cloud-api.sh index 582c09b1..f5c3b3fd 100644 --- a/deploy/runtime/boot/hwlab-cloud-api.sh +++ b/deploy/runtime/boot/hwlab-cloud-api.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-cloud-api" export HWLAB_ARTIFACT_KIND="bun-command" export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-cloud-api/main.ts" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" @@ -13,7 +13,7 @@ export PORT="${PORT:-${HWLAB_CLOUD_API_PORT:-${HWLAB_PORT:-6667}}}" export HWLAB_PORT="$PORT" export HWLAB_CLOUD_API_PORT="$PORT" export HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT="${HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT:-${HWLAB_BOOT_COMMIT}}" -export HWLAB_CODE_AGENT_AGENTRUN_REPO_URL="${HWLAB_CODE_AGENT_AGENTRUN_REPO_URL:-${HWLAB_BOOT_READ_URL:-http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git}}" +export HWLAB_CODE_AGENT_AGENTRUN_REPO_URL="${HWLAB_CODE_AGENT_AGENTRUN_REPO_URL:-${HWLAB_BOOT_READ_URL:-http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git}}" export HWLAB_CODE_AGENT_WORKSPACE="${HWLAB_CODE_AGENT_WORKSPACE:-$PWD}" export HWLAB_CODE_AGENT_CODEX_WORKSPACE="${HWLAB_CODE_AGENT_CODEX_WORKSPACE:-$HWLAB_CODE_AGENT_WORKSPACE}" export CODEX_HOME="${CODEX_HOME:-/codex-home}" diff --git a/deploy/runtime/boot/hwlab-cloud-web.sh b/deploy/runtime/boot/hwlab-cloud-web.sh index 1051e61e..8d4f75af 100755 --- a/deploy/runtime/boot/hwlab-cloud-web.sh +++ b/deploy/runtime/boot/hwlab-cloud-web.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-cloud-web" export HWLAB_ARTIFACT_KIND="cloud-web" export HWLAB_SERVICE_ENTRYPOINT="web/hwlab-cloud-web/index.html" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" @@ -71,11 +71,11 @@ function healthPayload() { environment, status: "ok", artifactKind: "cloud-web", - runtimeMode: process.env.HWLAB_RUNTIME_MODE || "env-reuse-git-mirror-checkout", + runtimeMode: process.env.HWLAB_RUNTIME_MODE || "env-reuse-gitea-checkout", revision: process.env.HWLAB_REVISION || commitId, commit: { id: commitId, - source: process.env.HWLAB_BUILD_SOURCE || "git-mirror-runtime-checkout" + source: process.env.HWLAB_BUILD_SOURCE || "gitea-runtime-checkout" }, image: { reference: imageReference, diff --git a/deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh b/deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh index ef3ad489..37c1a408 100644 --- a/deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh +++ b/deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-codex-api-responses-forwarder" export HWLAB_ARTIFACT_KIND="bun-command" export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-codex-api-responses-forwarder/main.ts" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" diff --git a/deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh b/deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh index dc2caeee..3efb6cd4 100644 --- a/deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh +++ b/deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-deepseek-responses-bridge" export HWLAB_ARTIFACT_KIND="bun-command" export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-deepseek-responses-bridge/main.ts" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" diff --git a/deploy/runtime/boot/hwlab-edge-proxy.sh b/deploy/runtime/boot/hwlab-edge-proxy.sh index dd315aa0..fd4cc910 100644 --- a/deploy/runtime/boot/hwlab-edge-proxy.sh +++ b/deploy/runtime/boot/hwlab-edge-proxy.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-edge-proxy" export HWLAB_ARTIFACT_KIND="node-command" export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-edge-proxy/main.mjs" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" diff --git a/deploy/runtime/boot/hwlab-gateway.sh b/deploy/runtime/boot/hwlab-gateway.sh index 54b3655f..47f4ae4b 100644 --- a/deploy/runtime/boot/hwlab-gateway.sh +++ b/deploy/runtime/boot/hwlab-gateway.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-gateway" export HWLAB_ARTIFACT_KIND="bun-command" export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-gateway/main.ts" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" diff --git a/deploy/runtime/boot/hwlab-project-management.sh b/deploy/runtime/boot/hwlab-project-management.sh index 6a0ddd25..b173f832 100644 --- a/deploy/runtime/boot/hwlab-project-management.sh +++ b/deploy/runtime/boot/hwlab-project-management.sh @@ -4,7 +4,7 @@ set -eu export HWLAB_SERVICE_ID="hwlab-project-management" export HWLAB_ARTIFACT_KIND="bun-command" export HWLAB_SERVICE_ENTRYPOINT="cmd/hwlab-project-management/main.ts" -export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-git-mirror-checkout}" +export HWLAB_RUNTIME_MODE="${HWLAB_RUNTIME_MODE:-env-reuse-gitea-checkout}" export HWLAB_COMMIT_ID="${HWLAB_BOOT_COMMIT:?HWLAB_BOOT_COMMIT is required}" export HWLAB_REVISION="${HWLAB_BOOT_COMMIT}" export HWLAB_IMAGE="${HWLAB_ENVIRONMENT_IMAGE:-${HWLAB_IMAGE:-}}" diff --git a/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts b/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts index 4aa17d42..bd452619 100644 --- a/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts +++ b/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts @@ -17,7 +17,7 @@ if ( bootRef.includes("..") ) fail("HWLAB_BOOT_REF must be a safe branch or ref name"); -const readUrl = process.env.HWLAB_BOOT_READ_URL || mirrorReadUrl(bootRepo); +const readUrl = process.env.HWLAB_BOOT_READ_URL || giteaReadUrl(bootRepo); process.env.HWLAB_BUN_COMMAND ||= process.execPath; mkdirSync(path.dirname(checkoutDir), { recursive: true }); rmSync(checkoutDir, { recursive: true, force: true }); @@ -37,11 +37,11 @@ function requiredEnv(name: string): string { return value; } -function mirrorReadUrl(repo: string): string { +function giteaReadUrl(repo: string): string { if (/github\.com[:/]pikasTech\/HWLAB(?:\.git)?$/u.test(repo)) { - return "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; + return "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"; } - fail("no mirror resolver for HWLAB_BOOT_REPO; refusing direct runtime GitHub fallback"); + fail("no Gitea resolver for HWLAB_BOOT_REPO; refusing direct runtime GitHub fallback"); } function linkNodeModules(): void { diff --git a/deploy/runtime/launcher/hwlab-go-env-reuse-launcher.mjs b/deploy/runtime/launcher/hwlab-go-env-reuse-launcher.mjs index 907c0c9a..19bcc02e 100644 --- a/deploy/runtime/launcher/hwlab-go-env-reuse-launcher.mjs +++ b/deploy/runtime/launcher/hwlab-go-env-reuse-launcher.mjs @@ -14,7 +14,7 @@ if (!/^[A-Za-z0-9._/-]+$/u.test(bootRef) || bootRef.startsWith("-") || bootRef.i fail("HWLAB_BOOT_REF must be a safe branch or ref name"); } -const readUrl = process.env.HWLAB_BOOT_READ_URL || mirrorReadUrl(bootRepo); +const readUrl = process.env.HWLAB_BOOT_READ_URL || giteaReadUrl(bootRepo); mkdirSync(path.dirname(checkoutDir), { recursive: true }); rmSync(checkoutDir, { recursive: true, force: true }); // Go env images avoid npm dependency installation; Go modules stay in Docker/BuildKit layers. @@ -31,11 +31,11 @@ function requiredEnv(name) { return value; } -function mirrorReadUrl(repo) { +function giteaReadUrl(repo) { if (/github\.com[:/]pikasTech\/HWLAB(?:\.git)?$/u.test(repo)) { - return "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; + return "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"; } - fail("no mirror resolver for HWLAB_BOOT_REPO; refusing direct runtime GitHub fallback"); + fail("no Gitea resolver for HWLAB_BOOT_REPO; refusing direct runtime GitHub fallback"); } function run(command, args, cwd, options = {}) { diff --git a/gitops/reuse.ymal b/gitops/reuse.ymal index ecd107d1..6a5c4c32 100644 --- a/gitops/reuse.ymal +++ b/gitops/reuse.ymal @@ -7,7 +7,7 @@ spec: envArtifactGroups: hwlab-go-runtime-env: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout buildService: hwlab-user-billing imageRepository: hwlab-go-runtime-env cacheRepository: cache/hwlab-go-runtime-env @@ -16,7 +16,7 @@ spec: - hwlab-workbench-runtime hwlab-ts-runtime-env: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout buildService: hwlab-cloud-api imageRepository: hwlab-ts-runtime-env cacheRepository: cache/hwlab-ts-runtime-env @@ -55,7 +55,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json @@ -81,7 +81,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - go.mod @@ -108,7 +108,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - go.mod @@ -133,7 +133,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json @@ -157,7 +157,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json @@ -180,7 +180,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json @@ -207,7 +207,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json @@ -230,7 +230,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json @@ -253,7 +253,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json @@ -275,7 +275,7 @@ spec: - internal/dev-entrypoint/artifact-runtime.mjs envReuse: enabled: true - mode: env-reuse-git-mirror-checkout + mode: env-reuse-gitea-checkout nodeDepsPath: /opt/hwlab-env/node_modules envIdentityFiles: - package.json diff --git a/scripts/artifact-publish.mjs b/scripts/artifact-publish.mjs index 3e899036..20303e1d 100644 --- a/scripts/artifact-publish.mjs +++ b/scripts/artifact-publish.mjs @@ -32,7 +32,7 @@ const defaultRegistryPrefix = const defaultBaseImage = process.env.HWLAB_DEV_BASE_IMAGE || null; const defaultReportPath = tempReportPath("dev-artifacts.json"); const buildBackend = "buildkit"; -const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout"; +const v02EnvReuseRuntimeMode = "env-reuse-gitea-checkout"; const defaultBuildkitCommand = process.env.HWLAB_BUILDKIT_BUILDCTL || "buildctl-daemonless.sh"; const defaultBuildkitAddr = process.env.HWLAB_BUILDKIT_ADDR || null; const defaultBuildCacheMode = process.env.HWLAB_BUILDKIT_CACHE_MODE || "registry"; @@ -1028,7 +1028,7 @@ function envReuseDockerfile(baseImage, labels = [], recipe, service = null) { : `COPY ${normalizedRecipe.launcherPath} /usr/local/bin/hwlab-env-reuse-launcher.ts`, `RUN ${stepTimingHelpers}; timed_stage "env-readiness" sh -c ${shellQuote(readinessScript)}`, ...labels.map(([name, value]) => `LABEL ${name}=${dockerfileQuote(value)}`), - "ENV HWLAB_RUNTIME_MODE=env-reuse-git-mirror-checkout", + "ENV HWLAB_RUNTIME_MODE=env-reuse-gitea-checkout", ...(goCacheEnabled ? [] : [`ENV HWLAB_RUNTIME_NODE_MODULES=${normalizedRecipe.runtimeNodeModulesPath}`]), ...goEnv, "ENV PATH=/usr/local/bin:$PATH", diff --git a/scripts/ci-plan.mjs b/scripts/ci-plan.mjs index d735d0a0..378664f0 100644 --- a/scripts/ci-plan.mjs +++ b/scripts/ci-plan.mjs @@ -97,7 +97,7 @@ function tryCreateNoDepsPlan(options) { affected: false, buildRequired: false, skipImageBuild: true, - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, envChanged: false, codeChanged: false, diff --git a/scripts/ci-plan.test.mjs b/scripts/ci-plan.test.mjs index 3d88595c..068b77e0 100644 --- a/scripts/ci-plan.test.mjs +++ b/scripts/ci-plan.test.mjs @@ -505,7 +505,7 @@ test("v02 planner rolls cloud-api service code changes without rebuilding servic }); const cloudApi = plan.services.find((service) => service.serviceId === "hwlab-cloud-api"); assert.equal(cloudApi.affected, true); - assert.equal(cloudApi.runtimeMode, "env-reuse-git-mirror-checkout"); + assert.equal(cloudApi.runtimeMode, "env-reuse-gitea-checkout"); assert.equal(cloudApi.envChanged, false); assert.equal(cloudApi.codeChanged, true); assert.equal(cloudApi.buildRequired, false); @@ -818,7 +818,7 @@ test("artifact publish ignores stale external reports for envreuse services that ciCdPlan: {}, services: [{ serviceId: "hwlab-cloud-web", - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, affected: false, buildRequired: false, @@ -846,7 +846,7 @@ test("artifact publish ignores stale external reports for envreuse services that imageTag: "env-old", digest, repositoryDigest: `127.0.0.1:5000/hwlab/hwlab-cloud-web-env@${digest}`, - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true }] } @@ -926,7 +926,7 @@ test("artifact publish accepts stale reused envreuse external reports for unchan ciCdPlan: {}, services: [{ serviceId: "hwlab-cloud-web", - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, affected: false, ciAffected: false, @@ -955,7 +955,7 @@ test("artifact publish accepts stale reused envreuse external reports for unchan imageTag: "env-stable", digest, repositoryDigest: `${image}@${digest}`, - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, affected: false, ciAffected: false, @@ -1290,7 +1290,7 @@ test("v02 planner marks cloud-web code-only change as env reuse rollout", async services: ["hwlab-cloud-web"] }); const cloudWeb = plan.services.find((service) => service.serviceId === "hwlab-cloud-web"); - assert.equal(cloudWeb.runtimeMode, "env-reuse-git-mirror-checkout"); + assert.equal(cloudWeb.runtimeMode, "env-reuse-gitea-checkout"); assert.equal(cloudWeb.envChanged, false); assert.equal(cloudWeb.codeChanged, true); assert.equal(cloudWeb.buildRequired, false); @@ -1348,7 +1348,7 @@ test("v02 planner enables env reuse for all retained runtime services", async () assert.deepEqual(plan.buildServices, []); assert.equal(plan.imageBuildRequired, false); for (const service of plan.services) { - assert.equal(service.runtimeMode, "env-reuse-git-mirror-checkout", service.serviceId); + assert.equal(service.runtimeMode, "env-reuse-gitea-checkout", service.serviceId); assert.equal(service.envReuse, true, service.serviceId); assert.equal(service.envChanged, false, service.serviceId); assert.equal(service.codeChanged, true, service.serviceId); @@ -1541,7 +1541,7 @@ test("v03 planner reads env reuse declarations and catalog from v03 lane config" assert.deepEqual(plan.compatibility.envReuseServiceIds, ["hwlab-cloud-api", "hwlab-cloud-web"]); assert.equal(plan.services.every((service) => service.envReuseRecipe.downloadStack.npmFetchTimeoutMs === 300000), true); for (const service of plan.services) { - assert.equal(service.runtimeMode, "env-reuse-git-mirror-checkout", service.serviceId); + assert.equal(service.runtimeMode, "env-reuse-gitea-checkout", service.serviceId); assert.equal(service.envReuse, true, service.serviceId); assert.equal(service.bootRepo, "git@github.com:pikasTech/HWLAB.git", service.serviceId); assert.equal(service.bootSh, `deploy/runtime/boot/${service.serviceId}.sh`, service.serviceId); @@ -1565,7 +1565,7 @@ test("v03 TaskTree API and worker share one TypeScript environment artifact", as assert.equal(group.services?.includes(serviceId), true, `${serviceId} shared environment membership`); assert.deepEqual(services[serviceId]?.runtimeReuse?.envIdentity?.paths, expectedEnvIdentity, `${serviceId} runtime env identity`); assert.deepEqual(services[serviceId]?.envReuse?.envIdentityFiles, expectedEnvIdentity, `${serviceId} env reuse identity`); - assert.equal(services[serviceId]?.envReuse?.mode, "env-reuse-git-mirror-checkout", `${serviceId} runtime mode`); + assert.equal(services[serviceId]?.envReuse?.mode, "env-reuse-gitea-checkout", `${serviceId} runtime mode`); } assert.deepEqual(services["hwlab-tasktree-api"].runtimeReuse.codeIdentity.paths, [ "cmd/hwlab-tasktree-api", @@ -1874,7 +1874,7 @@ async function addGoEnvReuseFixture(repo) { envArtifactGroups: { "hwlab-go-runtime-env": { enabled: true, - mode: "env-reuse-git-mirror-checkout", + mode: "env-reuse-gitea-checkout", buildService: "hwlab-user-billing", imageRepository: "hwlab-go-runtime-env", cacheRepository: "cache/hwlab-go-runtime-env", @@ -1889,7 +1889,7 @@ async function addGoEnvReuseFixture(repo) { }, envReuse: { enabled: true, - mode: "env-reuse-git-mirror-checkout", + mode: "env-reuse-gitea-checkout", envIdentityFiles: ["go.mod", "go.sum", "deploy/runtime/launcher/hwlab-go-env-reuse-launcher.mjs"] } }])) @@ -2048,7 +2048,7 @@ function createCatalogFixture(mode, options = {}) { componentInputHash: options.cloudApiComponentInputHash ?? options.componentInputHashes?.[serviceId] } : {}), ...(envReuseCatalogServiceIds.has(serviceId) ? { - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, environmentImage: `127.0.0.1:5000/hwlab/${serviceId}-env:env-abc1234`, environmentDigest: digest, diff --git a/scripts/gitops-render.mjs b/scripts/gitops-render.mjs index f0811601..8922ae44 100644 --- a/scripts/gitops-render.mjs +++ b/scripts/gitops-render.mjs @@ -47,10 +47,7 @@ import { usage, workbenchRuntimeRedisConfigForProfile } from "./src/gitops-render/core.mjs"; -import { - devopsInfraGitMirrorManifest, - registryManifest -} from "./src/gitops-render/infra-manifests.mjs"; +import { registryManifest } from "./src/gitops-render/infra-manifests.mjs"; import { argoApplication, argoProject, @@ -228,7 +225,6 @@ async function plannedFiles(args) { putJson("source.json", sourceDescriptor); putText("README.md", readme({ source, args, deploy })); putJson("registry/registry.yaml", registryManifest()); - if (isRuntimeLane(args.lane)) putJson("devops-infra/git-mirror.yaml", devopsInfraGitMirrorManifest(args, deploy)); putJson(`${settings.tektonDir}/rbac.yaml`, tektonRbac(args, deploy)); putJson(`${settings.tektonDir}/tasks.yaml`, { apiVersion: "v1", kind: "List", items: tektonTasks(args, deploy) }); putJson(`${settings.tektonDir}/pipeline.yaml`, tektonPipeline(args, deploy)); diff --git a/scripts/gitops-render.test.ts b/scripts/gitops-render.test.ts index a9f229e9..65095a10 100644 --- a/scripts/gitops-render.test.ts +++ b/scripts/gitops-render.test.ts @@ -180,11 +180,11 @@ async function runtimeNoopDecision(gitopsPromoteScript, { oldRuntime, newRuntime } } -test("opencode APK proxy prefers explicit gitMirror proxyUrl", async () => { +test("opencode APK proxy prefers the lane egress proxy URL", async () => { assert.equal(configString(" value "), "value"); - assert.equal(opencodeEgressProxyUrlForProfile({ lanes: { v03: { gitMirror: { egressProxy: { required: true, proxyUrl: "http://10.42.0.1:10808", namespace: "platform-infra", serviceName: "jd01-host-proxy", port: 10808 } } } } }, "v03"), "http://10.42.0.1:10808"); - assert.equal(opencodeEgressProxyUrlForProfile({ lanes: { v03: { gitMirror: { egressProxy: { required: true, namespace: "platform-infra", serviceName: "sub2api-egress-proxy", port: 10808 } } } } }, "v03"), "http://sub2api-egress-proxy.platform-infra.svc.cluster.local:10808"); - assert.equal(opencodeEgressProxyUrlForProfile({ lanes: { v03: { gitMirror: { egressProxy: { required: false, proxyUrl: "http://10.42.0.1:10808" } } } } }, "v03"), ""); + assert.equal(opencodeEgressProxyUrlForProfile({ lanes: { v03: { egressProxy: { required: true, proxyUrl: "http://10.42.0.1:10808", namespace: "platform-infra", serviceName: "jd01-host-proxy", port: 10808 } } } }, "v03"), "http://10.42.0.1:10808"); + assert.equal(opencodeEgressProxyUrlForProfile({ lanes: { v03: { egressProxy: { required: true, namespace: "platform-infra", serviceName: "sub2api-egress-proxy", port: 10808 } } } }, "v03"), "http://sub2api-egress-proxy.platform-infra.svc.cluster.local:10808"); + assert.equal(opencodeEgressProxyUrlForProfile({ lanes: { v03: { egressProxy: { required: false, proxyUrl: "http://10.42.0.1:10808" } } } }, "v03"), ""); }); test("v02 render follows TypeScript runtime checks and does not self-patch bootstrap RBAC", async () => { @@ -235,14 +235,14 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots const planArtifacts = taskByName(pipelineJson, "plan-artifacts"); const planArtifactsScript = planArtifacts.taskSpec.steps[0].script; assert.match(planArtifactsScript, /node scripts\/ci\/restore-artifact-catalog\.mjs/u); - assert.match(planArtifactsScript, /HWLAB_GIT_READ_URL="\$\(params\.git-read-url\)"/u); + assert.match(planArtifactsScript, /HWLAB_GIT_READ_URL="\$\(params\.gitops-read-url\)"/u); assert.match(planArtifactsScript, /HWLAB_SERVICES="\$\(params\.services\)"/u); assert.match(planArtifactsScript, /rolloutWithoutImageBuildServices/u); assert.match(planArtifactsScript, /serviceReusedCount/u); assert.match(planArtifactsScript, /artifactCatalog: plan\.artifactCatalog/u); assert.match(planArtifactsScript, /--base-ref "\$\(params\.release-base-commit\)"/u); assert.match(planArtifactsScript, /verify-reviewed-plan\.mjs/u); - assert.ok(planArtifacts.taskSpec.params.some((param) => param.name === "git-read-url")); + assert.ok(planArtifacts.taskSpec.params.some((param) => param.name === "gitops-read-url")); assert.ok(planArtifacts.taskSpec.params.some((param) => param.name === "gitops-branch")); assert.ok(planArtifacts.params.some((param) => param.name === "lane" && param.value === "$(params.lane)")); assert.ok(pipelineJson.spec.params.some((param) => param.name === "runtime-ready-timeout-ms" && param.default === "60000")); @@ -272,12 +272,13 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.match(gitopsPromoteScript, /ARGO_APPLICATION="hwlab-node-\$lane" ARGO_FIELD_MANAGER="hwlab-\$lane-gitops-promote"/u); assert.match(gitopsPromoteScript, /argocd\.argoproj\.io\/refresh": "hard"/u); assert.match(gitopsPromoteScript, /applications\/" \+ encodeURIComponent\(application\) \+ "\?fieldManager=" \+ encodeURIComponent\(fieldManager\)/u); - assert.match(gitopsPromoteScript, /BatchMode=yes/u); - assert.match(gitopsPromoteScript, /ConnectTimeout=10/u); + assert.match(gitopsPromoteScript, /GIT_ASKPASS_REQUIRE=force/u); + assert.match(gitopsPromoteScript, /test -s \/workspace\/gitea-auth\/token/u); assert.match(gitopsPromoteScript, /git_timed "source-head-\$phase" 45 git ls-remote/u); - assert.match(gitopsPromoteScript, /"event":"git-ssh-setup","phase":"gitops-promote","status":"skipped"/u); + assert.doesNotMatch(gitopsPromoteScript, /git-ssh-setup/u); assert.match(gitopsPromoteScript, /source_head_url="\$\(params\.git-read-url\)"/u); assert.match(gitopsPromoteScript, /gitops_write_url="\$\(params\.git-write-url\)"/u); + assert.match(gitopsPromoteScript, /gitops_read_url="\$\(params\.gitops-read-url\)"/u); assert.match(gitopsPromoteScript, /git clone --no-checkout "\$gitops_read_url"/u); assert.match(gitopsPromoteScript, /git_timed gitops-push 120 git push origin/u); assert.doesNotMatch(gitopsPromoteScript, /gitops-promote-proxy-preflight/u); @@ -285,13 +286,14 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots const prepareSource = taskByName(pipelineJson, "prepare-source"); assert.ok(prepareSource.taskSpec.params.some((param) => param.name === "git-read-url")); + assert.ok(prepareSource.taskSpec.params.some((param) => param.name === "gitops-read-url")); assert.ok(prepareSource.params.some((param) => param.name === "git-read-url" && param.value === "$(params.git-read-url)")); const prepareSourceScript = prepareSource.taskSpec.steps[0].script; assert.match(prepareSourceScript, /git_read_url="\$\(params\.git-read-url\)"/u); - assert.match(prepareSourceScript, /"event":"git-ssh-setup","phase":"prepare-source","status":"skipped"/u); + assert.doesNotMatch(prepareSourceScript, /git-ssh-setup/u); assert.match(prepareSourceScript, /git_timed source-clone 180 git clone --branch "\$\(params\.source-branch\)" "\$git_read_url"/u); - assert.match(prepareSourceScript, /git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads "\$git_read_url"/u); - assert.match(prepareSourceScript, /git remote set-url gitops-catalog "\$git_read_url"/u); + assert.match(prepareSourceScript, /git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads "\$gitops_read_url"/u); + assert.match(prepareSourceScript, /git remote set-url gitops-catalog "\$gitops_read_url"/u); assert.match(prepareSourceScript, /"event":"prepare-source-dependencies","status":"skipped","reason":"renderer-dependency-install-disabled","dependency":"yaml"/u); assert.doesNotMatch(prepareSourceScript, /require\.resolve\("yaml"\)/u); assert.doesNotMatch(prepareSourceScript, /npm ci --ignore-scripts --no-audit --prefer-offline/u); @@ -308,71 +310,17 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots const pipelineGitReadParam = pipelineJson.spec.params.find((param) => param.name === "git-read-url"); assert.equal(pipelineGitReadParam?.default, "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"); const pipelineGitWriteParam = pipelineJson.spec.params.find((param) => param.name === "git-write-url"); - assert.equal(pipelineGitWriteParam?.default, "http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"); + assert.equal(pipelineGitWriteParam?.default, "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"); + const pipelineGitopsReadParam = pipelineJson.spec.params.find((param) => param.name === "gitops-read-url"); + assert.equal(pipelineGitopsReadParam?.default, pipelineGitReadParam?.default); + assert.ok(pipelineJson.spec.workspaces.some((workspace) => workspace.name === "gitea-auth")); + assert.ok(!pipelineJson.spec.workspaces.some((workspace) => workspace.name === "git-ssh")); const sourceDescriptor = JSON.parse(await readFile(path.join(outDir, "source.json"), "utf8")); assert.equal(sourceDescriptor.gitReadUrl, "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"); - assert.equal(sourceDescriptor.gitWriteUrl, "http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"); + assert.equal(sourceDescriptor.gitWriteUrl, "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"); - const gitMirror = await readFile(path.join(outDir, "devops-infra", "git-mirror.yaml"), "utf8"); - assert.match(gitMirror, /"name": "devops-infra"/u); - assert.match(gitMirror, /"name": "git-mirror-http"/u); - assert.match(gitMirror, /"name": "git-mirror-write"/u); - assert.match(gitMirror, /"name": "git-mirror-sync-script"/u); - assert.match(gitMirror, /git-receive-pack/u); - assert.match(gitMirror, /last-flush\.json/u); - assert.match(gitMirror, /last-write\.json/u); - assert.match(gitMirror, /ssh:\/\/git@ssh\.github\.com:443\/pikasTech\/HWLAB\.git/u); - assert.doesNotMatch(gitMirror, /"kind": "CronJob"/u); - assert.doesNotMatch(gitMirror, /"name": "git-mirror-hwlab-sync"/u); - const gitMirrorJson = JSON.parse(gitMirror); - const gitMirrorConfigMap = gitMirrorJson.items.find((item) => item.kind === "ConfigMap" && item.metadata?.name === "git-mirror-sync-script"); - const gitMirrorScripts = gitMirrorConfigMap?.data ?? {}; - const gitMirrorScript = gitMirrorScripts["sync.sh"] ?? ""; - assert.match(gitMirrorScript, /uploadpack\.allowReachableSHA1InWant true/u); - assert.match(gitMirrorScript, /uploadpack\.allowAnySHA1InWant true/u); - assert.match(gitMirrorScript, /refs\/heads\/v0\.2:refs\/mirror-stage\/heads\/v0\.2/u); - assert.match(gitMirrorScript, /refs\/heads\/v0\.2-gitops:refs\/mirror-stage\/heads\/v0\.2-gitops/u); - assert.match(gitMirrorScript, /refs\/heads\/v0\.3:refs\/mirror-stage\/heads\/v0\.3/u); - assert.match(gitMirrorScript, /refs\/heads\/v0\.3-gitops:refs\/mirror-stage\/heads\/v0\.3-gitops/u); - assert.match(gitMirrorScript, /refs\/heads\/release:refs\/mirror-stage\/heads\/release/u); - assert.match(gitMirrorScript, /refs\/heads\/release-gitops:refs\/mirror-stage\/heads\/release-gitops/u); - assert.doesNotMatch(gitMirrorScript, /refs\/heads\/G14:refs\/mirror-stage\/heads\/G14/u); - assert.doesNotMatch(gitMirrorScript, /refs\/heads\/G14-gitops:refs\/mirror-stage\/heads\/G14-gitops/u); - assert.doesNotMatch(gitMirrorScript, /refs\/heads\/\*:refs\/mirror-stage\/heads\/\*/u); - assert.match(gitMirrorScript, /git -C "\$repo_path" update-ref "refs\/heads\/\$name" "\$sha"/u); - assert.match(gitMirrorScript, /source_snapshot_ref_prefix='refs\/unidesk\/snapshots\/hwlab-node-runtime'/u); - assert.match(gitMirrorScript, /source_stage_ref="\$\{source_snapshot_ref_prefix%\/\}\/\$source_branch\/\$source_sha"/u); - assert.match(gitMirrorScript, /git -C "\$repo_path" update-ref "\$source_stage_ref" "\$source_sha"/u); - assert.match(gitMirrorScript, /git-mirror-sync-timing/u); - assert.match(gitMirrorScript, /emit_timing fetch succeeded/u); - assert.match(gitMirrorScript, /emit_timing total succeeded/u); - assert.match(gitMirrorScript, /sourceInSync/u); - assert.match(gitMirrorScript, /sourceSnapshots/u); - assert.match(gitMirrorScript, /refs\/unidesk\/snapshots\/hwlab-node-runtime/u); - assert.match(gitMirrorScript, /refs\/mirror-stage\/heads\/v0\.2/u); - assert.match(gitMirrorScript, /refs\/mirror-stage\/heads\/v0\.3/u); - assert.match(gitMirrorScript, /for gitops_branch in 'release-gitops' 'v0\.2-gitops' 'v0\.3-gitops'/u); - assert.match(gitMirrorScript, /keeps local \$name ahead of GitHub/u); - assert.doesNotMatch(gitMirror, /not allowlisted/u); - assert.doesNotMatch(gitMirror, /path allowlist/u); - assert.doesNotMatch(gitMirror, /outside .* GitOps outputs/u); - assert.ok((gitMirrorJson.items || []).every((item) => item.kind !== "Secret")); - assert.ok((gitMirrorJson.items || []).every((item) => item.kind !== "CronJob")); - assert.ok((gitMirrorJson.items || []).every((item) => !item.data?.["ssh-privatekey"])); - - const scriptCheckDir = path.join(outDir, "script-check"); - await mkdir(scriptCheckDir, { recursive: true }); - for (const scriptName of ["http.mjs", "sync.sh", "install-hooks.sh", "flush.sh"]) { - assert.ok(gitMirrorScripts[scriptName], `missing git mirror script ${scriptName}`); - await writeFile(path.join(scriptCheckDir, scriptName), gitMirrorScripts[scriptName]); - } - const httpCheck = spawnSync("node", ["--check", path.join(scriptCheckDir, "http.mjs")], { encoding: "utf8" }); - assert.equal(httpCheck.status, 0, httpCheck.stderr || httpCheck.stdout); - for (const scriptName of ["sync.sh", "install-hooks.sh", "flush.sh"]) { - const shellCheck = spawnSync("sh", ["-n", path.join(scriptCheckDir, scriptName)], { encoding: "utf8" }); - assert.equal(shellCheck.status, 0, `${scriptName}: ${shellCheck.stderr || shellCheck.stdout}`); - } + await assert.rejects(() => readFile(path.join(outDir, "devops-infra", "git-mirror.yaml"), "utf8"), { code: "ENOENT" }); const removedServiceIds = [ "hwlab-agent-mgr", @@ -501,11 +449,11 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots const cloudApiEnv = cloudApiContainer.env; const cloudApiCommit = cloudApiEnv.find((entry) => entry.name === "HWLAB_COMMIT_ID")?.value; const cloudApiBootEnv = new Map(cloudApiEnv.map((entry) => [entry.name, entry.value])); - assert.equal(cloudApiBootEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-git-mirror-checkout"); + assert.equal(cloudApiBootEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-gitea-checkout"); assert.equal(cloudApiBootEnv.get("HWLAB_BOOT_SH"), "deploy/runtime/boot/hwlab-cloud-api.sh"); const codexForwarderEnv = new Map(codexForwarder.env.map((entry) => [entry.name, entry.value])); assert.equal(codexForwarder.command, undefined); - assert.equal(codexForwarderEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-git-mirror-checkout"); + assert.equal(codexForwarderEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-gitea-checkout"); assert.equal(codexForwarderEnv.get("HWLAB_BOOT_SH"), "deploy/runtime/boot/hwlab-codex-api-responses-forwarder.sh"); assert.equal(codexForwarderEnv.get("HWLAB_BOOT_COMMIT"), cloudApiCommit); assert.equal(codexForwarderEnv.get("HWLAB_BOOT_READ_URL"), "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"); @@ -527,7 +475,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots assert.ok(deepseekResponsesBridge, "expected DeepSeek responses bridge container"); const deepseekBridgeEnv = new Map(deepseekResponsesBridge.env.map((entry) => [entry.name, entry.value])); assert.equal(deepseekResponsesBridge.command, undefined); - assert.equal(deepseekBridgeEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-git-mirror-checkout"); + assert.equal(deepseekBridgeEnv.get("HWLAB_RUNTIME_MODE"), "env-reuse-gitea-checkout"); assert.equal(deepseekBridgeEnv.get("HWLAB_BOOT_SH"), "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh"); assert.equal(deepseekBridgeEnv.get("HWLAB_BOOT_COMMIT"), cloudApiCommit); assert.equal(deepseekBridgeEnv.get("HWLAB_BOOT_READ_URL"), "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"); @@ -620,7 +568,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots const pipelineRunSample = await readFile(path.join(outDir, "tekton-v02", "pipelinerun.sample.yaml"), "utf8"); assert.match(pipelineRunSample, /"name": "git-read-url"[\s\S]{0,180}gitea-http\.devops-infra\.svc\.cluster\.local:3000\/mirrors\/pikasTech-HWLAB\.git/u); - assert.match(pipelineRunSample, /"name": "git-write-url"[\s\S]{0,140}git-mirror-write\.devops-infra\.svc\.cluster\.local/u); + assert.match(pipelineRunSample, /"name": "git-write-url"[\s\S]{0,180}gitea-http\.devops-infra\.svc\.cluster\.local:3000\/mirrors\/pikasTech-HWLAB\.git/u); assert.match(pipelineRunSample, /"name": "revision"[\s\S]{0,80}"value": "[0-9a-f]{40}"/u); assert.match(pipelineRunSample, /"name": "source-branch"[\s\S]{0,80}"value": "v0\.2"/u); } finally { @@ -648,7 +596,7 @@ test("v03 render keeps node identity as data instead of generated structure", as artifactState: "ready", services: [{ serviceId: "hwlab-cloud-api", - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, sourceCommitId: staleRevision, commitId: staleRevision, @@ -848,7 +796,7 @@ test("v03 render includes JD01 secret-plane smoke on JD01 gitops root", { timeou artifactState: "ready", services: [{ serviceId: "hwlab-cloud-api", - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, sourceCommitId: staleRevision, commitId: staleRevision, diff --git a/scripts/refresh-artifact-catalog.mjs b/scripts/refresh-artifact-catalog.mjs index e8bff069..61074f6c 100644 --- a/scripts/refresh-artifact-catalog.mjs +++ b/scripts/refresh-artifact-catalog.mjs @@ -26,7 +26,7 @@ const commitPattern = /^[a-f0-9]{7,40}$/; const runtimeArtifactLanePattern = /^[a-z][a-z0-9-]*$/u; const publishReadyStatuses = new Set(["published", "reused"]); -const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout"; +const v02EnvReuseRuntimeMode = "env-reuse-gitea-checkout"; function isRuntimeArtifactLane(lane) { return runtimeArtifactLanePattern.test(String(lane ?? "")); diff --git a/scripts/refresh-artifact-catalog.test.mjs b/scripts/refresh-artifact-catalog.test.mjs index 999f1d97..b5e01ced 100644 --- a/scripts/refresh-artifact-catalog.test.mjs +++ b/scripts/refresh-artifact-catalog.test.mjs @@ -431,7 +431,7 @@ test("v03 refresh accepts runtime lane config and env-reuse image tags", async ( serviceId, status: "published", artifactRequired: true, - runtimeMode: "env-reuse-git-mirror-checkout", + runtimeMode: "env-reuse-gitea-checkout", envReuse: true, commitId, sourceCommitId: commitId, @@ -476,7 +476,7 @@ test("v03 refresh accepts runtime lane config and env-reuse image tags", async ( assert.equal(payload.endpoint, "https://hwlab-v03.74-48-78-17.nip.io"); assert.equal(payload.requiredServiceCount, serviceIds.length); assert.deepEqual(payload.services.map((service) => service.serviceId), serviceIds); - assert.equal(payload.services.every((service) => service.runtimeMode === "env-reuse-git-mirror-checkout"), true); + assert.equal(payload.services.every((service) => service.runtimeMode === "env-reuse-gitea-checkout"), true); assert.equal(payload.services.every((service) => service.imageTag.startsWith("env-")), true); }); diff --git a/scripts/src/ci-plan-lib.mjs b/scripts/src/ci-plan-lib.mjs index a88d9809..62e3e0f0 100644 --- a/scripts/src/ci-plan-lib.mjs +++ b/scripts/src/ci-plan-lib.mjs @@ -15,7 +15,7 @@ import { parseStructuredConfig, readStructuredFileIfPresent } from "./structured const execFileAsync = promisify(execFile); export const CI_PLAN_VERSION = "v1"; -export const ENV_REUSE_RUNTIME_MODE = "env-reuse-git-mirror-checkout"; +export const ENV_REUSE_RUNTIME_MODE = "env-reuse-gitea-checkout"; export const V02_ENV_REUSE_RUNTIME_MODE = ENV_REUSE_RUNTIME_MODE; export const DEFAULT_RUNTIME_DEP_PATHS = Object.freeze([ diff --git a/scripts/src/gitops-render/core.mjs b/scripts/src/gitops-render/core.mjs index c89ec81b..7482796e 100644 --- a/scripts/src/gitops-render/core.mjs +++ b/scripts/src/gitops-render/core.mjs @@ -29,10 +29,10 @@ const defaultOutDir = "deploy/gitops/node"; const defaultRegistryPrefix = process.env.HWLAB_NODE_REGISTRY_PREFIX || "127.0.0.1:5000/hwlab"; const defaultSourceRepo = process.env.HWLAB_NODE_GIT_URL || "git@github.com:pikasTech/HWLAB.git"; const defaultGitReadUrl = process.env.HWLAB_NODE_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 defaultV02GitReadUrl = process.env.HWLAB_V02_GIT_READ_URL || "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"; +const defaultV02GitWriteUrl = process.env.HWLAB_V02_GIT_WRITE_URL || defaultV02GitReadUrl; const defaultRuntimeLaneGitReadUrl = process.env.HWLAB_RUNTIME_LANE_GIT_READ_URL || "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git"; -const defaultRuntimeLaneGitWriteUrl = process.env.HWLAB_RUNTIME_LANE_GIT_WRITE_URL || defaultV02GitWriteUrl; +const defaultRuntimeLaneGitWriteUrl = process.env.HWLAB_RUNTIME_LANE_GIT_WRITE_URL || defaultRuntimeLaneGitReadUrl; const defaultBranch = process.env.HWLAB_NODE_BRANCH || "node"; const defaultGitopsBranch = process.env.HWLAB_NODE_GITOPS_BRANCH || "node-gitops"; const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json"; @@ -44,7 +44,7 @@ const defaultV02RuntimeEndpoint = process.env.HWLAB_V02_RUNTIME_ENDPOINT || "htt const defaultV02WebEndpoint = process.env.HWLAB_V02_WEB_ENDPOINT || "https://hwlab.74-48-78-17.nip.io"; const defaultProxyUrl = process.env.HWLAB_NODE_PROXY_URL || "http://127.0.0.1:10808"; const defaultAllProxyUrl = process.env.HWLAB_NODE_ALL_PROXY_URL || "socks5h://127.0.0.1:10808"; -const defaultNoProxy = process.env.HWLAB_NODE_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 defaultNoProxy = process.env.HWLAB_NODE_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,gitea-http,gitea-http.devops-infra,gitea-http.devops-infra.svc,gitea-http.devops-infra.svc.cluster.local"; const ciToolsRunnerImage = process.env.HWLAB_NODE_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1"; const buildkitRunnerImage = process.env.HWLAB_NODE_BUILDKIT_IMAGE || "moby/buildkit:rootless"; const defaultDevBaseImage = process.env.HWLAB_NODE_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim"; diff --git a/scripts/src/gitops-render/infra-manifests.mjs b/scripts/src/gitops-render/infra-manifests.mjs index ab9fd877..9cb7fea6 100644 --- a/scripts/src/gitops-render/infra-manifests.mjs +++ b/scripts/src/gitops-render/infra-manifests.mjs @@ -1,17 +1,3 @@ -import assert from "node:assert/strict"; - -import { - ciToolsRunnerImage, - configObject, - defaultOutDir, - label, - optionalConfigString, - requiredConfigPositiveInteger, - requiredConfigString, - runtimeLaneMirrorSpecs -} from "./core.mjs"; -import { renderTemplate, shellSingleQuote } from "./tekton-scripts.mjs"; - function registryManifest() { return { apiVersion: "v1", @@ -55,200 +41,6 @@ function registryManifest() { }; } -function uniqueStrings(values) { - return Array.from(new Set(values.filter((value) => typeof value === "string" && value.length > 0))); -} - -function renderGitMirrorSshPrelude({ direct, proxySummary, proxyCommand, proxyUrl, proxyNoProxy }) { - if (direct) { - return renderTemplate("git-mirror-ssh-direct.sh", { - "__HWLAB_PROXY_SUMMARY_SHELL__": shellSingleQuote(proxySummary) - }); - } - return renderTemplate("git-mirror-ssh-proxied.sh", { - "// __HWLAB_GIT_MIRROR_PROXY_CONNECT_MJS__": renderTemplate("git-mirror-proxy-connect.mjs"), - "__HWLAB_PROXY_SUMMARY_SHELL__": shellSingleQuote(proxySummary), - "__HWLAB_PROXY_COMMAND_SHELL__": shellSingleQuote(proxyCommand), - "__HWLAB_PROXY_URL_SHELL__": shellSingleQuote(proxyUrl), - "__HWLAB_PROXY_NO_PROXY_SHELL__": shellSingleQuote(proxyNoProxy) - }); -} - -function gitMirrorConfigScripts({ - fetchConfigCommands, - gitMirrorSshPrelude, - gitopsBranchArgs, - gitopsBranchesJson, - mirrorBranchesJson, - requiredRefArgs, - sourceBranchArgs, - sourceBranchesJson, - sourceSnapshotRefPrefix -}) { - const gitopsBranchesJsonShell = shellSingleQuote(gitopsBranchesJson); - return { - "sync.sh": renderTemplate("git-mirror-sync.sh", { - "# __HWLAB_GIT_MIRROR_SSH_PRELUDE__": gitMirrorSshPrelude, - "__HWLAB_SOURCE_SNAPSHOT_REF_PREFIX_SHELL__": shellSingleQuote(sourceSnapshotRefPrefix), - "# __HWLAB_FETCH_CONFIG_COMMANDS__": fetchConfigCommands, - "__HWLAB_REQUIRED_REF_ARGS__": requiredRefArgs, - "__HWLAB_SOURCE_BRANCH_ARGS__": sourceBranchArgs, - "__HWLAB_GITOPS_BRANCH_ARGS__": gitopsBranchArgs, - "__HWLAB_MIRROR_BRANCHES_JSON_SHELL__": shellSingleQuote(mirrorBranchesJson), - "__HWLAB_SOURCE_BRANCHES_JSON_SHELL__": shellSingleQuote(sourceBranchesJson), - "__HWLAB_GITOPS_BRANCHES_JSON_SHELL__": gitopsBranchesJsonShell - }), - "install-hooks.sh": renderTemplate("git-mirror-install-hooks.sh", { - "__HWLAB_GITOPS_BRANCHES_JSON_SHELL__": gitopsBranchesJsonShell - }), - "flush.sh": renderTemplate("git-mirror-flush.sh", { - "# __HWLAB_GIT_MIRROR_SSH_PRELUDE__": gitMirrorSshPrelude, - "__HWLAB_GITOPS_BRANCH_ARGS__": gitopsBranchArgs, - "__HWLAB_GITOPS_BRANCHES_JSON_SHELL__": gitopsBranchesJsonShell - }), - "http.mjs": renderTemplate("git-mirror-http.mjs") - }; -} - -function devopsInfraGitMirrorManifest(args, deploy) { - const mirrorSpecs = runtimeLaneMirrorSpecs(deploy, { defaultNodeId: args.nodeId, defaultGitopsRoot: defaultOutDir }); - assert.ok(mirrorSpecs.length > 0, "runtime lane git mirror requires at least one deploy.lanes entry"); - const sourceBranches = uniqueStrings(mirrorSpecs.map((spec) => spec.sourceBranch)); - const gitopsBranches = uniqueStrings(mirrorSpecs.map((spec) => spec.gitopsBranch)); - const mirrorBranches = uniqueStrings([...sourceBranches, ...gitopsBranches]); - const fetchConfigCommands = mirrorBranches - .map((branch) => `git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/${branch}:refs/mirror-stage/heads/${branch}'`) - .join("\n"); - const requiredRefArgs = mirrorBranches.map((branch) => shellSingleQuote(`refs/mirror-stage/heads/${branch}`)).join(" "); - const sourceBranchArgs = sourceBranches.map((branch) => shellSingleQuote(branch)).join(" "); - const gitopsBranchArgs = gitopsBranches.map((branch) => shellSingleQuote(branch)).join(" "); - const mirrorBranchesJson = JSON.stringify(mirrorBranches); - const sourceBranchesJson = JSON.stringify(sourceBranches); - const gitopsBranchesJson = JSON.stringify(gitopsBranches); - const sourceSnapshotRefPrefix = "refs/unidesk/snapshots/hwlab-node-runtime"; - const laneConfig = configObject(configObject(deploy.lanes, "deploy.lanes")[args.lane], `deploy.lanes.${args.lane}`); - const gitMirrorConfig = configObject(laneConfig.gitMirror, `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorProxy = configObject(gitMirrorConfig.egressProxy, `deploy.lanes.${args.lane}.gitMirror.egressProxy`); - const gitMirrorProxyMode = requiredConfigString(gitMirrorProxy, "mode", `deploy.lanes.${args.lane}.gitMirror.egressProxy`); - assert.ok(gitMirrorProxyMode === "node-global" || gitMirrorProxyMode === "host-proxy-client" || gitMirrorProxyMode === "direct", `deploy.lanes.${args.lane}.gitMirror.egressProxy.mode must be node-global, host-proxy-client, or direct`); - const useDirectGitMirrorProxy = gitMirrorProxyMode === "direct"; - const useHostGitMirrorProxy = gitMirrorProxyMode === "host-proxy-client"; - const gitMirrorNamespace = requiredConfigString(gitMirrorConfig, "namespace", `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorReadName = requiredConfigString(gitMirrorConfig, "serviceReadName", `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorWriteName = requiredConfigString(gitMirrorConfig, "serviceWriteName", `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorCachePvcName = requiredConfigString(gitMirrorConfig, "cachePvcName", `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorCachePvcStorage = requiredConfigString(gitMirrorConfig, "cachePvcStorage", `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorCacheHostPath = optionalConfigString(gitMirrorConfig, "cacheHostPath", `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorServicePort = requiredConfigPositiveInteger(gitMirrorConfig, "servicePort", `deploy.lanes.${args.lane}.gitMirror`); - const gitMirrorConfigMapName = requiredConfigString(gitMirrorConfig, "syncConfigMapName", `deploy.lanes.${args.lane}.gitMirror`); - const proxyNamespace = useDirectGitMirrorProxy || useHostGitMirrorProxy ? "" : requiredConfigString(gitMirrorProxy, "namespace", `deploy.lanes.${args.lane}.gitMirror.egressProxy`); - const proxyServiceName = useDirectGitMirrorProxy || useHostGitMirrorProxy ? "" : requiredConfigString(gitMirrorProxy, "serviceName", `deploy.lanes.${args.lane}.gitMirror.egressProxy`); - const proxyPort = useDirectGitMirrorProxy ? 0 : requiredConfigPositiveInteger(gitMirrorProxy, "port", `deploy.lanes.${args.lane}.gitMirror.egressProxy`); - const proxyClientName = useDirectGitMirrorProxy ? "" : requiredConfigString(gitMirrorProxy, "clientName", `deploy.lanes.${args.lane}.gitMirror.egressProxy`); - const proxyNoProxy = !useDirectGitMirrorProxy && Array.isArray(gitMirrorProxy.noProxy) ? gitMirrorProxy.noProxy.filter((value) => typeof value === "string" && value.length > 0).join(",") : ""; - const proxyHost = useHostGitMirrorProxy - ? requiredConfigString(gitMirrorProxy, "host", `deploy.lanes.${args.lane}.gitMirror.egressProxy`) - : `${proxyServiceName}.${proxyNamespace}.svc.cluster.local`; - const proxyUrl = `http://${proxyHost}:${proxyPort}`; - const proxySummary = useDirectGitMirrorProxy - ? "git-mirror-egress-proxy mode=direct required=false transport=ssh source=yaml" - : `git-mirror-egress-proxy client=${proxyClientName} mode=${gitMirrorProxyMode} required=true host=${proxyHost} port=${proxyPort} ssh=GIT_SSH-wrapper source=yaml`; - const proxyCommand = useDirectGitMirrorProxy ? "" : `ProxyCommand=node /tmp/hwlab-github-proxy-connect.mjs ${proxyHost} ${proxyPort} %h %p`; - const gitMirrorSshPrelude = renderGitMirrorSshPrelude({ - direct: useDirectGitMirrorProxy, - proxySummary, - proxyCommand, - proxyUrl, - proxyNoProxy - }); - const cacheVolume = gitMirrorCacheHostPath === null - ? { name: "cache", persistentVolumeClaim: { claimName: gitMirrorCachePvcName } } - : { name: "cache", hostPath: { path: gitMirrorCacheHostPath, type: "DirectoryOrCreate" } }; - const labels = { - "app.kubernetes.io/part-of": "hwlab-node-control-plane", - "hwlab.pikastech.local/node": args.nodeId, - "hwlab.pikastech.local/lane": args.lane - }; - const configLabels = { ...labels, "app.kubernetes.io/name": "git-mirror" }; - const readLabels = { ...labels, "app.kubernetes.io/name": gitMirrorReadName, "hwlab.pikastech.local/git-mirror-mode": "read" }; - const writeLabels = { ...labels, "app.kubernetes.io/name": gitMirrorWriteName, "hwlab.pikastech.local/git-mirror-mode": "write" }; - return { - apiVersion: "v1", - kind: "List", - items: [ - { apiVersion: "v1", kind: "Namespace", metadata: { name: gitMirrorNamespace } }, - ...(gitMirrorCacheHostPath === null ? [{ - apiVersion: "v1", - kind: "PersistentVolumeClaim", - metadata: { name: gitMirrorCachePvcName, namespace: gitMirrorNamespace, labels: configLabels }, - spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: gitMirrorCachePvcStorage } } } - }] : []), - { - apiVersion: "v1", - kind: "ConfigMap", - metadata: { name: gitMirrorConfigMapName, namespace: gitMirrorNamespace, labels: configLabels }, - data: gitMirrorConfigScripts({ - fetchConfigCommands, - gitMirrorSshPrelude, - gitopsBranchArgs, - gitopsBranchesJson, - mirrorBranchesJson, - requiredRefArgs, - sourceBranchArgs, - sourceBranchesJson, - sourceSnapshotRefPrefix - }) - }, - { - apiVersion: "apps/v1", - kind: "Deployment", - metadata: { name: gitMirrorReadName, namespace: gitMirrorNamespace, labels: readLabels }, - spec: { - replicas: 1, - selector: { matchLabels: { "app.kubernetes.io/name": gitMirrorReadName } }, - template: { - metadata: { labels: readLabels }, - spec: { - containers: [{ - name: "git-mirror", - image: ciToolsRunnerImage, - imagePullPolicy: "IfNotPresent", - command: ["node"], - args: ["/etc/git-mirror/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: "config", mountPath: "/etc/git-mirror", readOnly: true } - ] - }], - volumes: [ - cacheVolume, - { name: "config", configMap: { name: gitMirrorConfigMapName, defaultMode: 0o555 } } - ] - } - } - } - }, - { - apiVersion: "v1", - kind: "Service", - metadata: { name: gitMirrorReadName, namespace: gitMirrorNamespace, labels: readLabels }, - spec: { selector: { "app.kubernetes.io/name": gitMirrorReadName }, ports: [{ name: "http", port: gitMirrorServicePort, targetPort: "http" }] } - }, - { - apiVersion: "v1", - kind: "Service", - metadata: { name: gitMirrorWriteName, namespace: gitMirrorNamespace, labels: writeLabels }, - spec: { selector: { "app.kubernetes.io/name": gitMirrorReadName }, ports: [{ name: "http", port: gitMirrorServicePort, targetPort: "http" }] } - } - ] - }; -} - export { - devopsInfraGitMirrorManifest, - registryManifest, - uniqueStrings + registryManifest }; diff --git a/scripts/src/gitops-render/runtime-manifests.mjs b/scripts/src/gitops-render/runtime-manifests.mjs index 6d0e37e8..cb099fb3 100644 --- a/scripts/src/gitops-render/runtime-manifests.mjs +++ b/scripts/src/gitops-render/runtime-manifests.mjs @@ -1237,7 +1237,7 @@ function opencodeConfigBoolean(value, fallback, label) { } function opencodeEgressProxyUrlForProfile(deploy, profile) { - const proxy = deploy?.lanes?.[profile]?.gitMirror?.egressProxy; + const proxy = deploy?.lanes?.[profile]?.egressProxy; if (!proxy || proxy.required === false) return ""; const proxyUrl = configString(proxy.proxyUrl); if (proxyUrl) return proxyUrl; @@ -1249,7 +1249,7 @@ function opencodeEgressProxyUrlForProfile(deploy, profile) { } function opencodeEgressNoProxyForProfile(deploy, profile) { - const proxy = deploy?.lanes?.[profile]?.gitMirror?.egressProxy; + const proxy = deploy?.lanes?.[profile]?.egressProxy; const entries = Array.isArray(proxy?.noProxy) ? proxy.noProxy.map(configString).filter(Boolean) : []; return entries.length > 0 ? entries.join(",") : "localhost,127.0.0.1,::1,.svc,.svc.cluster.local,.cluster.local"; } diff --git a/scripts/src/gitops-render/tekton-manifests.mjs b/scripts/src/gitops-render/tekton-manifests.mjs index b0dfe83f..5fe20637 100644 --- a/scripts/src/gitops-render/tekton-manifests.mjs +++ b/scripts/src/gitops-render/tekton-manifests.mjs @@ -307,7 +307,7 @@ function planArtifactsTask({ serviceIds = defaultServiceIds } = {}) { taskSpec: { params: [ { name: "git-url" }, - { name: "git-read-url" }, + { name: "gitops-read-url" }, { name: "gitops-branch" }, { name: "lane" }, { name: "revision" }, @@ -338,7 +338,7 @@ function planArtifactsTask({ serviceIds = defaultServiceIds } = {}) { }, params: [ { name: "git-url", value: "$(params.git-url)" }, - { name: "git-read-url", value: "$(params.git-read-url)" }, + { name: "gitops-read-url", value: "$(params.gitops-read-url)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" }, { name: "lane", value: "$(params.lane)" }, { name: "revision", value: "$(params.revision)" }, @@ -516,11 +516,11 @@ function tektonTasks(args = { lane: "node" }, deploy = null) { name: "prepare-source", taskSpec: { params: [ - { name: "git-url" }, { name: "git-read-url" }, { name: "source-branch" }, + { name: "git-url" }, { name: "git-read-url" }, { name: "gitops-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" }], + workspaces: [{ name: "source" }], steps: [{ name: "prepare-source", image: ciToolsRunnerImage, env: proxyEnv(), script: prepareSourceScript() }] } }; @@ -532,13 +532,13 @@ function tektonTasks(args = { lane: "node" }, deploy = null) { name: "gitops-promote", taskSpec: { params: [ - { name: "git-url" }, { name: "git-read-url" }, { name: "git-write-url" }, + { name: "git-url" }, { name: "git-read-url" }, { name: "gitops-read-url" }, { name: "git-write-url" }, { name: "gitops-username" }, { 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" }], + workspaces: [{ name: "source" }, { name: "gitea-auth" }], steps: [{ name: "promote", image: ciToolsRunnerImage, env: proxyEnv(), script: gitopsPromoteScript() }] } }, @@ -576,7 +576,9 @@ function tektonPipeline(args = { lane: "node" }, deploy = null) { params: [ { name: "git-url", type: "string", default: defaultSourceRepo }, { name: "git-read-url", type: "string", default: args.gitReadUrl }, + { name: "gitops-read-url", type: "string", default: args.gitReadUrl }, { name: "git-write-url", type: "string", default: args.gitWriteUrl }, + { name: "gitops-username", type: "string", default: "unidesk-admin" }, { name: "source-branch", type: "string", default: args.sourceBranch }, { name: "gitops-branch", type: "string", default: args.gitopsBranch }, { name: "lane", type: "string", default: settings.lane }, @@ -596,19 +598,19 @@ function tektonPipeline(args = { lane: "node" }, deploy = null) { ], workspaces: [ { name: "source" }, - { name: "git-ssh" } + { name: "gitea-auth" } ], tasks: [ { name: "prepare-source", workspaces: [ - { name: "source", workspace: "source" }, - { name: "git-ssh", workspace: "git-ssh" } + { name: "source", workspace: "source" } ], taskSpec: { params: [ { name: "git-url" }, { name: "git-read-url" }, + { name: "gitops-read-url" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, @@ -618,12 +620,13 @@ function tektonPipeline(args = { lane: "node" }, deploy = null) { { name: "services" }, { name: "revision" } ], - workspaces: [{ name: "source" }, { name: "git-ssh" }], + workspaces: [{ name: "source" }], 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: "gitops-read-url", value: "$(params.gitops-read-url)" }, { name: "source-branch", value: "$(params.source-branch)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" }, { name: "lane", value: "$(params.lane)" }, @@ -641,13 +644,15 @@ function tektonPipeline(args = { lane: "node" }, deploy = null) { runAfter: ["collect-artifacts"], workspaces: [ { name: "source", workspace: "source" }, - { name: "git-ssh", workspace: "git-ssh" } + { name: "gitea-auth", workspace: "gitea-auth" } ], taskSpec: { params: [ { name: "git-url" }, { name: "git-read-url" }, + { name: "gitops-read-url" }, { name: "git-write-url" }, + { name: "gitops-username" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, @@ -658,13 +663,15 @@ function tektonPipeline(args = { lane: "node" }, deploy = null) { { 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" }], + workspaces: [{ name: "source" }, { name: "gitea-auth" }], 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: "gitops-read-url", value: "$(params.gitops-read-url)" }, { name: "git-write-url", value: "$(params.git-write-url)" }, + { name: "gitops-username", value: "$(params.gitops-username)" }, { name: "source-branch", value: "$(params.source-branch)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" }, { name: "lane", value: "$(params.lane)" }, @@ -709,7 +716,9 @@ function tektonPipelineRunTemplate({ source, args, deploy = null }) { params: [ { name: "git-url", value: args.sourceRepo }, { name: "git-read-url", value: args.gitReadUrl }, + { name: "gitops-read-url", value: args.gitReadUrl }, { name: "git-write-url", value: args.gitWriteUrl }, + { name: "gitops-username", value: "unidesk-admin" }, { name: "source-branch", value: args.sourceBranch }, { name: "gitops-branch", value: args.gitopsBranch }, { name: "lane", value: settings.lane }, @@ -723,7 +732,7 @@ function tektonPipelineRunTemplate({ source, args, deploy = null }) { ], workspaces: [ { name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } }, - { name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } } + { name: "gitea-auth", secret: { secretName: "pac-gitea-hwlab", items: [{ key: "token", path: "token" }] } } ] } }; diff --git a/scripts/src/gitops-render/tekton-scripts.mjs b/scripts/src/gitops-render/tekton-scripts.mjs index 1f207e33..8fcf5b38 100644 --- a/scripts/src/gitops-render/tekton-scripts.mjs +++ b/scripts/src/gitops-render/tekton-scripts.mjs @@ -63,7 +63,6 @@ function gitSshShellFunction() { function prepareSourceScript() { return renderTemplate("prepare-source.sh", { "# __HWLAB_CI_TIMING_SHELL__\n": ciTimingShellFunction(), - "# __HWLAB_GIT_SSH_SHELL__\n": gitSshShellFunction(), "__HWLAB_CI_TOOLS_IMAGE__": ciToolsRunnerImage, "__HWLAB_V02_RUNTIME_ENDPOINT__": JSON.stringify(defaultV02RuntimeEndpoint).slice(1, -1), "__HWLAB_VALIDATION_TASK_COUNT__": primitiveValidationTasks.length @@ -140,7 +139,6 @@ function collectArtifactsScript() { function gitopsPromoteScript() { return renderTemplate("gitops-promote.sh", { "# __HWLAB_CI_TIMING_SHELL__\n": ciTimingShellFunction(), - "# __HWLAB_GIT_SSH_SHELL__\n": gitSshShellFunction(), "__HWLAB_CI_TOOLS_IMAGE__": ciToolsRunnerImage }); } diff --git a/scripts/src/gitops-render/templates/git-mirror-flush.sh b/scripts/src/gitops-render/templates/git-mirror-flush.sh deleted file mode 100644 index cf0931b2..00000000 --- a/scripts/src/gitops-render/templates/git-mirror-flush.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/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"} -' "$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 -# __HWLAB_GIT_MIRROR_SSH_PRELUDE__ -/script/install-hooks.sh -for gitops_branch in __HWLAB_GITOPS_BRANCH_ARGS__; do - local_head=$(git -C "$repo_path" show-ref --verify --hash "refs/heads/$gitops_branch" 2>/dev/null || true) - if [ -z "$local_head" ]; then - echo "git mirror flush skips missing local refs/heads/$gitops_branch" - continue - fi - fetch_started=$(now_ms) - timeout 90 git -C "$repo_path" fetch --quiet "$repo_url" "refs/heads/$gitops_branch:refs/mirror-stage/heads/$gitops_branch" - emit_timing "fetch-$gitops_branch" succeeded "$fetch_started" - github_head=$(git -C "$repo_path" show-ref --verify --hash "refs/mirror-stage/heads/$gitops_branch" 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 $gitops_branch local=$local_head github=$github_head" >&2 - exit 52 - fi - if [ "$local_head" != "$github_head" ]; then - push_started=$(now_ms) - timeout 120 git -C "$repo_path" push "$repo_url" "$local_head:refs/heads/$gitops_branch" - emit_timing "push-$gitops_branch" succeeded "$push_started" - else - echo "git mirror flush $gitops_branch already matches GitHub" - fi - git -C "$repo_path" update-ref "refs/mirror-stage/heads/$gitops_branch" "$local_head" -done -git -C "$repo_path" update-server-info -flushed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) -export flushed_at -export gitops_branches_json=__HWLAB_GITOPS_BRANCHES_JSON_SHELL__ -node - <<'NODE' | tee /cache/HWLAB.last-flush.json -const { execFileSync } = require("node:child_process"); -const repo = "/cache/pikasTech/HWLAB.git"; -const gitopsBranches = JSON.parse(process.env.gitops_branches_json || "[]"); -function rev(ref) { - try { return execFileSync("git", ["-C", repo, "rev-parse", ref], { encoding: "utf8" }).trim() || null; } catch { return null; } -} -const refs = {}; -for (const branch of gitopsBranches) { - refs["refs/heads/" + branch] = rev("refs/heads/" + branch); - refs["refs/mirror-stage/heads/" + branch] = rev("refs/mirror-stage/heads/" + branch); -} -const pendingFlush = gitopsBranches.some((branch) => refs["refs/heads/" + branch] && refs["refs/mirror-stage/heads/" + branch] && refs["refs/heads/" + branch] !== refs["refs/mirror-stage/heads/" + branch]); -console.log(JSON.stringify({ - event: "git-mirror-flush", - status: "flushed", - repository: "pikasTech/HWLAB", - flushedAt: process.env.flushed_at, - pendingFlush, - refs -})); -NODE -emit_timing total succeeded "$started_ms" diff --git a/scripts/src/gitops-render/templates/git-mirror-http.mjs b/scripts/src/gitops-render/templates/git-mirror-http.mjs deleted file mode 100644 index a69446d9..00000000 --- a/scripts/src/gitops-render/templates/git-mirror-http.mjs +++ /dev/null @@ -1,196 +0,0 @@ -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"; -} diff --git a/scripts/src/gitops-render/templates/git-mirror-install-hooks.sh b/scripts/src/gitops-render/templates/git-mirror-install-hooks.sh deleted file mode 100644 index b8f70d18..00000000 --- a/scripts/src/gitops-render/templates/git-mirror-install-hooks.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/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 [ "$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 -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 -written_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) -export repo_path written_at -export gitops_branches_json=__HWLAB_GITOPS_BRANCHES_JSON_SHELL__ -node - <<'NODE' > /cache/HWLAB.last-write.json -const { execFileSync } = require("node:child_process"); -const repo = process.env.repo_path || "/cache/pikasTech/HWLAB.git"; -const gitopsBranches = JSON.parse(process.env.gitops_branches_json || "[]"); -function rev(ref) { - try { return execFileSync("git", ["-C", repo, "rev-parse", ref], { encoding: "utf8" }).trim() || null; } catch { return null; } -} -const refs = {}; -for (const branch of gitopsBranches) { - refs["refs/heads/" + branch] = rev("refs/heads/" + branch); - refs["refs/mirror-stage/heads/" + branch] = rev("refs/mirror-stage/heads/" + branch); -} -const pendingFlush = gitopsBranches.some((branch) => refs["refs/heads/" + branch] && refs["refs/heads/" + branch] !== refs["refs/mirror-stage/heads/" + branch]); -console.log(JSON.stringify({ - event: "git-mirror-write", - status: "received", - repository: "pikasTech/HWLAB", - writtenAt: process.env.written_at, - pendingFlush, - refs -})); -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 diff --git a/scripts/src/gitops-render/templates/git-mirror-proxy-connect.mjs b/scripts/src/gitops-render/templates/git-mirror-proxy-connect.mjs deleted file mode 100644 index 372bd7a4..00000000 --- a/scripts/src/gitops-render/templates/git-mirror-proxy-connect.mjs +++ /dev/null @@ -1,48 +0,0 @@ -#!/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("hwlab git-mirror proxy-connect: invalid ProxyCommand arguments"); - process.exit(64); -} -let settled = false; -let tunnelEstablished = false; -function finish(code, message) { - if (settled) return; - settled = true; - if (message) console.error("hwlab git-mirror proxy-connect: " + message); - process.exit(code); -} -const socket = net.createConnection({ host: proxyHost, port: proxyPort }); -let buffer = Buffer.alloc(0); -socket.setTimeout(15000, () => { socket.destroy(); finish(65, "timeout connecting via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort); }); -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) => finish(tunnelEstablished ? 69 : 66, (tunnelEstablished ? "tunnel socket error: " : "tcp error connecting to proxy: ") + (error && error.message ? error.message : String(error)))); -socket.on("close", () => { if (!tunnelEstablished) finish(68, "proxy closed before CONNECT completed via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort); else finish(0); }); -function onData(chunk) { - buffer = Buffer.concat([buffer, chunk]); - const headerEnd = buffer.indexOf("\r\n\r\n"); - if (headerEnd === -1 && buffer.length < 8192) return; - if (headerEnd === -1) { socket.destroy(); finish(68, "proxy response header exceeded 8192 bytes before CONNECT status via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort); 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) { - const safeStatus = statusLine.replace(/[^\x20-\x7e]/g, "?").slice(0, 160); - socket.destroy(); - finish(67, "proxy CONNECT failed via " + proxyHost + ":" + proxyPort + " to " + targetHost + ":" + targetPort + ": " + safeStatus); - return; - } - socket.off("data", onData); - socket.setTimeout(0); - tunnelEstablished = true; - const rest = buffer.slice(headerEnd + 4); - if (rest.length) process.stdout.write(rest); - process.stdin.on("error", () => {}); - process.stdout.on("error", () => {}); - process.stdin.pipe(socket); - socket.pipe(process.stdout); -} -socket.on("data", onData); \ No newline at end of file diff --git a/scripts/src/gitops-render/templates/git-mirror-ssh-direct.sh b/scripts/src/gitops-render/templates/git-mirror-ssh-direct.sh deleted file mode 100644 index b2ce5451..00000000 --- a/scripts/src/gitops-render/templates/git-mirror-ssh-direct.sh +++ /dev/null @@ -1,11 +0,0 @@ -printf '%s\n' __HWLAB_PROXY_SUMMARY_SHELL__ >&2 -cat > /tmp/hwlab-git-ssh-proxy.sh <<'SH_PROXY' -#!/bin/sh -exec ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=15 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 "$@" -SH_PROXY -chmod 0700 /tmp/hwlab-git-ssh-proxy.sh -unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy -export NO_PROXY='*' -export no_proxy='*' -export GIT_SSH=/tmp/hwlab-git-ssh-proxy.sh -unset GIT_SSH_COMMAND diff --git a/scripts/src/gitops-render/templates/git-mirror-ssh-proxied.sh b/scripts/src/gitops-render/templates/git-mirror-ssh-proxied.sh deleted file mode 100644 index 354bb9a9..00000000 --- a/scripts/src/gitops-render/templates/git-mirror-ssh-proxied.sh +++ /dev/null @@ -1,20 +0,0 @@ -printf '%s\n' __HWLAB_PROXY_SUMMARY_SHELL__ >&2 -cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY' -// __HWLAB_GIT_MIRROR_PROXY_CONNECT_MJS__ -NODE_PROXY -chmod 0700 /tmp/hwlab-github-proxy-connect.mjs -cat > /tmp/hwlab-git-ssh-proxy.sh <<'SH_PROXY' -#!/bin/sh -exec ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=15 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 -o __HWLAB_PROXY_COMMAND_SHELL__ "$@" -SH_PROXY -chmod 0700 /tmp/hwlab-git-ssh-proxy.sh -export HTTP_PROXY=__HWLAB_PROXY_URL_SHELL__ -export HTTPS_PROXY=__HWLAB_PROXY_URL_SHELL__ -export ALL_PROXY=__HWLAB_PROXY_URL_SHELL__ -export http_proxy=__HWLAB_PROXY_URL_SHELL__ -export https_proxy=__HWLAB_PROXY_URL_SHELL__ -export all_proxy=__HWLAB_PROXY_URL_SHELL__ -export NO_PROXY=__HWLAB_PROXY_NO_PROXY_SHELL__ -export no_proxy=__HWLAB_PROXY_NO_PROXY_SHELL__ -export GIT_SSH=/tmp/hwlab-git-ssh-proxy.sh -unset GIT_SSH_COMMAND \ No newline at end of file diff --git a/scripts/src/gitops-render/templates/git-mirror-sync.sh b/scripts/src/gitops-render/templates/git-mirror-sync.sh deleted file mode 100644 index b007bc27..00000000 --- a/scripts/src/gitops-render/templates/git-mirror-sync.sh +++ /dev/null @@ -1,181 +0,0 @@ -#!/bin/sh -set -eu -repo_url="ssh://git@ssh.github.com:443/pikasTech/HWLAB.git" -repo_path="/cache/pikasTech/HWLAB.git" -source_snapshot_ref_prefix=__HWLAB_SOURCE_SNAPSHOT_REF_PREFIX_SHELL__ -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"} -' "$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 -# __HWLAB_GIT_MIRROR_SSH_PRELUDE__ -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 -# __HWLAB_FETCH_CONFIG_COMMANDS__ -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 __HWLAB_REQUIRED_REF_ARGS__; 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 -' "$ref" | sed 's#^refs/mirror-stage/heads/##') - is_gitops_branch=false - for gitops_branch in __HWLAB_GITOPS_BRANCH_ARGS__; do - if [ "$name" = "$gitops_branch" ]; then is_gitops_branch=true; break; fi - done - if [ "$is_gitops_branch" = true ]; then - local_sha=$(git -C "$repo_path" show-ref --verify --hash "refs/heads/$name" 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 $name ahead of GitHub" - continue - fi - if ! git -C "$repo_path" merge-base --is-ancestor "$local_sha" "$sha"; then - echo "git mirror sync found divergent $name 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 -for source_branch in __HWLAB_SOURCE_BRANCH_ARGS__; do - source_sha=$(git -C "$repo_path" show-ref --verify --hash "refs/heads/$source_branch" 2>/dev/null || true) - if [ -n "$source_sha" ]; then - source_stage_ref="${source_snapshot_ref_prefix%/}/$source_branch/$source_sha" - git -C "$repo_path" update-ref "$source_stage_ref" "$source_sha" - fi -done -while read -r sha ref; do - [ -n "$sha" ] || continue - name=$(printf '%s -' "$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 -' "$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 -' "$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" -for head_branch in __HWLAB_SOURCE_BRANCH_ARGS__; do - if git -C "$repo_path" show-ref --verify --quiet "refs/heads/$head_branch"; then - git -C "$repo_path" symbolic-ref HEAD "refs/heads/$head_branch" - break - fi -done -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 -published_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) -for source_branch in __HWLAB_SOURCE_BRANCH_ARGS__; do - git -C "$repo_path" rev-parse "refs/heads/$source_branch" 2>/dev/null | tee "/cache/HWLAB-$source_branch.head" >/dev/null || true -done -printf '%s -' "$published_at" > /cache/HWLAB.last-sync -export published_at -export source_snapshot_ref_prefix -export mirror_branches_json=__HWLAB_MIRROR_BRANCHES_JSON_SHELL__ -export source_branches_json=__HWLAB_SOURCE_BRANCHES_JSON_SHELL__ -export gitops_branches_json=__HWLAB_GITOPS_BRANCHES_JSON_SHELL__ -node - <<'NODE' | tee /cache/HWLAB.last-sync.json -const { execFileSync } = require("node:child_process"); -const repo = "/cache/pikasTech/HWLAB.git"; -const mirrorBranches = JSON.parse(process.env.mirror_branches_json || "[]"); -const sourceBranches = JSON.parse(process.env.source_branches_json || "[]"); -const gitopsBranches = JSON.parse(process.env.gitops_branches_json || "[]"); -const sourceSnapshotRefPrefix = (process.env.source_snapshot_ref_prefix || "refs/unidesk/snapshots/hwlab-node-runtime").replace(//+$/u, ""); -function rev(ref) { - try { return execFileSync("git", ["-C", repo, "rev-parse", ref], { encoding: "utf8" }).trim() || null; } catch { return null; } -} -const refs = {}; -for (const branch of mirrorBranches) { - refs["refs/heads/" + branch] = rev("refs/heads/" + branch); - refs["refs/mirror-stage/heads/" + branch] = rev("refs/mirror-stage/heads/" + branch); -} -const sourceSnapshots = {}; -for (const branch of sourceBranches) { - const source = refs["refs/heads/" + branch] || null; - const stageRef = source ? sourceSnapshotRefPrefix + "/" + branch + "/" + source : null; - const sourceSnapshot = stageRef ? rev(stageRef) : null; - if (stageRef) refs[stageRef] = sourceSnapshot; - sourceSnapshots[branch] = { stageRef, sourceSnapshot }; -} -const sourceInSync = sourceBranches.every((branch) => refs["refs/heads/" + branch] && refs["refs/heads/" + branch] === refs["refs/mirror-stage/heads/" + branch] && sourceSnapshots[branch]?.sourceSnapshot === refs["refs/mirror-stage/heads/" + branch]); -const gitopsInSync = gitopsBranches.every((branch) => refs["refs/heads/" + branch] && refs["refs/heads/" + branch] === refs["refs/mirror-stage/heads/" + branch]); -const pendingFlush = gitopsBranches.some((branch) => refs["refs/heads/" + branch] && refs["refs/mirror-stage/heads/" + branch] && refs["refs/heads/" + branch] !== refs["refs/mirror-stage/heads/" + branch]); -const payload = { - event: "git-mirror-sync", - status: "published", - repository: "pikasTech/HWLAB", - publishedAt: process.env.published_at, - pendingFlush, - sourceInSync, - gitopsInSync, - sourceSnapshots, - refs -}; -console.log(JSON.stringify(payload)); -NODE -emit_timing total succeeded "$sync_started_ms" diff --git a/scripts/src/gitops-render/templates/gitops-promote.sh b/scripts/src/gitops-render/templates/gitops-promote.sh index 595b5b76..940eafa6 100644 --- a/scripts/src/gitops-render/templates/gitops-promote.sh +++ b/scripts/src/gitops-render/templates/gitops-promote.sh @@ -8,9 +8,6 @@ export HWLAB_TEKTON_TASK="gitops-promote" export HWLAB_SOURCE_REVISION="$(params.revision)" echo '{"event":"ci-base-image","phase":"gitops-promote","image":"__HWLAB_CI_TOOLS_IMAGE__","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 -# __HWLAB_GIT_SSH_SHELL__ - -git_url_requires_ssh() { case "$1" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; } lane="$(params.lane)" case "$lane" in node) runtime_lane=false ;; @@ -21,29 +18,27 @@ if [ "$runtime_lane" = "true" ]; then else printf 'true' > /tekton/results/runtime-ready-required fi -source_head_url_for_setup="$(params.git-url)" -gitops_read_url_for_setup="$(params.git-url)" -if [ "$runtime_lane" = "true" ]; 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 +test -s /workspace/gitea-auth/token || { echo '{"event":"gitea-auth","phase":"gitops-promote","status":"failed","reason":"token-missing"}'; exit 31; } +askpass_dir="$(mktemp -d)" +cat > "$askpass_dir/askpass.sh" <<'SH' +#!/bin/sh +case "$1" in + *sername*) printf '%s\n' "$GITEA_USERNAME" ;; + *) cat /workspace/gitea-auth/token ;; +esac +SH +chmod 0700 "$askpass_dir/askpass.sh" +export GITEA_USERNAME="$(params.gitops-username)" +export GIT_ASKPASS="$askpass_dir/askpass.sh" +export GIT_ASKPASS_REQUIRE=force +export GIT_TERMINAL_PROMPT=0 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 [ "$runtime_lane" = "true" ]; then - source_head_url="$(params.git-read-url)" - fi + source_head_url="$(params.git-read-url)" 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)"'"}' @@ -182,11 +177,8 @@ node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$lane" --catalog-path 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_read_url="$(params.gitops-read-url)" gitops_write_url="$(params.git-write-url)" -if [ "$runtime_lane" = "true" ]; 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" diff --git a/scripts/src/gitops-render/templates/per-service-publish.sh b/scripts/src/gitops-render/templates/per-service-publish.sh index 70c4e0e5..174bf894 100644 --- a/scripts/src/gitops-render/templates/per-service-publish.sh +++ b/scripts/src/gitops-render/templates/per-service-publish.sh @@ -28,7 +28,7 @@ const catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PA 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 envReuse = planned.runtimeMode === "env-reuse-gitea-checkout" || service.runtimeMode === "env-reuse-gitea-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) : ""); @@ -48,7 +48,7 @@ const values = { "component-input-hash": componentInputHash, "environment-input-hash": environmentInputHash, "code-input-hash": codeInputHash, - "runtime-mode": envReuse ? "env-reuse-git-mirror-checkout" : "service-image", + "runtime-mode": envReuse ? "env-reuse-gitea-checkout" : "service-image", "boot-repo": planned.bootRepo || service.bootRepo || "", "boot-commit": envReuse ? revision : (service.bootCommit || ""), "boot-sh": planned.bootSh || service.bootSh || "", diff --git a/scripts/src/gitops-render/templates/plan-artifacts.sh b/scripts/src/gitops-render/templates/plan-artifacts.sh index 8aa28861..6fd07c5e 100644 --- a/scripts/src/gitops-render/templates/plan-artifacts.sh +++ b/scripts/src/gitops-render/templates/plan-artifacts.sh @@ -9,7 +9,7 @@ if [ -d "$ci_node_deps/yaml" ] && [ ! -e node_modules/yaml ]; then ln -s "$ci_node_deps/yaml" node_modules/yaml fi test "$(git rev-parse HEAD)" = "$(params.revision)" -HWLAB_CATALOG_PATH="$(params.catalog-path)" HWLAB_GIT_URL="$(params.git-url)" HWLAB_GIT_READ_URL="$(params.git-read-url)" HWLAB_GITOPS_BRANCH="$(params.gitops-branch)" HWLAB_SERVICES="$(params.services)" node scripts/ci/restore-artifact-catalog.mjs +HWLAB_CATALOG_PATH="$(params.catalog-path)" HWLAB_GIT_URL="$(params.git-url)" HWLAB_GIT_READ_URL="$(params.gitops-read-url)" HWLAB_GITOPS_BRANCH="$(params.gitops-branch)" HWLAB_SERVICES="$(params.services)" node scripts/ci/restore-artifact-catalog.mjs node scripts/ci-plan.mjs --lane "$(params.lane)" --base-ref "$(params.release-base-commit)" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog "$(params.catalog-path)" --registry-prefix "$(params.registry-prefix)" --services "$(params.services)" --release-target "$(params.release-target)" --release-consumer "$(params.release-consumer)" --verify-reuse-registry > /workspace/source/ci-plan.json node scripts/ci/verify-reviewed-plan.mjs --actual-plan /workspace/source/ci-plan.json --reviewed-plan "$(params.reviewed-plan)" node - <<'NODE' @@ -25,7 +25,7 @@ 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 envReuse = service.runtimeMode === "env-reuse-gitea-checkout" || service.envReuse === true; const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected)); return { serviceId, diff --git a/scripts/src/gitops-render/templates/prepare-source.sh b/scripts/src/gitops-render/templates/prepare-source.sh index 40896017..9b8bf3ae 100644 --- a/scripts/src/gitops-render/templates/prepare-source.sh +++ b/scripts/src/gitops-render/templates/prepare-source.sh @@ -9,16 +9,8 @@ export HWLAB_SOURCE_REVISION="$(params.revision)" echo '{"event":"ci-base-image","phase":"prepare-source","image":"__HWLAB_CI_TOOLS_IMAGE__","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}))' -# __HWLAB_GIT_SSH_SHELL__ - -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 +gitops_read_url="$(params.gitops-read-url)" 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 @@ -35,8 +27,8 @@ ci_timing_emit prepare-source-dependencies succeeded "$prepare_source_dependenci 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" +if git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads "$gitops_read_url" "$(params.gitops-branch)" >/dev/null; then + git remote set-url gitops-catalog "$gitops_read_url" 2>/dev/null || git remote add gitops-catalog "$gitops_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