-
Notifications
You must be signed in to change notification settings - Fork 5
feat(dns): manage DNS zones and records #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
43bb65c
first pass
jamie-at-bunny 7f18945
updated structure from previous iteration
jamie-at-bunny 3f66933
fix logging and stats ui
jamie-at-bunny 48697c5
apply code suggestions
jamie-at-bunny 012303b
fix AcceleratedPullZoneId
jamie-at-bunny File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@bunny.net/openapi-client": patch | ||
| --- | ||
|
|
||
| fix: let legitimate non-JSON 200 responses pass the proxy-interception guard |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@bunny.net/cli": minor | ||
| --- | ||
|
|
||
| feat(dns): add experimental `bunny dns` commands for managing DNS zones and records |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import type { createCoreClient } from "@bunny.net/openapi-client"; | ||
| import type { components } from "@bunny.net/openapi-client/generated/core.d.ts"; | ||
| import { UserError } from "../../core/errors.ts"; | ||
|
|
||
| export type CoreClient = ReturnType<typeof createCoreClient>; | ||
| export type DnsZoneModel = components["schemas"]["DnsZoneModel"]; | ||
| export type DnsRecordModel = components["schemas"]["DnsRecordModel"]; | ||
|
|
||
| /** Fetch all DNS zones on the account, paginated and sorted by domain. */ | ||
| export async function fetchZones(client: CoreClient): Promise<DnsZoneModel[]> { | ||
| const zones: DnsZoneModel[] = []; | ||
| let page = 1; | ||
| for (;;) { | ||
| const { data } = await client.GET("/dnszone", { | ||
| params: { query: { page, perPage: 1000 } }, | ||
| }); | ||
| zones.push(...(data?.Items ?? [])); | ||
| if (!data?.HasMoreItems) break; | ||
| page++; | ||
| } | ||
| return zones.sort((a, b) => (a.Domain ?? "").localeCompare(b.Domain ?? "")); | ||
| } | ||
|
|
||
| /** Fetch a single DNS zone (including its records) by ID. */ | ||
| export async function fetchZone( | ||
| client: CoreClient, | ||
| id: number, | ||
| ): Promise<DnsZoneModel> { | ||
| const { data } = await client.GET("/dnszone/{id}", { | ||
| params: { path: { id } }, | ||
| }); | ||
| if (!data) throw new UserError(`DNS zone ${id} not found.`); | ||
| return data; | ||
| } | ||
|
|
||
| /** | ||
| * Resolve a zone reference (numeric ID or domain name) to a full zone. | ||
| * | ||
| * Numeric input is treated as a zone ID; anything else is matched against | ||
| * the account's zones by domain name. | ||
| */ | ||
| export async function resolveZone( | ||
| client: CoreClient, | ||
| domainOrId: string, | ||
| ): Promise<DnsZoneModel> { | ||
| const ref = domainOrId.trim(); | ||
| if (!ref) throw new UserError("A domain or zone ID is required."); | ||
|
|
||
| if (/^\d+$/.test(ref)) return fetchZone(client, Number(ref)); | ||
|
|
||
| const { data } = await client.GET("/dnszone", { | ||
| params: { query: { search: ref, perPage: 1000 } }, | ||
| }); | ||
| const match = (data?.Items ?? []).find( | ||
| (z) => (z.Domain ?? "").toLowerCase() === ref.toLowerCase(), | ||
| ); | ||
| if (!match?.Id) { | ||
| throw new UserError( | ||
| `No DNS zone found for "${domainOrId}".`, | ||
| 'Run "bunny dns zone list" to see your zones.', | ||
| ); | ||
| } | ||
| return fetchZone(client, match.Id); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { defineNamespace } from "../../core/define-namespace.ts"; | ||
| import { dnsRecordNamespace } from "./record/index.ts"; | ||
| import { dnsZoneHiddenAliases, dnsZoneNamespace } from "./zone/index.ts"; | ||
|
|
||
| // Hidden from help while experimental, matching the apps and registries namespaces. | ||
| export const dnsNamespace = defineNamespace("dns", false, [ | ||
| dnsRecordNamespace, | ||
| dnsZoneNamespace, | ||
| ...dnsZoneHiddenAliases, | ||
| ]); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import prompts from "prompts"; | ||
| import { UserError } from "../../core/errors.ts"; | ||
| import { spinner } from "../../core/ui.ts"; | ||
| import { | ||
| type CoreClient, | ||
| type DnsRecordModel, | ||
| type DnsZoneModel, | ||
| fetchZone, | ||
| fetchZones, | ||
| resolveZone, | ||
| } from "./api.ts"; | ||
| import { | ||
| formatRecordValue, | ||
| recordName, | ||
| recordTypeLabel, | ||
| } from "./record-types.ts"; | ||
|
|
||
| /** | ||
| * Resolve a zone by name/ID, or prompt the user to pick one when no | ||
| * reference is given. Manages its own spinner so it never spins over a prompt. | ||
| */ | ||
| export async function resolveZoneInteractive( | ||
| client: CoreClient, | ||
| ref: string | undefined, | ||
| ): Promise<DnsZoneModel> { | ||
| if (ref) { | ||
| const spin = spinner("Resolving zone..."); | ||
| spin.start(); | ||
| try { | ||
| return await resolveZone(client, ref); | ||
| } finally { | ||
| spin.stop(); | ||
| } | ||
| } | ||
|
|
||
| const spin = spinner("Fetching zones..."); | ||
| spin.start(); | ||
| let zones: DnsZoneModel[]; | ||
| try { | ||
| zones = await fetchZones(client); | ||
| } finally { | ||
| spin.stop(); | ||
| } | ||
|
|
||
| if (zones.length === 0) { | ||
| throw new UserError( | ||
| "No DNS zones found.", | ||
| 'Create one with "bunny dns zone add <domain>".', | ||
| ); | ||
| } | ||
|
|
||
| const { id } = await prompts({ | ||
| type: "select", | ||
| name: "id", | ||
| message: "Zone:", | ||
| choices: zones.map((z) => ({ title: z.Domain ?? "", value: z.Id })), | ||
| }); | ||
| if (id === undefined) throw new UserError("A zone is required."); | ||
|
|
||
| const resolveSpin = spinner("Loading zone..."); | ||
| resolveSpin.start(); | ||
| try { | ||
| return await fetchZone(client, id); | ||
| } finally { | ||
| resolveSpin.stop(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Return the record matching `id`, or prompt the user to pick one from the | ||
| * zone when no ID is given. | ||
| */ | ||
| export async function resolveRecordInteractive( | ||
| zone: DnsZoneModel, | ||
| id: number | undefined, | ||
| action: string, | ||
| ): Promise<DnsRecordModel> { | ||
| const records = zone.Records ?? []; | ||
|
|
||
| if (id !== undefined) { | ||
| const match = records.find((r) => r.Id === id); | ||
| if (!match) | ||
| throw new UserError(`Record ${id} not found in ${zone.Domain}.`); | ||
| return match; | ||
| } | ||
|
|
||
| if (records.length === 0) { | ||
| throw new UserError(`No records in ${zone.Domain}.`); | ||
| } | ||
|
|
||
| const { record } = await prompts({ | ||
| type: "select", | ||
| name: "record", | ||
| message: `Record to ${action}:`, | ||
| choices: records.map((r) => ({ | ||
| title: `${recordTypeLabel(r.Type)} ${recordName(r.Name)} → ${formatRecordValue(r)}`, | ||
| value: r, | ||
| })), | ||
| }); | ||
| if (!record) throw new UserError("A record is required."); | ||
| return record; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.