Add decision-center k3s artifact consumer paths
This commit is contained in:
+218
-121
@@ -78,6 +78,10 @@ interface ArtifactConsumerSpec {
|
||||
kind: "compose" | "d601-k3s";
|
||||
registryRepository: string;
|
||||
dockerfile: string;
|
||||
targets: Partial<Record<ArtifactDeployEnvironment, ArtifactConsumerTarget>>;
|
||||
}
|
||||
|
||||
interface ArtifactConsumerTarget {
|
||||
targetImage: string;
|
||||
targetCommitImage: (commit: string) => string;
|
||||
deployRef: string;
|
||||
@@ -90,7 +94,7 @@ interface ArtifactConsumerSpec {
|
||||
};
|
||||
k3s?: {
|
||||
namespace: string;
|
||||
manifestPath: string;
|
||||
manifestRepoPath: string;
|
||||
deploymentName: string;
|
||||
serviceName: string;
|
||||
servicePort: number;
|
||||
@@ -108,15 +112,19 @@ const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
|
||||
kind: "compose",
|
||||
registryRepository: "unidesk/backend-core",
|
||||
dockerfile: "src/components/backend-core/Dockerfile",
|
||||
targetImage: "unidesk-backend-core",
|
||||
targetCommitImage: (commit: string) => `unidesk-backend-core:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.backend-core",
|
||||
compose: {
|
||||
serviceName: "backend-core",
|
||||
containerName: "unidesk-backend-core",
|
||||
deployEnvPrefix: "UNIDESK_DEPLOY",
|
||||
healthProbeCommand: "if command -v backend-core >/dev/null 2>&1; then backend-core --fetch-json http://127.0.0.1:8080/health --require-ok; elif command -v bun >/dev/null 2>&1; then bun -e \"fetch('http://127.0.0.1:8080/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"; else exit 1; fi",
|
||||
requireHealthCommit: false,
|
||||
targets: {
|
||||
prod: {
|
||||
targetImage: "unidesk-backend-core",
|
||||
targetCommitImage: (commit: string) => `unidesk-backend-core:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.backend-core",
|
||||
compose: {
|
||||
serviceName: "backend-core",
|
||||
containerName: "unidesk-backend-core",
|
||||
deployEnvPrefix: "UNIDESK_DEPLOY",
|
||||
healthProbeCommand: "if command -v backend-core >/dev/null 2>&1; then backend-core --fetch-json http://127.0.0.1:8080/health --require-ok; elif command -v bun >/dev/null 2>&1; then bun -e \"fetch('http://127.0.0.1:8080/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"; else exit 1; fi",
|
||||
requireHealthCommit: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"baidu-netdisk": {
|
||||
@@ -125,15 +133,19 @@ const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
|
||||
kind: "compose",
|
||||
registryRepository: "unidesk/baidu-netdisk",
|
||||
dockerfile: "src/components/microservices/baidu-netdisk/Dockerfile",
|
||||
targetImage: "baidu-netdisk",
|
||||
targetCommitImage: (commit: string) => `baidu-netdisk:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.baidu-netdisk",
|
||||
compose: {
|
||||
serviceName: "baidu-netdisk",
|
||||
containerName: "baidu-netdisk-backend",
|
||||
deployEnvPrefix: "UNIDESK_BAIDU_NETDISK_DEPLOY",
|
||||
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4244/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
|
||||
requireHealthCommit: true,
|
||||
targets: {
|
||||
prod: {
|
||||
targetImage: "baidu-netdisk",
|
||||
targetCommitImage: (commit: string) => `baidu-netdisk:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.baidu-netdisk",
|
||||
compose: {
|
||||
serviceName: "baidu-netdisk",
|
||||
containerName: "baidu-netdisk-backend",
|
||||
deployEnvPrefix: "UNIDESK_BAIDU_NETDISK_DEPLOY",
|
||||
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4244/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
|
||||
requireHealthCommit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"decision-center": {
|
||||
@@ -142,17 +154,36 @@ const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
|
||||
kind: "d601-k3s",
|
||||
registryRepository: "unidesk/decision-center",
|
||||
dockerfile: "src/components/microservices/decision-center/Dockerfile",
|
||||
targetImage: "unidesk-decision-center:d601",
|
||||
targetCommitImage: (commit: string) => `unidesk-decision-center:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.decision-center",
|
||||
k3s: {
|
||||
namespace: "unidesk",
|
||||
manifestPath: "/home/ubuntu/cq-deploy/src/components/microservices/k3sctl-adapter/k3s/decision-center.k8s.yaml",
|
||||
deploymentName: "decision-center",
|
||||
serviceName: "decision-center",
|
||||
servicePort: 4277,
|
||||
containerName: "decision-center",
|
||||
healthPath: "/health",
|
||||
targets: {
|
||||
dev: {
|
||||
targetImage: "unidesk-decision-center:dev",
|
||||
targetCommitImage: (commit: string) => `unidesk-decision-center:${commit}`,
|
||||
deployRef: "deploy.json#environments.dev.services.decision-center",
|
||||
k3s: {
|
||||
namespace: "unidesk-dev",
|
||||
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-decision-center.k8s.yaml",
|
||||
deploymentName: "decision-center-dev",
|
||||
serviceName: "decision-center-dev",
|
||||
servicePort: 4277,
|
||||
containerName: "decision-center",
|
||||
healthPath: "/health",
|
||||
podLabelSelector: "app.kubernetes.io/name=decision-center,unidesk.ai/environment=dev",
|
||||
},
|
||||
},
|
||||
prod: {
|
||||
targetImage: "unidesk-decision-center:d601",
|
||||
targetCommitImage: (commit: string) => `unidesk-decision-center:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.decision-center",
|
||||
k3s: {
|
||||
namespace: "unidesk",
|
||||
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/decision-center.k8s.yaml",
|
||||
deploymentName: "decision-center",
|
||||
serviceName: "decision-center",
|
||||
servicePort: 4277,
|
||||
containerName: "decision-center",
|
||||
healthPath: "/health",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"frontend": {
|
||||
@@ -161,15 +192,19 @@ const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
|
||||
kind: "compose",
|
||||
registryRepository: "unidesk/frontend",
|
||||
dockerfile: "src/components/frontend/Dockerfile",
|
||||
targetImage: "unidesk-frontend",
|
||||
targetCommitImage: (commit: string) => `unidesk-frontend:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.frontend",
|
||||
compose: {
|
||||
serviceName: "frontend",
|
||||
containerName: "unidesk-frontend",
|
||||
deployEnvPrefix: "UNIDESK_FRONTEND_DEPLOY",
|
||||
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:8080/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
|
||||
requireHealthCommit: true,
|
||||
targets: {
|
||||
prod: {
|
||||
targetImage: "unidesk-frontend",
|
||||
targetCommitImage: (commit: string) => `unidesk-frontend:${commit}`,
|
||||
deployRef: "deploy.json#environments.prod.services.frontend",
|
||||
compose: {
|
||||
serviceName: "frontend",
|
||||
containerName: "unidesk-frontend",
|
||||
deployEnvPrefix: "UNIDESK_FRONTEND_DEPLOY",
|
||||
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:8080/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
|
||||
requireHealthCommit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"dev:frontend": {
|
||||
@@ -178,19 +213,23 @@ const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
|
||||
kind: "d601-k3s",
|
||||
registryRepository: "unidesk/frontend",
|
||||
dockerfile: "src/components/frontend/Dockerfile",
|
||||
targetImage: "unidesk-frontend:dev",
|
||||
targetCommitImage: (commit: string) => `unidesk-frontend:${commit}`,
|
||||
deployRef: "origin/master:deploy.json#environments.dev.services.frontend",
|
||||
k3s: {
|
||||
namespace: "unidesk-dev",
|
||||
manifestPath: "/home/ubuntu/cq-deploy/src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml",
|
||||
deploymentName: "frontend-dev",
|
||||
serviceName: "frontend-dev",
|
||||
servicePort: 8080,
|
||||
containerName: "frontend",
|
||||
healthPath: "/health",
|
||||
applySelector: "app.kubernetes.io/name=frontend",
|
||||
podLabelSelector: "app.kubernetes.io/name=frontend,app.kubernetes.io/component=web,unidesk.ai/environment=dev",
|
||||
targets: {
|
||||
dev: {
|
||||
targetImage: "unidesk-frontend:dev",
|
||||
targetCommitImage: (commit: string) => `unidesk-frontend:${commit}`,
|
||||
deployRef: "origin/master:deploy.json#environments.dev.services.frontend",
|
||||
k3s: {
|
||||
namespace: "unidesk-dev",
|
||||
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml",
|
||||
deploymentName: "frontend-dev",
|
||||
serviceName: "frontend-dev",
|
||||
servicePort: 8080,
|
||||
containerName: "frontend",
|
||||
healthPath: "/health",
|
||||
applySelector: "app.kubernetes.io/name=frontend",
|
||||
podLabelSelector: "app.kubernetes.io/name=frontend,app.kubernetes.io/component=web,unidesk.ai/environment=dev",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -236,6 +275,11 @@ function parseOptions(args: string[]): ArtifactRegistryOptions {
|
||||
options.dryRun = true;
|
||||
} else if (arg === "--run-now") {
|
||||
options.runNow = true;
|
||||
} else if (arg === "--env" || arg === "--environment") {
|
||||
const environment = requireValue(args, index, arg);
|
||||
if (environment !== "dev" && environment !== "prod") throw new Error(`${arg} must be dev or prod`);
|
||||
options.environment = environment;
|
||||
index += 1;
|
||||
} else if (arg === "--provider-id") {
|
||||
options.providerId = requireValue(args, index, arg);
|
||||
index += 1;
|
||||
@@ -556,15 +600,22 @@ function commandTail(result: CommandResult): Record<string, unknown> {
|
||||
|
||||
function artifactConsumerSpec(serviceId: string, environment: ArtifactDeployEnvironment | null): ArtifactConsumerSpec | null {
|
||||
const key = environment === null || environment === "prod" ? serviceId : `${environment}:${serviceId}`;
|
||||
return artifactConsumerSpecs[key] ?? null;
|
||||
const explicit = artifactConsumerSpecs[key];
|
||||
if (explicit !== undefined) return explicit;
|
||||
const shared = artifactConsumerSpecs[serviceId];
|
||||
return environment !== null && shared?.targets[environment] !== undefined ? shared : null;
|
||||
}
|
||||
|
||||
function supportedArtifactConsumers(): Array<{ environment: ArtifactDeployEnvironment; serviceId: SupportedArtifactConsumerService; kind: ArtifactConsumerSpec["kind"] }> {
|
||||
return Object.values(artifactConsumerSpecs).map((spec) => ({
|
||||
environment: spec.environment ?? "prod",
|
||||
return Object.values(artifactConsumerSpecs).flatMap((spec) => Object.keys(spec.targets).map((environment) => ({
|
||||
environment: environment as ArtifactDeployEnvironment,
|
||||
serviceId: spec.serviceId,
|
||||
kind: spec.kind,
|
||||
}));
|
||||
})));
|
||||
}
|
||||
|
||||
function artifactConsumerTarget(spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment | null): ArtifactConsumerTarget | null {
|
||||
return spec.targets[environment ?? "prod"] ?? null;
|
||||
}
|
||||
|
||||
function unsupportedService(serviceId: string, options: ArtifactRegistryOptions): Record<string, unknown> {
|
||||
@@ -582,12 +633,28 @@ function unsupportedService(serviceId: string, options: ArtifactRegistryOptions)
|
||||
};
|
||||
}
|
||||
|
||||
function unsupportedEnvironment(spec: ArtifactConsumerSpec, options: ArtifactRegistryOptions): Record<string, unknown> {
|
||||
const environment = options.environment ?? "prod";
|
||||
return {
|
||||
ok: false,
|
||||
supported: false,
|
||||
error: "unsupported-environment",
|
||||
serviceId: spec.serviceId,
|
||||
environment,
|
||||
providerId: options.providerId,
|
||||
reason: `No standardized ${environment} registry artifact consumer is implemented for ${spec.serviceId}.`,
|
||||
supportedEnvironments: Object.keys(spec.targets),
|
||||
policy: "artifact CD must not silently fall back to maintenance-channel source builds or legacy direct deployment",
|
||||
};
|
||||
}
|
||||
|
||||
function artifactImageRef(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string): string {
|
||||
return `127.0.0.1:${options.port}/${spec.registryRepository}:${commit}`;
|
||||
}
|
||||
|
||||
function deployRefFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
|
||||
return options.deployRef ?? spec.deployRef;
|
||||
const target = artifactConsumerTarget(spec, options.environment);
|
||||
return options.deployRef ?? target?.deployRef ?? `deploy.json#environments.${options.environment ?? "prod"}.services.${spec.serviceId}`;
|
||||
}
|
||||
|
||||
function runRemoteScript(options: ArtifactRegistryOptions, script: string, timeoutMs = options.timeoutMs): CommandResult {
|
||||
@@ -879,9 +946,9 @@ function d601DevFrontendAuthPatchScript(config: UniDeskConfig): string {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function composeArtifactEnvValues(spec: ArtifactConsumerSpec, options: ArtifactRegistryOptions, commit: string): Record<string, string> {
|
||||
if (spec.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
|
||||
const prefix = spec.compose.deployEnvPrefix;
|
||||
function composeArtifactEnvValues(spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, options: ArtifactRegistryOptions, commit: string): Record<string, string> {
|
||||
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
|
||||
const prefix = target.compose.deployEnvPrefix;
|
||||
return {
|
||||
[`${prefix}_SERVICE_ID`]: spec.serviceId,
|
||||
[`${prefix}_REF`]: deployRefFor(options, spec),
|
||||
@@ -891,17 +958,17 @@ function composeArtifactEnvValues(spec: ArtifactConsumerSpec, options: ArtifactR
|
||||
};
|
||||
}
|
||||
|
||||
async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): Promise<Record<string, unknown>> {
|
||||
async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
|
||||
const commit = options.commit;
|
||||
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
|
||||
if (spec.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
|
||||
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
|
||||
const health = runReadonlyStatus(options, true);
|
||||
if (health.ok !== true) {
|
||||
return { ok: false, serviceId: spec.serviceId, error: "D601 artifact registry is not healthy", health };
|
||||
}
|
||||
const sourceImage = artifactImageRef(options, spec, commit);
|
||||
const composeImage = options.targetImage ?? spec.targetImage;
|
||||
const commitImage = options.targetImage === null ? spec.targetCommitImage(commit) : `${options.targetImage}:${commit}`;
|
||||
const composeImage = options.targetImage ?? target.targetImage;
|
||||
const commitImage = `${composeImage}:${commit}`;
|
||||
const registryProbe = runRemoteScript(options, registryArtifactProbeScript(options, spec, commit), Math.max(options.timeoutMs, 120_000));
|
||||
if (registryProbe.exitCode !== 0 || registryProbe.timedOut) {
|
||||
return {
|
||||
@@ -938,13 +1005,13 @@ async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec:
|
||||
const config = readConfig();
|
||||
const runtimeEnv = writeComposeEnv(config, false);
|
||||
upsertEnvFileValues(runtimeEnv.envFile, {
|
||||
...composeArtifactEnvValues(spec, options, commit),
|
||||
...composeArtifactEnvValues(spec, target, options, commit),
|
||||
});
|
||||
const compose = resolveComposeCommand(config, runtimeEnv.envFile);
|
||||
const projectIndex = compose.indexOf("-p");
|
||||
const composeProject = projectIndex >= 0 && compose[projectIndex + 1] !== undefined ? compose[projectIndex + 1] : "unidesk";
|
||||
const serviceName = spec.compose.serviceName;
|
||||
const containerName = spec.compose.containerName;
|
||||
const serviceName = target.compose.serviceName;
|
||||
const containerName = target.compose.containerName;
|
||||
const serviceLogPrefix = spec.serviceId.replace(/-/gu, "_");
|
||||
const upScript = [
|
||||
"set -euo pipefail",
|
||||
@@ -968,9 +1035,9 @@ async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec:
|
||||
"actual_commit=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}' \"$image_id\")",
|
||||
`test "$actual_commit" = ${shellQuote(commit)}`,
|
||||
`health_json=/tmp/unidesk-${safeName(spec.serviceId)}-health.json`,
|
||||
`docker exec "$cid" sh -lc ${shellQuote(spec.compose.healthProbeCommand)} > "$health_json"`,
|
||||
`docker exec "$cid" sh -lc ${shellQuote(target.compose.healthProbeCommand)} > "$health_json"`,
|
||||
"cat \"$health_json\"",
|
||||
...(spec.compose.requireHealthCommit ? [
|
||||
...(target.compose.requireHealthCommit ? [
|
||||
"health_commit=$(python3 -c 'import json,sys; print(((json.load(open(sys.argv[1])).get(\"deploy\") or {}).get(\"commit\") or \"\"))' \"$health_json\")",
|
||||
`test "$health_commit" = ${shellQuote(commit)}`,
|
||||
] : []),
|
||||
@@ -1005,7 +1072,7 @@ async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec:
|
||||
validation: {
|
||||
liveCommit: commit,
|
||||
imageLabelCommit: commit,
|
||||
serviceHealthCommit: spec.compose.requireHealthCommit ? commit : "not-required",
|
||||
serviceHealthCommit: target.compose.requireHealthCommit ? commit : "not-required",
|
||||
healthyOldVersionAccepted: false,
|
||||
},
|
||||
rollback: {
|
||||
@@ -1017,11 +1084,17 @@ async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec:
|
||||
|
||||
async function deployBackendCoreNow(options: ArtifactRegistryOptions): Promise<Record<string, unknown>> {
|
||||
if (options.commit === null) throw new Error("artifact-registry deploy-backend-core requires --commit <full-sha>");
|
||||
return deployComposeArtifactNow(options, artifactConsumerSpecs["backend-core"]);
|
||||
const spec = artifactConsumerSpecs["backend-core"];
|
||||
const target = artifactConsumerTarget(spec, options.environment);
|
||||
if (target === null) return unsupportedEnvironment(spec, options);
|
||||
return deployComposeArtifactNow(options, spec, target);
|
||||
}
|
||||
|
||||
function deployBackendCoreJob(args: string[], options: ArtifactRegistryOptions): Record<string, unknown> {
|
||||
if (options.commit === null) throw new Error("artifact-registry deploy-backend-core requires --commit <full-sha>");
|
||||
const spec = artifactConsumerSpecs["backend-core"];
|
||||
const target = artifactConsumerTarget(spec, options.environment);
|
||||
if (target === null) return unsupportedEnvironment(spec, options);
|
||||
const runArgs = args.includes("--run-now") ? args : [...args, "--run-now"];
|
||||
const command = [process.execPath, rootPath("scripts", "cli.ts"), "artifact-registry", ...runArgs];
|
||||
const job = startJob("artifact_registry_backend_core_cd", command, `Pull and deploy backend-core artifact ${options.commit} from D601 registry`);
|
||||
@@ -1035,13 +1108,15 @@ function deployBackendCoreJob(args: string[], options: ArtifactRegistryOptions):
|
||||
};
|
||||
}
|
||||
|
||||
function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string): Record<string, unknown> {
|
||||
function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): Record<string, unknown> {
|
||||
const environment = options.environment ?? "prod";
|
||||
const sourceImage = artifactImageRef(options, spec, commit);
|
||||
const common = {
|
||||
ok: true,
|
||||
supported: true,
|
||||
dryRun: true,
|
||||
mutation: false,
|
||||
environment,
|
||||
providerId: options.providerId,
|
||||
serviceId: spec.serviceId,
|
||||
commit,
|
||||
@@ -1057,42 +1132,42 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
|
||||
method: "HEAD",
|
||||
url: `http://127.0.0.1:${options.port}/v2/${spec.registryRepository}/manifests/${commit}`,
|
||||
},
|
||||
boundary: "artifact CD is artifact-consumer only: verify commit-pinned registry image, pull/import, deploy, then verify live commit/image/health; it never builds source on the runtime target",
|
||||
boundary: `${environment} CD is artifact-consumer only: verify commit-pinned registry image, pull/import, deploy, then verify live commit/image/health; it never builds source on the runtime target`,
|
||||
};
|
||||
if (spec.kind === "compose") {
|
||||
if (spec.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
|
||||
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
|
||||
return {
|
||||
...common,
|
||||
target: {
|
||||
kind: "compose",
|
||||
composeService: spec.compose.serviceName,
|
||||
containerName: spec.compose.containerName,
|
||||
targetImage: options.targetImage ?? spec.targetImage,
|
||||
runtimeImage: spec.targetCommitImage(commit),
|
||||
deployEnvPrefix: spec.compose.deployEnvPrefix,
|
||||
deployCommandShape: `docker compose up -d --no-build --no-deps --force-recreate ${spec.compose.serviceName}`,
|
||||
composeService: target.compose.serviceName,
|
||||
containerName: target.compose.containerName,
|
||||
targetImage: options.targetImage ?? target.targetImage,
|
||||
runtimeImage: target.targetCommitImage(commit),
|
||||
deployEnvPrefix: target.compose.deployEnvPrefix,
|
||||
deployCommandShape: `docker compose up -d --no-build --no-deps --force-recreate ${target.compose.serviceName}`,
|
||||
},
|
||||
validation: [
|
||||
"D601 registry /v2 manifest exists for the commit tag",
|
||||
"loaded image labels match service id, source commit, and Dockerfile",
|
||||
"running Compose container image label matches the requested commit",
|
||||
spec.compose.requireHealthCommit
|
||||
target.compose.requireHealthCommit
|
||||
? `${spec.serviceId} /health succeeds and reports deploy.commit matching the artifact commit`
|
||||
: `${spec.serviceId} /health succeeds for the recreated container`,
|
||||
],
|
||||
rollback: rollbackInfo(spec, commit),
|
||||
rollback: rollbackInfo(spec, target, environment, commit),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...common,
|
||||
target: {
|
||||
kind: "d601-k3s",
|
||||
namespace: spec.k3s?.namespace,
|
||||
deployment: spec.k3s?.deploymentName,
|
||||
service: spec.k3s?.serviceName,
|
||||
stableImage: spec.targetImage,
|
||||
runtimeImage: spec.targetCommitImage(commit),
|
||||
manifestPath: spec.k3s?.manifestPath,
|
||||
namespace: target.k3s?.namespace,
|
||||
deployment: target.k3s?.deploymentName,
|
||||
service: target.k3s?.serviceName,
|
||||
stableImage: target.targetImage,
|
||||
runtimeImage: target.targetCommitImage(commit),
|
||||
manifestRepoPath: target.k3s?.manifestRepoPath,
|
||||
deployCommandShape: "kubectl set image + set env + annotate + rollout status",
|
||||
},
|
||||
validation: [
|
||||
@@ -1100,17 +1175,16 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
|
||||
"D601 Docker-pulled image labels match service id, source commit, and Dockerfile",
|
||||
"native k3s containerd has the commit image and stable runtime image tag",
|
||||
"Deployment annotation and pod image id label match the requested commit",
|
||||
"service health via Kubernetes API service proxy returns the same deploy.commit",
|
||||
...(spec.environment === "dev" && spec.serviceId === "frontend" ? ["dev frontend auth/session config is synced from main-server config before rollout"] : []),
|
||||
"service health via Kubernetes API service proxy returns the same deploy.commit and deploy.requestedCommit",
|
||||
...(environment === "dev" && spec.serviceId === "frontend" ? ["dev frontend auth/session config is synced from main-server config before rollout"] : []),
|
||||
],
|
||||
rollback: rollbackInfo(spec, commit),
|
||||
rollback: rollbackInfo(spec, target, environment, commit),
|
||||
};
|
||||
}
|
||||
|
||||
function rollbackInfo(spec: ArtifactConsumerSpec, commit: string): Record<string, unknown> {
|
||||
const environment = spec.environment ?? "prod";
|
||||
function rollbackInfo(spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, environment: ArtifactDeployEnvironment, commit: string): Record<string, unknown> {
|
||||
if (spec.kind === "compose") {
|
||||
const compose = spec.compose;
|
||||
const compose = target.compose;
|
||||
return {
|
||||
type: "compose-retag-recreate",
|
||||
composeService: compose?.serviceName,
|
||||
@@ -1121,18 +1195,22 @@ function rollbackInfo(spec: ArtifactConsumerSpec, commit: string): Record<string
|
||||
return {
|
||||
type: "d601-k3s-previous-commit",
|
||||
serviceId: spec.serviceId,
|
||||
environment,
|
||||
currentCommit: commit,
|
||||
discovery: `kubectl -n ${spec.k3s?.namespace} rollout history deployment/${spec.k3s?.deploymentName} && kubectl -n ${spec.k3s?.namespace} get deployment ${spec.k3s?.deploymentName} -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-previous-commit}'`,
|
||||
commandShape: `bun scripts/cli.ts deploy apply --env ${environment} --service ${spec.serviceId}${environment === "prod" ? " --commit <previous-full-sha>" : ""}`,
|
||||
discovery: `kubectl -n ${target.k3s?.namespace} rollout history deployment/${target.k3s?.deploymentName} && kubectl -n ${target.k3s?.namespace} get deployment ${target.k3s?.deploymentName} -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-previous-commit}'`,
|
||||
commandShape: `bun scripts/cli.ts deploy apply --env ${environment} --service ${spec.serviceId} --commit <previous-full-sha>`,
|
||||
note: "Rollback is exposed as the same artifact consumer pointed at a previous commit-pinned image that still exists in D601 registry.",
|
||||
};
|
||||
}
|
||||
|
||||
function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string, config: UniDeskConfig): string {
|
||||
if (spec.k3s === undefined) throw new Error(`${spec.serviceId} missing k3s artifact consumer config`);
|
||||
function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
|
||||
const environment = options.environment ?? "prod";
|
||||
if (target.k3s === undefined) throw new Error(`${spec.serviceId} missing k3s artifact consumer config for ${environment}`);
|
||||
const manifestText = readFileSync(rootPath(target.k3s.manifestRepoPath), "utf8");
|
||||
const manifestBase64 = Buffer.from(manifestText, "utf8").toString("base64");
|
||||
const sourceImage = artifactImageRef(options, spec, commit);
|
||||
const commitImage = spec.targetCommitImage(commit);
|
||||
const k3s = spec.k3s;
|
||||
const commitImage = target.targetCommitImage(commit);
|
||||
const k3s = target.k3s;
|
||||
const labels = [
|
||||
`unidesk.ai/deploy-service-id=${spec.serviceId}`,
|
||||
`unidesk.ai/deploy-ref=${deployRefFor(options, spec)}`,
|
||||
@@ -1140,13 +1218,15 @@ function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: Art
|
||||
`unidesk.ai/deploy-commit=${commit}`,
|
||||
`unidesk.ai/deploy-requested-commit=${commit}`,
|
||||
`unidesk.ai/image-source=${sourceImage}`,
|
||||
`unidesk.ai/deploy-environment=${environment}`,
|
||||
];
|
||||
return [
|
||||
"set -euo pipefail",
|
||||
rootExecPrelude(),
|
||||
`registry_image=${shellQuote(sourceImage)}`,
|
||||
`stable_image=${shellQuote(spec.targetImage)}`,
|
||||
`stable_image=${shellQuote(target.targetImage)}`,
|
||||
`commit_image=${shellQuote(commitImage)}`,
|
||||
`environment=${shellQuote(environment)}`,
|
||||
`service_id=${shellQuote(spec.serviceId)}`,
|
||||
`source_repo=${shellQuote(options.sourceRepo)}`,
|
||||
`deploy_ref=${shellQuote(deployRefFor(options, spec))}`,
|
||||
@@ -1158,10 +1238,11 @@ function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: Art
|
||||
`service_name=${shellQuote(k3s.serviceName)}`,
|
||||
`service_port=${shellQuote(String(k3s.servicePort))}`,
|
||||
`health_path=${shellQuote(k3s.healthPath)}`,
|
||||
`manifest=${shellQuote(k3s.manifestPath)}`,
|
||||
`apply_selector=${shellQuote(k3s.applySelector ?? "")}`,
|
||||
`pod_selector=${shellQuote(k3s.podLabelSelector ?? `app.kubernetes.io/name=${k3s.deploymentName}`)}`,
|
||||
"health_tmp=",
|
||||
`manifest_repo_path=${shellQuote(k3s.manifestRepoPath)}`,
|
||||
`manifest_b64=${shellQuote(manifestBase64)}`,
|
||||
"export KUBECONFIG=/etc/rancher/k3s/k3s.yaml",
|
||||
"command -v docker >/dev/null",
|
||||
"command -v kubectl >/dev/null",
|
||||
@@ -1178,13 +1259,16 @@ function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: Art
|
||||
"docker tag \"$registry_image\" \"$stable_image\"",
|
||||
"docker tag \"$registry_image\" \"$commit_image\"",
|
||||
"archive=$(mktemp /tmp/unidesk-artifact-k3s-image.XXXXXX.tar)",
|
||||
"trap 'rm -f \"$archive\" \"$health_tmp\"' EXIT",
|
||||
"manifest=$(mktemp /tmp/unidesk-artifact-k3s-manifest.XXXXXX.yaml)",
|
||||
"trap 'rm -f \"$archive\" \"$manifest\" \"$health_tmp\"' EXIT",
|
||||
"docker save \"$commit_image\" \"$stable_image\" -o \"$archive\"",
|
||||
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import \"$archive\" >/dev/null",
|
||||
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F \"$stable_image\" >/dev/null",
|
||||
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep -F \"$commit_image\" >/dev/null",
|
||||
"if [ -f \"$manifest\" ]; then if [ -n \"$apply_selector\" ]; then kubectl apply -f \"$manifest\" -l \"$apply_selector\"; else kubectl apply -f \"$manifest\"; fi; else echo artifact_cd_manifest_missing=$manifest; fi",
|
||||
...(spec.environment === "dev" && spec.serviceId === "frontend" ? [d601DevFrontendAuthPatchScript(config)] : []),
|
||||
"printf '%s' \"$manifest_b64\" | base64 -d > \"$manifest\"",
|
||||
"grep -F \"name: $deployment\" \"$manifest\" >/dev/null",
|
||||
"if [ -n \"$apply_selector\" ]; then kubectl apply -f \"$manifest\" -l \"$apply_selector\"; else kubectl apply -f \"$manifest\"; fi",
|
||||
...(environment === "dev" && spec.serviceId === "frontend" ? [d601DevFrontendAuthPatchScript(readConfig())] : []),
|
||||
"previous_commit=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-commit}' 2>/dev/null || true)",
|
||||
"kubectl -n \"$namespace\" set image \"deployment/$deployment\" \"$container_name=$commit_image\"",
|
||||
"kubectl -n \"$namespace\" set env \"deployment/$deployment\" \"UNIDESK_DEPLOY_SERVICE_ID=$service_id\" \"UNIDESK_DEPLOY_REF=$deploy_ref\" \"UNIDESK_DEPLOY_REPO=$source_repo\" \"UNIDESK_DEPLOY_COMMIT=$commit\" \"UNIDESK_DEPLOY_REQUESTED_COMMIT=$commit\"",
|
||||
@@ -1192,7 +1276,9 @@ function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: Art
|
||||
"if [ -n \"$previous_commit\" ] && [ \"$previous_commit\" != \"$commit\" ]; then kubectl -n \"$namespace\" annotate \"deployment/$deployment\" \"unidesk.ai/deploy-previous-commit=$previous_commit\" --overwrite; fi",
|
||||
"kubectl -n \"$namespace\" rollout status \"deployment/$deployment\" --timeout=180s",
|
||||
"deployment_commit=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-commit}')",
|
||||
"deployment_requested_commit=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-requested-commit}')",
|
||||
"test \"$deployment_commit\" = \"$commit\"",
|
||||
"test \"$deployment_requested_commit\" = \"$commit\"",
|
||||
"deployment_image=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.spec.template.spec.containers[?(@.name==\"'\"$container_name\"'\")].image}')",
|
||||
"test \"$deployment_image\" = \"$commit_image\"",
|
||||
"containerd_config_label() {",
|
||||
@@ -1247,20 +1333,22 @@ function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: Art
|
||||
"for attempt in $(seq 1 60); do",
|
||||
" if kubectl get --raw \"$proxy_path\" > \"$health_tmp\" 2>/tmp/unidesk-artifact-health.err; then",
|
||||
" health_commit=$(python3 -c 'import json,sys; print(((json.load(open(sys.argv[1])).get(\"deploy\") or {}).get(\"commit\") or \"\"))' \"$health_tmp\" 2>/dev/null || true)",
|
||||
" health_requested_commit=$(python3 -c 'import json,sys; print(((json.load(open(sys.argv[1])).get(\"deploy\") or {}).get(\"requestedCommit\") or \"\"))' \"$health_tmp\" 2>/dev/null || true)",
|
||||
" health_ok=$(python3 -c 'import json,sys; print(\"true\" if json.load(open(sys.argv[1])).get(\"ok\") is True else \"false\")' \"$health_tmp\" 2>/dev/null || true)",
|
||||
" echo \"artifact_cd_health_probe attempt=$attempt ok=$health_ok commit=$health_commit\"",
|
||||
" if [ \"$health_ok\" = \"true\" ] && [ \"$health_commit\" = \"$commit\" ]; then break; fi",
|
||||
" echo \"artifact_cd_health_probe attempt=$attempt ok=$health_ok commit=$health_commit requestedCommit=$health_requested_commit\"",
|
||||
" if [ \"$health_ok\" = \"true\" ] && [ \"$health_commit\" = \"$commit\" ] && [ \"$health_requested_commit\" = \"$commit\" ]; then break; fi",
|
||||
" else",
|
||||
" echo \"artifact_cd_health_probe attempt=$attempt request=failed\"",
|
||||
" fi",
|
||||
" if [ \"$attempt\" = \"60\" ]; then echo artifact_cd_health_failed >&2; cat \"$health_tmp\" >&2 || true; exit 1; fi",
|
||||
" sleep 2",
|
||||
"done",
|
||||
"printf 'artifact_cd_service=%s\\nartifact_cd_source_image=%s\\nartifact_cd_stable_image=%s\\nartifact_cd_runtime_image=%s\\nartifact_cd_commit=%s\\nartifact_cd_previous_commit=%s\\nartifact_cd_pod=%s\\nartifact_cd_pod_image=%s\\nartifact_cd_pod_image_id=%s\\nartifact_cd_image_label_commit=%s\\nartifact_cd_health_commit=%s\\n' \"$service_id\" \"$registry_image\" \"$stable_image\" \"$commit_image\" \"$commit\" \"$previous_commit\" \"$pod\" \"$pod_image\" \"$pod_image_id\" \"$image_label_commit\" \"$health_commit\"",
|
||||
"printf 'artifact_cd_service=%s\\nartifact_cd_environment=%s\\nartifact_cd_manifest_repo_path=%s\\nartifact_cd_source_image=%s\\nartifact_cd_stable_image=%s\\nartifact_cd_runtime_image=%s\\nartifact_cd_commit=%s\\nartifact_cd_requested_commit=%s\\nartifact_cd_previous_commit=%s\\nartifact_cd_pod=%s\\nartifact_cd_pod_image=%s\\nartifact_cd_pod_image_id=%s\\nartifact_cd_image_label_commit=%s\\nartifact_cd_health_commit=%s\\nartifact_cd_health_requested_commit=%s\\n' \"$service_id\" \"$environment\" \"$manifest_repo_path\" \"$registry_image\" \"$stable_image\" \"$commit_image\" \"$commit\" \"$deployment_requested_commit\" \"$previous_commit\" \"$pod\" \"$pod_image\" \"$pod_image_id\" \"$image_label_commit\" \"$health_commit\" \"$health_requested_commit\"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function deployD601K3sArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): Promise<Record<string, unknown>> {
|
||||
async function deployD601K3sArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
|
||||
const environment = options.environment ?? "prod";
|
||||
const commit = options.commit;
|
||||
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
|
||||
const health = runReadonlyStatus(options, true);
|
||||
@@ -1278,8 +1366,7 @@ async function deployD601K3sArtifactNow(options: ArtifactRegistryOptions, spec:
|
||||
registryProbe: commandTail(registryProbe),
|
||||
};
|
||||
}
|
||||
const config = readConfig();
|
||||
const deployScript = d601K3sArtifactDeployScript(options, spec, commit, config);
|
||||
const deployScript = d601K3sArtifactDeployScript(options, spec, target, commit);
|
||||
const deploy = runRemoteScript(options, deployScript, Math.max(options.timeoutMs, 420_000));
|
||||
if (deploy.exitCode !== 0 || deploy.timedOut) {
|
||||
return {
|
||||
@@ -1290,29 +1377,33 @@ async function deployD601K3sArtifactNow(options: ArtifactRegistryOptions, spec:
|
||||
sourceImage,
|
||||
registryProbe: commandTail(registryProbe),
|
||||
deploy: commandTail(deploy),
|
||||
rollback: rollbackInfo(spec, commit),
|
||||
rollback: rollbackInfo(spec, target, environment, commit),
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
supported: true,
|
||||
serviceId: spec.serviceId,
|
||||
environment,
|
||||
commit,
|
||||
providerId: options.providerId,
|
||||
sourceRepo: options.sourceRepo,
|
||||
deployRef: deployRefFor(options, spec),
|
||||
sourceImage,
|
||||
stableImage: spec.targetImage,
|
||||
runtimeImage: spec.targetCommitImage(commit),
|
||||
stableImage: target.targetImage,
|
||||
runtimeImage: target.targetCommitImage(commit),
|
||||
manifestRepoPath: target.k3s?.manifestRepoPath,
|
||||
registryProbe: commandTail(registryProbe),
|
||||
deploy: commandTail(deploy),
|
||||
validation: {
|
||||
liveCommit: commit,
|
||||
liveRequestedCommit: commit,
|
||||
imageLabelCommit: commit,
|
||||
serviceHealthCommit: commit,
|
||||
serviceHealthRequestedCommit: commit,
|
||||
healthyOldVersionAccepted: false,
|
||||
},
|
||||
rollback: rollbackInfo(spec, commit),
|
||||
rollback: rollbackInfo(spec, target, environment, commit),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1321,9 +1412,11 @@ async function deployServiceNow(options: ArtifactRegistryOptions): Promise<Recor
|
||||
if (options.commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
|
||||
const spec = artifactConsumerSpec(options.serviceId, options.environment);
|
||||
if (spec === null) return unsupportedService(options.serviceId, options);
|
||||
if (options.dryRun) return dryRunArtifactConsumerPlan(options, spec, options.commit);
|
||||
if (spec.kind === "compose") return deployComposeArtifactNow(options, spec);
|
||||
return deployD601K3sArtifactNow(options, spec);
|
||||
const target = artifactConsumerTarget(spec, options.environment);
|
||||
if (target === null) return unsupportedEnvironment(spec, options);
|
||||
if (options.dryRun) return dryRunArtifactConsumerPlan(options, spec, target, options.commit);
|
||||
if (spec.kind === "compose") return deployComposeArtifactNow(options, spec, target);
|
||||
return deployD601K3sArtifactNow(options, spec, target);
|
||||
}
|
||||
|
||||
function deployServiceJob(args: string[], options: ArtifactRegistryOptions): Record<string, unknown> {
|
||||
@@ -1331,7 +1424,9 @@ function deployServiceJob(args: string[], options: ArtifactRegistryOptions): Rec
|
||||
if (options.commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
|
||||
const spec = artifactConsumerSpec(options.serviceId, options.environment);
|
||||
if (spec === null) return unsupportedService(options.serviceId, options);
|
||||
if (options.dryRun) return dryRunArtifactConsumerPlan(options, spec, options.commit);
|
||||
const target = artifactConsumerTarget(spec, options.environment);
|
||||
if (target === null) return unsupportedEnvironment(spec, options);
|
||||
if (options.dryRun) return dryRunArtifactConsumerPlan(options, spec, target, options.commit);
|
||||
const runArgs = args.includes("--run-now") ? args : [...args, "--run-now"];
|
||||
const command = [process.execPath, rootPath("scripts", "cli.ts"), "artifact-registry", ...runArgs];
|
||||
const job = startJob("artifact_registry_service_cd", command, `Pull and deploy ${options.environment ?? "prod"} ${options.serviceId} artifact ${options.commit} from D601 registry`);
|
||||
@@ -1344,7 +1439,7 @@ function deployServiceJob(args: string[], options: ArtifactRegistryOptions): Rec
|
||||
statusCommand: `bun scripts/cli.ts job status ${job.id}`,
|
||||
tailCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`,
|
||||
note: "User-service CD continues in the background: D601 registry health, commit-pinned artifact check, pull/import, rollout, image-label and live health commit verification.",
|
||||
rollback: rollbackInfo(spec, options.commit),
|
||||
rollback: rollbackInfo(spec, target, options.environment ?? "prod", options.commit),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1367,7 +1462,8 @@ function localHelp(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service baidu-netdisk --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env prod --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env dev --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
],
|
||||
firstStage: "install now writes the rendered systemd/Compose/config files and starts the registry",
|
||||
artifactConsumers: {
|
||||
@@ -1382,6 +1478,7 @@ function localHelp(): Record<string, unknown> {
|
||||
],
|
||||
devCommands: [
|
||||
"bun scripts/cli.ts deploy apply --env dev --service frontend",
|
||||
"bun scripts/cli.ts deploy apply --env dev --service decision-center",
|
||||
],
|
||||
rollbackShape: "rerun the same artifact consumer with a previous commit-pinned image",
|
||||
},
|
||||
|
||||
+143
-25
@@ -79,6 +79,7 @@ interface ServiceRuntimeState {
|
||||
deploymentMode: string;
|
||||
currentCommit: string | null;
|
||||
healthCommit: string | null;
|
||||
healthRequestedCommit: string | null;
|
||||
imageCommit: string | null;
|
||||
orchestratorCommit: string | null;
|
||||
healthOk: boolean;
|
||||
@@ -135,7 +136,7 @@ const nativeK3sCtrAddress = "/run/k3s/containerd/containerd.sock";
|
||||
const unideskRepoUrl = "https://github.com/pikasTech/unidesk";
|
||||
const d601MaintenanceDeployAllowedServiceIds = new Set<string>(["backend-core", "k3sctl-adapter", "code-queue"]);
|
||||
const devApplySupportedServiceIds = new Set<string>(["backend-core"]);
|
||||
const devArtifactConsumerServiceIds = new Set<string>(["baidu-netdisk", "frontend"]);
|
||||
const devArtifactConsumerServiceIds = new Set<string>(["baidu-netdisk", "decision-center", "frontend"]);
|
||||
const prodArtifactConsumerServiceIds = new Set<string>(["backend-core", "baidu-netdisk", "decision-center", "frontend"]);
|
||||
const deployEnvironmentTargets: Record<DeployEnvironment, DeployEnvironmentTarget> = {
|
||||
dev: {
|
||||
@@ -199,7 +200,7 @@ export function deployHelp(action: string | undefined = undefined): Record<strin
|
||||
},
|
||||
options: [
|
||||
{ name: "--file <path>", default: defaultDeployFile, description: "Desired-state manifest path relative to the repo root. JSON and ESM JS manifests are supported, for example deploy.json or develop.js. Local manifest apply allows k3sctl-adapter and explicit production code-queue controlled rollout on D601." },
|
||||
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Dev apply supports backend-core target-side rollout plus frontend/baidu-netdisk artifact consumers; prod apply uses the D601 registry artifact consumer for backend-core, frontend, baidu-netdisk, and decision-center." },
|
||||
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Dev apply supports backend-core target-side rollout plus reviewed artifact consumers for frontend, baidu-netdisk, and decision-center; prod apply uses the D601 registry artifact consumer for backend-core, frontend, baidu-netdisk, and decision-center." },
|
||||
{ name: "--service <id>", description: "Limit reconcile to one service from the manifest." },
|
||||
{ name: "--commit <full-sha>", description: "Prod artifact rollback/apply override for a selected service; the image must already exist in D601 registry." },
|
||||
{ name: "--dry-run", description: "Prepare and validate without mutating the target service." },
|
||||
@@ -662,6 +663,20 @@ function devK3sDeployService(id: string): UniDeskMicroserviceConfig | undefined
|
||||
allowedMethods: ["GET", "HEAD"],
|
||||
allowedPathPrefixes: ["/"],
|
||||
},
|
||||
"decision-center": {
|
||||
name: "UniDesk Dev Decision Center",
|
||||
description: "Isolated dev Decision Center deployed into D601 native k3s namespace unidesk-dev.",
|
||||
dockerfile: "src/components/microservices/decision-center/Dockerfile",
|
||||
composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-decision-center.k8s.yaml",
|
||||
composeService: "decision-center-dev",
|
||||
containerName: "k3s:decision-center-dev",
|
||||
nodeBaseUrl: "k3s://decision-center-dev",
|
||||
nodePort: 4277,
|
||||
healthPath: "/health",
|
||||
route: "/dev/decision-center",
|
||||
allowedMethods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
|
||||
allowedPathPrefixes: ["/", "/api/", "/logs"],
|
||||
},
|
||||
"code-queue": {
|
||||
name: "UniDesk Dev Code Queue",
|
||||
description: "Isolated dev Code Queue execution plane deployed into D601 native k3s namespace unidesk-dev.",
|
||||
@@ -738,7 +753,12 @@ function isCoreDeployService(service: UniDeskMicroserviceConfig): boolean {
|
||||
|
||||
function isDevK3sDeployService(service: UniDeskMicroserviceConfig): boolean {
|
||||
return service.deployment.mode === "k3sctl-managed"
|
||||
&& devApplySupportedServiceIds.has(service.id);
|
||||
&& (devApplySupportedServiceIds.has(service.id) || devArtifactConsumerServiceIds.has(service.id));
|
||||
}
|
||||
|
||||
function isDevArtifactConsumerService(service: UniDeskMicroserviceConfig): boolean {
|
||||
return service.deployment.mode === "k3sctl-managed"
|
||||
&& devArtifactConsumerServiceIds.has(service.id);
|
||||
}
|
||||
|
||||
function isD601MaintenanceDeployBlocked(service: UniDeskMicroserviceConfig): boolean {
|
||||
@@ -1822,6 +1842,12 @@ function healthDeployCommit(body: Record<string, unknown> | null): string | null
|
||||
return commit.length > 0 ? commit : null;
|
||||
}
|
||||
|
||||
function healthDeployRequestedCommit(body: Record<string, unknown> | null): string | null {
|
||||
const deploy = asRecord(body?.deploy);
|
||||
const commit = asString(deploy?.requestedCommit).toLowerCase();
|
||||
return commit.length > 0 ? commit : null;
|
||||
}
|
||||
|
||||
function healthSummary(response: unknown): Record<string, unknown> {
|
||||
const record = asRecord(response);
|
||||
const body = asRecord(record?.body);
|
||||
@@ -1943,23 +1969,29 @@ function commitMatches(actual: string | null, desired: string): boolean {
|
||||
function runtimeCommitVerified(
|
||||
service: UniDeskMicroserviceConfig,
|
||||
healthCommit: string | null,
|
||||
healthRequestedCommit: string | null,
|
||||
imageCommit: string | null,
|
||||
orchestratorCommit: string | null,
|
||||
desired: string,
|
||||
): boolean {
|
||||
if (service.deployment.mode === "k3sctl-managed") return commitMatches(orchestratorCommit, desired);
|
||||
if (service.deployment.mode === "k3sctl-managed") {
|
||||
if (healthRequestedCommit !== null && healthRequestedCommit.length > 0 && !commitMatches(healthRequestedCommit, desired)) return false;
|
||||
return commitMatches(orchestratorCommit, desired);
|
||||
}
|
||||
if (healthCommit !== null && healthCommit.length > 0 && !commitMatches(healthCommit, desired)) return false;
|
||||
if (healthRequestedCommit !== null && healthRequestedCommit.length > 0 && !commitMatches(healthRequestedCommit, desired)) return false;
|
||||
return commitMatches(imageCommit, desired);
|
||||
}
|
||||
|
||||
function runtimeCurrentCommit(
|
||||
service: UniDeskMicroserviceConfig,
|
||||
healthCommit: string | null,
|
||||
healthRequestedCommit: string | null,
|
||||
imageCommit: string | null,
|
||||
orchestratorCommit: string | null,
|
||||
): string | null {
|
||||
if (service.deployment.mode === "k3sctl-managed") return orchestratorCommit ?? imageCommit;
|
||||
return healthCommit ?? imageCommit ?? orchestratorCommit;
|
||||
if (service.deployment.mode === "k3sctl-managed") return orchestratorCommit ?? healthCommit ?? healthRequestedCommit ?? imageCommit;
|
||||
return healthCommit ?? healthRequestedCommit ?? imageCommit ?? orchestratorCommit;
|
||||
}
|
||||
|
||||
function coreBody(response: unknown): Record<string, unknown> | null {
|
||||
@@ -2195,18 +2227,37 @@ async function readK8sCommit(config: UniDeskConfig, service: UniDeskMicroservice
|
||||
return commit.length > 0 ? commit : null;
|
||||
}
|
||||
|
||||
function manifestEnvironmentForService(service: UniDeskMicroserviceConfig): DeployEnvironment {
|
||||
return service.id === "decision-center" && service.deployment.namespace === "unidesk-dev" ? "dev" : "prod";
|
||||
}
|
||||
|
||||
function serviceDeployRef(service: UniDeskMicroserviceConfig): string {
|
||||
return `deploy.json#environments.${manifestEnvironmentForService(service)}.services.${service.id}`;
|
||||
}
|
||||
|
||||
function k3sDeploymentManifestPath(service: UniDeskMicroserviceConfig): string {
|
||||
if (service.id === "decision-center" && service.deployment.namespace === "unidesk-dev") {
|
||||
return "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-decision-center.k8s.yaml";
|
||||
}
|
||||
if (service.id === "decision-center") {
|
||||
return "src/components/microservices/k3sctl-adapter/k3s/decision-center.k8s.yaml";
|
||||
}
|
||||
return k8sManifestPath(service);
|
||||
}
|
||||
|
||||
async function readRuntimeState(config: UniDeskConfig, service: UniDeskMicroserviceConfig, desired: DeployManifestService): Promise<ServiceRuntimeState> {
|
||||
const reason = unsupportedReason(service);
|
||||
const health = await serviceHealth(config, service);
|
||||
const healthBody = coreBody(health);
|
||||
const healthCommit = healthDeployCommit(healthBody);
|
||||
const healthRequestedCommit = healthDeployRequestedCommit(healthBody);
|
||||
const healthRecord = asRecord(health);
|
||||
const healthOk = healthRecord?.ok === true && healthBody?.ok !== false;
|
||||
const [imageCommit, orchestratorCommit] = await Promise.all([
|
||||
readDockerImageCommit(config, service).catch(() => null),
|
||||
readK8sCommit(config, service).catch(() => null),
|
||||
]);
|
||||
const currentCommit = runtimeCurrentCommit(service, healthCommit, imageCommit, orchestratorCommit);
|
||||
const currentCommit = runtimeCurrentCommit(service, healthCommit, healthRequestedCommit, imageCommit, orchestratorCommit);
|
||||
return {
|
||||
serviceId: service.id,
|
||||
ok: reason === null,
|
||||
@@ -2218,10 +2269,11 @@ async function readRuntimeState(config: UniDeskConfig, service: UniDeskMicroserv
|
||||
deploymentMode: service.deployment.mode,
|
||||
currentCommit,
|
||||
healthCommit,
|
||||
healthRequestedCommit,
|
||||
imageCommit,
|
||||
orchestratorCommit,
|
||||
healthOk,
|
||||
upToDate: reason === null && healthOk && runtimeCommitVerified(service, healthCommit, imageCommit, orchestratorCommit, desired.commitId),
|
||||
upToDate: reason === null && healthOk && runtimeCommitVerified(service, healthCommit, healthRequestedCommit, imageCommit, orchestratorCommit, desired.commitId),
|
||||
raw: { health: healthSummary(health) },
|
||||
};
|
||||
}
|
||||
@@ -2233,9 +2285,9 @@ async function healthVerify(config: UniDeskConfig, service: UniDeskMicroserviceC
|
||||
let latest: ServiceRuntimeState | null = null;
|
||||
while (Date.now() < deadline) {
|
||||
latest = await readRuntimeState(config, service, { ...desired, commitId: resolvedCommit });
|
||||
const commitOk = runtimeCommitVerified(service, latest.healthCommit, latest.imageCommit, latest.orchestratorCommit, resolvedCommit);
|
||||
const commitOk = runtimeCommitVerified(service, latest.healthCommit, latest.healthRequestedCommit, latest.imageCommit, latest.orchestratorCommit, resolvedCommit);
|
||||
const ok = latest.healthOk && commitOk;
|
||||
progressLine("live-health", "probe", { serviceId: service.id, ok, healthOk: latest.healthOk, expectedCommit: resolvedCommit, healthCommit: latest.healthCommit, imageCommit: latest.imageCommit, orchestratorCommit: latest.orchestratorCommit });
|
||||
progressLine("live-health", "probe", { serviceId: service.id, ok, healthOk: latest.healthOk, expectedCommit: resolvedCommit, healthCommit: latest.healthCommit, healthRequestedCommit: latest.healthRequestedCommit, imageCommit: latest.imageCommit, orchestratorCommit: latest.orchestratorCommit });
|
||||
if (ok) {
|
||||
return {
|
||||
step: "live-health",
|
||||
@@ -2297,23 +2349,75 @@ async function ensureGithubSshIdentityStep(config: UniDeskConfig, service: UniDe
|
||||
}
|
||||
}
|
||||
|
||||
async function runDevArtifactConsumerService(
|
||||
service: UniDeskMicroserviceConfig,
|
||||
desired: DeployManifestService,
|
||||
options: DeployOptions,
|
||||
before: ServiceRuntimeState,
|
||||
): Promise<Record<string, unknown>> {
|
||||
const commit = options.commitOverride ?? desired.commitId;
|
||||
const artifactArgs = [
|
||||
"deploy-service",
|
||||
"--env", "dev",
|
||||
"--service", service.id,
|
||||
"--commit", commit,
|
||||
"--source-repo", desired.repo,
|
||||
"--timeout-ms", String(options.timeoutMs),
|
||||
"--run-now",
|
||||
...(options.dryRun ? ["--dry-run"] : []),
|
||||
];
|
||||
progressLine("dev-artifact-cd", options.dryRun ? "dry-run" : "reconcile", { serviceId: service.id, commit });
|
||||
const result = await runArtifactRegistryCommand(artifactArgs);
|
||||
const ok = asRecord(result)?.ok === true;
|
||||
return {
|
||||
ok,
|
||||
action: ok ? "deployed" : "failed",
|
||||
environment: "dev",
|
||||
executor: "d601-registry-artifact-consumer",
|
||||
resolvedCommit: commit,
|
||||
before,
|
||||
after: result,
|
||||
steps: [{
|
||||
step: "artifact-registry-consumer",
|
||||
ok,
|
||||
detail: JSON.stringify(result),
|
||||
startedAt: nowIso(),
|
||||
finishedAt: nowIso(),
|
||||
raw: result,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
async function applyOneService(config: UniDeskConfig, service: UniDeskMicroserviceConfig, desired: DeployManifestService, options: DeployOptions): Promise<Record<string, unknown>> {
|
||||
const steps: StepResult[] = [];
|
||||
const startedAt = nowIso();
|
||||
const reason = unsupportedReason(service);
|
||||
if (reason !== null) return { ok: false, serviceId: service.id, skipped: true, reason, steps };
|
||||
const before = await readRuntimeState(config, service, desired);
|
||||
if (!options.force && before.upToDate) return { ok: true, serviceId: service.id, action: "noop", before, steps };
|
||||
|
||||
if (manifestEnvironmentForService(service) === "dev" && isDevArtifactConsumerService(service)) {
|
||||
const artifact = await runDevArtifactConsumerService(service, desired, options, before);
|
||||
return {
|
||||
...artifact,
|
||||
before,
|
||||
serviceId: service.id,
|
||||
startedAt,
|
||||
finishedAt: nowIso(),
|
||||
};
|
||||
}
|
||||
|
||||
if (options.dryRun) return { ok: true, serviceId: service.id, action: "would-deploy", before, steps };
|
||||
|
||||
if (!options.dryRun && isD601MaintenanceDeployBlocked(service)) {
|
||||
return {
|
||||
ok: false,
|
||||
serviceId: service.id,
|
||||
skipped: true,
|
||||
reason: `D601 target-side deployment is allowed only for k3sctl-adapter and dev backend-core; ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`,
|
||||
reason: `D601 target-side deployment is allowed only for k3sctl-adapter and dev backend-core; artifact consumers must use registry CD. ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`,
|
||||
steps,
|
||||
};
|
||||
}
|
||||
const reason = unsupportedReason(service);
|
||||
if (reason !== null) return { ok: false, serviceId: service.id, skipped: true, reason, steps };
|
||||
const before = await readRuntimeState(config, service, desired);
|
||||
if (!options.force && before.upToDate) return { ok: true, serviceId: service.id, action: "noop", before, steps };
|
||||
if (options.dryRun) return { ok: true, serviceId: service.id, action: "would-deploy", before, steps };
|
||||
|
||||
const runId = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
|
||||
const exportDir = targetExportDir(service, runId);
|
||||
@@ -2438,6 +2542,8 @@ function environmentDryRunPlan(
|
||||
commitId: options.commitOverride !== null && service.id === options.serviceId ? options.commitOverride : service.commitId,
|
||||
}));
|
||||
const fingerprint = databaseFingerprint(target);
|
||||
const devUnsupported = devUnsupportedServices(manifest, options.serviceId);
|
||||
const prodArtifactUnsupported = prodArtifactUnsupportedServices(manifest, options.serviceId);
|
||||
return {
|
||||
ok: environment === "prod"
|
||||
? services.every((service) => prodArtifactConsumerServiceIds.has(service.id))
|
||||
@@ -2480,7 +2586,7 @@ function environmentDryRunPlan(
|
||||
? "d601-registry-artifact-consumer"
|
||||
: "unsupported"
|
||||
: devArtifactConsumerServiceIds.has(service.id)
|
||||
? service.id === "frontend" ? "d601-registry-artifact-consumer" : "d601-registry-artifact-consumer-dev-validation"
|
||||
? "d601-registry-artifact-consumer"
|
||||
: devApplySupportedServiceIds.has(service.id)
|
||||
? "d601-dev-target-side-build"
|
||||
: "unsupported",
|
||||
@@ -2493,8 +2599,15 @@ function environmentDryRunPlan(
|
||||
? "No standardized prod D601 registry artifact consumer is implemented for this service; legacy maintenance-channel deployment is not allowed."
|
||||
: "No standardized dev deployment or artifact validation path is implemented for this service.",
|
||||
}
|
||||
: environment === "dev" && !devApplySupportedServiceIds.has(service.id) && !devArtifactConsumerServiceIds.has(service.id)
|
||||
? {
|
||||
ok: false,
|
||||
supported: false,
|
||||
reason: "No standardized dev D601 registry artifact consumer is implemented for this service; legacy maintenance-channel deployment is not allowed.",
|
||||
}
|
||||
: undefined,
|
||||
})),
|
||||
unsupported: environment === "prod" ? prodArtifactUnsupported : devUnsupported,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2512,7 +2625,7 @@ function blockedD601MaintenanceDeployServices(config: UniDeskConfig, manifest: D
|
||||
}
|
||||
|
||||
function d601MaintenanceDeployBlockMessage(blocked: string[]): string {
|
||||
return `D601 target-side deployment is enabled only for k3sctl-adapter and dev backend-core; blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`;
|
||||
return `D601 target-side deployment is enabled only for k3sctl-adapter and dev backend-core; artifact consumers must use registry CD. Blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`;
|
||||
}
|
||||
|
||||
function selectedDevArtifactServices(manifest: DeployManifest, serviceId: string | null): DeployManifestService[] {
|
||||
@@ -2532,6 +2645,11 @@ function selectedEnvironmentServices(manifest: DeployManifest, serviceId: string
|
||||
return services;
|
||||
}
|
||||
|
||||
function devUnsupportedServices(manifest: DeployManifest, serviceId: string | null): DeployManifestService[] {
|
||||
if (manifest.environment !== "dev") return [];
|
||||
return selectedEnvironmentServices(manifest, serviceId).filter((service) => !devApplySupportedServiceIds.has(service.id) && !devArtifactConsumerServiceIds.has(service.id));
|
||||
}
|
||||
|
||||
function prodArtifactUnsupportedServices(manifest: DeployManifest, serviceId: string | null): DeployManifestService[] {
|
||||
return selectedEnvironmentServices(manifest, serviceId).filter((service) => !prodArtifactConsumerServiceIds.has(service.id));
|
||||
}
|
||||
@@ -2569,7 +2687,7 @@ async function runArtifactConsumerApplyNow(
|
||||
"--commit", commit,
|
||||
"--source-repo", service.repo,
|
||||
"--deploy-ref", `${deployEnvironmentTargets[environment].gitRef}:deploy.json#environments.${environment}.services.${service.id}`,
|
||||
...(environment === "prod" || service.id === "frontend" ? ["--env", environment] : []),
|
||||
...(environment === "prod" || service.id === "frontend" || service.id === "decision-center" ? ["--env", environment] : []),
|
||||
"--timeout-ms", String(options.timeoutMs),
|
||||
"--run-now",
|
||||
...(options.dryRun ? ["--dry-run"] : []),
|
||||
@@ -2690,7 +2808,7 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
|
||||
}
|
||||
const unsupported = unsupportedDevApplyServices(manifest, options.serviceId);
|
||||
if (unsupported.length > 0) {
|
||||
throw new Error(`deploy apply --env dev currently supports backend-core target-side rollout plus frontend/baidu-netdisk artifact consumers; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for smoke verification.`);
|
||||
throw new Error(`deploy apply --env dev currently supports backend-core target-side rollout plus frontend/baidu-netdisk/decision-center artifact consumers; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for smoke verification.`);
|
||||
}
|
||||
const devArtifactServices = selectedDevArtifactServices(manifest, options.serviceId);
|
||||
const devTargetServices = selectedDevTargetServices(manifest, options.serviceId);
|
||||
@@ -2703,11 +2821,11 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
|
||||
}
|
||||
if (config === null) throw new Error("deploy apply --env dev requires config.json");
|
||||
if (!options.dryRun) {
|
||||
const blocked = blockedD601MaintenanceDeployServices(config, manifest, options.serviceId);
|
||||
const blocked = blockedD601MaintenanceDeployServices(config, manifest, options.serviceId).filter((serviceId) => serviceId !== "decision-center");
|
||||
if (blocked.length > 0) throw new Error(d601MaintenanceDeployBlockMessage(blocked));
|
||||
}
|
||||
if (!options.runNow) return applyJob(config, args, options);
|
||||
return await runApplyNow(config, manifest, options);
|
||||
if (options.dryRun || options.runNow) return await runApplyNow(config, manifest, options);
|
||||
return applyJob(config, args, options);
|
||||
}
|
||||
if (config === null) throw new Error("deploy local manifest mode requires config.json");
|
||||
const manifest = resolveManifestCommits(await readDeployManifest(options.file), options.serviceId);
|
||||
@@ -2716,8 +2834,8 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
|
||||
const blocked = blockedD601MaintenanceDeployServices(config, manifest, options.serviceId);
|
||||
if (blocked.length > 0) throw new Error(d601MaintenanceDeployBlockMessage(blocked));
|
||||
}
|
||||
if (!options.runNow) return applyJob(config, args, options);
|
||||
return await runApplyNow(config, manifest, options);
|
||||
if (options.dryRun || options.runNow) return await runApplyNow(config, manifest, options);
|
||||
return applyJob(config, args, options);
|
||||
}
|
||||
|
||||
export async function runCodeQueueDeployCompatCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {
|
||||
|
||||
+2
-2
@@ -36,9 +36,9 @@ export function rootHelp(): unknown {
|
||||
{ command: "decision list [--type ...] [--status ...] [--level ...] [--linked-goal-id id] [--limit N]", description: "List Decision Center records through the user-service proxy." },
|
||||
{ command: "decision requirement list|upsert [--id id] [--title text] [--body-file path] [--type goal|decision|blocker|debt|experiment]", description: "Manage requirement records over the existing records model, excluding meeting records." },
|
||||
{ command: "decision show <id>", description: "Show one Decision Center record." },
|
||||
{ command: "deploy check|plan|apply [--file deploy.json|--env dev|prod] [--service id] [--commit full-sha] [--dry-run] [--force]", description: "Reconcile services from a repo+commit manifest; --env reads origin/master:deploy.json environments and can apply supported dev services or supported prod artifact consumers." },
|
||||
{ command: "deploy check|plan|apply [--file deploy.json|--env dev|prod] [--service id] [--commit full-sha] [--dry-run] [--force]", description: "Reconcile services from a repo+commit manifest; --env reads origin/master:deploy.json environments and can apply supported dev target-side builds plus decision-center's dev artifact consumer, or supported prod artifact consumers." },
|
||||
{ command: "dev-env validate|prewarm-images", description: "Validate D601 unidesk-dev guardrails or prewarm dev foundation images into native k3s containerd through a bounded async job." },
|
||||
{ command: "artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service", description: "Manage the D601 host-managed CNCF Distribution registry and run pull-only artifact CD for supported services." },
|
||||
{ command: "artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service", description: "Manage the D601 host-managed CNCF Distribution registry and run pull-only artifact CD for supported services, including decision-center dev/prod artifact consumers." },
|
||||
{ command: "schedule list|get|runs|run|delete", description: "Manage backend-core scheduled tasks and run history; schedule run <id> supports --wait-ms N." },
|
||||
{ command: "schedule upsert-pgdata-backup [--time HH:MM] [--remote-base /SERVER_DATA/UNIDESK_PG_DATA]", description: "Create or update the daily PGDATA physical backup task that uploads monthly rotated archives to Baidu Netdisk." },
|
||||
{ command: "codex deploy <commitId> [--provider-id D601] [--timeout-ms N]", description: "Compatibility wrapper for deploy apply --service code-queue with a temporary repo+commit manifest." },
|
||||
|
||||
Reference in New Issue
Block a user