Merge pull request #2726 from pikasTech/fix/2721-disable-automatic-webhooks-action
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

fix(cicd): 增加自动 webhook 窄域清理入口
This commit is contained in:
Lyon
2026-07-21 12:10:59 +08:00
committed by GitHub
3 changed files with 100 additions and 1 deletions
@@ -263,6 +263,18 @@ test("PaC apply checks the Gitea repository before dry-run return and cluster mu
}
});
test("PaC automatic webhook disable is target-scoped and does not touch Kubernetes", async () => {
const plan = await runPlatformInfraPipelinesAsCodeCommand({} as never, ["automatic-webhooks", "disable", "--target", "NC01"]);
expect(plan).toMatchObject({ ok: true, mutation: false, mode: "plan", target: { id: "NC01" }, plan: { automaticWebhookEnabled: false } });
expect(Number((plan as any).plan.repositoryCount)).toBeGreaterThan(1);
const remote = readFileSync(resolve(import.meta.dir, "platform-infra-pipelines-as-code-remote.sh"), "utf8");
const actionStart = remote.indexOf("disable_automatic_webhooks_action()");
const action = remote.slice(actionStart, remote.indexOf("repository_manifest()", actionStart));
expect(action).toContain("gitea_api DELETE");
expect(action).not.toContain("kubectl");
expect(action).not.toContain("ensure_webhook");
});
test("PaC release manifest renders YAML-owned watcher startup backlog probes", () => {
const pac = readPacConfig({ consumerId: "hwlab-nc01-v03", selectDefault: true });
const source = readFileSync(resolve(import.meta.dir, "fixtures/pac/release-leading-comment-only.yaml"), "utf8");
@@ -192,6 +192,37 @@ NODE
done
}
disable_automatic_webhooks_action() {
repositories=$(UNIDESK_PAC_WEBHOOK_REPOSITORIES_JSON="$UNIDESK_PAC_WEBHOOK_REPOSITORIES_JSON" node <<'NODE'
const data = JSON.parse(process.env.UNIDESK_PAC_WEBHOOK_REPOSITORIES_JSON || '[]');
for (const item of data) console.log(`${item.id}\t${item.owner}\t${item.repo}`);
NODE
)
repository_count=0
removed_count=0
tab=$(printf '\t')
while IFS="$tab" read -r repository_id owner repo; do
[ -n "$repository_id" ] || continue
repository_count=$((repository_count + 1))
UNIDESK_PAC_GITEA_OWNER=$owner
UNIDESK_PAC_GITEA_REPO=$repo
hooks=$(gitea_api GET "repos/$owner/$repo/hooks")
hook_ids=$(HOOKS_JSON="$hooks" node <<'NODE'
const data = JSON.parse(process.env.HOOKS_JSON || '[]');
const url = process.env.UNIDESK_PAC_WEBHOOK_URL;
for (const item of data) if (item?.config?.url === url && item?.id) console.log(item.id);
NODE
)
for hook_id in $hook_ids; do
gitea_api DELETE "repos/$owner/$repo/hooks/$hook_id" >/dev/null
removed_count=$((removed_count + 1))
done
done <<EOF
$repositories
EOF
printf '{"ok":true,"mutation":true,"repositoryCount":%s,"removedWebhookCount":%s,"automaticWebhookEnabled":false,"valuesPrinted":false}\n' "$repository_count" "$removed_count"
}
repository_manifest() {
cat <<EOF
apiVersion: pipelinesascode.tekton.dev/v1alpha1
@@ -2091,5 +2122,6 @@ case "$UNIDESK_PAC_ACTION" in
history) history_action ;;
diagnose-regression) diagnose_regression_action ;;
debug-step) debug_step_action ;;
disable-automatic-webhooks) disable_automatic_webhooks_action ;;
*) printf '{"ok":false,"error":"unsupported-action","valuesPrinted":false}\n'; exit 2 ;;
esac
@@ -350,6 +350,11 @@ export async function runPlatformInfraPipelinesAsCodeCommand(config: UniDeskConf
const result = plan(options);
return options.full || options.raw ? result : options.json ? compactPlanJson(result) : renderPlan(result);
}
if (action === "automatic-webhooks") {
if (args[1] !== "disable") return { ok: false, action: "platform-infra-pipelines-as-code-automatic-webhooks", mutation: false, error: "unsupported-automatic-webhooks-action", usage: "pipelines-as-code automatic-webhooks disable --target <NODE> [--confirm]", valuesPrinted: false };
const options = parseApplyOptions(args.slice(2));
return await disableAutomaticWebhooks(config, options);
}
if (action === "apply") {
const options = parseApplyOptions(args.slice(1));
const result = await apply(config, options);
@@ -583,6 +588,7 @@ function help(scope: string | null): Record<string, unknown> {
configTruth: configLabel,
usage: [
"bun scripts/cli.ts platform-infra pipelines-as-code plan --target JD01",
"bun scripts/cli.ts platform-infra pipelines-as-code automatic-webhooks disable --target <NODE> [--confirm]",
"bun scripts/cli.ts platform-infra pipelines-as-code status --target JD01 [--json|--full|--raw]",
"bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 [--consumer hwlab-jd01-v03] [--limit 10]",
"bun scripts/cli.ts platform-infra pipelines-as-code diagnose-regression --target NC01 --consumer agentrun-nc01-v02 [--limit 20] [--json|--full]",
@@ -1517,6 +1523,54 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
};
}
async function disableAutomaticWebhooks(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const repositories = [...new Map(validPacConsumers(pac)
.filter((consumer) => consumer.node.toLowerCase() === target.id.toLowerCase())
.map((consumer) => {
const repository = resolveRepository(pac, consumer.repositoryRef);
return [repository.id, repository] as const;
})).values()];
const plan = {
mode: pac.deliveryTrigger.mode,
mechanism: pac.deliveryTrigger.mechanism,
configPath: `${configLabel}#deliveryTrigger`,
repositoryCount: repositories.length,
repositories: repositories.map((repository) => ({ id: repository.id, owner: repository.owner, repo: repository.repo })),
automaticWebhookEnabled: pac.gitea.webhook.enabled,
valuesPrinted: false,
};
if (!options.confirm) {
return {
ok: true,
action: "platform-infra-pipelines-as-code-automatic-webhooks-disable",
mutation: false,
mode: "plan",
target: targetSummary(target),
plan,
next: `bun scripts/cli.ts platform-infra pipelines-as-code automatic-webhooks disable --target ${target.id} --confirm`,
valuesPrinted: false,
};
}
const consumer = validPacConsumers(pac).find((item) => item.node.toLowerCase() === target.id.toLowerCase());
if (consumer === undefined || repositories.length === 0) throw new Error(`target ${target.id} has no valid PaC repositories`);
const repository = resolveRepository(pac, consumer.repositoryRef);
const secrets = ensureSecrets(pac, false);
const result = await capture(config, target.route, ["sh"], remoteScript("disable-automatic-webhooks", pac, target, repository, consumer, options, secrets, "", [consumer], false, repositories));
const remote = parseJsonOutput(result.stdout) ?? {};
return {
ok: result.exitCode === 0 && remote.ok === true,
action: "platform-infra-pipelines-as-code-automatic-webhooks-disable",
mutation: remote.mutation === true,
mode: "confirmed",
target: targetSummary(target),
plan,
remote: { ...remote, exitCode: result.exitCode, stderrTail: result.stderr.slice(-1200) },
valuesPrinted: false,
};
}
async function bootstrap(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig({ consumerId: options.consumerId, selectDefault: true });
const target = resolveTarget(pac, options.targetId);
@@ -2230,7 +2284,7 @@ export class PacReleaseManifestRenderError extends Error {
}
}
function remoteScript(action: "apply" | "status" | "history" | "diagnose-regression" | "debug-step", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | HistoryOptions, secrets: SecretMaterial, releaseManifest: string, historyConsumers: PacConsumer[] = [consumer], collectArtifactLogs = false): string {
function remoteScript(action: "apply" | "status" | "history" | "diagnose-regression" | "debug-step" | "disable-automatic-webhooks", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | HistoryOptions, secrets: SecretMaterial, releaseManifest: string, historyConsumers: PacConsumer[] = [consumer], collectArtifactLogs = false, webhookRepositories: PacRepository[] = [repository]): string {
const webhookUrl = `${pac.gitea.internalBaseUrl.replace(/\/+$/u, "").replace(/gitea-http\.[^.]+\.svc\.cluster\.local:3000/u, `${pac.release.controllerServiceName}.${pac.release.namespace}.svc.cluster.local:${pac.release.controllerServicePort}`)}`;
const admissionIdentity = pacAdmissionDesiredIdentity(target.id);
const rbacIdentity = pacConsumerRbacDesiredIdentity(target.id);
@@ -2256,6 +2310,7 @@ function remoteScript(action: "apply" | "status" | "history" | "diagnose-regress
UNIDESK_PAC_GITEA_API_USERNAME: pac.gitea.admin.apiUsername,
UNIDESK_PAC_GITEA_OWNER: repository.owner,
UNIDESK_PAC_GITEA_REPO: repository.repo,
UNIDESK_PAC_WEBHOOK_REPOSITORIES_JSON: JSON.stringify(webhookRepositories.map((item) => ({ id: item.id, owner: item.owner, repo: item.repo }))),
UNIDESK_PAC_WEBHOOK_URL: webhookUrl,
UNIDESK_PAC_WEBHOOK_SECRET: secrets.webhookSecret,
UNIDESK_PAC_REPOSITORY_NAME: repository.name,