feat: 增加 Basic Auth typed Web smoke
This commit is contained in:
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user