@alchemy.run/fly 2.0.0-beta.84 → 2.0.0-beta.86
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/lib/App.d.ts +27 -5
- package/lib/App.d.ts.map +1 -1
- package/lib/App.js +29 -8
- package/lib/App.js.map +1 -1
- package/lib/AuthProvider.d.ts +1 -1
- package/lib/AuthProvider.d.ts.map +1 -1
- package/lib/AuthProvider.js.map +1 -1
- package/lib/BindService.d.ts +7 -7
- package/lib/BindService.d.ts.map +1 -1
- package/lib/BindService.js +21 -28
- package/lib/BindService.js.map +1 -1
- package/lib/Bootstrap.d.ts.map +1 -1
- package/lib/Bootstrap.js +2 -1
- package/lib/Bootstrap.js.map +1 -1
- package/lib/Bucket.d.ts +38 -4
- package/lib/Bucket.d.ts.map +1 -1
- package/lib/Bucket.js +190 -37
- package/lib/Bucket.js.map +1 -1
- package/lib/BucketBinding.d.ts.map +1 -1
- package/lib/BucketBinding.js +1 -1
- package/lib/BucketBinding.js.map +1 -1
- package/lib/Catalog.d.ts.map +1 -1
- package/lib/Catalog.js.map +1 -1
- package/lib/Certificate.d.ts +25 -1
- package/lib/Certificate.d.ts.map +1 -1
- package/lib/Certificate.js +22 -4
- package/lib/Certificate.js.map +1 -1
- package/lib/Checkpoint.d.ts.map +1 -1
- package/lib/Checkpoint.js.map +1 -1
- package/lib/CheckpointHttp.d.ts.map +1 -1
- package/lib/CheckpointHttp.js.map +1 -1
- package/lib/ConnectPostgres.d.ts +1 -1
- package/lib/ConnectPostgres.d.ts.map +1 -1
- package/lib/ConnectPostgres.js.map +1 -1
- package/lib/ConnectPostgresHttp.d.ts.map +1 -1
- package/lib/ConnectPostgresHttp.js +1 -1
- package/lib/ConnectPostgresHttp.js.map +1 -1
- package/lib/Contributions.d.ts +1 -1
- package/lib/Contributions.d.ts.map +1 -1
- package/lib/Contributions.js.map +1 -1
- package/lib/Credentials.d.ts +5 -4
- package/lib/Credentials.d.ts.map +1 -1
- package/lib/Credentials.js +18 -18
- package/lib/Credentials.js.map +1 -1
- package/lib/Decrypt.d.ts.map +1 -1
- package/lib/Decrypt.js.map +1 -1
- package/lib/DecryptHttp.d.ts.map +1 -1
- package/lib/DecryptHttp.js.map +1 -1
- package/lib/DeleteObject.d.ts.map +1 -1
- package/lib/DeleteObject.js.map +1 -1
- package/lib/DeleteObjectHttp.d.ts.map +1 -1
- package/lib/DeleteObjectHttp.js.map +1 -1
- package/lib/Deployment.d.ts.map +1 -1
- package/lib/Deployment.js.map +1 -1
- package/lib/DeploymentImages.d.ts.map +1 -1
- package/lib/DeploymentImages.js +1 -1
- package/lib/DeploymentImages.js.map +1 -1
- package/lib/DeploymentState.d.ts.map +1 -1
- package/lib/DeploymentState.js.map +1 -1
- package/lib/Encrypt.d.ts.map +1 -1
- package/lib/Encrypt.js.map +1 -1
- package/lib/EncryptHttp.d.ts.map +1 -1
- package/lib/EncryptHttp.js.map +1 -1
- package/lib/Environment.d.ts.map +1 -1
- package/lib/Environment.js.map +1 -1
- package/lib/Errors.d.ts +1 -1
- package/lib/Errors.d.ts.map +1 -1
- package/lib/Errors.js.map +1 -1
- package/lib/Exec.d.ts.map +1 -1
- package/lib/Exec.js.map +1 -1
- package/lib/ExecHttp.d.ts.map +1 -1
- package/lib/ExecHttp.js.map +1 -1
- package/lib/GetObject.d.ts.map +1 -1
- package/lib/GetObject.js.map +1 -1
- package/lib/GetObjectHttp.d.ts.map +1 -1
- package/lib/GetObjectHttp.js.map +1 -1
- package/lib/GetSecret.d.ts.map +1 -1
- package/lib/GetSecret.js.map +1 -1
- package/lib/GetSecretHttp.d.ts.map +1 -1
- package/lib/GetSecretHttp.js.map +1 -1
- package/lib/HeadObject.d.ts.map +1 -1
- package/lib/HeadObject.js.map +1 -1
- package/lib/HeadObjectHttp.d.ts.map +1 -1
- package/lib/HeadObjectHttp.js.map +1 -1
- package/lib/IpAssignment.d.ts +24 -1
- package/lib/IpAssignment.d.ts.map +1 -1
- package/lib/IpAssignment.js +21 -4
- package/lib/IpAssignment.js.map +1 -1
- package/lib/ListObjectsV2.d.ts.map +1 -1
- package/lib/ListObjectsV2.js.map +1 -1
- package/lib/ListObjectsV2Http.d.ts.map +1 -1
- package/lib/ListObjectsV2Http.js.map +1 -1
- package/lib/ListSecrets.d.ts.map +1 -1
- package/lib/ListSecrets.js.map +1 -1
- package/lib/ListSecretsHttp.d.ts.map +1 -1
- package/lib/ListSecretsHttp.js.map +1 -1
- package/lib/Machine.d.ts +27 -2
- package/lib/Machine.d.ts.map +1 -1
- package/lib/Machine.js +18 -3
- package/lib/Machine.js.map +1 -1
- package/lib/MachineContainers.d.ts.map +1 -1
- package/lib/MachineContainers.js.map +1 -1
- package/lib/Metadata.d.ts.map +1 -1
- package/lib/Metadata.js.map +1 -1
- package/lib/MountVolume.d.ts +7 -2
- package/lib/MountVolume.d.ts.map +1 -1
- package/lib/MountVolume.js.map +1 -1
- package/lib/Postgres.d.ts +34 -2
- package/lib/Postgres.d.ts.map +1 -1
- package/lib/Postgres.js +26 -6
- package/lib/Postgres.js.map +1 -1
- package/lib/PostgresMigrations.d.ts +7 -22
- package/lib/PostgresMigrations.d.ts.map +1 -1
- package/lib/PostgresMigrations.js +37 -89
- package/lib/PostgresMigrations.js.map +1 -1
- package/lib/Providers.d.ts +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js.map +1 -1
- package/lib/PutObject.d.ts.map +1 -1
- package/lib/PutObject.js.map +1 -1
- package/lib/PutObjectHttp.d.ts.map +1 -1
- package/lib/PutObjectHttp.js.map +1 -1
- package/lib/ReadRedis.d.ts.map +1 -1
- package/lib/ReadRedis.js.map +1 -1
- package/lib/ReadRedisHttp.d.ts.map +1 -1
- package/lib/ReadRedisHttp.js.map +1 -1
- package/lib/ReadWriteRedis.d.ts.map +1 -1
- package/lib/ReadWriteRedis.js.map +1 -1
- package/lib/ReadWriteRedisHttp.d.ts.map +1 -1
- package/lib/ReadWriteRedisHttp.js.map +1 -1
- package/lib/Redis.d.ts +23 -3
- package/lib/Redis.d.ts.map +1 -1
- package/lib/Redis.js +37 -6
- package/lib/Redis.js.map +1 -1
- package/lib/RedisBinding.d.ts.map +1 -1
- package/lib/RedisBinding.js +1 -1
- package/lib/RedisBinding.js.map +1 -1
- package/lib/RedisHttp.d.ts +1 -1
- package/lib/RedisHttp.d.ts.map +1 -1
- package/lib/RedisHttp.js +4 -1
- package/lib/RedisHttp.js.map +1 -1
- package/lib/Region.d.ts.map +1 -1
- package/lib/Region.js.map +1 -1
- package/lib/Secret.d.ts +23 -2
- package/lib/Secret.d.ts.map +1 -1
- package/lib/Secret.js +17 -2
- package/lib/Secret.js.map +1 -1
- package/lib/SecretHttp.d.ts.map +1 -1
- package/lib/SecretHttp.js.map +1 -1
- package/lib/SecretKey.d.ts +23 -2
- package/lib/SecretKey.d.ts.map +1 -1
- package/lib/SecretKey.js +17 -2
- package/lib/SecretKey.js.map +1 -1
- package/lib/SecretKeyHttp.d.ts.map +1 -1
- package/lib/SecretKeyHttp.js.map +1 -1
- package/lib/Service.d.ts +104 -34
- package/lib/Service.d.ts.map +1 -1
- package/lib/Service.js +301 -54
- package/lib/Service.js.map +1 -1
- package/lib/Sign.d.ts.map +1 -1
- package/lib/Sign.js.map +1 -1
- package/lib/SignHttp.d.ts.map +1 -1
- package/lib/SignHttp.js.map +1 -1
- package/lib/Sprite.d.ts +24 -5
- package/lib/Sprite.d.ts.map +1 -1
- package/lib/Sprite.js +22 -5
- package/lib/Sprite.js.map +1 -1
- package/lib/SpriteHttp.d.ts.map +1 -1
- package/lib/SpriteHttp.js.map +1 -1
- package/lib/Verify.d.ts.map +1 -1
- package/lib/Verify.js.map +1 -1
- package/lib/VerifyHttp.d.ts.map +1 -1
- package/lib/VerifyHttp.js.map +1 -1
- package/lib/Volume.d.ts +4 -4
- package/lib/Volume.d.ts.map +1 -1
- package/lib/Volume.js +21 -5
- package/lib/Volume.js.map +1 -1
- package/lib/VolumeSnapshot.d.ts +32 -1
- package/lib/VolumeSnapshot.d.ts.map +1 -1
- package/lib/VolumeSnapshot.js +24 -1
- package/lib/VolumeSnapshot.js.map +1 -1
- package/lib/Website/AssetDeployment.d.ts.map +1 -1
- package/lib/Website/AssetDeployment.js +9 -9
- package/lib/Website/AssetDeployment.js.map +1 -1
- package/lib/Website/Astro.d.ts +8 -6
- package/lib/Website/Astro.d.ts.map +1 -1
- package/lib/Website/Astro.js +12 -13
- package/lib/Website/Astro.js.map +1 -1
- package/lib/Website/Foldkit.d.ts.map +1 -1
- package/lib/Website/Foldkit.js.map +1 -1
- package/lib/Website/FrameworkSite.d.ts +39 -2
- package/lib/Website/FrameworkSite.d.ts.map +1 -1
- package/lib/Website/FrameworkSite.js +46 -3
- package/lib/Website/FrameworkSite.js.map +1 -1
- package/lib/Website/Nextjs.d.ts.map +1 -1
- package/lib/Website/Nextjs.js.map +1 -1
- package/lib/Website/Nuxt.d.ts.map +1 -1
- package/lib/Website/Nuxt.js.map +1 -1
- package/lib/Website/Octane.d.ts.map +1 -1
- package/lib/Website/Octane.js.map +1 -1
- package/lib/Website/ReactRouter.d.ts.map +1 -1
- package/lib/Website/ReactRouter.js.map +1 -1
- package/lib/Website/SolidStart.d.ts.map +1 -1
- package/lib/Website/SolidStart.js.map +1 -1
- package/lib/Website/StaticSite.d.ts +10 -3
- package/lib/Website/StaticSite.d.ts.map +1 -1
- package/lib/Website/StaticSite.js +16 -4
- package/lib/Website/StaticSite.js.map +1 -1
- package/lib/Website/SvelteKit.d.ts.map +1 -1
- package/lib/Website/SvelteKit.js.map +1 -1
- package/lib/Website/TanStackStart.d.ts.map +1 -1
- package/lib/Website/TanStackStart.js.map +1 -1
- package/lib/Website/Vinext.d.ts.map +1 -1
- package/lib/Website/Vinext.js.map +1 -1
- package/lib/Website/Vite.d.ts +9 -0
- package/lib/Website/Vite.d.ts.map +1 -1
- package/lib/Website/Vite.js +9 -0
- package/lib/Website/Vite.js.map +1 -1
- package/lib/Website/Vocs.d.ts.map +1 -1
- package/lib/Website/Vocs.js.map +1 -1
- package/lib/Website/Waku.d.ts.map +1 -1
- package/lib/Website/Waku.js.map +1 -1
- package/lib/Website/index.d.ts.map +1 -1
- package/lib/Website/index.js.map +1 -1
- package/lib/WriteCertificates.d.ts.map +1 -1
- package/lib/WriteCertificates.js.map +1 -1
- package/lib/WriteCertificatesHttp.d.ts.map +1 -1
- package/lib/WriteCertificatesHttp.js +2 -2
- package/lib/WriteCertificatesHttp.js.map +1 -1
- package/lib/WriteRedis.d.ts.map +1 -1
- package/lib/WriteRedis.js.map +1 -1
- package/lib/WriteRedisHttp.d.ts.map +1 -1
- package/lib/WriteRedisHttp.js.map +1 -1
- package/lib/WriteSecret.d.ts.map +1 -1
- package/lib/WriteSecret.js.map +1 -1
- package/lib/WriteSecretHttp.d.ts.map +1 -1
- package/lib/WriteSecretHttp.js.map +1 -1
- package/lib/bluegreen.d.ts +5 -4
- package/lib/bluegreen.d.ts.map +1 -1
- package/lib/bluegreen.js +7 -5
- package/lib/bluegreen.js.map +1 -1
- package/lib/hosted.d.ts +19 -4
- package/lib/hosted.d.ts.map +1 -1
- package/lib/hosted.js +101 -11
- package/lib/hosted.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/internal/Oidc.d.ts +15 -0
- package/lib/internal/Oidc.d.ts.map +1 -0
- package/lib/internal/Oidc.js +73 -0
- package/lib/internal/Oidc.js.map +1 -0
- package/lib/internal/RemoteBuilder.d.ts.map +1 -1
- package/lib/internal/RemoteBuilder.js +21 -8
- package/lib/internal/RemoteBuilder.js.map +1 -1
- package/lib/leases.d.ts +6 -2
- package/lib/leases.d.ts.map +1 -1
- package/lib/leases.js +25 -5
- package/lib/leases.js.map +1 -1
- package/lib/ports.d.ts.map +1 -1
- package/lib/ports.js.map +1 -1
- package/lib/replicas.d.ts +39 -27
- package/lib/replicas.d.ts.map +1 -1
- package/lib/replicas.js +63 -8
- package/lib/replicas.js.map +1 -1
- package/lib/rpc.d.ts +14 -12
- package/lib/rpc.d.ts.map +1 -1
- package/lib/rpc.js +38 -40
- package/lib/rpc.js.map +1 -1
- package/package.json +15 -15
- package/src/App.ts +41 -10
- package/src/BindService.ts +28 -36
- package/src/Bootstrap.ts +2 -0
- package/src/Bucket.ts +278 -45
- package/src/BucketBinding.ts +1 -1
- package/src/Certificate.ts +36 -5
- package/src/ConnectPostgres.ts +1 -1
- package/src/ConnectPostgresHttp.ts +1 -1
- package/src/Credentials.ts +18 -41
- package/src/DeploymentImages.ts +1 -1
- package/src/Errors.ts +1 -1
- package/src/IpAssignment.ts +35 -5
- package/src/Machine.ts +39 -4
- package/src/MountVolume.ts +5 -2
- package/src/Postgres.ts +50 -8
- package/src/PostgresMigrations.ts +39 -99
- package/src/Redis.ts +54 -9
- package/src/RedisBinding.ts +1 -1
- package/src/RedisHttp.ts +4 -1
- package/src/Secret.ts +32 -4
- package/src/SecretKey.ts +32 -4
- package/src/Service.ts +441 -88
- package/src/Sprite.ts +45 -17
- package/src/Volume.ts +30 -6
- package/src/VolumeSnapshot.ts +40 -2
- package/src/Website/AssetDeployment.ts +13 -15
- package/src/Website/Astro.ts +20 -15
- package/src/Website/FrameworkSite.ts +92 -6
- package/src/Website/StaticSite.ts +27 -7
- package/src/Website/Vite.ts +9 -0
- package/src/WriteCertificatesHttp.ts +2 -2
- package/src/bluegreen.ts +11 -5
- package/src/hosted.ts +121 -17
- package/src/internal/Oidc.ts +99 -0
- package/src/internal/RemoteBuilder.ts +37 -19
- package/src/leases.ts +35 -7
- package/src/replicas.ts +70 -9
- package/src/rpc.ts +37 -40
package/src/Bucket.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { fromCredentials, type Credentials } from "@distilled.cloud/aws/Credentials";
|
|
2
|
+
import * as AwsEndpoint from "@distilled.cloud/aws/Endpoint";
|
|
3
|
+
import type { RegionName } from "@distilled.cloud/aws/Region";
|
|
4
|
+
import * as S3 from "@distilled.cloud/aws/s3";
|
|
1
5
|
import type { AddOnsResponseEdgesItemNode } from "@distilled.cloud/fly-io/addons";
|
|
2
6
|
import * as addons from "@distilled.cloud/fly-io/addons";
|
|
3
7
|
import * as machines from "@distilled.cloud/fly-io/machines";
|
|
@@ -9,6 +13,9 @@ import { createInternalTags } from "alchemy/Tags";
|
|
|
9
13
|
import * as Data from "effect/Data";
|
|
10
14
|
import * as Duration from "effect/Duration";
|
|
11
15
|
import * as Effect from "effect/Effect";
|
|
16
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
17
|
+
import type * as HttpClient from "effect/http/HttpClient";
|
|
18
|
+
import * as Layer from "effect/Layer";
|
|
12
19
|
import * as Redacted from "effect/Redacted";
|
|
13
20
|
import * as Result from "effect/Result";
|
|
14
21
|
import * as Schedule from "effect/Schedule";
|
|
@@ -133,9 +140,19 @@ export type Bucket = Resource<
|
|
|
133
140
|
bucketName: Redacted.Redacted<string> | undefined;
|
|
134
141
|
},
|
|
135
142
|
never,
|
|
136
|
-
Providers
|
|
143
|
+
Providers,
|
|
144
|
+
BucketLookup
|
|
137
145
|
>;
|
|
138
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Props for {@link Bucket.lookup}: find a Tigris bucket this stack
|
|
149
|
+
* doesn't manage by its name.
|
|
150
|
+
*/
|
|
151
|
+
export interface BucketLookup {
|
|
152
|
+
/** Tigris bucket / add-on name. */
|
|
153
|
+
name: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
139
156
|
/**
|
|
140
157
|
* A Fly.Bucket is Tigris S3-compatible object storage, billed through
|
|
141
158
|
* Fly. It is an org-scoped GraphQL add-on (`AddOnType` `tigris`).
|
|
@@ -245,13 +262,25 @@ export type Bucket = Resource<
|
|
|
245
262
|
* });
|
|
246
263
|
* ```
|
|
247
264
|
*
|
|
265
|
+
* ### Looking up an existing bucket
|
|
266
|
+
* `Bucket.lookup` references a bucket Alchemy doesn't manage (created with
|
|
267
|
+
* `fly storage create` or by another stack). It is read during plan/deploy,
|
|
268
|
+
* never created, updated or deleted. Tigris returns a bucket's access keys
|
|
269
|
+
* only when it is created, so a looked-up bucket's credential attributes
|
|
270
|
+
* are `undefined` unless Fly still reports them.
|
|
271
|
+
*
|
|
272
|
+
* **Example:** Reference an existing bucket
|
|
273
|
+
* ```typescript
|
|
274
|
+
* const assets = Fly.Bucket.lookup({ name: "my-assets" });
|
|
275
|
+
* ```
|
|
276
|
+
*
|
|
248
277
|
* @resource
|
|
249
278
|
* @product Bucket
|
|
250
279
|
*/
|
|
251
280
|
export const Bucket = Resource<Bucket>("Fly.Bucket");
|
|
252
281
|
|
|
253
282
|
export class BucketNotCreated extends Data.TaggedError("Fly.BucketNotCreated")<{
|
|
254
|
-
|
|
283
|
+
resourceName: string;
|
|
255
284
|
}> {}
|
|
256
285
|
|
|
257
286
|
export class BucketOrgMissing extends Data.TaggedError("Fly.BucketOrgMissing")<{
|
|
@@ -259,18 +288,37 @@ export class BucketOrgMissing extends Data.TaggedError("Fly.BucketOrgMissing")<{
|
|
|
259
288
|
}> {}
|
|
260
289
|
|
|
261
290
|
export class BucketProvisionFailed extends Data.TaggedError("Fly.BucketProvisionFailed")<{
|
|
262
|
-
|
|
291
|
+
resourceName: string;
|
|
263
292
|
status: string | undefined;
|
|
264
293
|
errorMessage: string | undefined;
|
|
265
294
|
}> {}
|
|
266
295
|
|
|
267
296
|
class BucketPending extends Data.TaggedError("Fly.BucketPending")<{
|
|
268
|
-
|
|
297
|
+
resourceName: string;
|
|
269
298
|
status: string;
|
|
270
|
-
}> {
|
|
299
|
+
}> {
|
|
300
|
+
override get message() {
|
|
301
|
+
return `Tigris bucket ${this.resourceName} is still ${this.status}`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Fly lists an add-on whose Tigris bucket is gone, and Fly's delete
|
|
307
|
+
* fails on the missing bucket.
|
|
308
|
+
*/
|
|
309
|
+
export class BucketRecordStale extends Data.TaggedError("Fly.BucketRecordStale")<{
|
|
310
|
+
resourceName: string;
|
|
311
|
+
reason: string;
|
|
312
|
+
}> {
|
|
313
|
+
override get message() {
|
|
314
|
+
return `Fly keeps a stale add-on record for Tigris bucket ${this.resourceName}: ${this.reason}`;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
271
317
|
|
|
272
318
|
const TIGRIS = "tigris" as const;
|
|
273
319
|
|
|
320
|
+
const TIGRIS_ENDPOINT = "https://fly.storage.tigris.dev";
|
|
321
|
+
|
|
274
322
|
const backoff = Schedule.min([
|
|
275
323
|
Schedule.exponential(Duration.millis(500), 1.5),
|
|
276
324
|
Schedule.spaced(Duration.seconds(5)),
|
|
@@ -465,20 +513,22 @@ const waitUntilReady = (name: string, addOnId: string) =>
|
|
|
465
513
|
addOn,
|
|
466
514
|
): Effect.Effect<AddOnsResponseEdgesItemNode, BucketPending | BucketProvisionFailed> => {
|
|
467
515
|
if (addOn === undefined) {
|
|
468
|
-
return Effect.fail(new BucketPending({ name, status: "missing" }));
|
|
516
|
+
return Effect.fail(new BucketPending({ resourceName: name, status: "missing" }));
|
|
469
517
|
}
|
|
470
518
|
const status = addOn.status ?? undefined;
|
|
471
519
|
if (failedStatus(status)) {
|
|
472
520
|
return Effect.fail(
|
|
473
521
|
new BucketProvisionFailed({
|
|
474
|
-
name,
|
|
522
|
+
resourceName: name,
|
|
475
523
|
status,
|
|
476
524
|
errorMessage: addOn.errorMessage ?? undefined,
|
|
477
525
|
}),
|
|
478
526
|
);
|
|
479
527
|
}
|
|
480
528
|
if (pendingStatus(status)) {
|
|
481
|
-
return Effect.fail(
|
|
529
|
+
return Effect.fail(
|
|
530
|
+
new BucketPending({ resourceName: name, status: status ?? "creating" }),
|
|
531
|
+
);
|
|
482
532
|
}
|
|
483
533
|
return Effect.succeed(addOn);
|
|
484
534
|
},
|
|
@@ -491,37 +541,218 @@ const waitUntilReady = (name: string, addOnId: string) =>
|
|
|
491
541
|
Effect.catchTag("Fly.BucketPending", () => findById(addOnId)),
|
|
492
542
|
);
|
|
493
543
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
544
|
+
interface TigrisKeys {
|
|
545
|
+
accessKeyId: Redacted.Redacted<string>;
|
|
546
|
+
secretAccessKey: Redacted.Redacted<string>;
|
|
547
|
+
endpoint: string;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** The bucket's own S3 keys. Fly returns them only from createAddOn. */
|
|
551
|
+
const tigrisKeysOf = (
|
|
552
|
+
attrs: Pick<Bucket["Attributes"], "accessKeyId" | "secretAccessKey" | "endpoint">,
|
|
553
|
+
): TigrisKeys | undefined =>
|
|
554
|
+
attrs.accessKeyId !== undefined && attrs.secretAccessKey !== undefined
|
|
555
|
+
? {
|
|
556
|
+
accessKeyId: attrs.accessKeyId,
|
|
557
|
+
secretAccessKey: attrs.secretAccessKey,
|
|
558
|
+
endpoint: asString(attrs.endpoint) ?? TIGRIS_ENDPOINT,
|
|
559
|
+
}
|
|
560
|
+
: undefined;
|
|
561
|
+
|
|
562
|
+
const withTigrisKeys = <A, E>(
|
|
563
|
+
keys: TigrisKeys,
|
|
564
|
+
operation: Effect.Effect<A, E, Credentials | HttpClient.HttpClient>,
|
|
565
|
+
) =>
|
|
566
|
+
operation.pipe(
|
|
567
|
+
Effect.provide(
|
|
568
|
+
Layer.mergeAll(
|
|
569
|
+
fromCredentials(
|
|
570
|
+
{ accessKeyId: keys.accessKeyId, secretAccessKey: keys.secretAccessKey },
|
|
571
|
+
"auto" as RegionName,
|
|
572
|
+
),
|
|
573
|
+
AwsEndpoint.of(keys.endpoint),
|
|
574
|
+
FetchHttpClient.layer,
|
|
575
|
+
),
|
|
576
|
+
),
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Signed HeadBucket. Tigris answers 404 for a name it has no bucket
|
|
581
|
+
* under and 403 for a bucket the keys don't own. After a delete it can
|
|
582
|
+
* keep answering 200 to the deleted bucket's own keys for minutes.
|
|
583
|
+
*/
|
|
584
|
+
const tigrisHasBucket = (keys: TigrisKeys, bucketName: string) =>
|
|
585
|
+
withTigrisKeys(keys, S3.headBucket({ Bucket: bucketName })).pipe(
|
|
586
|
+
Effect.as(true),
|
|
587
|
+
Effect.catchTag(["NotFound", "NoSuchBucket"], () => Effect.succeed(false)),
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const requestDelete = (addOnId: string, name: string) =>
|
|
591
|
+
addons
|
|
592
|
+
.deleteAddOn({
|
|
593
|
+
input: addOnId.length > 0 ? { addOnId } : { name, provider: TIGRIS },
|
|
594
|
+
})
|
|
595
|
+
.pipe(
|
|
596
|
+
Effect.as("deleted" as const),
|
|
597
|
+
Effect.catchTag("TigrisBucketNotFound", () => Effect.succeed("missing" as const)),
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
interface DeleteTarget {
|
|
601
|
+
addOnId: string;
|
|
602
|
+
/** Fly add-on name. */
|
|
603
|
+
name: string;
|
|
604
|
+
/** Tigris bucket name (the add-on name unless Fly said otherwise). */
|
|
605
|
+
bucketName: string;
|
|
606
|
+
keys: TigrisKeys | undefined;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const deleteTargetOf = (attrs: Bucket["Attributes"]): DeleteTarget => ({
|
|
610
|
+
addOnId: attrs.addOnId,
|
|
611
|
+
name: attrs.name,
|
|
612
|
+
bucketName: asString(attrs.bucketName) ?? attrs.name,
|
|
613
|
+
keys: tigrisKeysOf(attrs),
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Delete through Fly (which also empties the bucket), then confirm.
|
|
618
|
+
* With the bucket's keys, Tigris decides (its missing-bucket answer to
|
|
619
|
+
* the delete, else HeadBucket): Fly's list lags it and can keep a stale
|
|
620
|
+
* record. Without keys, the add-on must leave Fly's list.
|
|
621
|
+
* Fails with BucketPending once the confirm runs out; `missing` reports
|
|
622
|
+
* whether Fly's last delete said Tigris has no such bucket while the
|
|
623
|
+
* record stayed listed (only without keys).
|
|
624
|
+
*/
|
|
625
|
+
const deleteVerified = (target: DeleteTarget) =>
|
|
626
|
+
Effect.suspend(() => {
|
|
627
|
+
const state = { missing: false };
|
|
628
|
+
return Effect.gen(function* () {
|
|
629
|
+
// Other delete failures (a bucket still attached to an App, a Fly
|
|
630
|
+
// 5xx) are transient; the confirm below decides.
|
|
631
|
+
const requested = yield* Effect.result(requestDelete(target.addOnId, target.name));
|
|
632
|
+
state.missing = Result.isSuccess(requested) && requested.success === "missing";
|
|
633
|
+
if (target.keys !== undefined) {
|
|
634
|
+
// Fly forwards Tigris's own "bucket does not exist" for the delete:
|
|
635
|
+
// the bucket is gone. HeadBucket can keep answering 200 to the
|
|
636
|
+
// deleted bucket's keys for minutes after that.
|
|
637
|
+
if (state.missing) return;
|
|
638
|
+
if (!(yield* tigrisHasBucket(target.keys, target.bucketName))) return;
|
|
639
|
+
return yield* new BucketPending({ resourceName: target.name, status: "deleting" });
|
|
640
|
+
}
|
|
641
|
+
const still =
|
|
642
|
+
(target.addOnId.length > 0 ? yield* findById(target.addOnId) : undefined) ??
|
|
643
|
+
(target.name.length > 0 ? yield* findByName(target.name) : undefined);
|
|
644
|
+
if (still !== undefined) {
|
|
645
|
+
return yield* new BucketPending({
|
|
646
|
+
resourceName: target.name,
|
|
647
|
+
status: still.status ?? "deleting",
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}).pipe(
|
|
651
|
+
Effect.retry({
|
|
652
|
+
while: (e) => e._tag === "Fly.BucketPending",
|
|
653
|
+
schedule: Schedule.spaced("2 seconds"),
|
|
654
|
+
times: 20,
|
|
503
655
|
}),
|
|
656
|
+
Effect.as({ missing: false }),
|
|
657
|
+
Effect.catchTag("Fly.BucketPending", (pending) =>
|
|
658
|
+
state.missing && target.keys === undefined
|
|
659
|
+
? Effect.succeed({ missing: true })
|
|
660
|
+
: Effect.fail(pending),
|
|
661
|
+
),
|
|
504
662
|
);
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const randomBase32 = (length: number) =>
|
|
666
|
+
Effect.sync(() => {
|
|
667
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
668
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(length)), (b) => alphabet[b % 32]).join(
|
|
669
|
+
"",
|
|
670
|
+
);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* A throwaway bucket, only for its keys. Its name matches the physical
|
|
675
|
+
* name shape, so a leaked one is listed and nuked like any other.
|
|
676
|
+
*/
|
|
677
|
+
const helperBucket = Effect.fn(function* (orgSlug: string) {
|
|
678
|
+
const org = yield* resolveOrganization(orgSlug);
|
|
679
|
+
if (org === undefined) {
|
|
680
|
+
return yield* new BucketOrgMissing({ orgSlug });
|
|
681
|
+
}
|
|
682
|
+
yield* ensureTos(orgSlug, org.id);
|
|
683
|
+
const name = `alchemy-fix-${yield* randomBase32(16)}`;
|
|
684
|
+
const created = yield* addons.createAddOn({
|
|
685
|
+
input: {
|
|
686
|
+
type: TIGRIS,
|
|
687
|
+
name,
|
|
688
|
+
organizationId: org.id,
|
|
689
|
+
options: desiredOptions({}),
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
return toAttrs(created.addOn, undefined, { name, orgSlug });
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Fly keeps an add-on record whose Tigris bucket is gone, and its
|
|
697
|
+
* delete fails on the missing bucket. Recreate the name in Tigris with a
|
|
698
|
+
* throwaway bucket's keys so Fly's delete has a bucket to remove.
|
|
699
|
+
* CreateBucket fails on a name Tigris still holds, so this never touches
|
|
700
|
+
* a live bucket. A name deleted in the last several minutes stays
|
|
701
|
+
* blocked (`BucketInaccessible`); the delete fails and a later one
|
|
702
|
+
* repairs it.
|
|
703
|
+
*/
|
|
704
|
+
const repairStaleRecord = Effect.fn(function* (target: DeleteTarget, orgSlug: string) {
|
|
705
|
+
yield* Effect.logWarning(
|
|
706
|
+
`Fly still lists Tigris bucket ${target.name} after Tigris reported it gone; repairing the stale record`,
|
|
707
|
+
);
|
|
708
|
+
yield* Effect.scoped(
|
|
709
|
+
Effect.gen(function* () {
|
|
710
|
+
const helper = yield* Effect.acquireRelease(helperBucket(orgSlug), (attrs) =>
|
|
711
|
+
deleteVerified(deleteTargetOf(attrs)).pipe(Effect.ignore),
|
|
712
|
+
);
|
|
713
|
+
const keys = tigrisKeysOf(helper);
|
|
714
|
+
if (keys === undefined) {
|
|
715
|
+
return yield* new BucketRecordStale({
|
|
716
|
+
resourceName: target.name,
|
|
717
|
+
reason: "Fly returned no keys for the temporary repair bucket",
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
const recreated = yield* Effect.result(
|
|
721
|
+
withTigrisKeys(keys, S3.createBucket({ Bucket: target.bucketName })),
|
|
722
|
+
);
|
|
723
|
+
if (Result.isFailure(recreated)) {
|
|
724
|
+
return yield* new BucketRecordStale({
|
|
725
|
+
resourceName: target.name,
|
|
726
|
+
reason: `Tigris would not recreate the name (${recreated.failure._tag}); a recently deleted name stays blocked for several minutes, so retry the delete later`,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
const result = yield* deleteVerified({ ...target, keys: undefined });
|
|
730
|
+
if (result.missing) {
|
|
731
|
+
return yield* new BucketRecordStale({
|
|
732
|
+
resourceName: target.name,
|
|
733
|
+
reason: "Fly's delete still reported a missing bucket after the name was recreated",
|
|
734
|
+
});
|
|
735
|
+
}
|
|
518
736
|
}),
|
|
519
737
|
);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Delete and confirm. A stale Fly record is repaired only when there are
|
|
742
|
+
* no keys to ask Tigris with (nuke, adopted buckets): with keys, Tigris's
|
|
743
|
+
* 404 already proves the bucket is gone.
|
|
744
|
+
*/
|
|
745
|
+
const waitUntilGone = Effect.fn(function* (attrs: Bucket["Attributes"]) {
|
|
746
|
+
const target = deleteTargetOf(attrs);
|
|
747
|
+
const result = yield* deleteVerified(target);
|
|
748
|
+
if (!result.missing) return;
|
|
749
|
+
yield* repairStaleRecord(target, attrs.orgSlug ?? (yield* resolveOrgSlug()));
|
|
750
|
+
});
|
|
520
751
|
|
|
521
752
|
const resolveOrganization = (orgSlug: string) =>
|
|
522
753
|
addons
|
|
523
754
|
.organization({ slug: orgSlug })
|
|
524
|
-
.pipe(Effect.catchTag("FlyIoParseError", () => Effect.
|
|
755
|
+
.pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
|
|
525
756
|
|
|
526
757
|
const ensureTos = (orgSlug: string, organizationId: string) =>
|
|
527
758
|
Effect.gen(function* () {
|
|
@@ -580,6 +811,15 @@ export const BucketProvider = () =>
|
|
|
580
811
|
return undefined;
|
|
581
812
|
}),
|
|
582
813
|
|
|
814
|
+
lookup: Effect.fn(function* ({ name }) {
|
|
815
|
+
const found = yield* findByName(name);
|
|
816
|
+
if (found === undefined) return undefined;
|
|
817
|
+
const detail = yield* addons
|
|
818
|
+
.addOn({ id: found.id, name: found.name ?? name, provider: TIGRIS })
|
|
819
|
+
.pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
|
|
820
|
+
return toAttrs(detail ?? found, undefined, { name });
|
|
821
|
+
}),
|
|
822
|
+
|
|
583
823
|
read: Effect.fn(function* ({ id, olds, output }) {
|
|
584
824
|
const name = yield* resolveBucketName(id, olds?.name, output?.name);
|
|
585
825
|
const found =
|
|
@@ -650,13 +890,13 @@ export const BucketProvider = () =>
|
|
|
650
890
|
}
|
|
651
891
|
|
|
652
892
|
if (current === undefined || current.id.length === 0) {
|
|
653
|
-
return yield* new BucketNotCreated({ name });
|
|
893
|
+
return yield* new BucketNotCreated({ resourceName: name });
|
|
654
894
|
}
|
|
655
895
|
|
|
656
896
|
const status = current.status ?? undefined;
|
|
657
897
|
if (failedStatus(status)) {
|
|
658
898
|
return yield* new BucketProvisionFailed({
|
|
659
|
-
name,
|
|
899
|
+
resourceName: name,
|
|
660
900
|
status,
|
|
661
901
|
errorMessage: current.errorMessage ?? undefined,
|
|
662
902
|
});
|
|
@@ -714,15 +954,8 @@ export const BucketProvider = () =>
|
|
|
714
954
|
}),
|
|
715
955
|
|
|
716
956
|
delete: Effect.fn(function* ({ output }) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (addOnId.length === 0 && name.length === 0) return;
|
|
720
|
-
// Always wait for the add-on to leave the list. A failed
|
|
721
|
-
// deleteAddOn plus a single list miss is not gone — Tigris
|
|
722
|
-
// list lags, and a bucket still attached to an App is a
|
|
723
|
-
// transient error. waitUntilGone retries delete and only
|
|
724
|
-
// succeeds once list (id and name) is empty.
|
|
725
|
-
yield* waitUntilGone(addOnId, name);
|
|
957
|
+
if (output.addOnId.length === 0 && output.name.length === 0) return;
|
|
958
|
+
yield* waitUntilGone(output);
|
|
726
959
|
}),
|
|
727
960
|
});
|
|
728
961
|
|
|
@@ -749,7 +982,7 @@ const requireBucketSecrets = Effect.fn(function* (appName: string, values: Recor
|
|
|
749
982
|
];
|
|
750
983
|
if (required.some((key) => !values[key]?.trim())) {
|
|
751
984
|
return yield* new TigrisCredentialsMissing({
|
|
752
|
-
|
|
985
|
+
resourceName: values.BUCKET_NAME || appName,
|
|
753
986
|
});
|
|
754
987
|
}
|
|
755
988
|
});
|
|
@@ -820,7 +1053,7 @@ const findAttachedBucket = (input: { id?: string; name: string }) =>
|
|
|
820
1053
|
: undefined;
|
|
821
1054
|
if (listed === undefined) {
|
|
822
1055
|
return yield* new BucketPending({
|
|
823
|
-
|
|
1056
|
+
resourceName: input.name || input.id || "",
|
|
824
1057
|
status: "missing",
|
|
825
1058
|
});
|
|
826
1059
|
}
|
|
@@ -830,7 +1063,7 @@ const findAttachedBucket = (input: { id?: string; name: string }) =>
|
|
|
830
1063
|
name: listed.name ?? input.name,
|
|
831
1064
|
provider: TIGRIS,
|
|
832
1065
|
})
|
|
833
|
-
.pipe(Effect.catchTag("FlyIoParseError", () => Effect.
|
|
1066
|
+
.pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
|
|
834
1067
|
return detail ?? listed;
|
|
835
1068
|
});
|
|
836
1069
|
|
package/src/BucketBinding.ts
CHANGED
|
@@ -106,7 +106,7 @@ export const makeTigrisS3Binding = <I extends { Bucket?: string }, A, E>(options
|
|
|
106
106
|
resolvedEndpoint === undefined
|
|
107
107
|
) {
|
|
108
108
|
return yield* new TigrisCredentialsMissing({
|
|
109
|
-
|
|
109
|
+
resourceName: resolvedName ?? bucket.LogicalId,
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
const scope = {
|
package/src/Certificate.ts
CHANGED
|
@@ -97,9 +97,21 @@ export type Certificate = Resource<
|
|
|
97
97
|
source: CertificateSource;
|
|
98
98
|
},
|
|
99
99
|
never,
|
|
100
|
-
Providers
|
|
100
|
+
Providers,
|
|
101
|
+
CertificateLookup
|
|
101
102
|
>;
|
|
102
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Props for {@link Certificate.lookup}: find a certificate this stack
|
|
106
|
+
* doesn't manage by its App and hostname.
|
|
107
|
+
*/
|
|
108
|
+
export interface CertificateLookup {
|
|
109
|
+
/** Fly App name the certificate is attached to. */
|
|
110
|
+
appName: string;
|
|
111
|
+
/** Hostname the certificate covers. */
|
|
112
|
+
hostname: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
103
115
|
/**
|
|
104
116
|
* A Fly.Certificate covers a hostname on an {@link App}. Default
|
|
105
117
|
* `kind` is `"acme"` (Let's Encrypt). `"custom"` uploads a PEM.
|
|
@@ -176,6 +188,20 @@ export type Certificate = Resource<
|
|
|
176
188
|
* });
|
|
177
189
|
* ```
|
|
178
190
|
*
|
|
191
|
+
* ### Looking up an existing certificate
|
|
192
|
+
* `Certificate.lookup` references a certificate Alchemy doesn't manage
|
|
193
|
+
* (added with `fly certs add` or by another stack). It is read during
|
|
194
|
+
* plan/deploy, never created, updated or deleted.
|
|
195
|
+
*
|
|
196
|
+
* **Example:** Read the DNS records Fly expects
|
|
197
|
+
* ```typescript
|
|
198
|
+
* const cert = Fly.Certificate.lookup({
|
|
199
|
+
* appName: "my-site",
|
|
200
|
+
* hostname: "www.example.com",
|
|
201
|
+
* });
|
|
202
|
+
* const target = cert.dnsRequirements;
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
179
205
|
* @resource
|
|
180
206
|
* @product Certificate
|
|
181
207
|
*/
|
|
@@ -314,7 +340,7 @@ const toAttrsFromSummary = (
|
|
|
314
340
|
const getByHostname = (appName: string, hostname: string) =>
|
|
315
341
|
machines
|
|
316
342
|
.getAppCertificate({ app_name: appName, hostname })
|
|
317
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.
|
|
343
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
318
344
|
|
|
319
345
|
const listAppCertificates = (appName: string) =>
|
|
320
346
|
Effect.gen(function* () {
|
|
@@ -372,7 +398,7 @@ const createAcme = (appName: string, hostname: string) =>
|
|
|
372
398
|
app_name: appName,
|
|
373
399
|
hostname,
|
|
374
400
|
})
|
|
375
|
-
.pipe(Effect.catchTag("Conflict", () => Effect.
|
|
401
|
+
.pipe(Effect.catchTag("Conflict", () => Effect.undefined));
|
|
376
402
|
|
|
377
403
|
const createCustom = (appName: string, hostname: string, fullchain: string, privateKey: string) =>
|
|
378
404
|
machines
|
|
@@ -382,7 +408,7 @@ const createCustom = (appName: string, hostname: string, fullchain: string, priv
|
|
|
382
408
|
fullchain,
|
|
383
409
|
private_key: privateKey,
|
|
384
410
|
})
|
|
385
|
-
.pipe(Effect.catchTag("Conflict", () => Effect.
|
|
411
|
+
.pipe(Effect.catchTag("Conflict", () => Effect.undefined));
|
|
386
412
|
|
|
387
413
|
const replaceCustom = (appName: string, hostname: string, fullchain: string, privateKey: string) =>
|
|
388
414
|
Effect.gen(function* () {
|
|
@@ -423,6 +449,11 @@ export const CertificateProvider = () =>
|
|
|
423
449
|
return undefined;
|
|
424
450
|
}),
|
|
425
451
|
|
|
452
|
+
lookup: Effect.fn(function* ({ appName, hostname }) {
|
|
453
|
+
const found = yield* getByHostname(appName, hostname);
|
|
454
|
+
return found === undefined ? undefined : toAttrs(appName, found, hostname);
|
|
455
|
+
}),
|
|
456
|
+
|
|
426
457
|
read: Effect.fn(function* ({ olds, output }) {
|
|
427
458
|
const appName = output?.appName ?? appNameOf(olds?.app);
|
|
428
459
|
const hostname = output?.hostname ?? olds?.hostname;
|
|
@@ -524,7 +555,7 @@ export const CertificateProvider = () =>
|
|
|
524
555
|
app_name: appName,
|
|
525
556
|
hostname,
|
|
526
557
|
})
|
|
527
|
-
.pipe(Effect.catchTag(["NotFound", "BadRequest"], () => Effect.
|
|
558
|
+
.pipe(Effect.catchTag(["NotFound", "BadRequest"], () => Effect.undefined));
|
|
528
559
|
if (checked !== undefined) {
|
|
529
560
|
current = checked;
|
|
530
561
|
}
|
package/src/ConnectPostgres.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const connectEnvKeys = (postgres: Pick<Postgres, "LogicalId">) => {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
export class PostgresUrlMissing extends Data.TaggedError("Fly.PostgresUrlMissing")<{
|
|
49
|
-
|
|
49
|
+
resourceName: string;
|
|
50
50
|
}> {}
|
|
51
51
|
|
|
52
52
|
export interface ConnectPostgresClient {
|
|
@@ -26,7 +26,7 @@ const asRedactedUrl = (
|
|
|
26
26
|
const url = typeof plain === "string" ? plain : "";
|
|
27
27
|
return url.length > 0
|
|
28
28
|
? Effect.succeed(Redacted.make(url))
|
|
29
|
-
: Effect.fail(new PostgresUrlMissing({ name }));
|
|
29
|
+
: Effect.fail(new PostgresUrlMissing({ resourceName: name }));
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
/**
|
package/src/Credentials.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import { ConfigError } from "@distilled.cloud/core/errors";
|
|
2
2
|
import { Credentials, CredentialsFromEnv } from "@distilled.cloud/fly-io";
|
|
3
|
-
import {
|
|
4
|
-
deferUntilFirstUse,
|
|
5
|
-
orDieCredentialsUnavailable,
|
|
6
|
-
resolveProviderConfig,
|
|
7
|
-
} from "alchemy/Auth/Resolve";
|
|
3
|
+
import { lazyCredentials, orDieCredentialsUnavailable } from "alchemy/Auth/Resolve";
|
|
8
4
|
import * as Output from "alchemy/Output";
|
|
9
5
|
import * as Effect from "effect/Effect";
|
|
10
6
|
import * as Layer from "effect/Layer";
|
|
11
7
|
import * as Option from "effect/Option";
|
|
12
8
|
import * as Redacted from "effect/Redacted";
|
|
13
|
-
import {
|
|
14
|
-
FLY_AUTH_PROVIDER_NAME,
|
|
15
|
-
type FlyAuthConfig,
|
|
16
|
-
type FlyResolvedCredentials,
|
|
17
|
-
} from "./AuthProvider.ts";
|
|
9
|
+
import { FLY_AUTH_PROVIDER_NAME, type FlyResolvedCredentials } from "./AuthProvider.ts";
|
|
18
10
|
|
|
19
11
|
export {
|
|
20
12
|
Credentials,
|
|
@@ -32,11 +24,17 @@ export {
|
|
|
32
24
|
*/
|
|
33
25
|
export const bindFlyApiToken = (): Effect.Effect<void, never, Credentials> =>
|
|
34
26
|
Effect.gen(function* () {
|
|
27
|
+
// Bound as Redacted so a Service that owns its App stores the token
|
|
28
|
+
// as an App secret, not plain Machine env. The runtime reads it back
|
|
29
|
+
// with `CredentialsFromEnv`.
|
|
35
30
|
const token = globalThis.__ALCHEMY_RUNTIME__
|
|
36
|
-
? ""
|
|
31
|
+
? Redacted.make("")
|
|
37
32
|
: yield* Credentials.pipe(
|
|
38
33
|
Effect.flatMap((resolve) => resolve),
|
|
39
|
-
Effect.map((cfg) =>
|
|
34
|
+
Effect.map((cfg) => cfg.apiKey),
|
|
35
|
+
// Binding initialization cannot fail; the engine reports this
|
|
36
|
+
// defect as `CredentialsRequired`.
|
|
37
|
+
orDieCredentialsUnavailable("Fly"),
|
|
40
38
|
);
|
|
41
39
|
yield* Output.named(Output.asOutput(token), "FLY_API_TOKEN").asEffect().pipe(Effect.asVoid);
|
|
42
40
|
});
|
|
@@ -49,9 +47,10 @@ export const bindFlyApiToken = (): Effect.Effect<void, never, Credentials> =>
|
|
|
49
47
|
* `Credentials`, and the binding must use them — a laptop deploy has no
|
|
50
48
|
* `FLY_API_TOKEN` in its env once the token lives in the Alchemy profile.
|
|
51
49
|
* Inside a deployed Machine there is no profile; `bindFlyApiToken`
|
|
52
|
-
* has already `RuntimeContext.set` the org token, which
|
|
53
|
-
*
|
|
54
|
-
* `Credentials` when present, otherwise read
|
|
50
|
+
* has already `RuntimeContext.set` the org token, which reaches the
|
|
51
|
+
* Machine as `FLY_API_TOKEN` (an App secret, or Machine env in a shared
|
|
52
|
+
* App). So: reuse the ambient `Credentials` when present, otherwise read
|
|
53
|
+
* it with {@link CredentialsFromEnv}.
|
|
55
54
|
*/
|
|
56
55
|
export const CredentialsFromAmbientOrEnv: Layer.Layer<Credentials> = Layer.effect(
|
|
57
56
|
Credentials,
|
|
@@ -73,30 +72,8 @@ export const CredentialsFromAmbientOrEnv: Layer.Layer<Credentials> = Layer.effec
|
|
|
73
72
|
export const fromAuthProvider = () =>
|
|
74
73
|
Layer.effect(
|
|
75
74
|
Credentials,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
FLY_AUTH_PROVIDER_NAME,
|
|
81
|
-
).pipe(
|
|
82
|
-
Effect.flatMap(({ profileName, resolve }) =>
|
|
83
|
-
resolve.pipe(
|
|
84
|
-
Effect.map((creds) => ({
|
|
85
|
-
apiKey: creds.apiKey,
|
|
86
|
-
apiBaseUrl: creds.apiBaseUrl,
|
|
87
|
-
})),
|
|
88
|
-
Effect.mapError((e) =>
|
|
89
|
-
ConfigError.make({
|
|
90
|
-
message: `Failed to resolve Fly credentials from ${profileName === undefined ? "the CI environment" : `profile '${profileName}'`}: ${(e as { message?: string }).message ?? String(e)}`,
|
|
91
|
-
}),
|
|
92
|
-
),
|
|
93
|
-
),
|
|
94
|
-
),
|
|
95
|
-
deferUntilFirstUse,
|
|
96
|
-
);
|
|
97
|
-
return yield* resolve.pipe(
|
|
98
|
-
orDieCredentialsUnavailable(FLY_AUTH_PROVIDER_NAME),
|
|
99
|
-
Effect.cached,
|
|
100
|
-
);
|
|
101
|
-
}),
|
|
75
|
+
lazyCredentials<FlyResolvedCredentials, ConfigError>()(FLY_AUTH_PROVIDER_NAME, (creds) => ({
|
|
76
|
+
apiKey: creds.apiKey,
|
|
77
|
+
apiBaseUrl: creds.apiBaseUrl,
|
|
78
|
+
})),
|
|
102
79
|
);
|
package/src/DeploymentImages.ts
CHANGED
|
@@ -35,7 +35,7 @@ export const encodeImageSet = (pins: readonly ContainerImagePin[]): string | und
|
|
|
35
35
|
|
|
36
36
|
export const decodeImageSet = (raw: string | undefined): ContainerImagePin[] | undefined => {
|
|
37
37
|
if (raw === undefined) return undefined;
|
|
38
|
-
const decoded = Schema.
|
|
38
|
+
const decoded = Schema.decodeOption(Schema.fromJsonString(ImageSet))(raw);
|
|
39
39
|
if (!Option.isSome(decoded)) return undefined;
|
|
40
40
|
const pins = decoded.value;
|
|
41
41
|
return valid(pins) ? canonical(pins) : undefined;
|
package/src/Errors.ts
CHANGED