fix: 将 production release 归并回 v0.3 单线历史
This commit is contained in:
@@ -12,7 +12,11 @@ import {
|
||||
applyRuntimeLaneDeployConfig,
|
||||
defaultEndpointForRuntimeLane,
|
||||
defaultPortForRuntimeLane,
|
||||
isNamedRuntimeLane,
|
||||
isRuntimeLane,
|
||||
isVersionRuntimeLane,
|
||||
runtimeNamespaceForRuntimeLane,
|
||||
runtimeRelativePathForRuntimeLane,
|
||||
runtimeLaneMirrorSpecs,
|
||||
runtimeLaneConfig,
|
||||
versionNameForRuntimeLane
|
||||
@@ -245,7 +249,7 @@ function parseArgs(argv) {
|
||||
else if (arg === "--help" || arg === "-h") args.help = true;
|
||||
else throw new Error(`unknown argument ${arg}`);
|
||||
}
|
||||
if (isRuntimeLane(args.lane)) {
|
||||
if (isVersionRuntimeLane(args.lane)) {
|
||||
const versionName = versionNameForRuntimeLane(args.lane);
|
||||
const defaultLaneEndpoint = defaultEndpointForRuntimeLane(args.lane);
|
||||
if (args.sourceBranch === defaultBranch) args.sourceBranch = versionName;
|
||||
@@ -261,7 +265,7 @@ function parseArgs(argv) {
|
||||
if (!args.gitWriteUrl) args.gitWriteUrl = args.sourceRepo;
|
||||
assert.ok(args.lane === "node" || isRuntimeLane(args.lane), `unknown lane ${args.lane}`);
|
||||
assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`);
|
||||
if (isRuntimeLane(args.lane)) {
|
||||
if (isVersionRuntimeLane(args.lane)) {
|
||||
const versionName = versionNameForRuntimeLane(args.lane);
|
||||
assert.equal(args.sourceBranch, versionName, `${args.lane} source branch must be ${versionName}`);
|
||||
assert.equal(args.gitopsBranch, `${versionName}-gitops`, `${args.lane} GitOps branch must be ${versionName}-gitops`);
|
||||
@@ -282,7 +286,7 @@ function usage() {
|
||||
"Render node-scoped Kubernetes-native CI/CD manifests from built-in Tekton CI primitives, deploy/deploy.yaml, and deploy/k8s/*.",
|
||||
"",
|
||||
"options:",
|
||||
" --lane LANE node or runtime lane such as v02/v03/v04; default: node",
|
||||
" --lane LANE node or a deploy.lanes runtime lane id such as v02/v03/staging; default: node",
|
||||
` --node NODE deployment node id from deploy.nodes; runtime lanes default from deploy.lanes.<lane>.node`,
|
||||
` --out DIR default: ${defaultOutDir}`,
|
||||
` --gitops-root DIR path inside GitOps repo; default comes from deploy.nodes.<node>.gitopsRoot`,
|
||||
@@ -807,7 +811,7 @@ function isV02RemovedServiceId(serviceId) {
|
||||
|
||||
function artifactCatalogSkeleton({ args, deploy, source }) {
|
||||
const environment = artifactEnvironment(args);
|
||||
const namespace = isRuntimeLane(args.lane) ? namespaceNameForProfile(args.lane) : "hwlab-dev";
|
||||
const namespace = isRuntimeLane(args.lane) ? namespaceNameForProfile(args.lane, deploy) : "hwlab-dev";
|
||||
const tag = imageTagForSource(args, source);
|
||||
return {
|
||||
catalogVersion: "v1",
|
||||
@@ -869,13 +873,13 @@ function artifactCatalogSkeletonService({ args, deploy, source, serviceId, envir
|
||||
};
|
||||
}
|
||||
|
||||
function namespaceNameForProfile(profile) {
|
||||
if (isRuntimeLane(profile)) return `hwlab-${profile}`;
|
||||
function namespaceNameForProfile(profile, deploy = null) {
|
||||
if (isRuntimeLane(profile)) return runtimeNamespaceForRuntimeLane(deploy, profile);
|
||||
return profile === "prod" ? "hwlab-prod" : "hwlab-dev";
|
||||
}
|
||||
|
||||
function runtimePathForProfile(profile) {
|
||||
if (isRuntimeLane(profile)) return `runtime-${profile}`;
|
||||
function runtimePathForProfile(profile, deploy = null) {
|
||||
if (isRuntimeLane(profile)) return runtimeRelativePathForRuntimeLane(deploy, profile);
|
||||
return profile === "prod" ? "runtime-prod" : "runtime-dev";
|
||||
}
|
||||
|
||||
@@ -892,8 +896,8 @@ function gitopsRootForArgs(args) {
|
||||
return String(args.gitopsRoot || defaultOutDir).replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
function gitopsPathForProfile(args, profile) {
|
||||
return `${gitopsRootForArgs(args)}/${runtimePathForProfile(profile)}`;
|
||||
function gitopsPathForProfile(args, profile, deploy = null) {
|
||||
return `${gitopsRootForArgs(args)}/${runtimePathForProfile(profile, deploy)}`;
|
||||
}
|
||||
|
||||
function gitopsPathFor(args, relativePath) {
|
||||
@@ -911,9 +915,9 @@ function laneRuntimeProfiles(args) {
|
||||
return isRuntimeLane(args.lane) ? [args.lane] : ["dev", "prod"];
|
||||
}
|
||||
|
||||
function laneNames(args) {
|
||||
function laneNames(args, deploy = null) {
|
||||
if (isRuntimeLane(args.lane)) {
|
||||
const namespace = namespaceNameForProfile(args.lane);
|
||||
const namespace = namespaceNameForProfile(args.lane, deploy);
|
||||
return {
|
||||
gitopsTarget: args.lane,
|
||||
pipeline: `${namespace}-ci-image-publish`,
|
||||
@@ -922,7 +926,7 @@ function laneNames(args) {
|
||||
serviceAccount: `${namespace}-tekton-runner`,
|
||||
pipelineRunPrefix: `${namespace}-ci-poll-`,
|
||||
runtimeNamespace: namespace,
|
||||
runtimePath: gitopsPathForProfile(args, args.lane),
|
||||
runtimePath: gitopsPathForProfile(args, args.lane, deploy),
|
||||
argoApplications: [`argocd/hwlab-node-${args.lane}`]
|
||||
};
|
||||
}
|
||||
@@ -1177,9 +1181,11 @@ function gitopsRootNodeId(gitopsRoot) {
|
||||
}
|
||||
|
||||
function runtimeFrpConfigForProfile(deploy, profile, args = {}) {
|
||||
const fallbackWebRemotePort = isRuntimeLane(profile) ? defaultPortForRuntimeLane(profile) : profile === "prod" ? 18666 : 17666;
|
||||
const namedRuntimeLane = isNamedRuntimeLane(profile);
|
||||
const fallbackWebRemotePort = isVersionRuntimeLane(profile) ? defaultPortForRuntimeLane(profile) : profile === "prod" ? 18666 : 17666;
|
||||
const fallbackEdgeRemotePort = isRuntimeLane(profile) ? fallbackWebRemotePort + 1 : profile === "prod" ? 18667 : 17667;
|
||||
const baseFrp = isRuntimeLane(profile) ? runtimeLaneConfig(deploy, profile)?.frp : deploy?.frp;
|
||||
if (namedRuntimeLane && baseFrp?.enabled === false) return { enabled: false };
|
||||
const nodeId = effectiveSecretPlaneNodeId(args);
|
||||
const nodeFrp = nodeId ? baseFrp?.nodes?.[nodeId] : null;
|
||||
const frp = nodeFrp && typeof nodeFrp === "object" && !Array.isArray(nodeFrp) ? { ...baseFrp, ...nodeFrp } : baseFrp;
|
||||
@@ -1190,17 +1196,32 @@ function runtimeFrpConfigForProfile(deploy, profile, args = {}) {
|
||||
.map((proxy) => [String(proxy.endpointId || proxy.name || ""), proxy]));
|
||||
const proxyValue = (endpointId, key, fallback) => {
|
||||
const value = proxyByEndpoint.get(endpointId)?.[key];
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
|
||||
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
||||
assert.ok(!namedRuntimeLane, `deploy.lanes.${profile}.frp.proxies.${endpointId}.${key} is required`);
|
||||
return fallback;
|
||||
};
|
||||
const proxyPort = (endpointId, fallback) => {
|
||||
const value = proxyByEndpoint.get(endpointId)?.remotePort;
|
||||
return Number.isInteger(value) ? value : fallback;
|
||||
const proxyPort = (endpointId, fallback, key = "remotePort") => {
|
||||
const value = proxyByEndpoint.get(endpointId)?.[key];
|
||||
if (Number.isInteger(value) && value > 0) return value;
|
||||
assert.ok(!namedRuntimeLane, `deploy.lanes.${profile}.frp.proxies.${endpointId}.${key} must be a positive integer`);
|
||||
return fallback;
|
||||
};
|
||||
if (namedRuntimeLane) {
|
||||
assert.ok(server && typeof server === "object" && !Array.isArray(server), `deploy.lanes.${profile}.frp.server is required`);
|
||||
assert.ok(typeof server.address === "string" && server.address.trim().length > 0, `deploy.lanes.${profile}.frp.server.address is required`);
|
||||
assert.ok(Number.isInteger(server.bindPort) && server.bindPort > 0, `deploy.lanes.${profile}.frp.server.bindPort must be a positive integer`);
|
||||
for (const endpointId of ["frontend", "api"]) assert.ok(proxyByEndpoint.has(endpointId), `deploy.lanes.${profile}.frp.proxies must declare endpointId ${endpointId}`);
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
serverAddr: typeof server?.address === "string" && server.address.trim().length > 0 ? server.address.trim() : "74.48.78.17",
|
||||
serverPort: Number.isInteger(server?.bindPort) ? server.bindPort : 7000,
|
||||
webRemotePort: proxyPort("frontend", fallbackWebRemotePort),
|
||||
edgeRemotePort: proxyPort("api", fallbackEdgeRemotePort),
|
||||
webLocalHost: proxyValue("frontend", "localHost", null),
|
||||
edgeLocalHost: proxyValue("api", "localHost", null),
|
||||
webLocalPort: proxyPort("frontend", 8080, "localPort"),
|
||||
edgeLocalPort: proxyPort("api", 6667, "localPort"),
|
||||
webProxyName: proxyValue("frontend", "name", null),
|
||||
edgeProxyName: proxyValue("api", "name", null),
|
||||
authSecretName: typeof frp?.auth?.secretName === "string" && frp.auth.secretName.trim().length > 0 ? frp.auth.secretName.trim() : null,
|
||||
@@ -1347,7 +1368,7 @@ function workbenchRuntimeRedisConfigForProfile(deploy, profile) {
|
||||
if (!redis || redis.enabled !== true) return null;
|
||||
configObject(redis, label);
|
||||
const namespace = requiredConfigString(redis, "namespace", label);
|
||||
assert.equal(namespace, namespaceNameForProfile(profile), `${label}.namespace must match ${namespaceNameForProfile(profile)}`);
|
||||
assert.equal(namespace, namespaceNameForProfile(profile, deploy), `${label}.namespace must match ${namespaceNameForProfile(profile, deploy)}`);
|
||||
const serviceName = requiredConfigString(redis, "serviceName", label);
|
||||
const image = requiredConfigString(redis, "image", label);
|
||||
const port = requiredConfigPositiveInteger(redis, "port", label);
|
||||
@@ -1446,7 +1467,7 @@ function upsertV02MetricsPort(service) {
|
||||
function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch = defaultBranch, gitReadUrl, registryPrefix, runtimeEndpoint, webEndpoint, profile = "dev", nodeId = "node", useDeployImages = false, metricsSidecarSha256 = null }) {
|
||||
const result = cloneJson(workloads);
|
||||
const deployServices = deployServicesForProfile(deploy, profile);
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
const profileLabel = runtimeLabelForProfile(profile);
|
||||
const gitopsTarget = gitopsTargetForProfile(profile);
|
||||
const runtimeLane = isRuntimeLane(profile);
|
||||
@@ -1737,6 +1758,7 @@ function transformServices({ services, namespace, labels, annotations, profile =
|
||||
|
||||
function observabilityManifest({ deploy, profile, namespace, labels, annotations, metricsSidecarScript }) {
|
||||
assert.ok(isRuntimeLane(profile), `observability profile must be a runtime lane, got ${profile}`);
|
||||
const laneDisplayName = runtimeLaneConfig(deploy, profile).name ?? versionNameForRuntimeLane(profile);
|
||||
const includePrometheusOperatorResources = runtimePrometheusOperatorResourcesEnabled(deploy, profile);
|
||||
const baseLabels = {
|
||||
...labels,
|
||||
@@ -1792,7 +1814,7 @@ function observabilityManifest({ deploy, profile, namespace, labels, annotations
|
||||
expr: `up{namespace="${namespace}"} == 0`,
|
||||
for: "5m",
|
||||
labels: { severity: "warning", lane: profile },
|
||||
annotations: { summary: `HWLAB ${versionNameForRuntimeLane(profile)} metrics target is down`, description: `Prometheus cannot scrape a HWLAB ${versionNameForRuntimeLane(profile)} metrics target.` }
|
||||
annotations: { summary: `HWLAB ${laneDisplayName} metrics target is down`, description: `Prometheus cannot scrape a HWLAB ${laneDisplayName} metrics target.` }
|
||||
},
|
||||
{
|
||||
alert: `HWLAB${profile.toUpperCase()}ServiceHealthProbeFailed`,
|
||||
@@ -1829,7 +1851,7 @@ export {
|
||||
effectiveSecretPlaneNodeId, ensureObject, envReuseBootShForContainer, envReuseServiceIdsForLane,
|
||||
generatedPath, gitopsPathFor, gitopsPathForProfile, gitopsRootForArgs, gitopsRootNodeId,
|
||||
gitopsTargetForProfile, gitValue, imageFor, imageTagForSource,
|
||||
imageTagFromReference, isRuntimeLane, isV02RemovedServiceId, jsonManifest,
|
||||
imageTagFromReference, isNamedRuntimeLane, isRuntimeLane, isV02RemovedServiceId, jsonManifest,
|
||||
k8sLabelHash, label, laneNames, laneRuntimeProfiles, mergeEnvMaps,
|
||||
moonBridgeImage, moonBridgeSourceRef, namespaceNameForProfile, namespaceScopedHost,
|
||||
normalizeAccessControlUser, normalizeNavProfile, normalizeProbeTiming,
|
||||
|
||||
@@ -44,15 +44,16 @@ import {
|
||||
versionNameForRuntimeLane
|
||||
} from "./core.mjs";
|
||||
|
||||
function argoProject(args = { lane: "node" }) {
|
||||
function argoProject(args = { lane: "node" }, deploy = null) {
|
||||
if (isRuntimeLane(args.lane)) {
|
||||
const namespace = namespaceNameForProfile(args.lane);
|
||||
const namespace = namespaceNameForProfile(args.lane, deploy);
|
||||
const laneName = runtimeLaneConfig(deploy, args.lane).name ?? versionNameForRuntimeLane(args.lane);
|
||||
return {
|
||||
apiVersion: "argoproj.io/v1alpha1",
|
||||
kind: "AppProject",
|
||||
metadata: { name: namespace, namespace: "argocd" },
|
||||
spec: {
|
||||
description: `HWLAB ${versionNameForRuntimeLane(args.lane)} additive GitOps project on target node; DEV/PROD remain outside this lane.`,
|
||||
description: `HWLAB ${laneName} additive GitOps project on target node; DEV/PROD remain outside this lane.`,
|
||||
sourceRepos: [args.gitReadUrl],
|
||||
destinations: [{ server: "https://kubernetes.default.svc", namespace }],
|
||||
clusterResourceWhitelist: [{ group: "", kind: "Namespace" }],
|
||||
@@ -77,9 +78,9 @@ function argoProject(args = { lane: "node" }) {
|
||||
};
|
||||
}
|
||||
|
||||
function argoApplication(args, profile = "dev") {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const runtimePath = runtimePathForProfile(profile);
|
||||
function argoApplication(args, profile = "dev", deploy = null) {
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
const runtimePath = runtimePathForProfile(profile, deploy);
|
||||
const gitopsTarget = gitopsTargetForProfile(profile);
|
||||
const project = isRuntimeLane(profile) ? namespace : "hwlab-node";
|
||||
const repoURL = isRuntimeLane(profile) ? args.gitReadUrl : args.sourceRepo;
|
||||
@@ -93,7 +94,7 @@ function argoApplication(args, profile = "dev") {
|
||||
},
|
||||
spec: {
|
||||
project,
|
||||
source: { repoURL, targetRevision: args.gitopsBranch, path: gitopsPathForProfile(args, profile) },
|
||||
source: { repoURL, targetRevision: args.gitopsBranch, path: gitopsPathForProfile(args, profile, deploy) },
|
||||
destination: { server: "https://kubernetes.default.svc", namespace },
|
||||
syncPolicy: { automated: { prune: isRuntimeLane(profile), selfHeal: true }, syncOptions: ["CreateNamespace=true", "ApplyOutOfSyncOnly=true"] }
|
||||
}
|
||||
@@ -101,12 +102,13 @@ function argoApplication(args, profile = "dev") {
|
||||
}
|
||||
|
||||
function nodeFrpcManifest({ profile = "dev", source = null, deploy = null, args = {} } = {}) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
const profileLabel = runtimeLabelForProfile(profile);
|
||||
const deploymentName = isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc";
|
||||
const configName = `${deploymentName}-config`;
|
||||
const proxyPrefix = isRuntimeLane(profile) ? `hwlab-${profile}` : profile === "prod" ? "hwlab-node-prod" : "hwlab-node";
|
||||
const frpConfig = runtimeFrpConfigForProfile(deploy, profile, args);
|
||||
if (frpConfig.enabled === false) return { apiVersion: "v1", kind: "List", items: [] };
|
||||
const webProxyName = frpConfig.webProxyName || `${proxyPrefix}-cloud-web`;
|
||||
const edgeProxyName = frpConfig.edgeProxyName || `${proxyPrefix}-edge-proxy`;
|
||||
const labels = {
|
||||
@@ -131,15 +133,15 @@ ${authConfigText}
|
||||
[[proxies]]
|
||||
name = "${webProxyName}"
|
||||
type = "tcp"
|
||||
localIP = "hwlab-cloud-web.${namespace}.svc.cluster.local"
|
||||
localPort = 8080
|
||||
localIP = "${frpConfig.webLocalHost || `hwlab-cloud-web.${namespace}.svc.cluster.local`}"
|
||||
localPort = ${frpConfig.webLocalPort}
|
||||
remotePort = ${frpConfig.webRemotePort}
|
||||
|
||||
[[proxies]]
|
||||
name = "${edgeProxyName}"
|
||||
type = "tcp"
|
||||
localIP = "hwlab-edge-proxy.${namespace}.svc.cluster.local"
|
||||
localPort = 6667
|
||||
localIP = "${frpConfig.edgeLocalHost || `hwlab-edge-proxy.${namespace}.svc.cluster.local`}"
|
||||
localPort = ${frpConfig.edgeLocalPort}
|
||||
remotePort = ${frpConfig.edgeRemotePort}
|
||||
`;
|
||||
const configSha256 = createHash("sha256").update(configText).digest("hex");
|
||||
@@ -194,7 +196,7 @@ remotePort = ${frpConfig.edgeRemotePort}
|
||||
}
|
||||
|
||||
function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = defaultBranch, sourceRepo = defaultSourceRepo, gitReadUrl, deploy = null, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
const bridgeServiceId = "hwlab-cloud-api";
|
||||
const runtimeLane = isRuntimeLane(profile);
|
||||
const digestPin = runtimeLane;
|
||||
@@ -642,8 +644,8 @@ function runtimePostgresImageForProfile(deploy, profile) {
|
||||
return typeof image === "string" && image.trim().length > 0 ? image.trim() : "postgres:16-alpine";
|
||||
}
|
||||
|
||||
function v02PostgresManifest({ profile = "v02", migrationSources, source, image = "postgres:16-alpine" }) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
function v02PostgresManifest({ profile = "v02", migrationSources, source, image = "postgres:16-alpine", deploy = null }) {
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
const name = `${namespace}-postgres`;
|
||||
const dbName = `hwlab_${profile}`;
|
||||
const labels = {
|
||||
@@ -735,11 +737,35 @@ function externalPostgresConfigForLane(deploy, profile, args = {}) {
|
||||
assert.ok(typeof effective.endpointAddress === "string" && effective.endpointAddress.length > 0, `deploy.lanes.${profile}.externalPostgres.endpointAddress is required`);
|
||||
const port = Number(effective.port ?? 5432);
|
||||
assert.ok(Number.isInteger(port) && port > 0 && port <= 65535, `deploy.lanes.${profile}.externalPostgres.port must be a valid TCP port`);
|
||||
return { serviceName: effective.serviceName, endpointAddress: effective.endpointAddress, port };
|
||||
const migrationSecretName = effective.migrationSecretName;
|
||||
const migrationSecretKey = effective.migrationSecretKey;
|
||||
assert.equal(typeof migrationSecretName === "string", typeof migrationSecretKey === "string", `deploy.lanes.${profile}.externalPostgres migration Secret name and key must be declared together`);
|
||||
return {
|
||||
serviceName: effective.serviceName,
|
||||
endpointAddress: effective.endpointAddress,
|
||||
port,
|
||||
...(typeof migrationSecretName === "string" ? { migrationSecretName, migrationSecretKey } : {})
|
||||
};
|
||||
}
|
||||
|
||||
function externalPostgresManifest({ profile = "v03", config, source }) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
function externalPostgresManifest({ profile = "v03", config, source, deploy = null, migrationSources = [], catalog = null, registryPrefix = defaultRegistryPrefix, useDeployImages = false }) {
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
assert.ok(Array.isArray(migrationSources) && migrationSources.length > 0, "external Postgres migration chain is required");
|
||||
const migrationData = Object.fromEntries(migrationSources.map((migration) => [path.basename(migration.path), migration.sql]));
|
||||
const migrationSha256 = createHash("sha256").update(migrationSources.map((migration) => migration.sql).join("\n")).digest("hex");
|
||||
const migrationRunner = externalPostgresMigrationRunner();
|
||||
const migrationImage = runtimeImageForService({
|
||||
catalog,
|
||||
deploy,
|
||||
serviceId: "hwlab-cloud-api",
|
||||
source,
|
||||
registryPrefix,
|
||||
useDeployImages,
|
||||
digestPin: true,
|
||||
envReuseServiceIds: envReuseServiceIdsForLane(deploy, profile)
|
||||
});
|
||||
const executionSha256 = createHash("sha256").update(`${migrationSha256}\n${migrationImage}\n${migrationRunner}`).digest("hex");
|
||||
const migrationName = `${namespace}-external-postgres-migrate-${executionSha256.slice(0, 12)}`;
|
||||
const labels = {
|
||||
"app.kubernetes.io/name": config.serviceName,
|
||||
"app.kubernetes.io/part-of": "hwlab",
|
||||
@@ -754,6 +780,64 @@ function externalPostgresManifest({ profile = "v03", config, source }) {
|
||||
apiVersion: "v1",
|
||||
kind: "List",
|
||||
items: [
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "ConfigMap",
|
||||
metadata: {
|
||||
name: migrationName,
|
||||
namespace,
|
||||
labels,
|
||||
annotations: {
|
||||
...annotations,
|
||||
"argocd.argoproj.io/sync-wave": "-2",
|
||||
"hwlab.pikastech.local/migration-sha256": migrationSha256
|
||||
}
|
||||
},
|
||||
data: {
|
||||
"run.mjs": migrationRunner,
|
||||
...migrationData
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersion: "batch/v1",
|
||||
kind: "Job",
|
||||
metadata: {
|
||||
name: migrationName,
|
||||
namespace,
|
||||
labels,
|
||||
annotations: {
|
||||
...annotations,
|
||||
"argocd.argoproj.io/sync-wave": "-1",
|
||||
"hwlab.pikastech.local/migration-sha256": migrationSha256
|
||||
}
|
||||
},
|
||||
spec: {
|
||||
backoffLimit: 2,
|
||||
template: {
|
||||
metadata: { labels: { ...labels, "app.kubernetes.io/name": migrationName } },
|
||||
spec: {
|
||||
restartPolicy: "Never",
|
||||
containers: [{
|
||||
name: "migrate",
|
||||
image: migrationImage,
|
||||
imagePullPolicy: "IfNotPresent",
|
||||
command: ["node", "/opt/hwlab-migrations/run.mjs"],
|
||||
env: [{
|
||||
name: "DATABASE_URL",
|
||||
valueFrom: {
|
||||
secretKeyRef: {
|
||||
name: config.migrationSecretName ?? `hwlab-cloud-api-${profile}-db`,
|
||||
key: config.migrationSecretKey ?? "database-url"
|
||||
}
|
||||
}
|
||||
}],
|
||||
volumeMounts: [{ name: "migrations", mountPath: "/opt/hwlab-migrations", readOnly: true }]
|
||||
}],
|
||||
volumes: [{ name: "migrations", configMap: { name: migrationName } }]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "Service",
|
||||
@@ -772,8 +856,32 @@ function externalPostgresManifest({ profile = "v03", config, source }) {
|
||||
};
|
||||
}
|
||||
|
||||
function v02OpenFgaManifest({ profile = "v02", source }) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
function externalPostgresMigrationRunner() {
|
||||
return [
|
||||
'import { readdir, readFile } from "node:fs/promises";',
|
||||
'import { createRequire } from "node:module";',
|
||||
'const require = createRequire("/opt/hwlab-env/package.json");',
|
||||
'const { Pool } = require("/opt/hwlab-env/node_modules/pg");',
|
||||
'const root = "/opt/hwlab-migrations";',
|
||||
'const files = (await readdir(root)).filter((name) => /^\\d+_.*\\.sql$/u.test(name)).sort();',
|
||||
'const connectionString = process.env.DATABASE_URL;',
|
||||
'if (!connectionString) throw new Error("DATABASE_URL is required");',
|
||||
'console.error(JSON.stringify({ event: "hwlab-runtime-migration-started", fileCount: files.length, valuesPrinted: false }));',
|
||||
'const pool = new Pool({ connectionString, max: 1 });',
|
||||
'try {',
|
||||
' for (const file of files) {',
|
||||
' await pool.query(await readFile(`${root}/${file}`, "utf8"));',
|
||||
' console.error(JSON.stringify({ event: "hwlab-runtime-migration-applied", file, valuesPrinted: false }));',
|
||||
' }',
|
||||
'} finally {',
|
||||
' await pool.end();',
|
||||
'}',
|
||||
''
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function v02OpenFgaManifest({ profile = "v02", source, deploy = null }) {
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
const name = "hwlab-openfga";
|
||||
const image = "127.0.0.1:5000/hwlab/openfga:v1.17.0";
|
||||
const labels = {
|
||||
@@ -1200,7 +1308,7 @@ function opencodeConfigContent(config) {
|
||||
|
||||
function opencodeServerManifest({ profile = "v03", source, deploy = null, catalog = null, registryPrefix = defaultRegistryPrefix, useDeployImages = false, sourceBranch = defaultBranch, gitReadUrl = defaultV02GitReadUrl, sourceRepo = defaultSourceRepo }) {
|
||||
assert.ok(isRuntimeLane(profile), `opencode-server profile must be a runtime lane, got ${profile}`);
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const namespace = namespaceNameForProfile(profile, deploy);
|
||||
const name = "opencode-server";
|
||||
const helperServiceId = "hwlab-cloud-web";
|
||||
const envReuseServiceIds = envReuseServiceIdsForLane(deploy, profile);
|
||||
|
||||
@@ -29,10 +29,10 @@ import {
|
||||
primitiveValidationTaskNames
|
||||
} from "./tekton-scripts.mjs";
|
||||
|
||||
function ciLaneSettings(argsOrLane = "node") {
|
||||
function ciLaneSettings(argsOrLane = "node", deploy = null) {
|
||||
const lane = typeof argsOrLane === "string" ? argsOrLane : argsOrLane.lane;
|
||||
if (isRuntimeLane(lane)) {
|
||||
const namespace = namespaceNameForProfile(lane);
|
||||
const namespace = namespaceNameForProfile(lane, deploy);
|
||||
return {
|
||||
lane,
|
||||
profile: lane,
|
||||
@@ -64,8 +64,8 @@ function ciLaneSettings(argsOrLane = "node") {
|
||||
};
|
||||
}
|
||||
|
||||
function tektonRbac(args = { lane: "node" }) {
|
||||
const settings = ciLaneSettings(args);
|
||||
function tektonRbac(args = { lane: "node" }, deploy = null) {
|
||||
const settings = ciLaneSettings(args, deploy);
|
||||
if (isRuntimeLane(settings.lane)) {
|
||||
const runtimeObserverName = `${settings.runtimeNamespace}-runtime-observer`;
|
||||
const pipelineRunWriterName = `${settings.runtimeNamespace}-pipelinerun-writer`;
|
||||
@@ -444,7 +444,7 @@ node scripts/ci/runtime-ready.mjs
|
||||
`;
|
||||
}
|
||||
|
||||
function runtimeReadyTask({ profile = "dev" } = {}) {
|
||||
function runtimeReadyTask({ profile = "dev", deploy = null } = {}) {
|
||||
return {
|
||||
name: "runtime-ready",
|
||||
runAfter: ["gitops-promote"],
|
||||
@@ -466,7 +466,7 @@ function runtimeReadyTask({ profile = "dev" } = {}) {
|
||||
},
|
||||
params: [
|
||||
{ name: "revision", value: "$(params.revision)" },
|
||||
{ name: "runtime-namespace", value: namespaceNameForProfile(profile) },
|
||||
{ name: "runtime-namespace", value: namespaceNameForProfile(profile, deploy) },
|
||||
{ name: "gitops-target", value: gitopsTargetForProfile(profile) },
|
||||
{ name: "argo-application", value: argoApplicationName(profile) },
|
||||
{ name: "timeout-ms", value: "$(params.runtime-ready-timeout-ms)" }
|
||||
@@ -505,7 +505,7 @@ function reusableTaskResource(pipelineName, task) {
|
||||
}
|
||||
|
||||
function tektonTasks(args = { lane: "node" }, deploy = null) {
|
||||
const settings = ciLaneSettings(args);
|
||||
const settings = ciLaneSettings(args, deploy);
|
||||
const prepareSource = {
|
||||
name: "prepare-source",
|
||||
taskSpec: {
|
||||
@@ -542,12 +542,12 @@ function tektonTasks(args = { lane: "node" }, deploy = null) {
|
||||
}
|
||||
|
||||
function tektonPipeline(args = { lane: "node" }, deploy = null) {
|
||||
const settings = ciLaneSettings(args);
|
||||
const runtimePath = gitopsPathForProfile(args, settings.profile);
|
||||
const settings = ciLaneSettings(args, deploy);
|
||||
const runtimePath = gitopsPathForProfile(args, settings.profile, deploy);
|
||||
const preFlushRuntimeReadyTasks = isRuntimeLane(settings.lane)
|
||||
? []
|
||||
: [{
|
||||
...runtimeReadyTask({ profile: settings.profile }),
|
||||
...runtimeReadyTask({ profile: settings.profile, deploy }),
|
||||
when: [{ input: "$(tasks.gitops-promote.results.runtime-ready-required)", operator: "in", values: ["true"] }]
|
||||
}];
|
||||
return {
|
||||
@@ -671,9 +671,9 @@ function tektonPipeline(args = { lane: "node" }, deploy = null) {
|
||||
};
|
||||
}
|
||||
|
||||
function tektonPipelineRunTemplate({ source, args }) {
|
||||
const settings = ciLaneSettings(args);
|
||||
const runtimePath = gitopsPathForProfile(args, settings.profile);
|
||||
function tektonPipelineRunTemplate({ source, args, deploy = null }) {
|
||||
const settings = ciLaneSettings(args, deploy);
|
||||
const runtimePath = gitopsPathForProfile(args, settings.profile, deploy);
|
||||
return {
|
||||
apiVersion: "tekton.dev/v1",
|
||||
kind: "PipelineRun",
|
||||
@@ -720,7 +720,7 @@ function tektonPipelineRunTemplate({ source, args }) {
|
||||
}
|
||||
|
||||
function tektonPollerCronJob(args, deploy) {
|
||||
const settings = ciLaneSettings(args);
|
||||
const settings = ciLaneSettings(args, deploy);
|
||||
if (settings.lane === "v02") throw new Error("v02 CI/CD is manually triggered by UniDesk CLI and must not render a branch poller CronJob");
|
||||
return {
|
||||
apiVersion: "batch/v1",
|
||||
@@ -771,7 +771,7 @@ function tektonPollerCronJob(args, deploy) {
|
||||
{ name: "LANE", value: settings.lane },
|
||||
{ name: "CATALOG_PATH", value: args.catalogPath || settings.catalogPath },
|
||||
{ name: "IMAGE_TAG_MODE", value: args.imageTagMode || settings.imageTagMode },
|
||||
{ name: "RUNTIME_PATH", value: gitopsPathForProfile(args, settings.profile) },
|
||||
{ name: "RUNTIME_PATH", value: gitopsPathForProfile(args, settings.profile, deploy) },
|
||||
{ name: "GIT_URL", value: args.sourceRepo },
|
||||
{ name: "GIT_READ_URL", value: args.gitReadUrl },
|
||||
{ name: "SOURCE_BRANCH", value: args.sourceBranch },
|
||||
|
||||
@@ -13,8 +13,8 @@ for tool in node git timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{
|
||||
git_url_requires_ssh() { case "$1" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }
|
||||
lane="$(params.lane)"
|
||||
case "$lane" in
|
||||
v[0-9][0-9]*) runtime_lane=true ;;
|
||||
*) runtime_lane=false ;;
|
||||
node) runtime_lane=false ;;
|
||||
*) runtime_lane=true ;;
|
||||
esac
|
||||
if [ "$runtime_lane" = "true" ]; then
|
||||
printf 'false' > /tekton/results/runtime-ready-required
|
||||
|
||||
Reference in New Issue
Block a user