526 lines · 19.0 KB · TypeScript
RawDownload
import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Arr from "effect/Array";import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";import * as Effect from "effect/Effect";import * as Encoding from "effect/Encoding";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Result from "effect/Result";import * as Schema from "effect/Schema"; import type {  RelayManagedEndpoint,  RelayManagedEndpointOrigin,  RelayManagedEndpointRuntimeConfig,} from "@t3tools/contracts/relay"; import * as RelayConfiguration from "../Config.ts";import {  managedEndpointDigestInput,  managedEndpointForHostname,  managedEndpointHostname,  managedEndpointTunnelName,} from "../deploymentConfig.ts";import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()(  "ManagedEndpointProvisioningNotConfigured",  {},) {  override get message(): string {    return "Managed endpoint provisioning is not configured";  }} export class ManagedEndpointProvisioningFailed extends Schema.TaggedErrorClass<ManagedEndpointProvisioningFailed>()(  "ManagedEndpointProvisioningFailed",  { cause: Schema.Defect() },) {  override get message(): string {    return "Managed endpoint provisioning failed";  }} export class ManagedEndpointDeprovisioningFailed extends Schema.TaggedErrorClass<ManagedEndpointDeprovisioningFailed>()(  "ManagedEndpointDeprovisioningFailed",  { cause: Schema.Defect() },) {  override get message(): string {    return "Managed endpoint deprovisioning failed";  }} export class ManagedEndpointOriginNotAllowed extends Schema.TaggedErrorClass<ManagedEndpointOriginNotAllowed>()(  "ManagedEndpointOriginNotAllowed",  {    host: Schema.String,    port: Schema.Number,  },) {  override get message(): string {    return `Managed endpoint origin '${this.host}:${this.port}' is not allowed`;  }} export type ManagedEndpointProviderError =  | ManagedEndpointProvisioningNotConfigured  | ManagedEndpointProvisioningFailed  | ManagedEndpointOriginNotAllowed; export interface ManagedEndpointProvisioningResult {  readonly endpoint: RelayManagedEndpoint;  readonly runtime: RelayManagedEndpointRuntimeConfig;} export class ManagedEndpointProvider extends Context.Service<  ManagedEndpointProvider,  {    readonly provision: (input: {      readonly userId: string;      readonly environmentId: string;      readonly origin: RelayManagedEndpointOrigin;    }) => Effect.Effect<ManagedEndpointProvisioningResult, ManagedEndpointProviderError>;    readonly deprovision: (input: {      readonly userId: string;      readonly environmentId: string;    }) => Effect.Effect<void, ManagedEndpointDeprovisioningFailed>;  }>()("t3code-relay/environments/ManagedEndpointProvider") {} interface ManagedEndpointTunnel {  readonly id?: string | null;  readonly name?: string | null;} export class ManagedEndpointTunnelClientError extends Schema.TaggedErrorClass<ManagedEndpointTunnelClientError>()(  "ManagedEndpointTunnelClientError",  { cause: Schema.Defect() },) {  override get message(): string {    return "Managed endpoint tunnel provider request failed";  }} export class ManagedEndpointTunnelClient extends Context.Service<  ManagedEndpointTunnelClient,  {    readonly list: (request: {      readonly name: string;      readonly isDeleted: false;    }) => Effect.Effect<      { readonly result: ReadonlyArray<ManagedEndpointTunnel> },      ManagedEndpointTunnelClientError    >;    readonly create: (request: {      readonly name: string;      readonly configSrc: "cloudflare";    }) => Effect.Effect<ManagedEndpointTunnel, ManagedEndpointTunnelClientError>;    readonly putConfiguration: (      tunnelId: string,      config: {        readonly ingress: Array<{          readonly hostname?: string;          readonly service: string;        }>;      },    ) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>;    readonly getToken: (      tunnelId: string,    ) => Effect.Effect<string, ManagedEndpointTunnelClientError>;    readonly delete: (tunnelId: string) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>;  }>()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointTunnelClient") {} export const makeTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) =>  ManagedEndpointTunnelClient.of(client); export const layerTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) =>  Layer.succeed(ManagedEndpointTunnelClient, makeTunnelClient(client)); interface ManagedEndpointCnameRecordInput {  readonly type: "CNAME";  readonly name: string;  readonly content: string;  readonly ttl: 1;  readonly proxied: true;} export class ManagedEndpointDnsClientError extends Schema.TaggedErrorClass<ManagedEndpointDnsClientError>()(  "ManagedEndpointDnsClientError",  { cause: Schema.Defect() },) {  override get message(): string {    return "Managed endpoint DNS provider request failed";  }} export class ManagedEndpointDnsClient extends Context.Service<  ManagedEndpointDnsClient,  {    readonly listRecords: (      hostname: string,    ) => Effect.Effect<ReadonlyArray<{ readonly id: string }>, ManagedEndpointDnsClientError>;    readonly createRecord: (      request: ManagedEndpointCnameRecordInput,    ) => Effect.Effect<{ readonly id: string }, ManagedEndpointDnsClientError>;    readonly updateRecord: (      dnsRecordId: string,      request: ManagedEndpointCnameRecordInput,    ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>;    readonly deleteRecord: (      dnsRecordId: string,    ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>;  }>()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointDnsClient") {} export const makeDnsClient = (client: ManagedEndpointDnsClient["Service"]) =>  ManagedEndpointDnsClient.of(client); export const layerDnsClient = (client: ManagedEndpointDnsClient["Service"]) =>  Layer.succeed(ManagedEndpointDnsClient, makeDnsClient(client)); const requireCloudflareSettings = Effect.fnUntraced(function* (  settings: RelayConfiguration.RelayConfiguration["Service"],) {  if (!settings.managedEndpointBaseDomain || !settings.managedEndpointNamespace) {    return yield* new ManagedEndpointProvisioningNotConfigured();  }  return {    baseDomain: settings.managedEndpointBaseDomain,    namespace: settings.managedEndpointNamespace,  };}); function formatOriginService(origin: RelayManagedEndpointOrigin): string {  const host = origin.localHttpHost.includes(":")    ? `[${origin.localHttpHost.replace(/^\[(.*)\]$/u, "$1")}]`    : origin.localHttpHost;  return `http://${host}:${origin.localHttpPort}`;} function normalizeHostname(hostname: string): string {  return hostname    .trim()    .toLowerCase()    .replace(/\.$/u, "")    .replace(/^\[(.*)\]$/u, "$1");} function isLoopbackOrigin(origin: RelayManagedEndpointOrigin): boolean {  const hostname = normalizeHostname(origin.localHttpHost);  return (    (hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost") &&    Number.isInteger(origin.localHttpPort) &&    origin.localHttpPort > 0 &&    origin.localHttpPort <= 65_535  );} function isNotFoundCause(cause: unknown): boolean {  if (typeof cause !== "object" || cause === null) {    return false;  }  if ("_tag" in cause && cause._tag === "NotFound") {    return true;  }  if ("status" in cause && cause.status === 404) {    return true;  }  return "cause" in cause && isNotFoundCause(cause.cause);} const ignoreNotFound = <A, E>(effect: Effect.Effect<A, E>): Effect.Effect<void, E> =>  effect.pipe(    Effect.asVoid,    Effect.catch((cause) => (isNotFoundCause(cause) ? Effect.void : Effect.fail(cause))),  ); const make = Effect.gen(function* () {  const config = yield* RelayConfiguration.RelayConfiguration;  const crypto = yield* Crypto.Crypto;  const tunnels = yield* ManagedEndpointTunnelClient;  const dns = yield* ManagedEndpointDnsClient;  const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations;   const updateExistingDnsRecords = Effect.fnUntraced(function* (    records: ReadonlyArray<{ readonly id: string }>,    preferredDnsRecordId: string | null,    dnsRecord: ManagedEndpointCnameRecordInput,  ) {    const keptRecord = records.find((record) => record.id === preferredDnsRecordId) ?? records[0];    if (keptRecord === undefined) {      return null;    }    yield* Effect.forEach(      records,      (record) => (record.id === keptRecord.id ? Effect.void : dns.deleteRecord(record.id)),      { discard: true },    );    yield* dns.updateRecord(keptRecord.id, dnsRecord);    return keptRecord.id;  });   const ensureDnsRecord = Effect.fnUntraced(function* (    hostname: string,    preferredDnsRecordId: string | null,    dnsRecord: ManagedEndpointCnameRecordInput,  ) {    if (preferredDnsRecordId !== null) {      const checkpointedRecordUpdated = yield* dns        .updateRecord(preferredDnsRecordId, dnsRecord)        .pipe(          Effect.as(true),          Effect.orElseSucceed(() => false),        );      if (checkpointedRecordUpdated) {        return preferredDnsRecordId;      }    }    const existingDnsRecords = yield* dns.listRecords(hostname);    const existingDnsRecordId = yield* updateExistingDnsRecords(      existingDnsRecords,      preferredDnsRecordId,      dnsRecord,    );    if (existingDnsRecordId !== null) {      return existingDnsRecordId;    }    return yield* dns.createRecord(dnsRecord).pipe(      Effect.map((record) => record.id),      Effect.catch((createError) =>        Effect.gen(function* () {          let records = yield* dns.listRecords(hostname);          for (let attempt = 0; records.length === 0 && attempt < 4; attempt++) {            yield* Effect.sleep("200 millis");            records = yield* dns.listRecords(hostname);          }          return records;        }).pipe(          Effect.flatMap((records) =>            records.length > 0              ? updateExistingDnsRecords(records, preferredDnsRecordId, dnsRecord)              : Effect.fail(createError),          ),          Effect.flatMap((dnsRecordId) =>            dnsRecordId === null ? Effect.fail(createError) : Effect.succeed(dnsRecordId),          ),        ),      ),    );  });   return ManagedEndpointProvider.of({    deprovision: Effect.fn("relay.managed_endpoint_provider.deprovision")(function* (input) {      yield* Effect.annotateCurrentSpan({        "relay.user_id": input.userId,        "relay.environment_id": input.environmentId,      });      const allocation = yield* allocations        .get(input)        .pipe(Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause })));      if (allocation === null) {        return;      }      if (allocation.dnsRecordId !== null) {        yield* ignoreNotFound(dns.deleteRecord(allocation.dnsRecordId)).pipe(          Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause })),        );      }      if (allocation.tunnelId !== null) {        yield* ignoreNotFound(tunnels.delete(allocation.tunnelId)).pipe(          Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause })),        );      }      yield* allocations        .remove(input)        .pipe(Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause })));    }),    provision: Effect.fn("relay.managed_endpoint_provider.provision")(function* (input) {      yield* Effect.annotateCurrentSpan({        "relay.user_id": input.userId,        "relay.environment_id": input.environmentId,        "relay.managed_endpoint.origin_host": input.origin.localHttpHost,        "relay.managed_endpoint.origin_port": input.origin.localHttpPort,      });      if (!isLoopbackOrigin(input.origin)) {        return yield* new ManagedEndpointOriginNotAllowed({          host: input.origin.localHttpHost,          port: input.origin.localHttpPort,        });      }      const cf = yield* requireCloudflareSettings(config);      const environmentHash = yield* crypto        .digest(          "SHA-256",          new TextEncoder().encode(            managedEndpointDigestInput(cf.namespace, input.userId, input.environmentId),          ),        )        .pipe(          Effect.map(Encoding.encodeHex),          Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })),        );      const allocation = yield* allocations        .reserve({          userId: input.userId,          environmentId: input.environmentId,          hostname: managedEndpointHostname(cf.namespace, cf.baseDomain, environmentHash),          tunnelName: managedEndpointTunnelName(cf.namespace, environmentHash),        })        .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })));      const { hostname, tunnelName } = allocation;       const tunnel = yield* tunnels.list({ name: tunnelName, isDeleted: false }).pipe(        Effect.map((tunnels) => tunnels.result),        Effect.map(Arr.findFirst((tunnel) => tunnel.name === tunnelName)),        Effect.flatMap(          Option.match({            onSome: (tunnel) => Effect.succeed(tunnel),            onNone: () => tunnels.create({ name: tunnelName, configSrc: "cloudflare" }),          }),        ),        Effect.filterMapOrFail((tunnel) =>          tunnel.id && tunnel.name            ? Result.succeed({ id: tunnel.id, name: tunnel.name })            : Result.fail(new ManagedEndpointProvisioningFailed({ cause: tunnel })),        ),        Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })),      );      yield* allocations        .recordTunnel({          userId: input.userId,          environmentId: input.environmentId,          tunnelId: tunnel.id,        })        .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })));       yield* tunnels        .putConfiguration(tunnel.id, {          ingress: [            {              hostname,              service: formatOriginService(input.origin),            },            { service: "http_status:404" },          ],        })        .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })));       const dnsRecord = {        type: "CNAME",        name: hostname,        content: `${tunnel.id}.cfargotunnel.com`,        ttl: 1,        proxied: true,      } as const;       const dnsRecordId = yield* ensureDnsRecord(hostname, allocation.dnsRecordId, dnsRecord).pipe(        Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })),      );      yield* allocations        .recordDns({          userId: input.userId,          environmentId: input.environmentId,          dnsRecordId,        })        .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })));       const connectorToken = yield* tunnels        .getToken(tunnel.id)        .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })));      yield* allocations        .markReady({          userId: input.userId,          environmentId: input.environmentId,        })        .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })));       return {        endpoint: managedEndpointForHostname(hostname),        runtime: {          providerKind: "cloudflare_tunnel",          connectorToken,          tunnelId: tunnel.id,          tunnelName: tunnel.name,        },      } satisfies ManagedEndpointProvisioningResult;    }),  });}); export const layer = Layer.effect(ManagedEndpointProvider, make); export const layerCloudflareBindings = (  tunnelClient: Cloudflare.TunnelReadWriteClient,  dnsClient: Cloudflare.DnsReadWriteClient,  alchemyRuntimeContext: Alchemy.BaseRuntimeContext,) =>  layer.pipe(    Layer.provide(      Layer.mergeAll(        layerTunnelClient(          ManagedEndpointTunnelClient.of({            list: (request) =>              tunnelClient.list(request).pipe(                Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })),                Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext),              ),            create: (request) =>              tunnelClient.create(request).pipe(                Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })),                Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext),              ),            putConfiguration: (tunnelId, config) =>              tunnelClient.putConfiguration(tunnelId, config).pipe(                Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })),                Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext),              ),            getToken: (tunnelId) =>              tunnelClient.getToken(tunnelId).pipe(                Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })),                Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext),              ),            delete: (tunnelId) =>              tunnelClient.delete(tunnelId).pipe(                Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })),                Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext),              ),          }),        ),        layerDnsClient(          ManagedEndpointDnsClient.of({            listRecords: (hostname) =>              dnsClient.listDnsRecords({ search: hostname }).pipe(                Effect.map((response) =>                  response.result.filter(                    (record): record is typeof record & { readonly id: string } =>                      typeof record.id === "string" &&                      normalizeHostname(record.name) === normalizeHostname(hostname),

Lines 1–500 of 526.