feat(cicd): generate PaC source artifacts from YAML
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
export function applyNodeRuntimeDeployYamlOverlay(document: Record<string, unknown>, overlay: Record<string, unknown>): Record<string, unknown> {
|
||||
const doc = structuredClone(document);
|
||||
const nodeId = requiredString(overlay.nodeId, "overlay.nodeId");
|
||||
const laneId = requiredString(overlay.lane, "overlay.lane");
|
||||
const nodes = record(doc.nodes);
|
||||
const node = record(nodes[nodeId]);
|
||||
nodes[nodeId] = { ...node, gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };
|
||||
doc.nodes = nodes;
|
||||
const lanes = record(doc.lanes);
|
||||
const lane = record(lanes[laneId]);
|
||||
const envRecipe = record(lane.envRecipe);
|
||||
const downloadStack = {
|
||||
...record(envRecipe.downloadStack),
|
||||
httpProxy: overlay.dockerProxyHttp,
|
||||
httpsProxy: overlay.dockerProxyHttps,
|
||||
noProxy: overlay.dockerNoProxyList,
|
||||
};
|
||||
const nextLane: Record<string, unknown> = {
|
||||
...lane,
|
||||
node: nodeId,
|
||||
sourceBranch: overlay.sourceBranch,
|
||||
gitopsBranch: overlay.gitopsBranch,
|
||||
namespace: overlay.runtimeNamespace,
|
||||
endpoint: overlay.publicApiUrl,
|
||||
publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },
|
||||
artifactCatalog: overlay.catalogPath,
|
||||
runtimePath: overlay.runtimePath,
|
||||
imageTagMode: "full",
|
||||
sourceRepo: overlay.gitUrl,
|
||||
observability: overlay.observability,
|
||||
envRecipe: { ...envRecipe, downloadStack },
|
||||
};
|
||||
if (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete nextLane.externalPostgres;
|
||||
else nextLane.externalPostgres = overlay.externalPostgres;
|
||||
if (overlay.runtimeStore !== undefined) nextLane.runtimeStore = overlay.runtimeStore;
|
||||
if (overlay.codeAgentRuntime !== undefined) nextLane.codeAgentRuntime = overlay.codeAgentRuntime;
|
||||
if (overlay.deployYamlGitMirror !== undefined) nextLane.gitMirror = overlay.deployYamlGitMirror;
|
||||
lanes[laneId] = nextLane;
|
||||
doc.lanes = lanes;
|
||||
return doc;
|
||||
}
|
||||
|
||||
export function nodeRuntimeDeployYamlOverlayShellScript(): string[] {
|
||||
return [
|
||||
"node - \"$overlay_b64\" <<'NODE_UNIDESK_DEPLOY_OVERLAY'",
|
||||
"const fs = require('fs');",
|
||||
"const YAML = require('yaml');",
|
||||
`const applyNodeRuntimeDeployYamlOverlay = ${applyNodeRuntimeDeployYamlOverlay.toString()};`,
|
||||
`const record = ${record.toString()};`,
|
||||
`const requiredString = ${requiredString.toString()};`,
|
||||
"const overlay = JSON.parse(Buffer.from(process.argv[2], 'base64').toString('utf8'));",
|
||||
"const path = 'deploy/deploy.yaml';",
|
||||
"const doc = YAML.parse(fs.readFileSync(path, 'utf8'));",
|
||||
"fs.writeFileSync(path, YAML.stringify(applyNodeRuntimeDeployYamlOverlay(doc, overlay)));",
|
||||
"NODE_UNIDESK_DEPLOY_OVERLAY",
|
||||
];
|
||||
}
|
||||
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
}
|
||||
|
||||
function requiredString(value: unknown, label: string): string {
|
||||
if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must be a non-empty string`);
|
||||
return value;
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import { externalPostgresBridgeStatus, externalPostgresSecretStatus, getNodeRunt
|
||||
import { webObserveShort, webObserveText } from "./web-probe-observe";
|
||||
import { readNodeRuntimeStatusPolicy, runNodeRuntimeStatusProbe, skippedNodeRuntimeStatusCommand, type NodeRuntimeStatusPolicy, type NodeRuntimeStatusWorkerEvent } from "./control-plane-status-observed";
|
||||
import { hwlabRuntimeActiveExternalPostgres } from "../hwlab-node-lanes";
|
||||
import { nodeRuntimeDeployYamlOverlayShellScript } from "./deploy-overlay";
|
||||
|
||||
const runtimeGitopsObservabilityNativeScript = readFileSync(rootPath("scripts/native/hwlab/runtime-gitops-observability.mjs"), "utf8").trimEnd();
|
||||
const runtimeGitopsPipelineGuardNativeScript = readFileSync(rootPath("scripts/native/hwlab/runtime-gitops-pipeline-guard.mjs"), "utf8").trimEnd();
|
||||
@@ -1468,44 +1469,7 @@ export function renderNodeRuntimeControlPlaneOnNode(spec: HwlabRuntimeLaneSpec,
|
||||
"git -C \"$worktree_dir\" checkout --detach \"$source_commit\"",
|
||||
"cd \"$worktree_dir\"",
|
||||
...yamlDependencyInstallScript(spec.downloadProfile.npm.registry, spec.downloadProfile.npm.fetchTimeoutSeconds, spec.downloadProfile.npm.retries, "control-plane-render"),
|
||||
"node - \"$overlay_b64\" <<'NODE'",
|
||||
"const fs = require('fs');",
|
||||
"const YAML = require('yaml');",
|
||||
"const overlay = JSON.parse(Buffer.from(process.argv[2], 'base64').toString('utf8'));",
|
||||
"const path = 'deploy/deploy.yaml';",
|
||||
"const doc = YAML.parse(fs.readFileSync(path, 'utf8'));",
|
||||
"doc.nodes = doc.nodes || {};",
|
||||
"doc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };",
|
||||
"doc.lanes = doc.lanes || {};",
|
||||
"const lane = doc.lanes[overlay.lane] || {};",
|
||||
"const downloadStack = {",
|
||||
" ...(lane.envRecipe?.downloadStack || {}),",
|
||||
" httpProxy: overlay.dockerProxyHttp,",
|
||||
" httpsProxy: overlay.dockerProxyHttps,",
|
||||
" noProxy: overlay.dockerNoProxyList,",
|
||||
"};",
|
||||
"doc.lanes[overlay.lane] = {",
|
||||
" ...lane,",
|
||||
" node: overlay.nodeId,",
|
||||
" sourceBranch: overlay.sourceBranch,",
|
||||
" gitopsBranch: overlay.gitopsBranch,",
|
||||
" namespace: overlay.runtimeNamespace,",
|
||||
" endpoint: overlay.publicApiUrl,",
|
||||
" publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },",
|
||||
" artifactCatalog: overlay.catalogPath,",
|
||||
" runtimePath: overlay.runtimePath,",
|
||||
" imageTagMode: 'full',",
|
||||
" sourceRepo: overlay.gitUrl,",
|
||||
" observability: overlay.observability,",
|
||||
" envRecipe: { ...(lane.envRecipe || {}), downloadStack },",
|
||||
"};",
|
||||
"if (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;",
|
||||
"else doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;",
|
||||
"if (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;",
|
||||
"if (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;",
|
||||
"if (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;",
|
||||
"fs.writeFileSync(path, YAML.stringify(doc));",
|
||||
"NODE",
|
||||
...nodeRuntimeDeployYamlOverlayShellScript(),
|
||||
"if [ -f scripts/gitops-render.mjs ]; then render_script=scripts/gitops-render.mjs; else echo 'render script missing: scripts/gitops-render.mjs' >&2; exit 43; fi",
|
||||
[
|
||||
"node scripts/run-bun.mjs \"$render_script\"",
|
||||
@@ -2557,6 +2521,11 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
||||
" doc.metadata = doc.metadata || {};",
|
||||
" if (typeof overlay.pipelineName === 'string' && overlay.pipelineName.length > 0) doc.metadata.name = overlay.pipelineName;",
|
||||
" doc.metadata.annotations = doc.metadata.annotations || {};",
|
||||
" const provenance = overlay.sourceArtifactProvenance || {};",
|
||||
" if (provenance.configRef) doc.metadata.annotations['unidesk.ai/owning-config-ref'] = provenance.configRef;",
|
||||
" if (provenance.effectiveConfigSha256) doc.metadata.annotations['unidesk.ai/effective-config-sha256'] = provenance.effectiveConfigSha256;",
|
||||
" if (provenance.renderer) doc.metadata.annotations['unidesk.ai/source-artifact-renderer'] = provenance.renderer;",
|
||||
" if (provenance.mode) doc.metadata.annotations['unidesk.ai/source-artifact-mode'] = provenance.mode;",
|
||||
" doc.metadata.annotations['hwlab.pikastech.local/download-profile'] = overlay.downloadProfileId;",
|
||||
" doc.metadata.annotations['hwlab.pikastech.local/network-profile'] = overlay.networkProfileId;",
|
||||
" for (const task of doc.spec?.tasks || []) {",
|
||||
|
||||
@@ -40,6 +40,7 @@ import { assertKnownOptions, nodeWebProbeAutoCommandTimeoutSeconds, normalizeNod
|
||||
import { resolveNodeWebProbeCliOrigin } from "./web-probe-origin";
|
||||
import { hwlabRuntimeActiveExternalPostgres } from "../hwlab-node-lanes";
|
||||
import { resolveEgressProxySourceRef } from "../egress-proxy-sources";
|
||||
import { stableJsonSha256 } from "../stable-json";
|
||||
|
||||
export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record<string, unknown> {
|
||||
const gitSshProxy = httpProxyEndpoint(spec.networkProfile.proxy.http);
|
||||
@@ -74,6 +75,12 @@ export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record<str
|
||||
},
|
||||
};
|
||||
return {
|
||||
sourceArtifactProvenance: {
|
||||
configRef: `${hwlabRuntimeLaneConfigPath}#lanes.${spec.lane}.targets.${spec.nodeId}`,
|
||||
effectiveConfigSha256: stableJsonSha256(spec),
|
||||
renderer: "hwlab-runtime-lane",
|
||||
mode: "remote-pipeline-annotation",
|
||||
},
|
||||
nodeId: spec.nodeId,
|
||||
lane: spec.lane,
|
||||
sourceBranch: spec.sourceBranch,
|
||||
|
||||
Reference in New Issue
Block a user