@sanoma-ai/connector-cloudflare 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -14
- package/dist/connector.d.ts +64 -3
- package/dist/connector.js +101 -2
- package/dist/connector.js.map +1 -1
- package/dist/driver.d.ts +3 -3
- package/dist/driver.js +41 -5
- package/dist/driver.js.map +1 -1
- package/dist/fake.d.ts +7 -2
- package/dist/fake.js +76 -5
- package/dist/fake.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/ops.d.ts +67 -1
- package/dist/ops.js +20 -10
- package/dist/ops.js.map +1 -1
- package/dist/records.d.ts +70 -0
- package/dist/records.js +325 -0
- package/dist/records.js.map +1 -0
- package/dist/resources.d.ts +25 -1
- package/dist/resources.js +4 -3
- package/dist/resources.js.map +1 -1
- package/features/records.feature +114 -0
- package/package.json +5 -4
package/dist/records.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { DriverError, errorMessage } from "@sanoma-ai/workflows";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { caaData, canonicalRecord, keyOf, records as recordsType } from "./connector.js";
|
|
4
|
+
import { inZone, NONE, refused, replanned, } from "./ops.js";
|
|
5
|
+
// `cloudflare.records`' operations over `CloudflareApi`: the driver's calls Cloudflare's DNS API
|
|
6
|
+
// with the SDK, the fake's answers from its zones. Both go through `recordsOps`, so they find,
|
|
7
|
+
// plan, adopt and refuse alike.
|
|
8
|
+
/** The permission a token needs for a set, as the dashboard names it. */
|
|
9
|
+
const DNS_EDIT = "Zone → DNS → Edit";
|
|
10
|
+
/** The longest comment a record of a Free zone holds, which the mark is. */
|
|
11
|
+
const MARK_MAX = 100;
|
|
12
|
+
const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
13
|
+
const heldOf = ({ id, comment, proxied, ...rest }) => {
|
|
14
|
+
const record = canonicalRecord(rest);
|
|
15
|
+
return { id, comment: comment ?? undefined, proxied: proxied === true, record, key: keyOf(record) };
|
|
16
|
+
};
|
|
17
|
+
/** `<zone>/<name>`, a set's id, split. */
|
|
18
|
+
function idOf(id) {
|
|
19
|
+
const at = id.indexOf("/");
|
|
20
|
+
if (at <= 0 || at === id.length - 1)
|
|
21
|
+
throw refused(`records: ${id} is not <zone>/<name>`);
|
|
22
|
+
return { zone: id.slice(0, at), name: id.slice(at + 1) };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The mark of the set `<zone>/<name>`, `sanoma:<zone>/<name>`: the comment of every record it
|
|
26
|
+
* writes; a refusal when it is longer than a record's comment holds on a Free zone.
|
|
27
|
+
*/
|
|
28
|
+
function markOf(zone, name) {
|
|
29
|
+
const mark = `sanoma:${zone}/${name}`;
|
|
30
|
+
if (mark.length > MARK_MAX) {
|
|
31
|
+
throw refused(`records ${zone}/${name}: its mark ${mark} is ${mark.length} characters, and a record's comment holds ${MARK_MAX}: give the set a shorter name`, "invalid");
|
|
32
|
+
}
|
|
33
|
+
return mark;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A set's declared records, each in its compared form, each once; a refusal for none, one the
|
|
37
|
+
* shared shape refuses, one whose name is not in the zone, an MX record without a priority, a
|
|
38
|
+
* CAA record's content not its fields, two of one type, name and content that differ otherwise
|
|
39
|
+
* (Cloudflare holds one of them), or a CNAME beside another record of its name.
|
|
40
|
+
*/
|
|
41
|
+
function declaredRecords(zone, name, given) {
|
|
42
|
+
const set = `${zone}/${name}`;
|
|
43
|
+
const parsed = recordsType.schema.shape.records.safeParse(given);
|
|
44
|
+
if (!parsed.success)
|
|
45
|
+
throw refused(`records ${set}: ${z.prettifyError(parsed.error)}`, "invalid");
|
|
46
|
+
const seen = new Map();
|
|
47
|
+
for (const record of parsed.data.map((r) => canonicalRecord(r))) {
|
|
48
|
+
if (!inZone(record.name, zone)) {
|
|
49
|
+
throw refused(`records ${set}: ${record.type} ${record.name} is not in the zone ${zone}`, "invalid");
|
|
50
|
+
}
|
|
51
|
+
if (record.type === "MX" && record.priority === undefined) {
|
|
52
|
+
throw refused(`records ${set}: the MX record ${record.name} needs a priority`, "invalid");
|
|
53
|
+
}
|
|
54
|
+
if (record.type === "CAA" && !caaData(record.content)) {
|
|
55
|
+
throw refused(`records ${set}: a CAA record's content is \`<flags> <tag> "<value>"\`, not ${record.content}`, "invalid");
|
|
56
|
+
}
|
|
57
|
+
const same = JSON.stringify([record.type, record.name, record.content]);
|
|
58
|
+
const before = seen.get(same);
|
|
59
|
+
if (before && keyOf(before) !== keyOf(record)) {
|
|
60
|
+
throw refused(`records ${set}: ${record.type} ${record.name} ${JSON.stringify(record.content)} is declared twice, with another ttl or priority`, "invalid");
|
|
61
|
+
}
|
|
62
|
+
seen.set(same, record);
|
|
63
|
+
}
|
|
64
|
+
const declared = [...seen.values()];
|
|
65
|
+
const cname = declared.find((r) => r.type === "CNAME" && declared.some((o) => o !== r && o.name === r.name));
|
|
66
|
+
if (cname) {
|
|
67
|
+
throw refused(`records ${set}: the CNAME ${cname.name} is declared beside another record of its name, which a CNAME cannot stand beside`, "invalid");
|
|
68
|
+
}
|
|
69
|
+
const second = declared.find((r) => spfRecord(r) && declared.some((o) => o !== r && o.name === r.name && spfRecord(o)));
|
|
70
|
+
if (second) {
|
|
71
|
+
throw refused(`records ${set}: two SPF policies are declared at ${second.name}, which makes both invalid: merge them into one`, "invalid");
|
|
72
|
+
}
|
|
73
|
+
return declared;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* How the zone's marked records become the declared ones: those kept as they are, those patched
|
|
77
|
+
* (a marked record of the same type and name, with other content, priority or ttl, or proxied by
|
|
78
|
+
* hand), the declared ones no marked record holds, which a write posts or adopts, and the marked
|
|
79
|
+
* ones no longer declared.
|
|
80
|
+
*/
|
|
81
|
+
function diff(declared, held) {
|
|
82
|
+
const left = [...held];
|
|
83
|
+
const take = (match) => {
|
|
84
|
+
const at = left.findIndex(match);
|
|
85
|
+
return at < 0 ? undefined : left.splice(at, 1)[0];
|
|
86
|
+
};
|
|
87
|
+
/** The records no marked one `matches`, the others paired with the one each takes, into `into`. */
|
|
88
|
+
const pair = (records, matches, into) => records.filter((record) => {
|
|
89
|
+
const slot = take((h) => matches(h, record));
|
|
90
|
+
if (slot)
|
|
91
|
+
into.push([slot, record]);
|
|
92
|
+
return !slot;
|
|
93
|
+
});
|
|
94
|
+
const kept = [];
|
|
95
|
+
const patched = [];
|
|
96
|
+
const at = (h, record) => h.record.type === record.type && h.record.name === record.name;
|
|
97
|
+
// Each declared record held exactly first, so a patch never takes a record another one keeps;
|
|
98
|
+
// then one of the same content (its ttl or priority changed), then any of its type and name.
|
|
99
|
+
const unheld = pair(declared, (h, record) => h.key === keyOf(record) && !h.proxied, kept);
|
|
100
|
+
const missing = pair(pair(unheld, (h, record) => at(h, record) && h.record.content === record.content, patched), at, patched);
|
|
101
|
+
return { kept: kept.map(([h]) => h), patched, missing, stale: left };
|
|
102
|
+
}
|
|
103
|
+
/** A declared record as a batch writes it, with the set's mark. */
|
|
104
|
+
const writeOf = (mark, record) => ({
|
|
105
|
+
type: record.type,
|
|
106
|
+
name: record.name,
|
|
107
|
+
ttl: record.ttl ?? 1,
|
|
108
|
+
proxied: false,
|
|
109
|
+
comment: mark,
|
|
110
|
+
...(record.type === "CAA" ? { data: caaData(record.content) } : { content: record.content }),
|
|
111
|
+
...(record.priority !== undefined && { priority: record.priority }),
|
|
112
|
+
});
|
|
113
|
+
/** A record as a refusal names it. */
|
|
114
|
+
const named = ({ record, comment }) => `${record.type} ${record.name} ${JSON.stringify(record.content)}${comment ? ` (its comment: ${comment})` : ""}`;
|
|
115
|
+
/**
|
|
116
|
+
* Whether an unmarked record at a declared record's name keeps it from being written: a CNAME,
|
|
117
|
+
* which no other record may stand beside (and a declared CNAME beside any record), a second SPF
|
|
118
|
+
* policy, which makes both invalid, or an MX record to the same server at another priority.
|
|
119
|
+
*/
|
|
120
|
+
const clashes = (record, { record: other }) => other.type === "CNAME" ||
|
|
121
|
+
record.type === "CNAME" ||
|
|
122
|
+
(spfRecord(record) && spfRecord(other)) ||
|
|
123
|
+
(record.type === "MX" && other.type === "MX" && record.content === other.content);
|
|
124
|
+
/** Whether a TXT record's text is an SPF policy: `v=spf1`, in any case. */
|
|
125
|
+
const isSpf = (text) => /^v=spf1(\s|$)/i.test(text);
|
|
126
|
+
/** Whether a record is an SPF policy: a TXT record whose text is one. */
|
|
127
|
+
const spfRecord = (record) => record.type === "TXT" && isSpf(record.content);
|
|
128
|
+
/** `call`'s answer, a 403 refused as the token's missing permission on the zone. */
|
|
129
|
+
async function dns(zone, call) {
|
|
130
|
+
try {
|
|
131
|
+
return await call();
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (!(error instanceof DriverError && error.status === 403))
|
|
135
|
+
throw error;
|
|
136
|
+
throw new DriverError(`cloudflare: the API token may not edit the DNS records of ${zone} (${errorMessage(error)}): give it ${DNS_EDIT} on the zone ${zone}`, { retryable: false, status: 403, vendorCode: "no_dns_permission", cause: error });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** The refusal of records given as `null` at a write: a backstop, since apply fails such a step before it gets here. */
|
|
140
|
+
const notKnown = (set) => refused(`records ${set}: its records are not known yet: apply the resource they come from first`, "not_known");
|
|
141
|
+
/** The refusal of a set whose zone is not listed: the account holds no such zone, or the token cannot read it. */
|
|
142
|
+
const noZone = (set, zone) => refused(`records ${set}: the account holds no zone ${zone}, or the API token cannot read it: add the zone to the account, or Zone Read and ${DNS_EDIT} on it to the token`, "no_zone");
|
|
143
|
+
/**
|
|
144
|
+
* The set `id`: its mark (`sanoma:<zone>/<name>`, the comment of every record it writes), its
|
|
145
|
+
* zone's id (undefined when the account holds no such zone) and the records that carry its mark.
|
|
146
|
+
*/
|
|
147
|
+
async function look(cf, id) {
|
|
148
|
+
const { zone, name } = idOf(id);
|
|
149
|
+
const mark = markOf(zone, name);
|
|
150
|
+
const zoneId = await cf.zone(zone);
|
|
151
|
+
// Cloudflare's `comment.exact` ignores case: a comment that is the mark in another case is not the set's.
|
|
152
|
+
const held = zoneId === undefined
|
|
153
|
+
? []
|
|
154
|
+
: (await dns(zone, () => cf.dnsRecords(zoneId, mark))).map(heldOf).filter((h) => h.comment === mark);
|
|
155
|
+
return { zone, name, mark, zoneId, held };
|
|
156
|
+
}
|
|
157
|
+
/** A set's state, from the records that carry its mark. */
|
|
158
|
+
function stateOf(zone, name, zoneId, held) {
|
|
159
|
+
const sorted = held.toSorted((a, b) => compare(a.key, b.key));
|
|
160
|
+
return { zone, name, records: sorted.map((h) => h.record), zone_id: zoneId, record_ids: sorted.map((h) => h.id) };
|
|
161
|
+
}
|
|
162
|
+
/** What `desired` changes of the set `found` found (none found: a create). */
|
|
163
|
+
function planOf(found, desired) {
|
|
164
|
+
const zone = String(desired.zone);
|
|
165
|
+
const name = String(desired.name);
|
|
166
|
+
markOf(zone, name);
|
|
167
|
+
// `null`: taken from another resource's state, known only after apply.
|
|
168
|
+
const given = desired.records;
|
|
169
|
+
const records = given === null || given === undefined
|
|
170
|
+
? given
|
|
171
|
+
: declaredRecords(zone, name, given).toSorted((a, b) => compare(keyOf(a), keyOf(b)));
|
|
172
|
+
/** A new set's unknown fields: Cloudflare's, and its records while they are not known. */
|
|
173
|
+
const unknown = [...(records == null ? ["records"] : []), ...recordsType.fields.vendorOwned];
|
|
174
|
+
const anew = { zone, name, records: records ?? null };
|
|
175
|
+
if (!found?.zoneId || found.held.length === 0)
|
|
176
|
+
return { action: "create", planned: anew, unknown, replace: [] };
|
|
177
|
+
const state = stateOf(found.zone, found.name, found.zoneId, found.held);
|
|
178
|
+
const replace = recordsType.fields.immutable.filter((field) => desired[field] !== state[field]);
|
|
179
|
+
if (replace.length > 0)
|
|
180
|
+
return { action: "replace", planned: anew, unknown, replace };
|
|
181
|
+
// Left out: its resource is absent or unreadable, so the set keeps what it holds.
|
|
182
|
+
if (records === undefined)
|
|
183
|
+
return { action: "none", planned: state, ...NONE };
|
|
184
|
+
if (records === null) {
|
|
185
|
+
return {
|
|
186
|
+
action: "update",
|
|
187
|
+
planned: { ...state, records: null, record_ids: null },
|
|
188
|
+
unknown: ["records", "record_ids"],
|
|
189
|
+
replace: [],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const { patched, missing, stale } = diff(records, found.held);
|
|
193
|
+
if (patched.length + missing.length + stale.length === 0)
|
|
194
|
+
return { action: "none", planned: state, ...NONE };
|
|
195
|
+
return { action: "update", planned: { ...state, records, record_ids: null }, unknown: ["record_ids"], replace: [] };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `cloudflare.records`' operations over `api()`, made on each call. A read finds a set's records
|
|
199
|
+
* by its mark alone; a write is one batch, all or nothing, of posts, patches (a marked record of a
|
|
200
|
+
* declared one's type and name, or an unmarked one identical to it, adopted: its comment replaced
|
|
201
|
+
* by the mark) and deletes (marked, no longer declared), and never touches another record: one in
|
|
202
|
+
* its way (a CNAME at the name, another SPF policy, an MX to the same server at another priority,
|
|
203
|
+
* a proxied one identical to it) is refused, naming it.
|
|
204
|
+
*/
|
|
205
|
+
export function recordsOps(api) {
|
|
206
|
+
/** The set `id` as the zone holds it, or gone: no record carries its mark, or the zone is not the account's. */
|
|
207
|
+
const read = async ({ id }) => {
|
|
208
|
+
const { zone, name, zoneId, held } = await look(api(), id);
|
|
209
|
+
if (zoneId === undefined || held.length === 0)
|
|
210
|
+
return { id, gone: true };
|
|
211
|
+
return { id, gone: false, state: stateOf(zone, name, zoneId, held) };
|
|
212
|
+
};
|
|
213
|
+
const plan = async ({ id, desired }) => {
|
|
214
|
+
// A create plans without the account: it needs no credentials yet.
|
|
215
|
+
const found = id === undefined ? undefined : await look(api(), id);
|
|
216
|
+
if (!desired)
|
|
217
|
+
return { action: found?.held.length ? "delete" : "none", ...NONE };
|
|
218
|
+
return planOf(found, desired);
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Makes the zone hold the declared records, in one batch: a create and an update alike, the
|
|
222
|
+
* latter with the account and what its plan found.
|
|
223
|
+
*/
|
|
224
|
+
const put = async (desired, before) => {
|
|
225
|
+
const set = `${String(desired.zone)}/${String(desired.name)}`;
|
|
226
|
+
if (desired.records === null || desired.records === undefined)
|
|
227
|
+
throw notKnown(set);
|
|
228
|
+
const declared = declaredRecords(String(desired.zone), String(desired.name), desired.records);
|
|
229
|
+
const cf = before?.cf ?? api();
|
|
230
|
+
const { zone, name, mark, zoneId, held } = before?.found ?? (await look(cf, set));
|
|
231
|
+
if (zoneId === undefined) {
|
|
232
|
+
throw noZone(set, zone);
|
|
233
|
+
}
|
|
234
|
+
const { kept, patched, missing, stale } = diff(declared, held);
|
|
235
|
+
const batch = { posts: [], patches: [], deletes: stale.map(({ id }) => ({ id })) };
|
|
236
|
+
// The other records at each name a record is written to, looked up once per name, before
|
|
237
|
+
// anything is written: one identical to it is adopted, one in its way refused.
|
|
238
|
+
const others = new Map();
|
|
239
|
+
const othersAt = async (at) => {
|
|
240
|
+
let found = others.get(at);
|
|
241
|
+
if (!found) {
|
|
242
|
+
found = (await dns(zone, () => cf.dnsRecordsAt(zoneId, at))).map(heldOf).filter((h) => h.comment !== mark);
|
|
243
|
+
others.set(at, found);
|
|
244
|
+
}
|
|
245
|
+
return found;
|
|
246
|
+
};
|
|
247
|
+
/** The other record at its name (`at`) identical to `record` (its type, content and priority), taken to adopt; a refusal for another set's or a proxied one. */
|
|
248
|
+
const adoptable = (record, at) => {
|
|
249
|
+
const same = at.findIndex((o) => o.record.type === record.type && o.record.content === record.content && o.record.priority === record.priority);
|
|
250
|
+
if (same < 0)
|
|
251
|
+
return undefined;
|
|
252
|
+
const [adopted] = at.splice(same, 1);
|
|
253
|
+
if (adopted.comment?.startsWith("sanoma:")) {
|
|
254
|
+
throw refused(`records ${set}: ${record.type} ${record.name} is another set's: ${named(adopted)}`, "conflict");
|
|
255
|
+
}
|
|
256
|
+
if (adopted.proxied) {
|
|
257
|
+
throw refused(`records ${set}: ${named(adopted)} is proxied through Cloudflare, and a set's records are not: turn its proxy off by hand to have it adopted, or delete it`, "conflict");
|
|
258
|
+
}
|
|
259
|
+
return adopted;
|
|
260
|
+
};
|
|
261
|
+
// Each record to patch (its marked `slot`) or to post, in that order.
|
|
262
|
+
const writes = [
|
|
263
|
+
...patched,
|
|
264
|
+
...missing.map((r) => [undefined, r]),
|
|
265
|
+
];
|
|
266
|
+
for (const [slot, record] of writes) {
|
|
267
|
+
// What another record already is: that one adopted instead (its comment replaced by the mark), its own deleted.
|
|
268
|
+
const at = await othersAt(record.name);
|
|
269
|
+
const adopted = adoptable(record, at);
|
|
270
|
+
if (adopted) {
|
|
271
|
+
if (slot)
|
|
272
|
+
batch.deletes.push({ id: slot.id });
|
|
273
|
+
batch.patches.push({ id: adopted.id, ...writeOf(mark, record) });
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const clash = at.find((other) => clashes(record, other));
|
|
277
|
+
if (clash) {
|
|
278
|
+
throw refused(`records ${set}: ${record.type} ${record.name} cannot be written beside ${named(clash)}, which is not this set's: delete or change it by hand, or declare it as it is, which adopts it`, "conflict");
|
|
279
|
+
}
|
|
280
|
+
if (slot)
|
|
281
|
+
batch.patches.push({ id: slot.id, ...writeOf(mark, record) });
|
|
282
|
+
else
|
|
283
|
+
batch.posts.push(writeOf(mark, record));
|
|
284
|
+
}
|
|
285
|
+
const written = batch.posts.length + batch.patches.length + batch.deletes.length === 0
|
|
286
|
+
? { patches: [], posts: [] }
|
|
287
|
+
: await dns(zone, () => cf.writeDns(zoneId, batch));
|
|
288
|
+
const now = [...kept, ...[...written.patches, ...written.posts].map(heldOf)];
|
|
289
|
+
return { id: set, state: stateOf(zone, name, zoneId, now) };
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
import: read,
|
|
293
|
+
read,
|
|
294
|
+
plan,
|
|
295
|
+
create: ({ desired }) => put(desired),
|
|
296
|
+
update: async ({ id, desired, expect, }) => {
|
|
297
|
+
// Before any request, as at a create.
|
|
298
|
+
if (desired.records === null)
|
|
299
|
+
throw notKnown(id);
|
|
300
|
+
const cf = api();
|
|
301
|
+
const found = await look(cf, id);
|
|
302
|
+
const fresh = planOf(found, desired);
|
|
303
|
+
// An update planned and found done (a retry after its batch was written) is done: the zone holds what was declared.
|
|
304
|
+
if (!(expect?.action === "update" && fresh.action === "none"))
|
|
305
|
+
replanned("records.update", fresh, expect);
|
|
306
|
+
// Records left out, or as declared: what the zone holds, unwritten.
|
|
307
|
+
if (fresh.action === "none")
|
|
308
|
+
return { id, state: fresh.planned };
|
|
309
|
+
return put(desired, { cf, found });
|
|
310
|
+
},
|
|
311
|
+
delete: async ({ id }) => {
|
|
312
|
+
const cf = api();
|
|
313
|
+
const { zone, zoneId, held } = await look(cf, id);
|
|
314
|
+
// A zone the token cannot read is not listed, as one the account does not hold: Cloudflare
|
|
315
|
+
// answers both alike, so a delete is refused rather than done with the records left behind.
|
|
316
|
+
if (zoneId === undefined)
|
|
317
|
+
throw noZone(id, zone);
|
|
318
|
+
if (held.length > 0) {
|
|
319
|
+
await dns(zone, () => cf.writeDns(zoneId, { posts: [], patches: [], deletes: held.map((h) => ({ id: h.id })) }));
|
|
320
|
+
}
|
|
321
|
+
return { id, gone: true };
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"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"}
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The constructors a data file declares the company's Cloudflare host and its wakeup with:
|
|
3
3
|
* `export const host = cloudflare.container({ name: "acme", image: "ghcr.io/sanoma-ai/runtime:0.7.0", source: repo, ref: "main", port: 3000, secrets: { SANOMA_DATABASE_URL: secret("SANOMA_DATABASE_URL"), SANOMA_WAKEUP_TOKEN: secret("SANOMA_WAKEUP_TOKEN", { generate: true }) } })`
|
|
4
|
-
* and `export const wakeup = cloudflare.alarm({ host })
|
|
4
|
+
* and `export const wakeup = cloudflare.alarm({ host })`, and a set of DNS records, such as a
|
|
5
|
+
* sending domain's: `export const mailDns = cloudflare.records({ zone: "example.com", name: "resend", records: mail })`.
|
|
5
6
|
*/
|
|
6
7
|
export declare const cloudflare: {
|
|
7
8
|
container: import("@sanoma-ai/workflows").Resource<import("zod").ZodObject<{
|
|
@@ -34,4 +35,27 @@ export declare const cloudflare: {
|
|
|
34
35
|
readonly by: readonly ["url"];
|
|
35
36
|
};
|
|
36
37
|
}, true>;
|
|
38
|
+
records: import("@sanoma-ai/workflows").Resource<import("zod").ZodObject<{
|
|
39
|
+
zone: import("zod").ZodString;
|
|
40
|
+
name: import("zod").ZodString;
|
|
41
|
+
records: import("zod").ZodArray<import("zod").ZodObject<{
|
|
42
|
+
type: import("zod").ZodEnum<{
|
|
43
|
+
CAA: "CAA";
|
|
44
|
+
CNAME: "CNAME";
|
|
45
|
+
MX: "MX";
|
|
46
|
+
TXT: "TXT";
|
|
47
|
+
}>;
|
|
48
|
+
name: import("zod").ZodString;
|
|
49
|
+
content: import("zod").ZodString;
|
|
50
|
+
priority: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
51
|
+
ttl: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
52
|
+
}, import("zod/v4/core").$strip>>;
|
|
53
|
+
zone_id: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
|
|
54
|
+
record_ids: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodArray<import("zod").ZodString>>>;
|
|
55
|
+
}, import("zod/v4/core").$strip>, "cloudflare.records", {
|
|
56
|
+
readonly records: {
|
|
57
|
+
readonly type: readonly ["resend.domain"];
|
|
58
|
+
readonly from: "dns_records";
|
|
59
|
+
};
|
|
60
|
+
}, true>;
|
|
37
61
|
};
|
package/dist/resources.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { alarm, container } from "./connector.js";
|
|
1
|
+
import { alarm, container, records } from "./connector.js";
|
|
2
2
|
/**
|
|
3
3
|
* The constructors a data file declares the company's Cloudflare host and its wakeup with:
|
|
4
4
|
* `export const host = cloudflare.container({ name: "acme", image: "ghcr.io/sanoma-ai/runtime:0.7.0", source: repo, ref: "main", port: 3000, secrets: { SANOMA_DATABASE_URL: secret("SANOMA_DATABASE_URL"), SANOMA_WAKEUP_TOKEN: secret("SANOMA_WAKEUP_TOKEN", { generate: true }) } })`
|
|
5
|
-
* and `export const wakeup = cloudflare.alarm({ host })
|
|
5
|
+
* and `export const wakeup = cloudflare.alarm({ host })`, and a set of DNS records, such as a
|
|
6
|
+
* sending domain's: `export const mailDns = cloudflare.records({ zone: "example.com", name: "resend", records: mail })`.
|
|
6
7
|
*/
|
|
7
|
-
export const cloudflare = { container, alarm };
|
|
8
|
+
export const cloudflare = { container, alarm, records };
|
|
8
9
|
//# sourceMappingURL=resources.js.map
|
package/dist/resources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Feature: Cloudflare DNS records
|
|
2
|
+
A set of DNS records in a zone, `<zone>/<name>`, each record marked with the comment
|
|
3
|
+
`sanoma:<zone>/<name>`, is created, read, planned, updated and deleted. Each scenario runs on
|
|
4
|
+
the fake and on the replies in test/fixtures/, named after the scenario (written from
|
|
5
|
+
Cloudflare's API reference until the first live run records them).
|
|
6
|
+
|
|
7
|
+
Background:
|
|
8
|
+
When cloudflare.records.create is called with
|
|
9
|
+
"""
|
|
10
|
+
{
|
|
11
|
+
"desired": {
|
|
12
|
+
"zone": "sanoma.work",
|
|
13
|
+
"name": "check",
|
|
14
|
+
"records": [
|
|
15
|
+
{ "type": "TXT", "name": "check-dkim._domainkey.sanoma.work", "content": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcheck" },
|
|
16
|
+
{ "type": "MX", "name": "check-send.sanoma.work", "content": "feedback-smtp.us-east-1.amazonses.com", "priority": 10 },
|
|
17
|
+
{ "type": "TXT", "name": "check-send.sanoma.work", "content": "v=spf1 include:amazonses.com ~all" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
Scenario: Records create
|
|
24
|
+
Then it returns
|
|
25
|
+
"""
|
|
26
|
+
{
|
|
27
|
+
"id": "sanoma.work/check",
|
|
28
|
+
"state": {
|
|
29
|
+
"zone": "sanoma.work",
|
|
30
|
+
"name": "check",
|
|
31
|
+
"records": [
|
|
32
|
+
{ "type": "MX", "name": "check-send.sanoma.work", "content": "feedback-smtp.us-east-1.amazonses.com", "priority": 10, "ttl": 1 },
|
|
33
|
+
{ "type": "TXT", "name": "check-dkim._domainkey.sanoma.work", "content": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcheck", "ttl": 1 },
|
|
34
|
+
{ "type": "TXT", "name": "check-send.sanoma.work", "content": "v=spf1 include:amazonses.com ~all", "ttl": 1 }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
Scenario: Records read
|
|
41
|
+
When cloudflare.records.read is called with
|
|
42
|
+
| id | sanoma.work/check |
|
|
43
|
+
Then it returns
|
|
44
|
+
"""
|
|
45
|
+
{
|
|
46
|
+
"gone": false,
|
|
47
|
+
"state": {
|
|
48
|
+
"zone": "sanoma.work",
|
|
49
|
+
"name": "check",
|
|
50
|
+
"records": [
|
|
51
|
+
{ "type": "MX", "name": "check-send.sanoma.work", "content": "feedback-smtp.us-east-1.amazonses.com", "priority": 10, "ttl": 1 },
|
|
52
|
+
{ "type": "TXT", "name": "check-dkim._domainkey.sanoma.work", "content": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcheck", "ttl": 1 },
|
|
53
|
+
{ "type": "TXT", "name": "check-send.sanoma.work", "content": "v=spf1 include:amazonses.com ~all", "ttl": 1 }
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
Scenario: Records plan none
|
|
60
|
+
When cloudflare.records.plan is called with
|
|
61
|
+
"""
|
|
62
|
+
{
|
|
63
|
+
"id": "sanoma.work/check",
|
|
64
|
+
"desired": {
|
|
65
|
+
"zone": "sanoma.work",
|
|
66
|
+
"name": "check",
|
|
67
|
+
"records": [
|
|
68
|
+
{ "type": "TXT", "name": "check-send.sanoma.work", "content": "v=spf1 include:amazonses.com ~all" },
|
|
69
|
+
{ "type": "MX", "name": "check-send.sanoma.work", "content": "feedback-smtp.us-east-1.amazonses.com", "priority": 10, "ttl": 1 },
|
|
70
|
+
{ "type": "TXT", "name": "check-dkim._domainkey.sanoma.work", "content": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcheck" }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
"""
|
|
75
|
+
Then it returns
|
|
76
|
+
"""
|
|
77
|
+
{ "action": "none", "unknown": [], "replace": [] }
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
Scenario: Records update
|
|
81
|
+
When cloudflare.records.update is called with
|
|
82
|
+
"""
|
|
83
|
+
{
|
|
84
|
+
"id": "sanoma.work/check",
|
|
85
|
+
"desired": {
|
|
86
|
+
"zone": "sanoma.work",
|
|
87
|
+
"name": "check",
|
|
88
|
+
"records": [
|
|
89
|
+
{ "type": "MX", "name": "check-send.sanoma.work", "content": "feedback-smtp.us-east-1.amazonses.com", "priority": 10 },
|
|
90
|
+
{ "type": "TXT", "name": "check-send.sanoma.work", "content": "v=spf1 include:amazonses.com -all" }
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
"""
|
|
95
|
+
Then it returns
|
|
96
|
+
"""
|
|
97
|
+
{
|
|
98
|
+
"id": "sanoma.work/check",
|
|
99
|
+
"state": {
|
|
100
|
+
"records": [
|
|
101
|
+
{ "type": "MX", "name": "check-send.sanoma.work", "content": "feedback-smtp.us-east-1.amazonses.com", "priority": 10, "ttl": 1 },
|
|
102
|
+
{ "type": "TXT", "name": "check-send.sanoma.work", "content": "v=spf1 include:amazonses.com -all", "ttl": 1 }
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
Scenario: Records delete
|
|
109
|
+
When cloudflare.records.delete is called with
|
|
110
|
+
| id | sanoma.work/check |
|
|
111
|
+
And cloudflare.records.read is called with
|
|
112
|
+
| id | sanoma.work/check |
|
|
113
|
+
Then it returns
|
|
114
|
+
| gone | true |
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanoma-ai/connector-cloudflare",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Cloudflare for Sanoma: a Container host that stops when quiet,
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Cloudflare for Sanoma: a Container host that stops when quiet, a Durable Object alarm that wakes it, from one Worker, and a zone's DNS records",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
7
7
|
"connector",
|
|
8
8
|
"containers",
|
|
9
|
+
"dns",
|
|
9
10
|
"durable-objects",
|
|
10
11
|
"host",
|
|
11
12
|
"resources",
|
|
@@ -61,12 +62,12 @@
|
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@sanoma-ai/testing": "0.1.0",
|
|
64
|
-
"@sanoma-ai/workflows": "0.
|
|
65
|
+
"@sanoma-ai/workflows": "0.11.0",
|
|
65
66
|
"msw": "^3.0.3",
|
|
66
67
|
"zod": "^4.6.5"
|
|
67
68
|
},
|
|
68
69
|
"peerDependencies": {
|
|
69
|
-
"@sanoma-ai/workflows": "^0.
|
|
70
|
+
"@sanoma-ai/workflows": "^0.11.0",
|
|
70
71
|
"zod": "^4"
|
|
71
72
|
},
|
|
72
73
|
"engines": {
|