quality / commitlint (push) Skipped
CD / update-wiki (push) Failing after 8s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 50s
quality / api (push) Successful in 41s
CD / quality (push) Successful in 1m40s
CD / publish (push) Successful in 1m33s
- Introduced origin health check routes and integrated them into the application. - Updated health check configuration to include success recovery thresholds. - Expanded error handling with new error codes for health check failures. - Added new service routes for managing health checks, including creation and listing. - Improved health check service logic to track consecutive successes and failures. This commit enhances the health check capabilities, providing better monitoring and management of service health.
119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
import type { Db } from "@cfdm/db";
|
|
import { repos } from "@cfdm/db";
|
|
import type { ChangeDomainInput } from "@cfdm/shared";
|
|
import type { CloudflareClient } from "../lib/cf-client.js";
|
|
import { AppError } from "../errors.js";
|
|
import * as dnsService from "./dns-service.js";
|
|
import { withBindingLock } from "./routing/index.js";
|
|
import { applyBindingDesiredDns, fqdnToDisplay } from "./service-config-service.js";
|
|
|
|
export interface ChangeDomainItem {
|
|
binding_id: number;
|
|
hostname: string;
|
|
from_fqdn: string;
|
|
to_fqdn: string;
|
|
}
|
|
|
|
export interface ChangeDomainPreview {
|
|
from_domain_id: number;
|
|
to_domain_id: number;
|
|
from_zone: string;
|
|
to_zone: string;
|
|
items: ChangeDomainItem[];
|
|
dry_run: boolean;
|
|
applied: boolean;
|
|
message: string;
|
|
}
|
|
|
|
export async function changeServiceDomain(
|
|
db: Db,
|
|
cf: CloudflareClient,
|
|
serviceId: number,
|
|
input: ChangeDomainInput,
|
|
): Promise<ChangeDomainPreview> {
|
|
repos.getService(db, serviceId);
|
|
if (input.from_domain_id === input.to_domain_id) {
|
|
throw AppError.validation("укажите другой целевой домен");
|
|
}
|
|
const fromDomain = repos.getDomain(db, input.from_domain_id);
|
|
const toDomain = repos.getDomain(db, input.to_domain_id);
|
|
const bindings = repos
|
|
.listBindingsByService(db, serviceId)
|
|
.filter((b) => b.domain_id === input.from_domain_id);
|
|
const selected = input.hostnames?.length
|
|
? bindings.filter((b) => input.hostnames!.includes(b.hostname))
|
|
: bindings;
|
|
if (selected.length === 0) {
|
|
throw AppError.validation("нет привязок для переноса");
|
|
}
|
|
|
|
const items: ChangeDomainItem[] = selected.map((binding) => ({
|
|
binding_id: binding.id,
|
|
hostname: binding.hostname,
|
|
from_fqdn: fqdnToDisplay(binding.hostname, fromDomain.zone_name),
|
|
to_fqdn: fqdnToDisplay(binding.hostname, toDomain.zone_name),
|
|
}));
|
|
|
|
const preview: ChangeDomainPreview = {
|
|
from_domain_id: fromDomain.id,
|
|
to_domain_id: toDomain.id,
|
|
from_zone: fromDomain.zone_name,
|
|
to_zone: toDomain.zone_name,
|
|
items,
|
|
dry_run: Boolean(input.dry_run),
|
|
applied: false,
|
|
message: `Перенос ${items.length} привязок ${fromDomain.zone_name} → ${toDomain.zone_name}`,
|
|
};
|
|
|
|
if (input.dry_run) return preview;
|
|
|
|
const createdRecordIds: number[] = [];
|
|
try {
|
|
for (const binding of selected) {
|
|
const existing = repos.findBinding(
|
|
db,
|
|
serviceId,
|
|
toDomain.id,
|
|
binding.hostname,
|
|
);
|
|
if (existing) {
|
|
throw AppError.conflict(
|
|
`привязка ${fqdnToDisplay(binding.hostname, toDomain.zone_name)} уже существует`,
|
|
);
|
|
}
|
|
await withBindingLock(binding.id, async () => {
|
|
repos.bumpBindingVersion(db, binding.id);
|
|
const ips = repos.listBindingIps(db, binding.id);
|
|
repos.updateBindingDomain(db, binding.id, toDomain.id, binding.hostname);
|
|
await applyBindingDesiredDns(db, cf, binding.id, ips);
|
|
const newRecords = repos.listRecordsForBinding(db, binding.id);
|
|
createdRecordIds.push(...newRecords.map((r) => r.id));
|
|
|
|
const oldRecords = newRecords.filter((r) => r.domain_id === fromDomain.id);
|
|
for (const record of oldRecords) {
|
|
repos.unlinkBindingRecord(db, binding.id, record.id);
|
|
try {
|
|
await dnsService.deleteRecord(db, cf, fromDomain.id, record.id);
|
|
} catch {
|
|
// best-effort cleanup of old zone
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} catch (err) {
|
|
throw err instanceof AppError
|
|
? err
|
|
: AppError.syncFailed(
|
|
err instanceof Error ? err.message : "не удалось перенести привязки",
|
|
);
|
|
}
|
|
|
|
void createdRecordIds;
|
|
return {
|
|
...preview,
|
|
dry_run: false,
|
|
applied: true,
|
|
message: `Привязки перенесены в ${toDomain.zone_name}. Старые записи зоны удалены.`,
|
|
};
|
|
}
|