refactor: primitive G14 CI without Docker fallback
This commit is contained in:
+69
-100
@@ -24,7 +24,6 @@ const defaultNoProxy = process.env.HWLAB_G14_NO_PROXY || "hyueapi.com,.hyueapi.c
|
||||
const ciToolsRunnerImage = process.env.HWLAB_G14_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-v1";
|
||||
const buildkitRunnerImage = process.env.HWLAB_G14_BUILDKIT_IMAGE || "moby/buildkit:rootless";
|
||||
const defaultDevBaseImage = process.env.HWLAB_G14_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim";
|
||||
const tektonSingleImagePublish = process.env.HWLAB_G14_TEKTON_SINGLE_IMAGE_PUBLISH === "1";
|
||||
const defaultServiceIds = Object.freeze([
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-cloud-web",
|
||||
@@ -61,6 +60,66 @@ const primitiveValidationTasks = Object.freeze([
|
||||
"render_check_revision=$(node --input-type=module -e \"import fs from 'node:fs'; const data = JSON.parse(fs.readFileSync('deploy/gitops/g14/source.json', 'utf8')); process.stdout.write(data.sourceCommit);\")",
|
||||
"test -n \"$render_check_revision\"",
|
||||
"node scripts/g14-gitops-render.mjs --check --source-revision \"$render_check_revision\"",
|
||||
`node --input-type=module <<'NODE'
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const root = process.cwd();
|
||||
const ciFileName = "CI" + ".json";
|
||||
const forbiddenCiFragments = [
|
||||
"ci" + "-json",
|
||||
"HWLAB_G14_TEKTON" + "_SINGLE_IMAGE_PUBLISH",
|
||||
"single" + "-dind",
|
||||
"docker" + ":29",
|
||||
"DOCKER" + "_HOST",
|
||||
"Docker" + "-in-Docker",
|
||||
"D" + "IND",
|
||||
"docker" + " push",
|
||||
"--build" + "-backend",
|
||||
"HWLAB_ARTIFACT" + "_BUILD_BACKEND",
|
||||
"--legacy" + "-source-images",
|
||||
"HWLAB_G14_USE_DEPLOY_IMAGES" + "=0"
|
||||
];
|
||||
const scanTargets = [
|
||||
"scripts/g14-gitops-render.mjs",
|
||||
"scripts/artifact-publish.mjs",
|
||||
"scripts/g14-artifact-publish.mjs",
|
||||
"scripts/src/g14-ci-plan-lib.mjs",
|
||||
"deploy/gitops/g14/tekton"
|
||||
];
|
||||
const skipDirs = new Set([".git", "node_modules", ".worktree"]);
|
||||
|
||||
function walkFiles(relativePath) {
|
||||
const absolutePath = path.join(root, relativePath);
|
||||
let stat;
|
||||
try {
|
||||
stat = statSync(absolutePath);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
if (stat.isFile()) return [relativePath];
|
||||
if (!stat.isDirectory()) return [];
|
||||
const files = [];
|
||||
for (const entry of readdirSync(absolutePath, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && skipDirs.has(entry.name)) continue;
|
||||
files.push(...walkFiles(path.join(relativePath, entry.name)));
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
const ciFiles = walkFiles(".").filter((filePath) => path.basename(filePath) === ciFileName);
|
||||
const violations = [];
|
||||
for (const filePath of scanTargets.flatMap(walkFiles)) {
|
||||
const text = readFileSync(path.join(root, filePath), "utf8");
|
||||
for (const fragment of forbiddenCiFragments) {
|
||||
if (text.includes(fragment)) violations.push({ filePath, fragment });
|
||||
}
|
||||
}
|
||||
if (ciFiles.length || violations.length) {
|
||||
console.error(JSON.stringify({ status: "failed", ciFiles, violations }, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
NODE`,
|
||||
"node --check scripts/artifact-publish.mjs",
|
||||
"node --check scripts/g14-artifact-publish.mjs"
|
||||
]
|
||||
@@ -100,7 +159,7 @@ function parseArgs(argv) {
|
||||
webEndpoint: defaultWebEndpoint,
|
||||
prodRuntimeEndpoint: defaultProdRuntimeEndpoint,
|
||||
prodWebEndpoint: defaultProdWebEndpoint,
|
||||
useDeployImages: process.env.HWLAB_G14_USE_DEPLOY_IMAGES !== "0",
|
||||
useDeployImages: true,
|
||||
check: false,
|
||||
write: true,
|
||||
help: false
|
||||
@@ -118,7 +177,6 @@ function parseArgs(argv) {
|
||||
else if (arg === "--prod-runtime-endpoint") args.prodRuntimeEndpoint = readOption(argv, ++index, arg);
|
||||
else if (arg === "--prod-web-endpoint") args.prodWebEndpoint = readOption(argv, ++index, arg);
|
||||
else if (arg === "--use-deploy-images") args.useDeployImages = true;
|
||||
else if (arg === "--legacy-source-images") args.useDeployImages = false;
|
||||
else if (arg === "--check") {
|
||||
args.check = true;
|
||||
args.write = false;
|
||||
@@ -152,8 +210,7 @@ function usage() {
|
||||
` --web-endpoint URL default: ${defaultWebEndpoint}`,
|
||||
` --prod-runtime-endpoint URL default: ${defaultProdRuntimeEndpoint}`,
|
||||
` --prod-web-endpoint URL default: ${defaultProdWebEndpoint}`,
|
||||
" --use-deploy-images default: render workload images/env from deploy/deploy.json per service",
|
||||
" --legacy-source-images render every workload with the source revision tag instead of per-service desired images",
|
||||
" --use-deploy-images default and only supported mode: render workload images/env from deploy/deploy.json per service",
|
||||
" --check verify generated files are current without writing",
|
||||
" --no-write plan and print generated file list without writing"
|
||||
].join("\n");
|
||||
@@ -669,39 +726,6 @@ function primitiveValidationTask(task) {
|
||||
};
|
||||
}
|
||||
|
||||
function publishScript() {
|
||||
return `#!/bin/sh
|
||||
set -eu
|
||||
${dependencyProxyProbeScript("image-publish-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")}
|
||||
echo '{"event":"ci-base-image","phase":"image-publish","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'
|
||||
for tool in node npm git python3 ssh docker; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"image-publish","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
|
||||
export DOCKER_HOST=unix:///var/run/docker.sock
|
||||
export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)"
|
||||
export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton"
|
||||
export HWLAB_DEV_REGISTRY_PREFIX="$(params.registry-prefix)"
|
||||
export HWLAB_DEV_REGISTRY_K8S_NATIVE=1
|
||||
if [ -n "$(params.base-image)" ]; then export HWLAB_DEV_BASE_IMAGE="$(params.base-image)"; fi
|
||||
for i in $(seq 1 90); do docker info >/dev/null 2>&1 && break; sleep 2; done
|
||||
docker info >/dev/null
|
||||
if [ -n "\${HWLAB_DEV_BASE_IMAGE:-}" ]; then
|
||||
echo '{"event":"dependency-local-registry-probe-start","phase":"image-publish-pre-base-image","target":"http://127.0.0.1:5000/v2/"}'
|
||||
registry_started_at="$(date +%s)"
|
||||
curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null
|
||||
registry_finished_at="$(date +%s)"
|
||||
echo '{"event":"dependency-local-registry-probe","phase":"image-publish-pre-base-image","ok":true,"durationSeconds":'"$((registry_finished_at - registry_started_at))"'}'
|
||||
started_at="$(date +%s)"
|
||||
echo '{"event":"dependency-download-start","phase":"image-publish-docker-pull","image":"'"$HWLAB_DEV_BASE_IMAGE"'"}'
|
||||
docker pull "$HWLAB_DEV_BASE_IMAGE"
|
||||
finished_at="$(date +%s)"
|
||||
echo '{"event":"dependency-download-complete","phase":"image-publish-docker-pull","image":"'"$HWLAB_DEV_BASE_IMAGE"'","durationSeconds":'"$((finished_at - started_at))"'}'
|
||||
fi
|
||||
cd /workspace/source/repo
|
||||
test "$(git rev-parse HEAD)" = "$(params.revision)"
|
||||
node scripts/g14-artifact-publish.mjs --publish --registry-prefix "$(params.registry-prefix)" --report /workspace/source/dev-artifacts.json --quiet-build --concurrency "$(params.concurrency)" --services "$(params.services)"
|
||||
node scripts/refresh-artifact-catalog.mjs --target-ref HEAD --publish-report /workspace/source/dev-artifacts.json --no-write
|
||||
`;
|
||||
}
|
||||
|
||||
function planArtifactsScript() {
|
||||
return `#!/bin/sh
|
||||
set -eu
|
||||
@@ -801,7 +825,7 @@ if [ "$buildkit_ready" != "1" ]; then
|
||||
echo '{"event":"buildkit-sidecar-not-ready","serviceId":"'"$(params.service-id)"'","addr":"'"$HWLAB_BUILDKIT_ADDR"'"}' >&2
|
||||
exit 1
|
||||
fi
|
||||
node scripts/g14-artifact-publish.mjs --publish --registry-prefix "$(params.registry-prefix)" --no-report --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --build-backend buildkit --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR"
|
||||
node scripts/g14-artifact-publish.mjs --publish --registry-prefix "$(params.registry-prefix)" --no-report --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR"
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1101,8 +1125,7 @@ const pipelineRun = {
|
||||
{ name: "revision", value: revision },
|
||||
{ name: "registry-prefix", value: env("REGISTRY_PREFIX", "127.0.0.1:5000/hwlab") },
|
||||
{ name: "services", value: env("SERVICES", "${defaultServicesParam}") },
|
||||
{ name: "base-image", value: env("BASE_IMAGE", "${defaultDevBaseImage}") },
|
||||
{ name: "concurrency", value: env("CONCURRENCY", "4") }
|
||||
{ name: "base-image", value: env("BASE_IMAGE", "${defaultDevBaseImage}") }
|
||||
],
|
||||
workspaces: [
|
||||
{ name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } },
|
||||
@@ -1290,56 +1313,6 @@ function buildkitSidecar() {
|
||||
};
|
||||
}
|
||||
|
||||
function dockerRunVolume() {
|
||||
return { name: "docker-run", emptyDir: {} };
|
||||
}
|
||||
|
||||
function dockerSidecar() {
|
||||
return {
|
||||
name: "docker",
|
||||
image: "docker:29-dind",
|
||||
command: ["dockerd"],
|
||||
args: ["--host=unix:///var/run/docker.sock", "--storage-driver=vfs"],
|
||||
env: [{ name: "DOCKER_TLS_CERTDIR", value: "" }, ...proxyEnv()],
|
||||
securityContext: { privileged: true },
|
||||
volumeMounts: [{ name: "docker-run", mountPath: "/var/run" }]
|
||||
};
|
||||
}
|
||||
|
||||
function legacyImagePublishTask() {
|
||||
return {
|
||||
name: "image-publish",
|
||||
runAfter: primitiveValidationTaskNames(),
|
||||
workspaces: [{ name: "source", workspace: "source" }],
|
||||
taskSpec: {
|
||||
params: [
|
||||
{ name: "revision" },
|
||||
{ name: "registry-prefix" },
|
||||
{ name: "services" },
|
||||
{ name: "base-image" },
|
||||
{ name: "concurrency" }
|
||||
],
|
||||
workspaces: [{ name: "source" }],
|
||||
volumes: [dockerRunVolume()],
|
||||
sidecars: [dockerSidecar()],
|
||||
steps: [{
|
||||
name: "publish",
|
||||
image: ciToolsRunnerImage,
|
||||
env: [{ name: "DOCKER_HOST", value: "unix:///var/run/docker.sock" }, ...proxyEnv()],
|
||||
volumeMounts: [{ name: "docker-run", mountPath: "/var/run" }],
|
||||
script: publishScript()
|
||||
}]
|
||||
},
|
||||
params: [
|
||||
{ name: "revision", value: "$(params.revision)" },
|
||||
{ name: "registry-prefix", value: "$(params.registry-prefix)" },
|
||||
{ name: "services", value: "$(params.services)" },
|
||||
{ name: "base-image", value: "$(params.base-image)" },
|
||||
{ name: "concurrency", value: "$(params.concurrency)" }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function planArtifactsTask() {
|
||||
return {
|
||||
name: "plan-artifacts",
|
||||
@@ -1432,7 +1405,6 @@ function collectArtifactsTask() {
|
||||
}
|
||||
|
||||
function imagePublishTaskSet() {
|
||||
if (tektonSingleImagePublish) return [legacyImagePublishTask()];
|
||||
return [
|
||||
planArtifactsTask(),
|
||||
...defaultServiceIds.map(perServiceBuildTask),
|
||||
@@ -1454,7 +1426,7 @@ function tektonPipeline() {
|
||||
annotations: {
|
||||
"hwlab.pikastech.local/source-config": "scripts/g14-gitops-render.mjs#tekton-native-primitive-ci",
|
||||
"hwlab.pikastech.local/ci-contract": "tekton-native-primitive-tasks",
|
||||
"hwlab.pikastech.local/policy": tektonSingleImagePublish ? "single-dind-image-publish-compat" : "native-per-service-taskrun-image-publish"
|
||||
"hwlab.pikastech.local/policy": "native-per-service-taskrun-image-publish"
|
||||
}
|
||||
},
|
||||
spec: {
|
||||
@@ -1465,8 +1437,7 @@ function tektonPipeline() {
|
||||
{ name: "revision", type: "string", description: "Full git commit SHA; the only source truth for image tags." },
|
||||
{ name: "registry-prefix", type: "string", default: defaultRegistryPrefix },
|
||||
{ name: "services", type: "string", default: defaultServicesParam },
|
||||
{ name: "base-image", type: "string", default: defaultDevBaseImage },
|
||||
{ name: "concurrency", type: "string", default: "4" }
|
||||
{ name: "base-image", type: "string", default: defaultDevBaseImage }
|
||||
],
|
||||
workspaces: [
|
||||
{ name: "source" },
|
||||
@@ -1498,7 +1469,7 @@ function tektonPipeline() {
|
||||
...imagePublishTaskSet(),
|
||||
{
|
||||
name: "gitops-promote",
|
||||
runAfter: [tektonSingleImagePublish ? "image-publish" : "collect-artifacts"],
|
||||
runAfter: ["collect-artifacts"],
|
||||
workspaces: [
|
||||
{ name: "source", workspace: "source" },
|
||||
{ name: "git-ssh", workspace: "git-ssh" }
|
||||
@@ -1556,8 +1527,7 @@ function tektonPipelineRunTemplate({ source, args }) {
|
||||
{ name: "gitops-branch", value: args.gitopsBranch },
|
||||
{ name: "revision", value: source.full },
|
||||
{ name: "registry-prefix", value: args.registryPrefix },
|
||||
{ name: "base-image", value: defaultDevBaseImage },
|
||||
{ name: "concurrency", value: "4" }
|
||||
{ name: "base-image", value: defaultDevBaseImage }
|
||||
],
|
||||
workspaces: [
|
||||
{ name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } },
|
||||
@@ -1616,8 +1586,7 @@ function tektonPollerCronJob(args) {
|
||||
{ name: "GITOPS_BRANCH", value: args.gitopsBranch },
|
||||
{ name: "REGISTRY_PREFIX", value: args.registryPrefix },
|
||||
{ name: "SERVICES", value: defaultServicesParam },
|
||||
{ name: "BASE_IMAGE", value: defaultDevBaseImage },
|
||||
{ name: "CONCURRENCY", value: "4" }
|
||||
{ name: "BASE_IMAGE", value: defaultDevBaseImage }
|
||||
],
|
||||
volumeMounts: [{ name: "git-ssh", mountPath: "/workspace/git-ssh", readOnly: true }],
|
||||
command: ["/bin/sh", "-c"],
|
||||
|
||||
Reference in New Issue
Block a user