feat(cloudflare/r2): add bucket custom domains#241
feat(cloudflare/r2): add bucket custom domains#241agcty wants to merge 4 commits intoalchemy-run:mainfrom
Conversation
|
Verification update:
|
john-royal
left a comment
There was a problem hiding this comment.
Just some minor changes here. Once you make those, should be good to go.
| Effect.flatMap((bucket) => | ||
| Effect.gen(function* () { | ||
| const jurisdiction = bucket.jurisdiction ?? "default"; | ||
| const existingDomains = | ||
| output?.domains && output.domains.length > 0 | ||
| ? yield* listCustomDomains(bucket.name!, jurisdiction) | ||
| : []; | ||
| const ownedDomains = new Set( | ||
| output?.domains.map((domain) => domain.domain) ?? [], | ||
| ); | ||
| return { | ||
| bucketName: bucket.name!, | ||
| storageClass: bucket.storageClass ?? "Standard", | ||
| jurisdiction, | ||
| location: normalizeLocation(bucket.location), | ||
| accountId: acct, | ||
| domains: existingDomains.filter((domain) => | ||
| ownedDomains.has(domain.domain), | ||
| ), | ||
| }; | ||
| }), | ||
| ), |
There was a problem hiding this comment.
We can probably remove this bit entirely since we don't actually read this result.
| for (const previousDomain of previous) { | ||
| if (!desiredDomains.has(previousDomain.domain)) { | ||
| yield* deleteBucketDomainCustom({ | ||
| accountId, | ||
| bucketName, | ||
| domain: previousDomain.domain, | ||
| jurisdiction, | ||
| }).pipe( | ||
| Effect.catchIf( | ||
| isMissingCustomDomainOrBucket, | ||
| () => Effect.void, | ||
| ), | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Can we do this in parallel instead of sequentially using Effect.forEach with concurrency: "unbounded"?
| const applied: R2Bucket.CustomDomain[] = []; | ||
| for (const domain of desired) { | ||
| const zoneId = yield* resolveZoneId(domain.zone); | ||
| const observedDomain = observedByDomain.get(domain.domain); | ||
| if (!sameCustomDomainConfig(observedDomain, domain, zoneId)) { | ||
| if (observedDomain) { | ||
| if (observedDomain.zoneId !== zoneId) { |
There was a problem hiding this comment.
Same thing here. I'd change to const applied = yield* Effect.forEach(..., { concurrency: "unbounded" }) so these run in parallel instead of sequentially.
| const refreshed = yield* r2 | ||
| .getBucketDomainCustom({ | ||
| accountId, | ||
| bucketName, | ||
| domain: domain.domain, | ||
| jurisdiction, | ||
| }) | ||
| .pipe(Effect.map(toCustomDomainAttributes)); | ||
| applied.push(refreshed); |
There was a problem hiding this comment.
Do the createBucketDomainCustom and updateBucketDomainCustom functions return the attributes we're looking for? If yes, we can optimize by relying on those results instead of making another API call here.
| const isZoneIdString = (zone: string): boolean => /^[a-f0-9]{32}$/i.test(zone); | ||
|
|
||
| const matchesHostname = (zoneName: string, hostname: string): boolean => | ||
| hostname === zoneName || hostname.endsWith(`.${zoneName}`); | ||
|
|
||
| const normalizeDomains = ( | ||
| domains: R2BucketProps["domains"], | ||
| ): R2BucketCustomDomain[] => | ||
| domains === undefined ? [] : Array.isArray(domains) ? domains : [domains]; | ||
|
|
||
| const toCustomDomainAttributes = ( | ||
| domain: | ||
| | r2.GetBucketDomainCustomResponse | ||
| | r2.ListBucketDomainCustomsResponse["domains"][number], | ||
| ): R2Bucket.CustomDomain => ({ | ||
| domain: domain.domain, | ||
| zoneId: domain.zoneId ?? undefined, | ||
| enabled: domain.enabled, | ||
| ciphers: domain.ciphers ?? undefined, | ||
| minTLS: domain.minTLS ?? undefined, | ||
| status: "status" in domain ? domain.status : undefined, | ||
| }); | ||
|
|
||
| const sameCustomDomainConfig = ( | ||
| observed: R2Bucket.CustomDomain | undefined, | ||
| desired: R2BucketCustomDomain, | ||
| zoneId: string, | ||
| ): boolean => | ||
| observed !== undefined && | ||
| observed.zoneId === zoneId && | ||
| observed.enabled === (desired.enabled ?? true) && | ||
| deepEqual(observed.ciphers, desired.ciphers) && | ||
| observed.minTLS === desired.minTLS; | ||
|
|
||
| const isMissingCustomDomainOrBucket = (error: unknown): boolean => | ||
| typeof error === "object" && | ||
| error !== null && | ||
| (("status" in error && (error as { status: unknown }).status === 404) || | ||
| ("_tag" in error && | ||
| ((error as { _tag: unknown })._tag === "DomainNotFound" || | ||
| (error as { _tag: unknown })._tag === "NoSuchBucket"))); | ||
|
|
There was a problem hiding this comment.
Nit: please move these to the bottom of the file (helps with being able to read the file from top to bottom)
3d31327 to
a02ef07
Compare
Adds R2 bucket custom-domain support to the existing Cloudflare R2Bucket resource.
domainsas a single custom domain or an array{ zoneId }, zone ID strings, and hostname strings for zone selectionzoneIdon the update endpointCLOUDFLARE_TEST_R2_DOMAIN_ZONE_IDandCLOUDFLARE_TEST_R2_DOMAINVerification run:
bun vitest run packages/alchemy/test/Cloudflare/R2/CustomDomain.test.tsbun tsc -b --pretty falsegit diff --check