feat: expand ci artifact catalog
This commit is contained in:
+170
-72
@@ -1,8 +1,9 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { posix as posixPath } from "node:path";
|
||||
import { blockedCatalogArtifactIds, catalogSummary, findCiCatalogArtifact, loadCiCatalog, supportedSourceBuildArtifactIds, type CiCatalogArtifact, type CiSourceBuildCatalogArtifact, type CiUpstreamImageCatalogArtifact } from "./ci-catalog";
|
||||
import { runCommand } from "./command";
|
||||
import { type UniDeskConfig, type UniDeskMicroserviceConfig, repoRoot, rootPath } from "./config";
|
||||
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
|
||||
import { ensureGithubSshIdentityForProvider, gitSshHttpConnectProxySource } from "./deploy-ssh-identity";
|
||||
import { startJob } from "./jobs";
|
||||
import { coreInternalFetch } from "./microservices";
|
||||
@@ -29,14 +30,6 @@ const ciRuntimeImages = [
|
||||
"alpine/git:2.45.2",
|
||||
ciCodeQueueImage,
|
||||
];
|
||||
const publishUserServiceArtifactAllowedServiceIds = new Set([
|
||||
"baidu-netdisk",
|
||||
"code-queue-mgr",
|
||||
"decision-center",
|
||||
"frontend",
|
||||
"oa-event-flow",
|
||||
"project-manager",
|
||||
]);
|
||||
|
||||
interface CiOptions {
|
||||
repoUrl: string;
|
||||
@@ -49,6 +42,8 @@ interface CiPublishBackendCoreOptions {
|
||||
commit: string;
|
||||
waitMs: number;
|
||||
sourceHostPath: string;
|
||||
dockerfile: string;
|
||||
imageRepository: string;
|
||||
dryRun: boolean;
|
||||
}
|
||||
|
||||
@@ -59,6 +54,7 @@ interface CiPublishUserServiceArtifactOptions {
|
||||
sourceHostPath: string;
|
||||
serviceId: string;
|
||||
dockerfile: string;
|
||||
imageRepository: string;
|
||||
dryRun: boolean;
|
||||
}
|
||||
|
||||
@@ -130,6 +126,7 @@ interface ArtifactSummaryContext {
|
||||
commit: string;
|
||||
repoUrl: string;
|
||||
dockerfile: string;
|
||||
imageRepository: string;
|
||||
}
|
||||
|
||||
function stringOption(args: string[], name: string): string | null {
|
||||
@@ -188,6 +185,10 @@ function shellQuote(value: string): string {
|
||||
return `'${value.replace(/'/gu, "'\\''")}'`;
|
||||
}
|
||||
|
||||
function safePathToken(value: string): string {
|
||||
return value.replace(/[^a-z0-9._-]/giu, "-").toLowerCase().replace(/^-+|-+$/gu, "").slice(0, 80) || "artifact";
|
||||
}
|
||||
|
||||
function repoSshUrl(repoUrl: string): string {
|
||||
if (repoUrl.startsWith("git@")) return repoUrl;
|
||||
if (repoUrl.startsWith("https://github.com/")) {
|
||||
@@ -197,6 +198,10 @@ function repoSshUrl(repoUrl: string): string {
|
||||
return repoUrl;
|
||||
}
|
||||
|
||||
function repoNeedsGithubSshIdentity(repoFetchUrl: string): boolean {
|
||||
return repoFetchUrl.startsWith("git@github.com:");
|
||||
}
|
||||
|
||||
function requireRepoRelativePath(path: string, label: string): string {
|
||||
if (path.length === 0 || path.startsWith("/") || path.includes("\0") || path.split("/").includes("..")) {
|
||||
throw new Error(`${label} must be a repo-relative path`);
|
||||
@@ -206,35 +211,45 @@ function requireRepoRelativePath(path: string, label: string): string {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function requireSupportedUserService(config: UniDeskConfig, serviceId: string): UniDeskMicroserviceConfig {
|
||||
if (serviceId === "backend-core") {
|
||||
throw new Error("backend-core uses ci publish-backend-core; publish-user-service is for registered user services");
|
||||
function resolveCatalogArtifact(serviceId: string): CiCatalogArtifact {
|
||||
const artifact = findCiCatalogArtifact(serviceId);
|
||||
if (artifact === null) {
|
||||
const known = loadCiCatalog().artifacts.map((item) => item.serviceId).sort().join(", ");
|
||||
throw new Error(`unknown CI artifact service: ${serviceId}. Known services: ${known}`);
|
||||
}
|
||||
const service = config.microservices.find((item) => item.id === serviceId);
|
||||
if (service === undefined) throw new Error(`unknown user service: ${serviceId}`);
|
||||
if (service.repository.artifactSource?.kind === "upstream-image") {
|
||||
throw new Error(`ci publish-user-service does not build ${serviceId}: it is an upstream image consumer (${service.repository.artifactSource.imageRef}). Use the upstream-image digest/mirror governance path; do not add it to Dockerfile CI artifacts.`);
|
||||
}
|
||||
const isD601K3sService = service.providerId === d601ProviderId
|
||||
&& service.development.providerId === d601ProviderId
|
||||
&& service.deployment.mode === "k3sctl-managed";
|
||||
const isMainServerDirectService = service.providerId === "main-server"
|
||||
&& service.development.providerId === "main-server"
|
||||
&& service.deployment.mode === "unidesk-direct";
|
||||
const isMainServerInternalSidecar = service.providerId === "main-server"
|
||||
&& service.development.providerId === "main-server"
|
||||
&& service.deployment.mode === "internal-sidecar";
|
||||
if (!isD601K3sService && !isMainServerDirectService && !isMainServerInternalSidecar) {
|
||||
throw new Error(`ci publish-user-service supports only reviewed k3sctl-managed D601 services or main-server unidesk-direct services; ${serviceId} is ${service.providerId}/${service.deployment.mode}`);
|
||||
}
|
||||
return service;
|
||||
return artifact;
|
||||
}
|
||||
|
||||
function frontendArtifactTarget(repoOverride: string | null): { repoUrl: string; dockerfile: string } {
|
||||
return {
|
||||
repoUrl: repoOverride ?? "https://github.com/pikasTech/unidesk",
|
||||
dockerfile: "src/components/frontend/Dockerfile",
|
||||
function blockedArtifactResult(artifact: CiUpstreamImageCatalogArtifact | CiSourceBuildCatalogArtifact, commit: string, note: string): Record<string, unknown> {
|
||||
const base = {
|
||||
ok: false,
|
||||
status: "blocked",
|
||||
error: "blocked",
|
||||
serviceId: artifact.serviceId,
|
||||
commit,
|
||||
reason: note,
|
||||
catalogArtifact: artifact,
|
||||
boundary: "CI catalog marks this service as blocked; it must not be treated as a source-build artifact producer",
|
||||
};
|
||||
return artifact.kind === "upstream-image"
|
||||
? {
|
||||
...base,
|
||||
upstream: artifact.upstream,
|
||||
next: [
|
||||
`document the upstream image contract in CI.json for ${artifact.serviceId}`,
|
||||
],
|
||||
}
|
||||
: {
|
||||
...base,
|
||||
next: [
|
||||
`unblock ${artifact.serviceId} in CI.json before attempting source-build publication`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function blockedReason(artifact: CiSourceBuildCatalogArtifact): string {
|
||||
if (artifact.blockedReason === undefined) throw new Error(`${artifact.serviceId} is blocked in CI.json but has no blockedReason`);
|
||||
return artifact.blockedReason;
|
||||
}
|
||||
|
||||
function chunks(value: string, size: number): string[] {
|
||||
@@ -621,6 +636,10 @@ spec:
|
||||
value: ${JSON.stringify(options.repoUrl)}
|
||||
- name: revision
|
||||
value: ${JSON.stringify(options.commit)}
|
||||
- name: dockerfile
|
||||
value: ${JSON.stringify(options.dockerfile)}
|
||||
- name: image-repository
|
||||
value: ${JSON.stringify(options.imageRepository)}
|
||||
- name: source-host-path
|
||||
value: ${JSON.stringify(options.sourceHostPath)}
|
||||
workspaces:
|
||||
@@ -656,6 +675,8 @@ spec:
|
||||
value: ${JSON.stringify(options.serviceId)}
|
||||
- name: dockerfile
|
||||
value: ${JSON.stringify(options.dockerfile)}
|
||||
- name: image-repository
|
||||
value: ${JSON.stringify(options.imageRepository)}
|
||||
- name: source-host-path
|
||||
value: ${JSON.stringify(options.sourceHostPath)}
|
||||
workspaces:
|
||||
@@ -674,18 +695,20 @@ function userServiceArtifactSourceHostPath(serviceId: string, commit: string): s
|
||||
}
|
||||
|
||||
async function prepareBackendCoreArtifactSource(config: UniDeskConfig, options: CiPublishBackendCoreOptions): Promise<Record<string, unknown>> {
|
||||
const sshIdentity = await ensureGithubSshIdentityForProvider(config, d601ProviderId);
|
||||
if (!sshIdentity.ok) throw new Error(sshIdentity.detail);
|
||||
const proxyPython = gitSshHttpConnectProxySource();
|
||||
const sourceRoot = "/home/ubuntu/.unidesk/ci/backend-core-artifacts";
|
||||
const sourceHostPath = options.sourceHostPath;
|
||||
const repoCache = "/home/ubuntu/.unidesk/ci/git/unidesk.git";
|
||||
const repoFetchUrl = repoSshUrl(options.repoUrl);
|
||||
const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, d601ProviderId) : null;
|
||||
if (sshIdentity !== null && !sshIdentity.ok) throw new Error(sshIdentity.detail);
|
||||
const proxyPython = gitSshHttpConnectProxySource();
|
||||
const dockerfile = requireRepoRelativePath(options.dockerfile, "CI.json.artifacts.backend-core.source.dockerfile");
|
||||
const script = [
|
||||
"set -euo pipefail",
|
||||
`commit=${shellQuote(options.commit)}`,
|
||||
`repo_url=${shellQuote(options.repoUrl)}`,
|
||||
`repo_fetch_url=${shellQuote(repoFetchUrl)}`,
|
||||
`dockerfile=${shellQuote(dockerfile)}`,
|
||||
`source_root=${shellQuote(sourceRoot)}`,
|
||||
`source_dir=${shellQuote(sourceHostPath)}`,
|
||||
`repo_cache=${shellQuote(repoCache)}`,
|
||||
@@ -708,7 +731,7 @@ async function prepareBackendCoreArtifactSource(config: UniDeskConfig, options:
|
||||
"git -C \"$repo_cache\" fetch --no-tags origin \"$commit\" || git -C \"$repo_cache\" fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'",
|
||||
"resolved=$(git -C \"$repo_cache\" rev-parse --verify \"$commit^{commit}\")",
|
||||
"test \"$resolved\" = \"$commit\" || { echo \"backend_core_artifact_resolved_commit_mismatch=$resolved expected=$commit\" >&2; exit 1; }",
|
||||
"git -C \"$repo_cache\" cat-file -e \"$commit:src/components/backend-core/Dockerfile\"",
|
||||
"git -C \"$repo_cache\" cat-file -e \"$commit:$dockerfile\"",
|
||||
"git -C \"$repo_cache\" cat-file -e \"$commit:src/components/backend-core/src\"",
|
||||
"tmp_dir=\"$source_root/.tmp-$commit-$$\"",
|
||||
"rm -rf \"$tmp_dir\"",
|
||||
@@ -718,7 +741,7 @@ async function prepareBackendCoreArtifactSource(config: UniDeskConfig, options:
|
||||
"printf '%s\\n' \"$repo_url\" > \"$tmp_dir/.unidesk-source-repo\"",
|
||||
"rm -rf \"$source_dir\"",
|
||||
"mv \"$tmp_dir\" \"$source_dir\"",
|
||||
"test -f \"$source_dir/src/components/backend-core/Dockerfile\"",
|
||||
"test -f \"$source_dir/$dockerfile\"",
|
||||
"test -d \"$source_dir/src/components/backend-core/src\"",
|
||||
"echo backend_core_artifact_source_host_path=$source_dir",
|
||||
].join("\n");
|
||||
@@ -726,13 +749,14 @@ async function prepareBackendCoreArtifactSource(config: UniDeskConfig, options:
|
||||
if (!result.ok) throw new Error(`failed to prepare backend-core source on D601: ${result.stderr || result.stdout || JSON.stringify(result.raw)}`);
|
||||
return {
|
||||
ok: true,
|
||||
mode: "d601-host-github-ssh-export",
|
||||
mode: repoNeedsGithubSshIdentity(repoFetchUrl) ? "d601-host-git-ssh-export" : "d601-host-git-https-export",
|
||||
providerId: d601ProviderId,
|
||||
repoUrl: options.repoUrl,
|
||||
repoFetchUrl,
|
||||
commit: options.commit,
|
||||
sourceHostPath,
|
||||
identity: {
|
||||
dockerfile,
|
||||
identity: sshIdentity === null ? null : {
|
||||
fingerprint: sshIdentity.fingerprint,
|
||||
seededFromLocal: sshIdentity.seededFromLocal,
|
||||
},
|
||||
@@ -741,14 +765,13 @@ async function prepareBackendCoreArtifactSource(config: UniDeskConfig, options:
|
||||
}
|
||||
|
||||
async function prepareUserServiceArtifactSource(config: UniDeskConfig, options: CiPublishUserServiceArtifactOptions): Promise<Record<string, unknown>> {
|
||||
const sshIdentity = await ensureGithubSshIdentityForProvider(config, d601ProviderId);
|
||||
if (!sshIdentity.ok) throw new Error(sshIdentity.detail);
|
||||
const proxyPython = gitSshHttpConnectProxySource();
|
||||
const sourceRoot = `/home/ubuntu/.unidesk/ci/user-service-artifacts/${options.serviceId}`;
|
||||
const sourceHostPath = options.sourceHostPath;
|
||||
const repoCache = "/home/ubuntu/.unidesk/ci/git/unidesk.git";
|
||||
const repoCache = `/home/ubuntu/.unidesk/ci/git/${safePathToken(options.serviceId)}.git`;
|
||||
const repoFetchUrl = repoSshUrl(options.repoUrl);
|
||||
const dockerfileDir = posixPath.dirname(options.dockerfile);
|
||||
const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, d601ProviderId) : null;
|
||||
if (sshIdentity !== null && !sshIdentity.ok) throw new Error(sshIdentity.detail);
|
||||
const proxyPython = gitSshHttpConnectProxySource();
|
||||
const script = [
|
||||
"set -euo pipefail",
|
||||
`service_id=${shellQuote(options.serviceId)}`,
|
||||
@@ -756,7 +779,6 @@ async function prepareUserServiceArtifactSource(config: UniDeskConfig, options:
|
||||
`repo_url=${shellQuote(options.repoUrl)}`,
|
||||
`repo_fetch_url=${shellQuote(repoFetchUrl)}`,
|
||||
`dockerfile=${shellQuote(options.dockerfile)}`,
|
||||
`dockerfile_dir=${shellQuote(dockerfileDir)}`,
|
||||
`source_root=${shellQuote(sourceRoot)}`,
|
||||
`source_dir=${shellQuote(sourceHostPath)}`,
|
||||
`repo_cache=${shellQuote(repoCache)}`,
|
||||
@@ -781,7 +803,6 @@ async function prepareUserServiceArtifactSource(config: UniDeskConfig, options:
|
||||
"resolved=$(git -C \"$repo_cache\" rev-parse --verify \"$commit^{commit}\")",
|
||||
"test \"$resolved\" = \"$commit\" || { echo \"user_service_artifact_resolved_commit_mismatch=$resolved expected=$commit\" >&2; exit 1; }",
|
||||
"git -C \"$repo_cache\" cat-file -e \"$commit:$dockerfile\"",
|
||||
"git -C \"$repo_cache\" cat-file -e \"$commit:$dockerfile_dir/src\"",
|
||||
"tmp_dir=\"$source_root/.tmp-$commit-$$\"",
|
||||
"rm -rf \"$tmp_dir\"",
|
||||
"mkdir -p \"$tmp_dir\"",
|
||||
@@ -793,14 +814,13 @@ async function prepareUserServiceArtifactSource(config: UniDeskConfig, options:
|
||||
"rm -rf \"$source_dir\"",
|
||||
"mv \"$tmp_dir\" \"$source_dir\"",
|
||||
"test -f \"$source_dir/$dockerfile\"",
|
||||
"test -d \"$source_dir/$dockerfile_dir/src\"",
|
||||
"echo user_service_artifact_source_host_path=$source_dir",
|
||||
].join("\n");
|
||||
const result = await runRemoteBackground(`prepare-${options.serviceId}-source`, script, 300_000);
|
||||
if (!result.ok) throw new Error(`failed to prepare ${options.serviceId} source on D601: ${result.stderr || result.stdout || JSON.stringify(result.raw)}`);
|
||||
return {
|
||||
ok: true,
|
||||
mode: "d601-host-github-ssh-export",
|
||||
mode: repoNeedsGithubSshIdentity(repoFetchUrl) ? "d601-host-git-ssh-export" : "d601-host-git-https-export",
|
||||
providerId: d601ProviderId,
|
||||
repoUrl: options.repoUrl,
|
||||
repoFetchUrl,
|
||||
@@ -808,7 +828,7 @@ async function prepareUserServiceArtifactSource(config: UniDeskConfig, options:
|
||||
serviceId: options.serviceId,
|
||||
dockerfile: options.dockerfile,
|
||||
sourceHostPath,
|
||||
identity: {
|
||||
identity: sshIdentity === null ? null : {
|
||||
fingerprint: sshIdentity.fingerprint,
|
||||
seededFromLocal: sshIdentity.seededFromLocal,
|
||||
},
|
||||
@@ -894,7 +914,7 @@ function pipelineRunWaitSucceeded(wait: DispatchResult | null, condition: Pipeli
|
||||
|
||||
function artifactSummaryDefaults(context: ArtifactSummaryContext): ArtifactSummary {
|
||||
const registry = "127.0.0.1:5000";
|
||||
const repository = `${registry}/unidesk/${context.serviceId}`;
|
||||
const repository = `${registry}/${context.imageRepository}`;
|
||||
return {
|
||||
serviceId: context.serviceId,
|
||||
sourceCommit: context.commit,
|
||||
@@ -917,7 +937,7 @@ function artifactSummaryField(fields: Map<string, string>, suffix: string): stri
|
||||
function parseArtifactSummaryFromFields(fields: Map<string, string>, context: ArtifactSummaryContext): ArtifactSummary {
|
||||
const planned = artifactSummaryDefaults(context);
|
||||
const registry = artifactSummaryField(fields, "registry") ?? planned.registry;
|
||||
const repository = artifactSummaryField(fields, "repository") ?? `${registry}/unidesk/${context.serviceId}`;
|
||||
const repository = artifactSummaryField(fields, "repository") ?? planned.repository;
|
||||
const tag = artifactSummaryField(fields, "tag") ?? planned.tag;
|
||||
const imageRef = artifactSummaryField(fields, "image") ?? (repository.length > 0 && tag.length > 0 ? `${repository}:${tag}` : planned.imageRef);
|
||||
const digest = artifactSummaryField(fields, "digest");
|
||||
@@ -1075,9 +1095,10 @@ async function run(options: CiOptions): Promise<Record<string, unknown>> {
|
||||
async function publishBackendCoreArtifact(config: UniDeskConfig, options: CiPublishBackendCoreOptions): Promise<Record<string, unknown>> {
|
||||
const summaryContext: ArtifactSummaryContext = {
|
||||
serviceId: "backend-core",
|
||||
dockerfile: "src/components/backend-core/Dockerfile",
|
||||
commit: options.commit,
|
||||
repoUrl: options.repoUrl,
|
||||
dockerfile: options.dockerfile,
|
||||
imageRepository: options.imageRepository,
|
||||
};
|
||||
const plannedArtifact = artifactSummaryDefaults(summaryContext);
|
||||
if (options.dryRun) {
|
||||
@@ -1096,7 +1117,8 @@ async function publishBackendCoreArtifact(config: UniDeskConfig, options: CiPubl
|
||||
repoUrl: options.repoUrl,
|
||||
repoFetchUrl: repoSshUrl(options.repoUrl),
|
||||
commit: options.commit,
|
||||
dockerfile: "src/components/backend-core/Dockerfile",
|
||||
dockerfile: options.dockerfile,
|
||||
imageRepository: options.imageRepository,
|
||||
sourceHostPath: options.sourceHostPath,
|
||||
},
|
||||
artifact: plannedArtifact.imageRef,
|
||||
@@ -1148,6 +1170,7 @@ async function publishUserServiceArtifact(config: UniDeskConfig, options: CiPubl
|
||||
dockerfile: options.dockerfile,
|
||||
commit: options.commit,
|
||||
repoUrl: options.repoUrl,
|
||||
imageRepository: options.imageRepository,
|
||||
};
|
||||
const plannedArtifact = artifactSummaryDefaults(summaryContext);
|
||||
if (options.dryRun) {
|
||||
@@ -1169,6 +1192,7 @@ async function publishUserServiceArtifact(config: UniDeskConfig, options: CiPubl
|
||||
commit: options.commit,
|
||||
serviceId: options.serviceId,
|
||||
dockerfile: options.dockerfile,
|
||||
imageRepository: options.imageRepository,
|
||||
sourceHostPath: options.sourceHostPath,
|
||||
},
|
||||
artifact: plannedArtifact.imageRef,
|
||||
@@ -1461,7 +1485,32 @@ async function logs(name: string): Promise<Record<string, unknown>> {
|
||||
};
|
||||
}
|
||||
|
||||
function catalogArtifactDescriptor(artifact: CiCatalogArtifact): Record<string, unknown> {
|
||||
if (artifact.kind === "source-build") {
|
||||
return {
|
||||
serviceId: artifact.serviceId,
|
||||
kind: artifact.kind,
|
||||
status: artifact.status,
|
||||
producer: artifact.producer,
|
||||
source: artifact.source,
|
||||
image: artifact.image,
|
||||
...(artifact.notes === undefined ? {} : { notes: artifact.notes }),
|
||||
...(artifact.blockedReason === undefined ? {} : { blockedReason: artifact.blockedReason }),
|
||||
};
|
||||
}
|
||||
return {
|
||||
serviceId: artifact.serviceId,
|
||||
kind: artifact.kind,
|
||||
status: artifact.status,
|
||||
producer: artifact.producer,
|
||||
upstream: artifact.upstream,
|
||||
blockedReason: artifact.blockedReason,
|
||||
...(artifact.notes === undefined ? {} : { notes: artifact.notes }),
|
||||
};
|
||||
}
|
||||
|
||||
export function ciHelp(): Record<string, unknown> {
|
||||
const catalog = loadCiCatalog();
|
||||
return {
|
||||
command: "ci install|status|run|publish-backend-core|publish-user-service|run-dev-e2e|logs",
|
||||
description: "Manage the D601 k3s Tekton CI gate. CI may publish commit-pinned image artifacts, but it intentionally does not deploy CD.",
|
||||
@@ -1492,9 +1541,13 @@ export function ciHelp(): Record<string, unknown> {
|
||||
userServiceArtifact: {
|
||||
producer: "D601 CI",
|
||||
command: "bun scripts/cli.ts ci publish-user-service --service <service-id> --commit <full-sha>",
|
||||
initiallySupportedServices: ["baidu-netdisk", "decision-center", "frontend"],
|
||||
supportedServices: supportedSourceBuildArtifactIds().filter((serviceId) => serviceId !== "backend-core"),
|
||||
blockedServices: blockedCatalogArtifactIds(),
|
||||
registry: "127.0.0.1:5000/unidesk/<service-id>:<commit>",
|
||||
outputFields: ["serviceId", "sourceCommit", "sourceRepo", "dockerfile", "imageRef", "tag", "digest", "digestRef"],
|
||||
summaryContract: catalog.summaryContract,
|
||||
catalogSummary: catalogSummary(),
|
||||
catalog: catalog.artifacts.map(catalogArtifactDescriptor),
|
||||
boundary: "artifact producer only; no prod deploy and no production namespace mutation",
|
||||
frontendNext: [
|
||||
"bun scripts/cli.ts deploy apply --env dev --service frontend",
|
||||
@@ -1528,37 +1581,82 @@ export async function runCiCommand(config: UniDeskConfig, args: string[]): Promi
|
||||
return run({ repoUrl, revision, waitMs });
|
||||
}
|
||||
if (action === "publish-backend-core") {
|
||||
const repoUrl = stringOption(args, "--repo") ?? stringOption(args, "--repo-url") ?? "https://github.com/pikasTech/unidesk";
|
||||
if (stringOption(args, "--repo") !== null || stringOption(args, "--repo-url") !== null) {
|
||||
throw new Error("ci publish-backend-core reads source repo from CI.json; edit CI.json instead of using --repo");
|
||||
}
|
||||
const commit = requireFullCommit(stringOption(args, "--commit") ?? stringOption(args, "--revision"));
|
||||
const waitMs = numberOption(args, "--wait-ms", 0);
|
||||
const dryRun = boolFlag(args, "--dry-run");
|
||||
return publishBackendCoreArtifact(config, { repoUrl, commit, waitMs, sourceHostPath: backendCoreArtifactSourceHostPath(commit), dryRun });
|
||||
const artifact = resolveCatalogArtifact("backend-core");
|
||||
if (artifact.kind !== "source-build") throw new Error("backend-core must be modeled as a source-build artifact in CI.json");
|
||||
if (artifact.status === "blocked") return blockedArtifactResult(artifact, commit, blockedReason(artifact));
|
||||
return publishBackendCoreArtifact(config, {
|
||||
repoUrl: artifact.source.repo,
|
||||
commit,
|
||||
waitMs,
|
||||
sourceHostPath: backendCoreArtifactSourceHostPath(commit),
|
||||
dockerfile: artifact.source.dockerfile,
|
||||
imageRepository: artifact.image.repository,
|
||||
dryRun,
|
||||
});
|
||||
}
|
||||
if (action === "publish-user-service") {
|
||||
const serviceId = requireServiceId(stringOption(args, "--service") ?? stringOption(args, "--service-id"));
|
||||
const repoOverride = stringOption(args, "--repo") ?? stringOption(args, "--repo-url");
|
||||
const target = serviceId === "frontend"
|
||||
? frontendArtifactTarget(repoOverride)
|
||||
: (() => {
|
||||
const service = requireSupportedUserService(config, serviceId);
|
||||
return {
|
||||
repoUrl: repoOverride ?? service.repository.url,
|
||||
dockerfile: service.repository.dockerfile,
|
||||
};
|
||||
})();
|
||||
const commit = requireFullCommit(stringOption(args, "--commit") ?? stringOption(args, "--revision"));
|
||||
const waitMs = numberOption(args, "--wait-ms", 0);
|
||||
const dryRun = boolFlag(args, "--dry-run");
|
||||
const dockerfile = requireRepoRelativePath(target.dockerfile, serviceId === "frontend" ? "frontend.dockerfile" : `microservices.${serviceId}.repository.dockerfile`);
|
||||
if (!publishUserServiceArtifactAllowedServiceIds.has(serviceId)) {
|
||||
throw new Error(`ci publish-user-service currently allows only ${Array.from(publishUserServiceArtifactAllowedServiceIds).join(", ")} until each user-service Dockerfile contract is reviewed`);
|
||||
if (stringOption(args, "--repo") !== null || stringOption(args, "--repo-url") !== null) {
|
||||
throw new Error("ci publish-user-service reads source repo from CI.json; edit CI.json instead of using --repo");
|
||||
}
|
||||
const artifact = resolveCatalogArtifact(serviceId);
|
||||
if (artifact.kind === "source-build" && artifact.serviceId === "backend-core") {
|
||||
throw new Error("backend-core uses ci publish-backend-core; publish-user-service is for registered user services");
|
||||
}
|
||||
if (artifact.kind === "upstream-image") {
|
||||
return blockedArtifactResult(artifact, commit, artifact.blockedReason);
|
||||
}
|
||||
if (artifact.status === "blocked") {
|
||||
return blockedArtifactResult(artifact, commit, blockedReason(artifact));
|
||||
}
|
||||
const repoUrl = artifact.source.repo;
|
||||
const dockerfile = requireRepoRelativePath(artifact.source.dockerfile, `CI.json.artifacts.${serviceId}.source.dockerfile`);
|
||||
const configService = config.microservices.find((item) => item.id === serviceId);
|
||||
if (configService !== undefined) {
|
||||
const isD601K3sService = configService.providerId === d601ProviderId
|
||||
&& configService.development.providerId === d601ProviderId
|
||||
&& configService.deployment.mode === "k3sctl-managed";
|
||||
const isD601DirectService = configService.providerId === d601ProviderId
|
||||
&& configService.development.providerId === d601ProviderId
|
||||
&& configService.deployment.mode === "unidesk-direct";
|
||||
const isMainServerDirectService = configService.providerId === "main-server"
|
||||
&& configService.development.providerId === "main-server"
|
||||
&& configService.deployment.mode === "unidesk-direct";
|
||||
const isMainServerInternalSidecar = configService.providerId === "main-server"
|
||||
&& configService.development.providerId === "main-server"
|
||||
&& configService.deployment.mode === "internal-sidecar";
|
||||
if (!isD601K3sService && !isD601DirectService && !isMainServerDirectService && !isMainServerInternalSidecar) {
|
||||
return {
|
||||
ok: false,
|
||||
status: "blocked",
|
||||
error: "blocked",
|
||||
serviceId,
|
||||
commit,
|
||||
reason: `config.json marks ${serviceId} as ${configService.providerId}/${configService.deployment.mode}, which is outside the reviewed CI artifact producer boundary`,
|
||||
catalogArtifact: artifact,
|
||||
configService: {
|
||||
providerId: configService.providerId,
|
||||
deploymentMode: configService.deployment.mode,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return publishUserServiceArtifact(config, {
|
||||
repoUrl: target.repoUrl,
|
||||
repoUrl,
|
||||
commit,
|
||||
waitMs,
|
||||
serviceId,
|
||||
dockerfile,
|
||||
imageRepository: artifact.image.repository,
|
||||
sourceHostPath: userServiceArtifactSourceHostPath(serviceId, commit),
|
||||
dryRun,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user