Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions alchemy-web/src/content/docs/providers/cloudflare/dns-records.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,32 @@ const emailRecords = await DnsRecords("email-dns", {
});
```

## Preserve Records on Destroy

Use `delete: false` to prevent DNS records from being deleted when the resource is removed from Alchemy. This is useful for critical DNS records that should persist even if their Alchemy configuration is removed.

```ts
import { DnsRecords } from "alchemy/cloudflare";

const records = await DnsRecords("critical-dns", {
zoneId: "YOUR_ZONE_ID",
delete: false,
records: [
{
name: "example.com",
type: "MX",
content: "aspmx.l.google.com",
priority: 1,
},
{
name: "example.com",
type: "TXT",
content: "v=spf1 include:_spf.google.com ~all",
},
],
});
```

## Proxied Records

Create proxied records to take advantage of Cloudflare's CDN and security features.
Expand Down
41 changes: 25 additions & 16 deletions alchemy/src/cloudflare/dns-records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ export interface DnsRecordsProps extends CloudflareApiOptions {
* Array of DNS records to manage
*/
records: DnsRecordProps[];

/**
* Whether to delete DNS records when the resource is destroyed.
* Set to false to preserve records in Cloudflare when removing from alchemy.
* @default true
*/
delete?: boolean;
}

/**
Expand Down Expand Up @@ -139,7 +146,7 @@ export const DnsRecords = Resource(
const zoneId = props.zoneId;

if (this.phase === "delete") {
if (this.output?.records) {
if (props.delete !== false && this.output?.records) {
// Delete all existing records
await Promise.all(
this.output.records.map(async (record) => {
Expand Down Expand Up @@ -174,23 +181,25 @@ export const DnsRecords = Resource(
}
}

// Delete orphaned records
await Promise.all(
recordsToDelete.map(async (record) => {
try {
const response = await api.delete(
`/zones/${zoneId}/dns_records/${record.id}`,
);
if (!response.ok && response.status !== 404) {
logger.error(
`Failed to delete DNS record ${record.name}: ${response.statusText}`,
// Delete orphaned records (skip if delete: false)
if (props.delete !== false) {
await Promise.all(
recordsToDelete.map(async (record) => {
try {
const response = await api.delete(
`/zones/${zoneId}/dns_records/${record.id}`,
);
if (!response.ok && response.status !== 404) {
logger.error(
`Failed to delete DNS record ${record.name}: ${response.statusText}`,
);
}
} catch (error) {
logger.error(`Error deleting DNS record ${record.name}:`, error);
}
} catch (error) {
logger.error(`Error deleting DNS record ${record.name}:`, error);
}
}),
);
}),
);
}

// Update or create records
const updatedRecords = await Promise.all(
Expand Down
42 changes: 42 additions & 0 deletions alchemy/test/cloudflare/dns-records.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,48 @@ describe.sequential("DnsRecords Resource", async () => {
}
});

test("delete: false preserves records on destroy", async (scope) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: is not allowed in test names, please remove the :

let dnsRecords;
try {
// Create records with delete: false
dnsRecords = await DnsRecords(`${testDomain}-nodelete-dns`, {
zoneId: zone.id,
delete: false,
records: [
{
name: `nodelete.${testDomain}`,
type: "A",
content: "192.0.2.1",
proxied: false,
},
],
});

expect(dnsRecords.records).toHaveLength(1);
} finally {
// Destroy should NOT delete the actual DNS records
await destroy(scope);

// Verify records still exist in Cloudflare
if (dnsRecords?.records) {
for (const record of dnsRecords.records) {
const response = await api.get(
`/zones/${dnsRecords.zoneId}/dns_records/${record.id}`,
);
// Record should still exist (NOT 404)
expect(response.ok).toBe(true);
}

// Manual cleanup since delete: false preserved them
for (const record of dnsRecords.records) {
await api.delete(
`/zones/${dnsRecords.zoneId}/dns_records/${record.id}`,
);
}
}
}
});

test("handles duplicate records gracefully", async (scope) => {
try {
const dnsRecords = await DnsRecords(`${testDomain}-duplicate-dns`, {
Expand Down
Loading