From e8f236c9dbce7fb30581e2661ed33e61f1b34104 Mon Sep 17 00:00:00 2001 From: Tom Ron Date: Wed, 7 Oct 2026 22:02:45 +0300 Subject: [PATCH] feat(dns): add optional zone to OVH provider Co-Authored-By: Claude Sonnet 5.5 --- apps/dokploy/__test__/dns/ovh.test.ts | 21 ++++++++++++++++++ .../settings/dns/handle-dns-provider.tsx | 22 +++++++++++++++++++ packages/server/src/db/schema/dns-provider.ts | 1 + packages/server/src/utils/dns/ovh.ts | 7 ++++++ 4 files changed, 51 insertions(+) diff --git a/apps/dokploy/__test__/dns/ovh.test.ts b/apps/dokploy/__test__/dns/ovh.test.ts index 7e08d7f99..38b968add 100644 --- a/apps/dokploy/__test__/dns/ovh.test.ts +++ b/apps/dokploy/__test__/dns/ovh.test.ts @@ -166,6 +166,16 @@ describe("ovhClient.listZones", () => { await expect(ovhClient.listZones(cfg)).rejects.toThrow("Invalid signature"); }); + + it("returns only the configured zone without listing every zone", async () => { + const cfg = { ...freshConfig(), zone: "example.com" }; + mockApi(); + + const zones = await ovhClient.listZones(cfg); + + expect(zones).toEqual([{ id: "example.com", name: "example.com" }]); + expect(apiCalls()).toHaveLength(0); + }); }); describe("ovhClient.listRecords", () => { @@ -578,4 +588,15 @@ describe("ovhClient.testConnection", () => { "Invalid signature", ); }); + + it("checks the configured zone instead of listing all zones", async () => { + const cfg = { ...freshConfig(), zone: "example.com" }; + mockApi(ovhSuccess({ name: "example.com" })); + + await expect(ovhClient.testConnection(cfg)).resolves.toBeUndefined(); + + const calls = apiCalls(); + expect(calls).toHaveLength(1); + expect(calls[0]?.[0]).toBe(`${cfg.baseUrl}/domain/zone/example.com`); + }); }); diff --git a/apps/dokploy/components/dashboard/settings/dns/handle-dns-provider.tsx b/apps/dokploy/components/dashboard/settings/dns/handle-dns-provider.tsx index d2d8c0d3f..0201999e8 100644 --- a/apps/dokploy/components/dashboard/settings/dns/handle-dns-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/dns/handle-dns-provider.tsx @@ -88,6 +88,7 @@ const DnsProviderSchema = z.object({ applicationKey: z.string(), applicationSecret: z.string(), consumerKey: z.string(), + zone: z.string(), }); type DnsProviderForm = z.infer; @@ -104,6 +105,7 @@ const defaultValues: DnsProviderForm = { applicationKey: "", applicationSecret: "", consumerKey: "", + zone: "", }; const buildConfig = (data: DnsProviderForm) => { @@ -137,6 +139,7 @@ const buildConfig = (data: DnsProviderForm) => { applicationKey: data.applicationKey, applicationSecret: data.applicationSecret, consumerKey: data.consumerKey, + zone: data.zone.trim() || undefined, }; } }; @@ -207,6 +210,7 @@ export const HandleDnsProvider = ({ dnsProviderId }: Props) => { applicationKey: provider.config.applicationKey, applicationSecret: provider.config.applicationSecret, consumerKey: provider.config.consumerKey, + zone: provider.config.zone ?? "", }), }); } else if (!dnsProviderId) { @@ -545,6 +549,24 @@ export const HandleDnsProvider = ({ dnsProviderId }: Props) => { )} /> + ( + + Zone (optional) + + + + + Restrict this provider to a single DNS zone. Leave empty + to use every zone the credentials can access. When set, + the GET /domain/zone right is not needed. + + + + )} + /> )} diff --git a/packages/server/src/db/schema/dns-provider.ts b/packages/server/src/db/schema/dns-provider.ts index 0ed6aca71..4cebb4562 100644 --- a/packages/server/src/db/schema/dns-provider.ts +++ b/packages/server/src/db/schema/dns-provider.ts @@ -50,6 +50,7 @@ export const ovhDnsConfigSchema = z.object({ applicationKey: z.string().trim().min(1), applicationSecret: z.string().trim().min(1), consumerKey: z.string().trim().min(1), + zone: z.string().trim().min(1).optional(), }); export const dnsProviderConfigSchema = z.discriminatedUnion("providerType", [ diff --git a/packages/server/src/utils/dns/ovh.ts b/packages/server/src/utils/dns/ovh.ts index 67fbd6ca6..8106d3e31 100644 --- a/packages/server/src/utils/dns/ovh.ts +++ b/packages/server/src/utils/dns/ovh.ts @@ -254,6 +254,9 @@ const listZoneNames = async (config: OvhConfig) => { export const ovhClient: DnsClient = { async listZones(config) { + if (config.zone) { + return [{ id: config.zone, name: config.zone }]; + } const zones = await listZoneNames(config); return zones.map((zone) => ({ id: zone, name: zone })); }, @@ -372,6 +375,10 @@ export const ovhClient: DnsClient = { }, async testConnection(config) { + if (config.zone) { + await ovhFetch(config, `/domain/zone/${encodeURIComponent(config.zone)}`); + return; + } await listZoneNames(config); }, };