fix: apply hwlab follower pipeline via k8s api
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// Responsibility: render and apply the HWLAB node Tekton Pipeline from a k8s git-mirror snapshot.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import https from "node:https";
|
||||
import { createRequire } from "node:module";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -13,6 +14,7 @@ const sourceStageRef = requiredEnv("SOURCE_STAGE_REF");
|
||||
const gitReadUrl = requiredEnv("GIT_READ_URL");
|
||||
const fieldManager = requiredEnv("FIELD_MANAGER");
|
||||
const tektonNamespace = requiredEnv("TEKTON_NAMESPACE");
|
||||
const kubeRequestTimeoutSeconds = requiredPositiveNumber("KUBE_REQUEST_TIMEOUT_SECONDS");
|
||||
const overlay = JSON.parse(Buffer.from(requiredEnv("HWLAB_RENDER_OVERLAY_B64"), "base64").toString("utf8"));
|
||||
const workDir = mkdtempSync(path.join(tmpdir(), `hwlab-control-plane-${sourceCommit.slice(0, 12)}-`));
|
||||
const repoDir = path.join(workDir, "repo");
|
||||
@@ -24,7 +26,7 @@ try {
|
||||
prepareYamlDependency();
|
||||
applyDeployOverlay();
|
||||
renderControlPlane();
|
||||
applyPipeline();
|
||||
await applyPipeline();
|
||||
emit({ ok: true, status: "applied" });
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
@@ -74,8 +76,7 @@ function canResolveYaml() {
|
||||
}
|
||||
|
||||
function applyDeployOverlay() {
|
||||
const requireFromDeps = createRequire(path.join(depsDir, "package.json"));
|
||||
const YAML = requireFromDeps("yaml");
|
||||
const YAML = yamlModule();
|
||||
const deployPath = path.join(repoDir, "deploy/deploy.yaml");
|
||||
const doc = YAML.parse(readFileSync(deployPath, "utf8"));
|
||||
doc.nodes = doc.nodes || {};
|
||||
@@ -132,10 +133,61 @@ function renderControlPlane() {
|
||||
], { cwd: repoDir, env: renderEnv() });
|
||||
}
|
||||
|
||||
function applyPipeline() {
|
||||
async function applyPipeline() {
|
||||
const pipelinePath = path.join(renderDir, overlay.tektonDir, "pipeline.yaml");
|
||||
if (!existsSync(pipelinePath)) throw new Error(`rendered Pipeline missing: ${pipelinePath}`);
|
||||
run("kubectl", ["apply", "--server-side", "--force-conflicts", `--field-manager=${fieldManager}`, "-f", pipelinePath], { cwd: repoDir, env: renderEnv() });
|
||||
const pipelineText = readFileSync(pipelinePath, "utf8");
|
||||
const pipeline = yamlModule().parse(pipelineText);
|
||||
const pipelineName = pipeline?.metadata?.name;
|
||||
if (typeof pipelineName !== "string" || pipelineName.length === 0) throw new Error(`rendered Pipeline metadata.name missing: ${pipelinePath}`);
|
||||
await kubeRequest(
|
||||
"PATCH",
|
||||
`/apis/tekton.dev/v1/namespaces/${encodeURIComponent(tektonNamespace)}/pipelines/${encodeURIComponent(pipelineName)}?fieldManager=${encodeURIComponent(fieldManager)}&force=true`,
|
||||
pipelineText,
|
||||
"application/apply-patch+yaml",
|
||||
);
|
||||
}
|
||||
|
||||
function yamlModule() {
|
||||
const requireFromDeps = createRequire(path.join(depsDir, "package.json"));
|
||||
return requireFromDeps("yaml");
|
||||
}
|
||||
|
||||
function kubeRequest(method, requestPath, body, contentType = "application/json") {
|
||||
const host = requiredEnv("KUBERNETES_SERVICE_HOST");
|
||||
const port = requiredPositiveNumber("KUBERNETES_SERVICE_PORT");
|
||||
const token = readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8").trim();
|
||||
const ca = readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt");
|
||||
return new Promise((resolve, reject) => {
|
||||
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||
const req = https.request({
|
||||
host,
|
||||
port,
|
||||
path: requestPath,
|
||||
method,
|
||||
ca,
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
"content-type": contentType,
|
||||
"content-length": Buffer.byteLength(payload),
|
||||
},
|
||||
}, (res) => {
|
||||
let text = "";
|
||||
res.setEncoding("utf8");
|
||||
res.on("data", (chunk) => { text += chunk; });
|
||||
res.on("end", () => {
|
||||
if ((res.statusCode || 0) < 200 || (res.statusCode || 0) >= 300) {
|
||||
reject(new Error(`kube api ${method} ${requestPath} status ${res.statusCode}: ${tail(text, 1200)}`));
|
||||
return;
|
||||
}
|
||||
resolve(text);
|
||||
});
|
||||
});
|
||||
req.setTimeout(kubeRequestTimeoutSeconds * 1000, () => req.destroy(new Error(`kube api ${method} ${requestPath} timed out after ${kubeRequestTimeoutSeconds}s`)));
|
||||
req.on("error", reject);
|
||||
req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function renderEnv(extra = {}) {
|
||||
|
||||
Reference in New Issue
Block a user