feat: add artifact publish preflight
This commit is contained in:
@@ -5,10 +5,10 @@ import { runCommand, type CommandResult } from "./command";
|
||||
import { readConfig, type UniDeskConfig, repoRoot, rootPath } from "./config";
|
||||
import { startJob } from "./jobs";
|
||||
|
||||
type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "install" | "deploy-backend-core" | "deploy-service";
|
||||
export type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "install" | "deploy-backend-core" | "deploy-service";
|
||||
type ArtifactDeployEnvironment = "prod" | "dev";
|
||||
|
||||
interface ArtifactRegistryOptions {
|
||||
export interface ArtifactRegistryOptions {
|
||||
environment: ArtifactDeployEnvironment | null;
|
||||
providerId: string;
|
||||
host: string;
|
||||
@@ -49,6 +49,15 @@ interface RenderedBundle {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ArtifactRegistryReadonlyProbe {
|
||||
action: "status" | "health";
|
||||
providerId: string;
|
||||
script: string;
|
||||
timeoutMs: number;
|
||||
healthMode: boolean;
|
||||
options: ArtifactRegistryOptions;
|
||||
}
|
||||
|
||||
const defaultOptions: ArtifactRegistryOptions = {
|
||||
environment: null,
|
||||
providerId: "D601",
|
||||
@@ -758,7 +767,7 @@ function environmentValue(value: string, option: string): ArtifactDeployEnvironm
|
||||
throw new Error(`${option} must be one of: prod, dev`);
|
||||
}
|
||||
|
||||
function parseOptions(args: string[]): ArtifactRegistryOptions {
|
||||
export function parseArtifactRegistryOptions(args: string[]): ArtifactRegistryOptions {
|
||||
const options = { ...defaultOptions };
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
@@ -1103,7 +1112,7 @@ function registryHealthDecision(checks: Record<string, boolean>, commandOk: bool
|
||||
};
|
||||
}
|
||||
|
||||
function commandTail(result: CommandResult): Record<string, unknown> {
|
||||
export function artifactRegistryCommandTail(result: CommandResult): Record<string, unknown> {
|
||||
return {
|
||||
command: result.command.length > 7 ? [...result.command.slice(0, 7), "<readonly-script>"] : result.command,
|
||||
exitCode: result.exitCode,
|
||||
@@ -1114,6 +1123,10 @@ function commandTail(result: CommandResult): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function commandTail(result: CommandResult): Record<string, unknown> {
|
||||
return artifactRegistryCommandTail(result);
|
||||
}
|
||||
|
||||
function artifactConsumerSpec(serviceId: string, environment: ArtifactDeployEnvironment | null): ArtifactConsumerSpec | null {
|
||||
const key = environment === null || environment === "prod" ? serviceId : `${environment}:${serviceId}`;
|
||||
const explicit = artifactConsumerSpecs[key];
|
||||
@@ -1329,12 +1342,53 @@ function runReadonlyStatus(options: ArtifactRegistryOptions, healthMode: boolean
|
||||
image: options.image,
|
||||
paths: bundle.paths,
|
||||
},
|
||||
command: commandTail(result),
|
||||
command: artifactRegistryCommandTail(result),
|
||||
};
|
||||
}
|
||||
return statusFromValues(options, parseKeyValueOutput(result.stdout), result, healthMode);
|
||||
}
|
||||
|
||||
export function buildArtifactRegistryReadonlyProbe(action: "status" | "health", options: ArtifactRegistryOptions): ArtifactRegistryReadonlyProbe {
|
||||
const bundle = renderBundle(options);
|
||||
const healthMode = action === "health";
|
||||
return {
|
||||
action,
|
||||
providerId: options.providerId,
|
||||
script: statusScript(options, bundle),
|
||||
timeoutMs: options.timeoutMs,
|
||||
healthMode,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
export function artifactRegistryReadonlyResultFromCommand(
|
||||
probe: ArtifactRegistryReadonlyProbe,
|
||||
command: CommandResult,
|
||||
): Record<string, unknown> {
|
||||
if (command.exitCode !== 0 || command.timedOut) {
|
||||
const bundle = renderBundle(probe.options);
|
||||
return {
|
||||
ok: false,
|
||||
readonly: true,
|
||||
installed: false,
|
||||
healthy: false,
|
||||
decision: "retryable-transient",
|
||||
retryable: true,
|
||||
healthyScopes: [],
|
||||
failedScopes: ["provider-ssh-command"],
|
||||
runtimeApiHealthy: false,
|
||||
checks: {},
|
||||
expected: {
|
||||
endpoint: `http://${probe.options.host}:${probe.options.port}`,
|
||||
image: probe.options.image,
|
||||
paths: bundle.paths,
|
||||
},
|
||||
command: artifactRegistryCommandTail(command),
|
||||
};
|
||||
}
|
||||
return statusFromValues(probe.options, parseKeyValueOutput(command.stdout), command, probe.healthMode);
|
||||
}
|
||||
|
||||
function remoteWriteFileCommand(item: RenderedFile): string {
|
||||
const encoded = Buffer.from(item.content, "utf8").toString("base64");
|
||||
const rootOwned = item.path.startsWith("/etc/");
|
||||
@@ -2540,7 +2594,7 @@ export async function runArtifactRegistryCommand(args: string[]): Promise<unknow
|
||||
throw new Error("artifact-registry usage: plan|render|status|health|install|deploy-backend-core|deploy-service");
|
||||
}
|
||||
const typedAction = action as ArtifactRegistryAction;
|
||||
const options = parseOptions(args.slice(1));
|
||||
const options = parseArtifactRegistryOptions(args.slice(1));
|
||||
if (action === "plan") return plan(options);
|
||||
if (action === "render") return { ok: true, providerId: options.providerId, render: renderBundle(options) };
|
||||
if (action === "status") return runReadonlyStatus(options, false);
|
||||
|
||||
Reference in New Issue
Block a user