feat: 增加 Basic Auth typed Web smoke

This commit is contained in:
Codex
2026-07-16 20:11:21 +02:00
parent 5797a0bf2f
commit 1bb896c553
11 changed files with 507 additions and 19 deletions
+92 -7
View File
@@ -22,7 +22,7 @@ const configLabel = "config/platform-infra/temporal.yaml";
const secretRoot = "/root/.unidesk/.state/secrets";
const fieldManager = "unidesk-platform-temporal";
interface TemporalTarget {
export interface TemporalTarget {
id: string;
route: string;
namespace: string;
@@ -30,7 +30,34 @@ interface TemporalTarget {
enabled: boolean;
}
interface TemporalConfig {
export interface TemporalWebProbeSmokeProfile {
path: string;
readySelector: string;
minimumBodyTextBytes: number;
screenshotName: string;
viewport: { width: number; height: number };
navigationTimeoutMs: number;
settleMs: number;
commandTimeoutSeconds: number;
outputLimits: { failures: number; network: number; console: number };
}
export interface TemporalWebProbeSpec {
enabled: boolean;
productId: "temporal";
runner: { node: string; lane: string };
origin: { browserProxyMode: "auto" | "direct" };
authentication: {
mode: "basic-auth";
path: string;
authenticatedSelector: string;
credentials: { configRef: string; targetId: string; secretName: string; passwordTargetKey: string };
};
defaultSmokeProfile: string;
smokeProfiles: Record<string, TemporalWebProbeSmokeProfile>;
}
export interface TemporalConfig {
defaults: { targetId: string };
images: { server: string; ui: string; authProxy: string; pullPolicy: string };
targets: TemporalTarget[];
@@ -73,6 +100,7 @@ interface TemporalConfig {
healthPath: string;
expectedA: string;
};
webProbe: TemporalWebProbeSpec;
}
interface DatabaseMaterial {
@@ -112,7 +140,7 @@ export async function runTemporalCommand(config: UniDeskConfig, args: string[]):
function plan(options: ReturnType<typeof parseOpsCommonOptions>): Record<string, unknown> {
const temporal = readTemporalConfig();
const target = resolveTarget(temporal, options.targetId);
const target = resolveTemporalTarget(temporal, options.targetId);
const manifest = renderManifest(temporal, target, null);
const checks = policyChecks(temporal, target, manifest);
return {
@@ -135,7 +163,7 @@ function plan(options: ReturnType<typeof parseOpsCommonOptions>): Record<string,
async function apply(config: UniDeskConfig, options: ReturnType<typeof parseOpsApplyOptions>): Promise<Record<string, unknown>> {
const temporal = readTemporalConfig();
const target = resolveTarget(temporal, options.targetId);
const target = resolveTemporalTarget(temporal, options.targetId);
const checks = policyChecks(temporal, target, renderManifest(temporal, target, null));
if (!checks.every((check) => check.ok)) return { ok: false, action: "platform-infra-temporal-apply", mode: "policy-blocked", policy: checks };
if (options.confirm && !options.wait) {
@@ -186,7 +214,7 @@ async function observe(
options: ReturnType<typeof parseOpsCommonOptions>,
): Promise<Record<string, unknown>> {
const temporal = readTemporalConfig();
const target = resolveTarget(temporal, options.targetId);
const target = resolveTemporalTarget(temporal, options.targetId);
const result = await capture(config, target.route, ["sh"], statusScript(temporal, target, action === "validate"));
const parsed = parseJsonOutput(result.stdout);
return {
@@ -513,7 +541,7 @@ function readDatabaseMaterial(temporal: TemporalConfig): DatabaseMaterial {
return { ...material, fingerprint: fingerprintSecretValues(material, Object.keys(material)) };
}
function readTemporalConfig(): TemporalConfig {
export function readTemporalConfig(): TemporalConfig {
const root = readYamlRecord(configPath, "platform-infra-temporal");
const defaults = recordField(root, "defaults", configLabel);
const images = recordField(root, "images", configLabel);
@@ -524,6 +552,41 @@ function readTemporalConfig(): TemporalConfig {
const ui = recordField(runtime, "ui", `${configLabel}.runtime`);
const uiAuth = recordField(ui, "auth", `${configLabel}.runtime.ui`);
const exposure = recordField(root, "publicExposure", configLabel);
const webProbe = recordField(root, "webProbe", configLabel);
const webProbeRunner = recordField(webProbe, "runner", `${configLabel}.webProbe`);
const webProbeOrigin = recordField(webProbe, "origin", `${configLabel}.webProbe`);
const webProbeAuth = recordField(webProbe, "authentication", `${configLabel}.webProbe`);
const webProbeCredentials = recordField(webProbeAuth, "credentials", `${configLabel}.webProbe.authentication`);
const webProbeProfiles = recordField(webProbe, "smokeProfiles", `${configLabel}.webProbe`);
const smokeProfiles = Object.fromEntries(Object.entries(webProbeProfiles).map(([name, value]) => {
const path = `${configLabel}.webProbe.smokeProfiles.${name}`;
const profile = recordField({ value }, "value", path);
const viewport = recordField(profile, "viewport", path);
const limits = recordField(profile, "outputLimits", path);
return [name, {
path: stringField(profile, "path", path),
readySelector: stringField(profile, "readySelector", path),
minimumBodyTextBytes: integerField(profile, "minimumBodyTextBytes", path),
screenshotName: stringField(profile, "screenshotName", path),
viewport: { width: integerField(viewport, "width", `${path}.viewport`), height: integerField(viewport, "height", `${path}.viewport`) },
navigationTimeoutMs: integerField(profile, "navigationTimeoutMs", path),
settleMs: integerField(profile, "settleMs", path),
commandTimeoutSeconds: integerField(profile, "commandTimeoutSeconds", path),
outputLimits: {
failures: integerField(limits, "failures", `${path}.outputLimits`),
network: integerField(limits, "network", `${path}.outputLimits`),
console: integerField(limits, "console", `${path}.outputLimits`),
},
} satisfies TemporalWebProbeSmokeProfile];
}));
const defaultSmokeProfile = stringField(webProbe, "defaultSmokeProfile", `${configLabel}.webProbe`);
if (smokeProfiles[defaultSmokeProfile] === undefined) throw new Error(`${configLabel}.webProbe.defaultSmokeProfile must reference smokeProfiles`);
const webProbeMode = stringField(webProbeAuth, "mode", `${configLabel}.webProbe.authentication`);
if (webProbeMode !== "basic-auth") throw new Error(`${configLabel}.webProbe.authentication.mode must be basic-auth`);
const productId = stringField(webProbe, "productId", `${configLabel}.webProbe`);
if (productId !== "temporal") throw new Error(`${configLabel}.webProbe.productId must be temporal`);
const browserProxyMode = stringField(webProbeOrigin, "browserProxyMode", `${configLabel}.webProbe.origin`);
if (browserProxyMode !== "auto" && browserProxyMode !== "direct") throw new Error(`${configLabel}.webProbe.origin.browserProxyMode must be auto or direct`);
return {
defaults: { targetId: stringField(defaults, "targetId", `${configLabel}.defaults`) },
images: {
@@ -585,6 +648,28 @@ function readTemporalConfig(): TemporalConfig {
healthPath: stringField(exposure, "healthPath", `${configLabel}.publicExposure`),
expectedA: stringField(exposure, "expectedA", `${configLabel}.publicExposure`),
},
webProbe: {
enabled: booleanField(webProbe, "enabled", `${configLabel}.webProbe`),
productId,
runner: {
node: stringField(webProbeRunner, "node", `${configLabel}.webProbe.runner`),
lane: stringField(webProbeRunner, "lane", `${configLabel}.webProbe.runner`),
},
origin: { browserProxyMode },
authentication: {
mode: webProbeMode,
path: stringField(webProbeAuth, "path", `${configLabel}.webProbe.authentication`),
authenticatedSelector: stringField(webProbeAuth, "authenticatedSelector", `${configLabel}.webProbe.authentication`),
credentials: {
configRef: stringField(webProbeCredentials, "configRef", `${configLabel}.webProbe.authentication.credentials`),
targetId: stringField(webProbeCredentials, "targetId", `${configLabel}.webProbe.authentication.credentials`),
secretName: stringField(webProbeCredentials, "secretName", `${configLabel}.webProbe.authentication.credentials`),
passwordTargetKey: stringField(webProbeCredentials, "passwordTargetKey", `${configLabel}.webProbe.authentication.credentials`),
},
},
defaultSmokeProfile,
smokeProfiles,
},
};
}
@@ -597,7 +682,7 @@ function deploymentConfig(record: Record<string, unknown>, path: string, portKey
};
}
function resolveTarget(temporal: TemporalConfig, requested: string | null): TemporalTarget {
export function resolveTemporalTarget(temporal: TemporalConfig, requested: string | null): TemporalTarget {
const id = requested ?? temporal.defaults.targetId;
const target = temporal.targets.find((item) => item.id === id);
if (target === undefined) throw new Error(`${configLabel}.targets does not contain ${id}`);