208 lines
9.3 KiB
TypeScript
208 lines
9.3 KiB
TypeScript
import {
|
|
PAC_AUTOMATIC_DELIVERY_REFERENCE,
|
|
decideCicdDeliveryMutation,
|
|
readCicdDeliveryAuthorityCatalog,
|
|
resolveCicdDeliveryAuthority,
|
|
resolveCicdDeliveryAuthorityFromCatalog,
|
|
type CicdDeliveryAuthority,
|
|
type CicdDeliveryAuthorityCatalog,
|
|
type CicdDeliveryMutationDecision,
|
|
} from "../cicd-delivery-authority";
|
|
import { authorizeSentinelPacInternalPublish } from "../sentinel-pac-execution-context";
|
|
import {
|
|
record,
|
|
rendered,
|
|
sentinelCliSuffix,
|
|
text,
|
|
type SentinelCicdState,
|
|
type WebProbeSentinelOptions,
|
|
type WebProbeSentinelSourceAuthority,
|
|
} from "../hwlab-node-web-sentinel-cicd-shared";
|
|
import { hwlabRuntimeLaneConfigPath, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
|
|
import type { RenderedCliResult } from "../output";
|
|
|
|
export function webProbeSentinelDeliveryAuthority(spec: HwlabRuntimeLaneSpec): CicdDeliveryAuthority {
|
|
try {
|
|
return webProbeSentinelDeliveryAuthorityFromCatalog(spec, readCicdDeliveryAuthorityCatalog());
|
|
} catch {
|
|
return resolveCicdDeliveryAuthority({
|
|
consumerId: sentinelPacConsumerId(spec),
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
});
|
|
}
|
|
}
|
|
|
|
export function webProbeSentinelDeliveryAuthorityFromCatalog(
|
|
spec: HwlabRuntimeLaneSpec,
|
|
catalog: CicdDeliveryAuthorityCatalog,
|
|
): CicdDeliveryAuthority {
|
|
const consumerId = sentinelPacConsumerId(spec);
|
|
const exactConsumer = resolveCicdDeliveryAuthorityFromCatalog(catalog, {
|
|
consumerId,
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
});
|
|
if (exactConsumer.kind === "pac-pr-merge") return exactConsumer;
|
|
|
|
const stableSentinelIdentity = spec.sourceAuthority?.mode === "giteaSnapshot"
|
|
|| catalog.consumers.some((consumer) => consumer.consumerId.toLowerCase() === consumerId.toLowerCase())
|
|
|| catalog.consumers.some((consumer) => consumer.consumerId.toLowerCase().startsWith("sentinel-")
|
|
&& consumer.node.toLowerCase() === spec.nodeId.toLowerCase()
|
|
&& consumer.lane.toLowerCase() === spec.lane.toLowerCase());
|
|
if (stableSentinelIdentity) return exactConsumer;
|
|
|
|
return resolveCicdDeliveryAuthorityFromCatalog(catalog, {
|
|
declaredSourceAuthorityMode: spec.sourceAuthority?.mode ?? null,
|
|
declaredConfigRef: hwlabRuntimeLaneConfigPath(),
|
|
});
|
|
}
|
|
|
|
export function sentinelPacConsumerId(spec: HwlabRuntimeLaneSpec): string {
|
|
return `sentinel-${spec.nodeId.toLowerCase()}-${spec.lane}`;
|
|
}
|
|
|
|
export function webProbeSentinelDeliveryMutationDecision(
|
|
spec: HwlabRuntimeLaneSpec,
|
|
command: string,
|
|
action: string,
|
|
): CicdDeliveryMutationDecision {
|
|
return decideCicdDeliveryMutation(webProbeSentinelDeliveryAuthority(spec), {
|
|
command,
|
|
statusCommand: `bun scripts/cli.ts web-probe sentinel control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
|
action,
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
});
|
|
}
|
|
|
|
export function guardedSentinelDeliveryAction(options: WebProbeSentinelOptions): { command: string; action: string } | null {
|
|
if (options.kind === "image" && options.action === "build") {
|
|
return { command: "web-probe sentinel image build", action: "sentinel-image-build" };
|
|
}
|
|
if (options.kind === "control-plane" && options.action === "trigger-current") {
|
|
return { command: "web-probe sentinel control-plane trigger-current", action: "sentinel-control-plane-trigger-current" };
|
|
}
|
|
if (options.kind === "publish" && (options.internalExecution === "pac-controller" || options.manualRecovery)) {
|
|
return { command: "web-probe sentinel publish-current", action: "sentinel-publish-current" };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function renderBlockedSentinelDeliveryMutation(
|
|
spec: HwlabRuntimeLaneSpec,
|
|
options: WebProbeSentinelOptions,
|
|
command: string,
|
|
action: string,
|
|
): RenderedCliResult | null {
|
|
const decision = webProbeSentinelDeliveryMutationDecision(spec, command, action);
|
|
if (decision.allowed) return null;
|
|
const executionAuthorization = options.kind === "publish" && options.internalExecution === "pac-controller"
|
|
? authorizeSentinelPacInternalPublish(options, decision.authority)
|
|
: null;
|
|
const result = {
|
|
...decision.result,
|
|
...(executionAuthorization === null ? {} : {
|
|
mode: executionAuthorization.mode,
|
|
reason: executionAuthorization.reason,
|
|
capability: executionAuthorization.capability,
|
|
}),
|
|
executionAuthorization,
|
|
next: sentinelReadOnlyNextForSpec(spec, options.sentinelId, decision.authority),
|
|
sentinelId: options.sentinelId,
|
|
valuesRedacted: true,
|
|
};
|
|
const next = record(result.next);
|
|
const fixReference = typeof next.fixAutomaticDelivery === "string"
|
|
? next.fixAutomaticDelivery
|
|
: record(next.fixAutomaticDelivery).reference ?? PAC_AUTOMATIC_DELIVERY_REFERENCE;
|
|
return Object.assign(
|
|
rendered(false, command, [
|
|
"WEB SENTINEL DELIVERY BLOCKED",
|
|
`mode=${text(result.mode)} mutation=false authority=${text(record(result.deliveryAuthority).kind)}`,
|
|
`status=${text(next.status)}`,
|
|
`history=${text(next.history ?? next.pacHistory)}`,
|
|
`fix=${text(fixReference)}`,
|
|
"GitHub PR merge 是唯一正式交付触发;修复自动链后用新的正常合并事件验收。",
|
|
].join("\n")),
|
|
result,
|
|
);
|
|
}
|
|
|
|
export function sentinelReadOnlyNextForSpec(
|
|
spec: HwlabRuntimeLaneSpec,
|
|
sentinelId: string | null,
|
|
authority: CicdDeliveryAuthority,
|
|
): Record<string, string> {
|
|
const suffix = sentinelId === null ? "" : ` --sentinel ${sentinelId}`;
|
|
const controlPlaneStatus = `bun scripts/cli.ts web-probe sentinel control-plane status --node ${spec.nodeId} --lane ${spec.lane}${suffix}`;
|
|
const base = {
|
|
status: controlPlaneStatus,
|
|
controlPlaneStatus,
|
|
fixAutomaticDelivery: PAC_AUTOMATIC_DELIVERY_REFERENCE,
|
|
};
|
|
if (authority.kind !== "pac-pr-merge") return base;
|
|
const pacHistory = `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${authority.consumer.node} --consumer ${authority.consumer.consumerId} --limit 10`;
|
|
return {
|
|
...base,
|
|
pacStatus: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${authority.consumer.node} --consumer ${authority.consumer.consumerId}`,
|
|
pacHistory,
|
|
history: pacHistory,
|
|
};
|
|
}
|
|
|
|
export function sentinelReadOnlyNext(
|
|
state: SentinelCicdState,
|
|
authority: CicdDeliveryAuthority = webProbeSentinelDeliveryAuthority(state.spec),
|
|
): Record<string, string> {
|
|
return sentinelReadOnlyNextForSpec(state.spec, state.sentinelId, authority);
|
|
}
|
|
|
|
export function publishCurrentNext(state: SentinelCicdState): Record<string, string> {
|
|
const deliveryAuthority = webProbeSentinelDeliveryAuthority(state.spec);
|
|
if (deliveryAuthority.kind !== "legacy-manual") return sentinelReadOnlyNext(state, deliveryAuthority);
|
|
const node = state.spec.nodeId;
|
|
const lane = state.spec.lane;
|
|
const suffix = sentinelCliSuffix(state);
|
|
const consumer = sentinelPacConsumerId(state.spec);
|
|
const sourceCommit = state.sourceHead.commit === null ? "" : ` --source-commit ${state.sourceHead.commit}`;
|
|
return {
|
|
pacCloseout: `bun scripts/cli.ts platform-infra pipelines-as-code closeout --target ${node} --consumer ${consumer}${sourceCommit} --wait`,
|
|
pacStatus: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${node} --consumer ${consumer}`,
|
|
pacHistory: `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${node} --consumer ${consumer} --limit 10`,
|
|
manualRecovery: `bun scripts/cli.ts web-probe sentinel publish-current --node ${node} --lane ${lane}${suffix} --confirm --wait --manual-recovery --reason <reason>`,
|
|
controlPlaneStatus: `bun scripts/cli.ts web-probe sentinel control-plane status --node ${node} --lane ${lane}${suffix}`,
|
|
dashboardVerify: `bun scripts/cli.ts web-probe sentinel dashboard verify --node ${node} --lane ${lane}${suffix}`,
|
|
gitMirrorStatus: `bun scripts/cli.ts hwlab nodes git-mirror status --node ${node} --lane ${lane}`,
|
|
gitMirrorSync: `bun scripts/cli.ts hwlab nodes git-mirror sync --node ${node} --lane ${lane} --confirm`,
|
|
gitMirrorFlush: `bun scripts/cli.ts hwlab nodes git-mirror flush --node ${node} --lane ${lane} --confirm --wait`,
|
|
};
|
|
}
|
|
|
|
export function sentinelSourceResolveMode(options: WebProbeSentinelOptions): "cached" | "sync" {
|
|
if (options.kind === "image" && options.action === "build" && options.confirm && options.wait) return "sync";
|
|
if (options.kind === "control-plane" && options.action === "trigger-current" && options.confirm && options.wait) return "sync";
|
|
if (options.kind === "publish" && options.confirm && options.wait) return "sync";
|
|
return "cached";
|
|
}
|
|
|
|
export interface SentinelSourceOverride {
|
|
readonly commit: string;
|
|
readonly stageRef?: string | null;
|
|
readonly mirrorCommit?: string | null;
|
|
readonly sourceAuthority: WebProbeSentinelSourceAuthority;
|
|
}
|
|
|
|
export function sentinelSourceOverrideFromOptions(options: WebProbeSentinelOptions): SentinelSourceOverride | null {
|
|
if (options.kind !== "image" && options.kind !== "control-plane" && options.kind !== "publish") return null;
|
|
const override = options.sourceOverride;
|
|
if (override.sourceCommit === null && override.sourceStageRef === null && override.sourceMirrorCommit === null && override.sourceAuthority === null) return null;
|
|
if (override.sourceCommit === null) throw new Error("--source-commit is required when overriding web-probe sentinel source");
|
|
return {
|
|
commit: override.sourceCommit,
|
|
stageRef: override.sourceStageRef,
|
|
mirrorCommit: override.sourceMirrorCommit,
|
|
sourceAuthority: override.sourceAuthority ?? "git-mirror-snapshot",
|
|
};
|
|
}
|