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", kind: "List", items: [ { apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } }, { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } }, spec: { replicas: 1, selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-registry" } }, template: { metadata: { labels: { "app.kubernetes.io/name": "hwlab-registry" } }, spec: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "registry", image: "registry:2.8.3", imagePullPolicy: "IfNotPresent", ports: [{ name: "registry", containerPort: 5000, hostPort: 5000 }], env: [{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" }], volumeMounts: [{ name: "storage", mountPath: "/var/lib/registry" }], readinessProbe: { httpGet: { path: "/v2/", port: "registry" } }, livenessProbe: { httpGet: { path: "/v2/", port: "registry" } } }], volumes: [{ name: "storage", hostPath: { path: "/var/lib/hwlab/registry", type: "DirectoryOrCreate" } }] } } } }, { apiVersion: "v1", kind: "Service", metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } }, spec: { selector: { "app.kubernetes.io/name": "hwlab-registry" }, ports: [{ name: "registry", port: 5000, targetPort: "registry" }] } } ] }; } function 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 };