// Injected into the HWLAB control-plane renderer. It intentionally uses the // surrounding renderer's overlay, stepEnv, patchScript, YAML, and text values. function patchPipelineTaskRuntime(task) { const governance = overlay.ciResourceGovernance?.build; const governed = governance && task.name === governance.matrixTaskName; if (governed) task.timeout = governance.taskTimeout; for (const sidecar of task.taskSpec?.sidecars || []) { if (overlay.buildkitSidecarImage && typeof sidecar.image === 'string' && sidecar.image.includes('buildkit')) sidecar.image = overlay.buildkitSidecarImage; if (governed) sidecar.computeResources = structuredClone(governance.sidecarResources); } for (const step of task.taskSpec?.steps || []) { if (step.image === overlay.toolsImage && overlay.toolsImagePullPolicy) step.imagePullPolicy = overlay.toolsImagePullPolicy; if (governed) step.computeResources = structuredClone(governance.stepResources); if (Array.isArray(step.env)) { for (const env of step.env) { if (Object.prototype.hasOwnProperty.call(stepEnv, env.name) && stepEnv[env.name] !== undefined) env.value = stepEnv[env.name]; } } step.env = Array.isArray(step.env) ? step.env : []; const existingEnv = new Set(step.env.map((env) => env.name)); for (const [name, value] of Object.entries(stepEnv)) { if (value !== undefined && !existingEnv.has(name)) step.env.push({ name, value }); } if (typeof step.script === 'string') step.script = patchScript(step.script); } } function applyPipelineBuildQueue(doc) { const governance = overlay.ciResourceGovernance?.build; if (!governance) return; const tasks = Array.isArray(doc.spec.tasks) ? doc.spec.tasks : []; const task = tasks.find((candidate) => candidate?.name === governance.matrixTaskName); if (!task) return; const matrixParams = Array.isArray(task.matrix?.params) ? task.matrix.params : []; const serviceParam = matrixParams.find((param) => param?.name === 'service-id'); if (!serviceParam || !Array.isArray(serviceParam.value) || serviceParam.value.length === 0) { throw new Error(`CI resource governance task ${governance.matrixTaskName} must declare a static service-id matrix`); } const maxParallel = governance.maxParallelServices; const publish = (task.taskSpec?.steps || []).find((step) => step?.name === 'publish'); if (!publish || typeof publish.script !== 'string') throw new Error(`CI resource governance task ${governance.matrixTaskName} must contain a publish script`); const serviceIds = serviceParam.value.map((value) => String(value)); let workerScript = publish.script .replaceAll('$(params.service-id)', '$service_id') .replaceAll('/workspace/service-work', '/workspace/service-work/$service_id') .replaceAll('"/tekton/results/" + name', 'process.env.HWLAB_TEKTON_RESULTS_DIR + "/" + name') .replaceAll('--tekton-results-dir /tekton/results', '--tekton-results-dir "$HWLAB_TEKTON_RESULTS_DIR"'); if (workerScript.includes('/tekton/results')) { throw new Error(`CI resource governance task ${governance.matrixTaskName} contains an unsupported Tekton result path context`); } publish.script = [ 'set -eu', 'build_service() {', ' service_id="$1"', ' mkdir -p "/workspace/service-work/$service_id" "/workspace/service-results-tekton/$service_id"', ' export HWLAB_TEKTON_RESULTS_DIR="/workspace/service-results-tekton/$service_id"', // Keep the worker body at column zero: POSIX heredoc terminators in the // generated publish script must not be indented by the function wrapper. workerScript, '}', `max_parallel=${maxParallel}`, 'service_csv="$(params.services)"', 'old_ifs="$IFS"', "IFS=','", 'set -- $service_csv', 'IFS="$old_ifs"', 'failed=0', 'while [ "$#" -gt 0 ]; do', ' pids=""', ' launched=0', ' while [ "$#" -gt 0 ] && [ "$launched" -lt "$max_parallel" ]; do', ' service_id="$1"', ' shift', ' (build_service "$service_id") &', ' pids="$pids $!"', ' launched=$((launched + 1))', ' done', ' for pid in $pids; do if ! wait "$pid"; then failed=1; fi; done', ' if [ "$failed" -ne 0 ]; then exit "$failed"; fi', 'done', ].join('\n'); task.params = (task.params || []).map((param) => param?.name === 'service-id' ? { name: 'services', value: '$(params.services)' } : param); if (!task.params.some((param) => param?.name === 'services')) task.params.push({ name: 'services', value: '$(params.services)' }); task.taskSpec.params = (task.taskSpec.params || []).map((param) => param?.name === 'service-id' ? { ...param, name: 'services' } : param); task.taskSpec.results = []; delete task.matrix; doc.metadata.annotations['unidesk.ai/ci-build-queue-mode'] = 'single-taskrun-bounded-workers'; doc.metadata.annotations['unidesk.ai/ci-build-service-count'] = String(serviceIds.length); doc.metadata.annotations['unidesk.ai/ci-build-batch-count'] = String(Math.ceil(serviceIds.length / maxParallel)); doc.metadata.annotations['unidesk.ai/ci-build-max-parallel'] = String(maxParallel); } function patchManifestObject(doc) { if (!doc || typeof doc !== 'object') return false; if (doc.kind !== 'Pipeline' || !doc.spec) return false; const defaults = { 'git-url': overlay.gitUrl, 'git-read-url': overlay.gitReadUrl, 'git-write-url': overlay.gitWriteUrl, 'catalog-path': overlay.catalogPath, 'runtime-path': overlay.runtimePath, 'registry-prefix': overlay.registryPrefix, }; for (const param of doc.spec?.params || []) { if (Object.prototype.hasOwnProperty.call(defaults, param.name)) param.default = defaults[param.name]; } doc.metadata = doc.metadata || {}; if (typeof overlay.pipelineName === 'string' && overlay.pipelineName.length > 0) doc.metadata.name = overlay.pipelineName; doc.metadata.annotations = doc.metadata.annotations || {}; doc.metadata.annotations['hwlab.pikastech.local/download-profile'] = overlay.downloadProfileId; doc.metadata.annotations['hwlab.pikastech.local/network-profile'] = overlay.networkProfileId; if (overlay.ciResourceGovernance) { doc.metadata.annotations['unidesk.ai/ci-resource-governance-config-ref'] = overlay.ciResourceGovernance.configRef; doc.metadata.annotations['unidesk.ai/ci-dedupe-identity'] = overlay.ciResourceGovernance.dedupe.identityFields.join('+'); } for (const task of doc.spec?.tasks || []) patchPipelineTaskRuntime(task); applyPipelineBuildQueue(doc); return true; } function patchStructuredPipeline() { try { const doc = JSON.parse(text); if (!patchManifestObject(doc)) return false; text = JSON.stringify(doc, null, 2) + '\n'; return true; } catch (error) { if (String(error?.message || '').includes('CI resource governance')) throw error; } if (YAML) { try { const docs = YAML.parseAllDocuments(text).map((document) => document.toJS()).filter((doc) => doc !== null); const changed = docs.some((doc) => patchManifestObject(doc)); if (!changed) return false; text = docs.map((doc) => YAML.stringify(doc).trimEnd()).join('\n---\n') + '\n'; return true; } catch (error) { if (String(error?.message || '').includes('CI resource governance')) throw error; } } return false; }