fix(integrations): отдавать origin IP и cnameTarget для CNAME sync
Публичный DNS даёт CF anycast; для матча в VPS Tracker нужны origin из dns_records и цель CNAME. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -68,13 +68,15 @@ async function resolveIpsViaDns(hostname: string): Promise<string[]> {
|
|||||||
* IP для матчинга в VPS Tracker:
|
* IP для матчинга в VPS Tracker:
|
||||||
* 1) A-записи binding
|
* 1) A-записи binding
|
||||||
* 2) разворот локальной CNAME-цепочки по другим bindings
|
* 2) разворот локальной CNAME-цепочки по другим bindings
|
||||||
* 3) публичный DNS (resolve4)
|
* 3) origin A/AAAA из dns_records CFDM (даже proxied — content = origin)
|
||||||
* 4) IP сервиса
|
* 4) публичный DNS (resolve4) — часто CF anycast, слабый сигнал
|
||||||
|
* 5) IP сервиса
|
||||||
*/
|
*/
|
||||||
export async function resolveBindingIpsForSync(
|
export async function resolveBindingIpsForSync(
|
||||||
binding: ServiceBindingView,
|
binding: ServiceBindingView,
|
||||||
serviceIps: string[],
|
serviceIps: string[],
|
||||||
index: BindingIpIndex,
|
index: BindingIpIndex,
|
||||||
|
db?: Db,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
if (binding.target_ips.length > 0) {
|
if (binding.target_ips.length > 0) {
|
||||||
return [...binding.target_ips];
|
return [...binding.target_ips];
|
||||||
@@ -86,6 +88,11 @@ export async function resolveBindingIpsForSync(
|
|||||||
const local = resolveIpsLocally(index, targetFqdn);
|
const local = resolveIpsLocally(index, targetFqdn);
|
||||||
if (local.length > 0) return local;
|
if (local.length > 0) return local;
|
||||||
|
|
||||||
|
if (db) {
|
||||||
|
const fromTable = repos.listOriginIpsForFqdn(db, targetFqdn);
|
||||||
|
if (fromTable.length > 0) return fromTable;
|
||||||
|
}
|
||||||
|
|
||||||
const viaDns = await resolveIpsViaDns(targetFqdn);
|
const viaDns = await resolveIpsViaDns(targetFqdn);
|
||||||
if (viaDns.length > 0) return viaDns;
|
if (viaDns.length > 0) return viaDns;
|
||||||
}
|
}
|
||||||
@@ -94,6 +101,13 @@ export async function resolveBindingIpsForSync(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cnameTargetForSync(binding: ServiceBindingView): string | undefined {
|
||||||
|
const raw = binding.cname_target?.trim();
|
||||||
|
if (!raw) return undefined;
|
||||||
|
const normalized = normalizeCnameHost(raw, binding.zone_name);
|
||||||
|
return normalized || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export async function buildServiceSyncBindingsAsync(
|
export async function buildServiceSyncBindingsAsync(
|
||||||
db: Db,
|
db: Db,
|
||||||
serviceId: number,
|
serviceId: number,
|
||||||
@@ -107,7 +121,7 @@ export async function buildServiceSyncBindingsAsync(
|
|||||||
|
|
||||||
const items: CfdmBindingSyncItem[] = [];
|
const items: CfdmBindingSyncItem[] = [];
|
||||||
for (const binding of bindings) {
|
for (const binding of bindings) {
|
||||||
const ips = await resolveBindingIpsForSync(binding, serviceIps, index);
|
const ips = await resolveBindingIpsForSync(binding, serviceIps, index, db);
|
||||||
items.push({
|
items.push({
|
||||||
bindingId: binding.id,
|
bindingId: binding.id,
|
||||||
serviceId: service.id,
|
serviceId: service.id,
|
||||||
@@ -117,6 +131,7 @@ export async function buildServiceSyncBindingsAsync(
|
|||||||
zoneName: binding.zone_name,
|
zoneName: binding.zone_name,
|
||||||
hostname: binding.hostname,
|
hostname: binding.hostname,
|
||||||
ips,
|
ips,
|
||||||
|
cnameTarget: cnameTargetForSync(binding),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +166,7 @@ export async function buildAllSyncBindings(
|
|||||||
serviceIps = repos.listServiceIps(db, binding.service_id);
|
serviceIps = repos.listServiceIps(db, binding.service_id);
|
||||||
serviceIpCache.set(binding.service_id, serviceIps);
|
serviceIpCache.set(binding.service_id, serviceIps);
|
||||||
}
|
}
|
||||||
const ips = await resolveBindingIpsForSync(binding, serviceIps, index);
|
const ips = await resolveBindingIpsForSync(binding, serviceIps, index, db);
|
||||||
items.push({
|
items.push({
|
||||||
bindingId: binding.id,
|
bindingId: binding.id,
|
||||||
serviceId: binding.service_id,
|
serviceId: binding.service_id,
|
||||||
@@ -161,6 +176,7 @@ export async function buildAllSyncBindings(
|
|||||||
zoneName: binding.zone_name,
|
zoneName: binding.zone_name,
|
||||||
hostname: binding.hostname,
|
hostname: binding.hostname,
|
||||||
ips,
|
ips,
|
||||||
|
cnameTarget: cnameTargetForSync(binding),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
|
|||||||
@@ -533,6 +533,49 @@ export function listDnsByDomain(db: Db, domainId: number): DnsRecord[] {
|
|||||||
.map(mapDnsRecord);
|
.map(mapDnsRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Origin A/AAAA из локальной таблицы dns_records для FQDN.
|
||||||
|
* content в CFDM — origin IP даже при proxied=true (в отличие от публичного DNS).
|
||||||
|
* Следует по CNAME-записям в той же БД.
|
||||||
|
*/
|
||||||
|
export function listOriginIpsForFqdn(
|
||||||
|
db: Db,
|
||||||
|
fqdn: string,
|
||||||
|
depth = 0,
|
||||||
|
): string[] {
|
||||||
|
if (depth > 8) return [];
|
||||||
|
const normalized = fqdn.trim().toLowerCase().replace(/\.+$/, "");
|
||||||
|
if (!normalized) return [];
|
||||||
|
|
||||||
|
const aIps: string[] = [];
|
||||||
|
let cnameNext: string | null = null;
|
||||||
|
|
||||||
|
for (const domain of listAllDomains(db)) {
|
||||||
|
const zone = domain.zone_name.trim().toLowerCase().replace(/\.+$/, "");
|
||||||
|
if (!zone) continue;
|
||||||
|
if (normalized !== zone && !normalized.endsWith(`.${zone}`)) continue;
|
||||||
|
|
||||||
|
const hostLabel =
|
||||||
|
normalized === zone ? "@" : normalized.slice(0, -(zone.length + 1));
|
||||||
|
|
||||||
|
for (const record of listDnsByDomain(db, domain.id)) {
|
||||||
|
const type = record.record_type.toUpperCase();
|
||||||
|
if (!dnsRecordNamesMatch(record.name, hostLabel, zone)) continue;
|
||||||
|
if (type === "A" || type === "AAAA") {
|
||||||
|
const ip = record.content.trim();
|
||||||
|
if (ip) aIps.push(ip);
|
||||||
|
} else if (type === "CNAME" && !cnameNext) {
|
||||||
|
const target = record.content.trim().replace(/\.+$/, "");
|
||||||
|
if (target) cnameNext = target.includes(".") ? target : `${target}.${zone}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aIps.length > 0) return [...new Set(aIps)];
|
||||||
|
if (cnameNext) return listOriginIpsForFqdn(db, cnameNext, depth + 1);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
export function findDnsByCfId(
|
export function findDnsByCfId(
|
||||||
db: Db,
|
db: Db,
|
||||||
domainId: number,
|
domainId: number,
|
||||||
|
|||||||
Vendored
+2
@@ -1467,6 +1467,7 @@ declare const cfdmBindingSyncItemSchema: z.ZodObject<{
|
|||||||
zoneName: z.ZodString;
|
zoneName: z.ZodString;
|
||||||
hostname: z.ZodString;
|
hostname: z.ZodString;
|
||||||
ips: z.ZodArray<z.ZodString>;
|
ips: z.ZodArray<z.ZodString>;
|
||||||
|
cnameTarget: z.ZodOptional<z.ZodString>;
|
||||||
deleted: z.ZodOptional<z.ZodBoolean>;
|
deleted: z.ZodOptional<z.ZodBoolean>;
|
||||||
}, z.core.$strip>;
|
}, z.core.$strip>;
|
||||||
declare const cfdmSyncBindingsBodySchema: z.ZodObject<{
|
declare const cfdmSyncBindingsBodySchema: z.ZodObject<{
|
||||||
@@ -1479,6 +1480,7 @@ declare const cfdmSyncBindingsBodySchema: z.ZodObject<{
|
|||||||
zoneName: z.ZodString;
|
zoneName: z.ZodString;
|
||||||
hostname: z.ZodString;
|
hostname: z.ZodString;
|
||||||
ips: z.ZodArray<z.ZodString>;
|
ips: z.ZodArray<z.ZodString>;
|
||||||
|
cnameTarget: z.ZodOptional<z.ZodString>;
|
||||||
deleted: z.ZodOptional<z.ZodBoolean>;
|
deleted: z.ZodOptional<z.ZodBoolean>;
|
||||||
}, z.core.$strip>>;
|
}, z.core.$strip>>;
|
||||||
fullSync: z.ZodOptional<z.ZodBoolean>;
|
fullSync: z.ZodOptional<z.ZodBoolean>;
|
||||||
|
|||||||
Vendored
+2
@@ -579,6 +579,8 @@ var cfdmBindingSyncItemSchema = z3.object({
|
|||||||
zoneName: z3.string().min(1),
|
zoneName: z3.string().min(1),
|
||||||
hostname: z3.string(),
|
hostname: z3.string(),
|
||||||
ips: z3.array(z3.string()),
|
ips: z3.array(z3.string()),
|
||||||
|
/** CNAME-цель (FQDN), если binding — CNAME; для матчинга в VPS Tracker. */
|
||||||
|
cnameTarget: z3.string().optional(),
|
||||||
deleted: z3.boolean().optional()
|
deleted: z3.boolean().optional()
|
||||||
});
|
});
|
||||||
var cfdmSyncBindingsBodySchema = z3.object({
|
var cfdmSyncBindingsBodySchema = z3.object({
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export const cfdmBindingSyncItemSchema = z.object({
|
|||||||
zoneName: z.string().min(1),
|
zoneName: z.string().min(1),
|
||||||
hostname: z.string(),
|
hostname: z.string(),
|
||||||
ips: z.array(z.string()),
|
ips: z.array(z.string()),
|
||||||
|
/** CNAME-цель (FQDN), если binding — CNAME; для матчинга в VPS Tracker. */
|
||||||
|
cnameTarget: z.string().optional(),
|
||||||
deleted: z.boolean().optional(),
|
deleted: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user