| ... | ... | @@ -7,7 +7,6 @@ import * as Effect from "effect/Effect"; |
| 7 | 7 | import * as Encoding from "effect/Encoding"; |
| 8 | 8 | import * as Layer from "effect/Layer"; |
| 9 | 9 | import * as Option from "effect/Option"; |
| 10 | | import * as Result from "effect/Result"; |
| 11 | 10 | import * as Schema from "effect/Schema"; |
| 12 | 11 | |
| 13 | 12 | import type { |
| ... | ... | @@ -27,40 +26,86 @@ import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; |
| 27 | 26 | |
| 28 | 27 | export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()( |
| 29 | 28 | "ManagedEndpointProvisioningNotConfigured", |
| 30 | | {}, |
| 29 | { |
| 30 | userId: Schema.String, |
| 31 | environmentId: Schema.String, |
| 32 | missingSettings: Schema.Array( |
| 33 | Schema.Literals(["managedEndpointBaseDomain", "managedEndpointNamespace"]), |
| 34 | ), |
| 35 | }, |
| 31 | 36 | ) { |
| 32 | 37 | override get message(): string { |
| 33 | | return "Managed endpoint provisioning is not configured"; |
| 38 | return `Managed endpoint provisioning is not configured for user '${this.userId}', environment '${this.environmentId}': missing ${this.missingSettings.join(", ")}`; |
| 34 | 39 | } |
| 35 | 40 | } |
| 36 | 41 | |
| 42 | const ManagedEndpointProvisioningStage = Schema.Literals([ |
| 43 | "derive-environment-hash", |
| 44 | "reserve-allocation", |
| 45 | "ensure-tunnel", |
| 46 | "validate-tunnel-response", |
| 47 | "record-tunnel", |
| 48 | "configure-tunnel", |
| 49 | "ensure-dns-record", |
| 50 | "record-dns", |
| 51 | "get-tunnel-token", |
| 52 | "mark-allocation-ready", |
| 53 | ]); |
| 54 | |
| 37 | 55 | export class ManagedEndpointProvisioningFailed extends Schema.TaggedErrorClass<ManagedEndpointProvisioningFailed>()( |
| 38 | 56 | "ManagedEndpointProvisioningFailed", |
| 39 | | { cause: Schema.Defect() }, |
| 57 | { |
| 58 | stage: ManagedEndpointProvisioningStage, |
| 59 | userId: Schema.String, |
| 60 | environmentId: Schema.String, |
| 61 | hostname: Schema.optionalKey(Schema.String), |
| 62 | tunnelName: Schema.optionalKey(Schema.String), |
| 63 | tunnelId: Schema.optionalKey(Schema.String), |
| 64 | dnsRecordId: Schema.optionalKey(Schema.String), |
| 65 | returnedTunnelName: Schema.optionalKey(Schema.String), |
| 66 | returnedTunnelId: Schema.optionalKey(Schema.String), |
| 67 | cause: Schema.optional(Schema.Defect()), |
| 68 | }, |
| 40 | 69 | ) { |
| 41 | 70 | override get message(): string { |
| 42 | | return "Managed endpoint provisioning failed"; |
| 71 | return `Managed endpoint provisioning failed during '${this.stage}' for user '${this.userId}', environment '${this.environmentId}'`; |
| 43 | 72 | } |
| 44 | 73 | } |
| 45 | 74 | |
| 75 | const ManagedEndpointDeprovisioningStage = Schema.Literals([ |
| 76 | "load-allocation", |
| 77 | "delete-dns-record", |
| 78 | "delete-tunnel", |
| 79 | "remove-allocation", |
| 80 | ]); |
| 81 | |
| 46 | 82 | export class ManagedEndpointDeprovisioningFailed extends Schema.TaggedErrorClass<ManagedEndpointDeprovisioningFailed>()( |
| 47 | 83 | "ManagedEndpointDeprovisioningFailed", |
| 48 | | { cause: Schema.Defect() }, |
| 84 | { |
| 85 | stage: ManagedEndpointDeprovisioningStage, |
| 86 | userId: Schema.String, |
| 87 | environmentId: Schema.String, |
| 88 | tunnelId: Schema.optionalKey(Schema.String), |
| 89 | dnsRecordId: Schema.optionalKey(Schema.String), |
| 90 | cause: Schema.Defect(), |
| 91 | }, |
| 49 | 92 | ) { |
| 50 | 93 | override get message(): string { |
| 51 | | return "Managed endpoint deprovisioning failed"; |
| 94 | return `Managed endpoint deprovisioning failed during '${this.stage}' for user '${this.userId}', environment '${this.environmentId}'`; |
| 52 | 95 | } |
| 53 | 96 | } |
| 54 | 97 | |
| 55 | 98 | export class ManagedEndpointOriginNotAllowed extends Schema.TaggedErrorClass<ManagedEndpointOriginNotAllowed>()( |
| 56 | 99 | "ManagedEndpointOriginNotAllowed", |
| 57 | 100 | { |
| 101 | userId: Schema.String, |
| 102 | environmentId: Schema.String, |
| 58 | 103 | host: Schema.String, |
| 59 | 104 | port: Schema.Number, |
| 60 | 105 | }, |
| 61 | 106 | ) { |
| 62 | 107 | override get message(): string { |
| 63 | | return `Managed endpoint origin '${this.host}:${this.port}' is not allowed`; |
| 108 | return `Managed endpoint origin '${this.host}:${this.port}' is not allowed for user '${this.userId}', environment '${this.environmentId}'`; |
| 64 | 109 | } |
| 65 | 110 | } |
| 66 | 111 | |
| ... | ... | @@ -94,12 +139,26 @@ interface ManagedEndpointTunnel { |
| 94 | 139 | readonly name?: string | null; |
| 95 | 140 | } |
| 96 | 141 | |
| 142 | const ManagedEndpointTunnelClientOperation = Schema.Literals([ |
| 143 | "list", |
| 144 | "create", |
| 145 | "put-configuration", |
| 146 | "get-token", |
| 147 | "delete", |
| 148 | ]); |
| 149 | |
| 97 | 150 | export class ManagedEndpointTunnelClientError extends Schema.TaggedErrorClass<ManagedEndpointTunnelClientError>()( |
| 98 | 151 | "ManagedEndpointTunnelClientError", |
| 99 | | { cause: Schema.Defect() }, |
| 152 | { |
| 153 | operation: ManagedEndpointTunnelClientOperation, |
| 154 | tunnelName: Schema.optionalKey(Schema.String), |
| 155 | tunnelId: Schema.optionalKey(Schema.String), |
| 156 | cause: Schema.Defect(), |
| 157 | }, |
| 100 | 158 | ) { |
| 101 | 159 | override get message(): string { |
| 102 | | return "Managed endpoint tunnel provider request failed"; |
| 160 | const target = this.tunnelId ?? this.tunnelName; |
| 161 | return `Managed endpoint tunnel provider '${this.operation}' request failed${target === undefined ? "" : ` for '${target}'`}`; |
| 103 | 162 | } |
| 104 | 163 | } |
| 105 | 164 | |
| ... | ... | @@ -147,12 +206,25 @@ interface ManagedEndpointCnameRecordInput { |
| 147 | 206 | readonly proxied: true; |
| 148 | 207 | } |
| 149 | 208 | |
| 209 | const ManagedEndpointDnsClientOperation = Schema.Literals([ |
| 210 | "list-records", |
| 211 | "create-record", |
| 212 | "update-record", |
| 213 | "delete-record", |
| 214 | ]); |
| 215 | |
| 150 | 216 | export class ManagedEndpointDnsClientError extends Schema.TaggedErrorClass<ManagedEndpointDnsClientError>()( |
| 151 | 217 | "ManagedEndpointDnsClientError", |
| 152 | | { cause: Schema.Defect() }, |
| 218 | { |
| 219 | operation: ManagedEndpointDnsClientOperation, |
| 220 | hostname: Schema.optionalKey(Schema.String), |
| 221 | dnsRecordId: Schema.optionalKey(Schema.String), |
| 222 | cause: Schema.Defect(), |
| 223 | }, |
| 153 | 224 | ) { |
| 154 | 225 | override get message(): string { |
| 155 | | return "Managed endpoint DNS provider request failed"; |
| 226 | const target = this.dnsRecordId ?? this.hostname; |
| 227 | return `Managed endpoint DNS provider '${this.operation}' request failed${target === undefined ? "" : ` for '${target}'`}`; |
| 156 | 228 | } |
| 157 | 229 | } |
| 158 | 230 | |
| ... | ... | @@ -183,13 +255,26 @@ export const layerDnsClient = (client: ManagedEndpointDnsClient["Service"]) => |
| 183 | 255 | |
| 184 | 256 | const requireCloudflareSettings = Effect.fnUntraced(function* ( |
| 185 | 257 | settings: RelayConfiguration.RelayConfiguration["Service"], |
| 258 | input: { readonly userId: string; readonly environmentId: string }, |
| 186 | 259 | ) { |
| 187 | | if (!settings.managedEndpointBaseDomain || !settings.managedEndpointNamespace) { |
| 188 | | return yield* new ManagedEndpointProvisioningNotConfigured(); |
| 260 | const baseDomain = settings.managedEndpointBaseDomain; |
| 261 | const namespace = settings.managedEndpointNamespace; |
| 262 | const missingSettings: Array<"managedEndpointBaseDomain" | "managedEndpointNamespace"> = []; |
| 263 | if (!baseDomain) { |
| 264 | missingSettings.push("managedEndpointBaseDomain"); |
| 265 | } |
| 266 | if (!namespace) { |
| 267 | missingSettings.push("managedEndpointNamespace"); |
| 268 | } |
| 269 | if (!baseDomain || !namespace) { |
| 270 | return yield* new ManagedEndpointProvisioningNotConfigured({ |
| 271 | ...input, |
| 272 | missingSettings, |
| 273 | }); |
| 189 | 274 | } |
| 190 | 275 | return { |
| 191 | | baseDomain: settings.managedEndpointBaseDomain, |
| 192 | | namespace: settings.managedEndpointNamespace, |
| 276 | baseDomain, |
| 277 | namespace, |
| 193 | 278 | }; |
| 194 | 279 | }); |
| 195 | 280 | |
| ... | ... | @@ -231,10 +316,19 @@ function isNotFoundCause(cause: unknown): boolean { |
| 231 | 316 | return "cause" in cause && isNotFoundCause(cause.cause); |
| 232 | 317 | } |
| 233 | 318 | |
| 234 | | const ignoreNotFound = <A, E>(effect: Effect.Effect<A, E>): Effect.Effect<void, E> => |
| 319 | type ManagedEndpointClientError = ManagedEndpointTunnelClientError | ManagedEndpointDnsClientError; |
| 320 | |
| 321 | const ignoreNotFound = <A>( |
| 322 | effect: Effect.Effect<A, ManagedEndpointClientError>, |
| 323 | ): Effect.Effect<void, ManagedEndpointClientError> => |
| 235 | 324 | effect.pipe( |
| 236 | 325 | Effect.asVoid, |
| 237 | | Effect.catch((cause) => (isNotFoundCause(cause) ? Effect.void : Effect.fail(cause))), |
| 326 | Effect.catchTags({ |
| 327 | ManagedEndpointTunnelClientError: (error) => |
| 328 | isNotFoundCause(error.cause) ? Effect.void : Effect.fail(error), |
| 329 | ManagedEndpointDnsClientError: (error) => |
| 330 | isNotFoundCause(error.cause) ? Effect.void : Effect.fail(error), |
| 331 | }), |
| 238 | 332 | ); |
| 239 | 333 | |
| 240 | 334 | const make = Effect.gen(function* () { |
| ... | ... | @@ -289,25 +383,26 @@ const make = Effect.gen(function* () { |
| 289 | 383 | } |
| 290 | 384 | return yield* dns.createRecord(dnsRecord).pipe( |
| 291 | 385 | Effect.map((record) => record.id), |
| 292 | | Effect.catch((createError) => |
| 293 | | Effect.gen(function* () { |
| 294 | | let records = yield* dns.listRecords(hostname); |
| 295 | | for (let attempt = 0; records.length === 0 && attempt < 4; attempt++) { |
| 296 | | yield* Effect.sleep("200 millis"); |
| 297 | | records = yield* dns.listRecords(hostname); |
| 298 | | } |
| 299 | | return records; |
| 300 | | }).pipe( |
| 301 | | Effect.flatMap((records) => |
| 302 | | records.length > 0 |
| 303 | | ? updateExistingDnsRecords(records, preferredDnsRecordId, dnsRecord) |
| 304 | | : Effect.fail(createError), |
| 305 | | ), |
| 306 | | Effect.flatMap((dnsRecordId) => |
| 307 | | dnsRecordId === null ? Effect.fail(createError) : Effect.succeed(dnsRecordId), |
| 386 | Effect.catchTags({ |
| 387 | ManagedEndpointDnsClientError: (createError) => |
| 388 | Effect.gen(function* () { |
| 389 | let records = yield* dns.listRecords(hostname); |
| 390 | for (let attempt = 0; records.length === 0 && attempt < 4; attempt++) { |
| 391 | yield* Effect.sleep("200 millis"); |
| 392 | records = yield* dns.listRecords(hostname); |
| 393 | } |
| 394 | return records; |
| 395 | }).pipe( |
| 396 | Effect.flatMap((records) => |
| 397 | records.length > 0 |
| 398 | ? updateExistingDnsRecords(records, preferredDnsRecordId, dnsRecord) |
| 399 | : Effect.fail(createError), |
| 400 | ), |
| 401 | Effect.flatMap((dnsRecordId) => |
| 402 | dnsRecordId === null ? Effect.fail(createError) : Effect.succeed(dnsRecordId), |
| 403 | ), |
| 308 | 404 | ), |
| 309 | | ), |
| 310 | | ), |
| 405 | }), |
| 311 | 406 | ); |
| 312 | 407 | }); |
| 313 | 408 | |
| ... | ... | @@ -317,25 +412,59 @@ const make = Effect.gen(function* () { |
| 317 | 412 | "relay.user_id": input.userId, |
| 318 | 413 | "relay.environment_id": input.environmentId, |
| 319 | 414 | }); |
| 320 | | const allocation = yield* allocations |
| 321 | | .get(input) |
| 322 | | .pipe(Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause }))); |
| 415 | const allocation = yield* allocations.get(input).pipe( |
| 416 | Effect.mapError( |
| 417 | (cause) => |
| 418 | new ManagedEndpointDeprovisioningFailed({ |
| 419 | ...input, |
| 420 | stage: "load-allocation", |
| 421 | cause, |
| 422 | }), |
| 423 | ), |
| 424 | ); |
| 323 | 425 | if (allocation === null) { |
| 324 | 426 | return; |
| 325 | 427 | } |
| 326 | | if (allocation.dnsRecordId !== null) { |
| 327 | | yield* ignoreNotFound(dns.deleteRecord(allocation.dnsRecordId)).pipe( |
| 328 | | Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause })), |
| 428 | const dnsRecordId = allocation.dnsRecordId; |
| 429 | if (dnsRecordId !== null) { |
| 430 | yield* ignoreNotFound(dns.deleteRecord(dnsRecordId)).pipe( |
| 431 | Effect.mapError( |
| 432 | (cause) => |
| 433 | new ManagedEndpointDeprovisioningFailed({ |
| 434 | ...input, |
| 435 | stage: "delete-dns-record", |
| 436 | dnsRecordId, |
| 437 | cause, |
| 438 | }), |
| 439 | ), |
| 329 | 440 | ); |
| 330 | 441 | } |
| 331 | | if (allocation.tunnelId !== null) { |
| 332 | | yield* ignoreNotFound(tunnels.delete(allocation.tunnelId)).pipe( |
| 333 | | Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause })), |
| 442 | const tunnelId = allocation.tunnelId; |
| 443 | if (tunnelId !== null) { |
| 444 | yield* ignoreNotFound(tunnels.delete(tunnelId)).pipe( |
| 445 | Effect.mapError( |
| 446 | (cause) => |
| 447 | new ManagedEndpointDeprovisioningFailed({ |
| 448 | ...input, |
| 449 | stage: "delete-tunnel", |
| 450 | tunnelId, |
| 451 | cause, |
| 452 | }), |
| 453 | ), |
| 334 | 454 | ); |
| 335 | 455 | } |
| 336 | | yield* allocations |
| 337 | | .remove(input) |
| 338 | | .pipe(Effect.mapError((cause) => new ManagedEndpointDeprovisioningFailed({ cause }))); |
| 456 | yield* allocations.remove(input).pipe( |
| 457 | Effect.mapError( |
| 458 | (cause) => |
| 459 | new ManagedEndpointDeprovisioningFailed({ |
| 460 | ...input, |
| 461 | stage: "remove-allocation", |
| 462 | ...(allocation.tunnelId === null ? {} : { tunnelId: allocation.tunnelId }), |
| 463 | ...(allocation.dnsRecordId === null ? {} : { dnsRecordId: allocation.dnsRecordId }), |
| 464 | cause, |
| 465 | }), |
| 466 | ), |
| 467 | ); |
| 339 | 468 | }), |
| 340 | 469 | provision: Effect.fn("relay.managed_endpoint_provider.provision")(function* (input) { |
| 341 | 470 | yield* Effect.annotateCurrentSpan({ |
| ... | ... | @@ -346,11 +475,13 @@ const make = Effect.gen(function* () { |
| 346 | 475 | }); |
| 347 | 476 | if (!isLoopbackOrigin(input.origin)) { |
| 348 | 477 | return yield* new ManagedEndpointOriginNotAllowed({ |
| 478 | userId: input.userId, |
| 479 | environmentId: input.environmentId, |
| 349 | 480 | host: input.origin.localHttpHost, |
| 350 | 481 | port: input.origin.localHttpPort, |
| 351 | 482 | }); |
| 352 | 483 | } |
| 353 | | const cf = yield* requireCloudflareSettings(config); |
| 484 | const cf = yield* requireCloudflareSettings(config, input); |
| 354 | 485 | const environmentHash = yield* crypto |
| 355 | 486 | .digest( |
| 356 | 487 | "SHA-256", |
| ... | ... | @@ -360,19 +491,45 @@ const make = Effect.gen(function* () { |
| 360 | 491 | ) |
| 361 | 492 | .pipe( |
| 362 | 493 | Effect.map(Encoding.encodeHex), |
| 363 | | Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })), |
| 494 | Effect.mapError( |
| 495 | (cause) => |
| 496 | new ManagedEndpointProvisioningFailed({ |
| 497 | userId: input.userId, |
| 498 | environmentId: input.environmentId, |
| 499 | stage: "derive-environment-hash", |
| 500 | cause, |
| 501 | }), |
| 502 | ), |
| 364 | 503 | ); |
| 504 | const requestedHostname = managedEndpointHostname( |
| 505 | cf.namespace, |
| 506 | cf.baseDomain, |
| 507 | environmentHash, |
| 508 | ); |
| 509 | const requestedTunnelName = managedEndpointTunnelName(cf.namespace, environmentHash); |
| 365 | 510 | const allocation = yield* allocations |
| 366 | 511 | .reserve({ |
| 367 | 512 | userId: input.userId, |
| 368 | 513 | environmentId: input.environmentId, |
| 369 | | hostname: managedEndpointHostname(cf.namespace, cf.baseDomain, environmentHash), |
| 370 | | tunnelName: managedEndpointTunnelName(cf.namespace, environmentHash), |
| 514 | hostname: requestedHostname, |
| 515 | tunnelName: requestedTunnelName, |
| 371 | 516 | }) |
| 372 | | .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause }))); |
| 517 | .pipe( |
| 518 | Effect.mapError( |
| 519 | (cause) => |
| 520 | new ManagedEndpointProvisioningFailed({ |
| 521 | userId: input.userId, |
| 522 | environmentId: input.environmentId, |
| 523 | stage: "reserve-allocation", |
| 524 | hostname: requestedHostname, |
| 525 | tunnelName: requestedTunnelName, |
| 526 | cause, |
| 527 | }), |
| 528 | ), |
| 529 | ); |
| 373 | 530 | const { hostname, tunnelName } = allocation; |
| 374 | 531 | |
| 375 | | const tunnel = yield* tunnels.list({ name: tunnelName, isDeleted: false }).pipe( |
| 532 | const tunnelResponse = yield* tunnels.list({ name: tunnelName, isDeleted: false }).pipe( |
| 376 | 533 | Effect.map((tunnels) => tunnels.result), |
| 377 | 534 | Effect.map(Arr.findFirst((tunnel) => tunnel.name === tunnelName)), |
| 378 | 535 | Effect.flatMap( |
| ... | ... | @@ -381,20 +538,50 @@ const make = Effect.gen(function* () { |
| 381 | 538 | onNone: () => tunnels.create({ name: tunnelName, configSrc: "cloudflare" }), |
| 382 | 539 | }), |
| 383 | 540 | ), |
| 384 | | Effect.filterMapOrFail((tunnel) => |
| 385 | | tunnel.id && tunnel.name |
| 386 | | ? Result.succeed({ id: tunnel.id, name: tunnel.name }) |
| 387 | | : Result.fail(new ManagedEndpointProvisioningFailed({ cause: tunnel })), |
| 541 | Effect.mapError( |
| 542 | (cause) => |
| 543 | new ManagedEndpointProvisioningFailed({ |
| 544 | userId: input.userId, |
| 545 | environmentId: input.environmentId, |
| 546 | stage: "ensure-tunnel", |
| 547 | hostname, |
| 548 | tunnelName, |
| 549 | cause, |
| 550 | }), |
| 388 | 551 | ), |
| 389 | | Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })), |
| 390 | 552 | ); |
| 553 | if (!tunnelResponse.id || !tunnelResponse.name) { |
| 554 | return yield* new ManagedEndpointProvisioningFailed({ |
| 555 | userId: input.userId, |
| 556 | environmentId: input.environmentId, |
| 557 | stage: "validate-tunnel-response", |
| 558 | hostname, |
| 559 | tunnelName, |
| 560 | ...(tunnelResponse.id ? { returnedTunnelId: tunnelResponse.id } : {}), |
| 561 | ...(tunnelResponse.name ? { returnedTunnelName: tunnelResponse.name } : {}), |
| 562 | }); |
| 563 | } |
| 564 | const tunnel = { id: tunnelResponse.id, name: tunnelResponse.name }; |
| 391 | 565 | yield* allocations |
| 392 | 566 | .recordTunnel({ |
| 393 | 567 | userId: input.userId, |
| 394 | 568 | environmentId: input.environmentId, |
| 395 | 569 | tunnelId: tunnel.id, |
| 396 | 570 | }) |
| 397 | | .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause }))); |
| 571 | .pipe( |
| 572 | Effect.mapError( |
| 573 | (cause) => |
| 574 | new ManagedEndpointProvisioningFailed({ |
| 575 | userId: input.userId, |
| 576 | environmentId: input.environmentId, |
| 577 | stage: "record-tunnel", |
| 578 | hostname, |
| 579 | tunnelName, |
| 580 | tunnelId: tunnel.id, |
| 581 | cause, |
| 582 | }), |
| 583 | ), |
| 584 | ); |
| 398 | 585 | |
| 399 | 586 | yield* tunnels |
| 400 | 587 | .putConfiguration(tunnel.id, { |
| ... | ... | @@ -406,7 +593,20 @@ const make = Effect.gen(function* () { |
| 406 | 593 | { service: "http_status:404" }, |
| 407 | 594 | ], |
| 408 | 595 | }) |
| 409 | | .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause }))); |
| 596 | .pipe( |
| 597 | Effect.mapError( |
| 598 | (cause) => |
| 599 | new ManagedEndpointProvisioningFailed({ |
| 600 | userId: input.userId, |
| 601 | environmentId: input.environmentId, |
| 602 | stage: "configure-tunnel", |
| 603 | hostname, |
| 604 | tunnelName, |
| 605 | tunnelId: tunnel.id, |
| 606 | cause, |
| 607 | }), |
| 608 | ), |
| 609 | ); |
| 410 | 610 | |
| 411 | 611 | const dnsRecord = { |
| 412 | 612 | type: "CNAME", |
| ... | ... | @@ -417,7 +617,19 @@ const make = Effect.gen(function* () { |
| 417 | 617 | } as const; |
| 418 | 618 | |
| 419 | 619 | const dnsRecordId = yield* ensureDnsRecord(hostname, allocation.dnsRecordId, dnsRecord).pipe( |
| 420 | | Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause })), |
| 620 | Effect.mapError( |
| 621 | (cause) => |
| 622 | new ManagedEndpointProvisioningFailed({ |
| 623 | userId: input.userId, |
| 624 | environmentId: input.environmentId, |
| 625 | stage: "ensure-dns-record", |
| 626 | hostname, |
| 627 | tunnelName, |
| 628 | tunnelId: tunnel.id, |
| 629 | ...(allocation.dnsRecordId === null ? {} : { dnsRecordId: allocation.dnsRecordId }), |
| 630 | cause, |
| 631 | }), |
| 632 | ), |
| 421 | 633 | ); |
| 422 | 634 | yield* allocations |
| 423 | 635 | .recordDns({ |
| ... | ... | @@ -425,17 +637,57 @@ const make = Effect.gen(function* () { |
| 425 | 637 | environmentId: input.environmentId, |
| 426 | 638 | dnsRecordId, |
| 427 | 639 | }) |
| 428 | | .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause }))); |
| 640 | .pipe( |
| 641 | Effect.mapError( |
| 642 | (cause) => |
| 643 | new ManagedEndpointProvisioningFailed({ |
| 644 | userId: input.userId, |
| 645 | environmentId: input.environmentId, |
| 646 | stage: "record-dns", |
| 647 | hostname, |
| 648 | tunnelName, |
| 649 | tunnelId: tunnel.id, |
| 650 | dnsRecordId, |
| 651 | cause, |
| 652 | }), |
| 653 | ), |
| 654 | ); |
| 429 | 655 | |
| 430 | | const connectorToken = yield* tunnels |
| 431 | | .getToken(tunnel.id) |
| 432 | | .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause }))); |
| 656 | const connectorToken = yield* tunnels.getToken(tunnel.id).pipe( |
| 657 | Effect.mapError( |
| 658 | (cause) => |
| 659 | new ManagedEndpointProvisioningFailed({ |
| 660 | userId: input.userId, |
| 661 | environmentId: input.environmentId, |
| 662 | stage: "get-tunnel-token", |
| 663 | hostname, |
| 664 | tunnelName, |
| 665 | tunnelId: tunnel.id, |
| 666 | dnsRecordId, |
| 667 | cause, |
| 668 | }), |
| 669 | ), |
| 670 | ); |
| 433 | 671 | yield* allocations |
| 434 | 672 | .markReady({ |
| 435 | 673 | userId: input.userId, |
| 436 | 674 | environmentId: input.environmentId, |
| 437 | 675 | }) |
| 438 | | .pipe(Effect.mapError((cause) => new ManagedEndpointProvisioningFailed({ cause }))); |
| 676 | .pipe( |
| 677 | Effect.mapError( |
| 678 | (cause) => |
| 679 | new ManagedEndpointProvisioningFailed({ |
| 680 | userId: input.userId, |
| 681 | environmentId: input.environmentId, |
| 682 | stage: "mark-allocation-ready", |
| 683 | hostname, |
| 684 | tunnelName, |
| 685 | tunnelId: tunnel.id, |
| 686 | dnsRecordId, |
| 687 | cause, |
| 688 | }), |
| 689 | ), |
| 690 | ); |
| 439 | 691 | |
| 440 | 692 | return { |
| 441 | 693 | endpoint: managedEndpointForHostname(hostname), |
| ... | ... | @@ -464,27 +716,62 @@ export const layerCloudflareBindings = ( |
| 464 | 716 | ManagedEndpointTunnelClient.of({ |
| 465 | 717 | list: (request) => |
| 466 | 718 | tunnelClient.list(request).pipe( |
| 467 | | Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })), |
| 719 | Effect.mapError( |
| 720 | (cause) => |
| 721 | new ManagedEndpointTunnelClientError({ |
| 722 | operation: "list", |
| 723 | tunnelName: request.name, |
| 724 | cause, |
| 725 | }), |
| 726 | ), |
| 468 | 727 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 469 | 728 | ), |
| 470 | 729 | create: (request) => |
| 471 | 730 | tunnelClient.create(request).pipe( |
| 472 | | Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })), |
| 731 | Effect.mapError( |
| 732 | (cause) => |
| 733 | new ManagedEndpointTunnelClientError({ |
| 734 | operation: "create", |
| 735 | tunnelName: request.name, |
| 736 | cause, |
| 737 | }), |
| 738 | ), |
| 473 | 739 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 474 | 740 | ), |
| 475 | 741 | putConfiguration: (tunnelId, config) => |
| 476 | 742 | tunnelClient.putConfiguration(tunnelId, config).pipe( |
| 477 | | Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })), |
| 743 | Effect.mapError( |
| 744 | (cause) => |
| 745 | new ManagedEndpointTunnelClientError({ |
| 746 | operation: "put-configuration", |
| 747 | tunnelId, |
| 748 | cause, |
| 749 | }), |
| 750 | ), |
| 478 | 751 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 479 | 752 | ), |
| 480 | 753 | getToken: (tunnelId) => |
| 481 | 754 | tunnelClient.getToken(tunnelId).pipe( |
| 482 | | Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })), |
| 755 | Effect.mapError( |
| 756 | (cause) => |
| 757 | new ManagedEndpointTunnelClientError({ |
| 758 | operation: "get-token", |
| 759 | tunnelId, |
| 760 | cause, |
| 761 | }), |
| 762 | ), |
| 483 | 763 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 484 | 764 | ), |
| 485 | 765 | delete: (tunnelId) => |
| 486 | 766 | tunnelClient.delete(tunnelId).pipe( |
| 487 | | Effect.mapError((cause) => new ManagedEndpointTunnelClientError({ cause })), |
| 767 | Effect.mapError( |
| 768 | (cause) => |
| 769 | new ManagedEndpointTunnelClientError({ |
| 770 | operation: "delete", |
| 771 | tunnelId, |
| 772 | cause, |
| 773 | }), |
| 774 | ), |
| 488 | 775 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 489 | 776 | ), |
| 490 | 777 | }), |
| ... | ... | @@ -500,23 +787,52 @@ export const layerCloudflareBindings = ( |
| 500 | 787 | normalizeHostname(record.name) === normalizeHostname(hostname), |
| 501 | 788 | ), |
| 502 | 789 | ), |
| 503 | | Effect.mapError((cause) => new ManagedEndpointDnsClientError({ cause })), |
| 790 | Effect.mapError( |
| 791 | (cause) => |
| 792 | new ManagedEndpointDnsClientError({ |
| 793 | operation: "list-records", |
| 794 | hostname, |
| 795 | cause, |
| 796 | }), |
| 797 | ), |
| 504 | 798 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 505 | 799 | ), |
| 506 | 800 | createRecord: (request) => |
| 507 | 801 | dnsClient.createDnsRecord(request).pipe( |
| 508 | 802 | Effect.map((response) => ({ id: response.id })), |
| 509 | | Effect.mapError((cause) => new ManagedEndpointDnsClientError({ cause })), |
| 803 | Effect.mapError( |
| 804 | (cause) => |
| 805 | new ManagedEndpointDnsClientError({ |
| 806 | operation: "create-record", |
| 807 | hostname: request.name, |
| 808 | cause, |
| 809 | }), |
| 810 | ), |
| 510 | 811 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 511 | 812 | ), |
| 512 | 813 | updateRecord: (dnsRecordId, request) => |
| 513 | 814 | dnsClient.updateDnsRecord(dnsRecordId, request).pipe( |
| 514 | | Effect.mapError((cause) => new ManagedEndpointDnsClientError({ cause })), |
| 815 | Effect.mapError( |
| 816 | (cause) => |
| 817 | new ManagedEndpointDnsClientError({ |
| 818 | operation: "update-record", |
| 819 | hostname: request.name, |
| 820 | dnsRecordId, |
| 821 | cause, |
| 822 | }), |
| 823 | ), |
| 515 | 824 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 516 | 825 | ), |
| 517 | 826 | deleteRecord: (dnsRecordId) => |
| 518 | 827 | dnsClient.deleteDnsRecord(dnsRecordId).pipe( |
| 519 | | Effect.mapError((cause) => new ManagedEndpointDnsClientError({ cause })), |
| 828 | Effect.mapError( |
| 829 | (cause) => |
| 830 | new ManagedEndpointDnsClientError({ |
| 831 | operation: "delete-record", |
| 832 | dnsRecordId, |
| 833 | cause, |
| 834 | }), |
| 835 | ), |
| 520 | 836 | Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), |
| 521 | 837 | ), |
| 522 | 838 | }), |