@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/replicas.ts
CHANGED
|
@@ -42,7 +42,7 @@ const waitBackoff = Schedule.exponential("500 millis");
|
|
|
42
42
|
const SERVICE_CHECK_NAME_PREFIX = "servicecheck-";
|
|
43
43
|
|
|
44
44
|
export class ReplicaNotCreated extends Data.TaggedError("Fly.ReplicaNotCreated")<{
|
|
45
|
-
|
|
45
|
+
resourceName: string;
|
|
46
46
|
appName: string;
|
|
47
47
|
}> {}
|
|
48
48
|
|
|
@@ -115,7 +115,7 @@ export const gone = (machine: FlyMachine | undefined) =>
|
|
|
115
115
|
export const getMachineById = (appName: string, machineId: string) =>
|
|
116
116
|
machines.getMachine({ app_name: appName, machine_id: machineId }).pipe(
|
|
117
117
|
Effect.map((machine) => (gone(machine) ? undefined : machine)),
|
|
118
|
-
Effect.catchTag("NotFound", () => Effect.
|
|
118
|
+
Effect.catchTag("NotFound", () => Effect.undefined),
|
|
119
119
|
);
|
|
120
120
|
|
|
121
121
|
/**
|
|
@@ -137,7 +137,7 @@ const getLeasedMachine = (appName: string, machineId: string) =>
|
|
|
137
137
|
const getLeasedMachineById = (appName: string, machineId: string) =>
|
|
138
138
|
getLeasedMachine(appName, machineId).pipe(
|
|
139
139
|
Effect.map((machine) => (gone(machine) ? undefined : machine)),
|
|
140
|
-
Effect.catchTag("NotFound", () => Effect.
|
|
140
|
+
Effect.catchTag("NotFound", () => Effect.undefined),
|
|
141
141
|
);
|
|
142
142
|
|
|
143
143
|
export const listMachinesByApp = (appName: string) =>
|
|
@@ -653,6 +653,8 @@ export const leaseSnapshot = Effect.fn(function* (
|
|
|
653
653
|
(machine) =>
|
|
654
654
|
Effect.gen(function* () {
|
|
655
655
|
if (!machine.id || machine.host_status === "unreachable") return;
|
|
656
|
+
// Already deleted by this operation (a scale-down retired it).
|
|
657
|
+
if (leases.wasRemoved(machine.id)) return;
|
|
656
658
|
const id = machine.id;
|
|
657
659
|
yield* leases.acquire([id]).pipe(
|
|
658
660
|
Effect.tap(() => Effect.sync(() => leased.add(id))),
|
|
@@ -670,7 +672,7 @@ export const leaseSnapshot = Effect.fn(function* (
|
|
|
670
672
|
).pipe(Effect.timeout("30 seconds"));
|
|
671
673
|
const observed: FlyMachine[] = [];
|
|
672
674
|
for (const machine of ordered) {
|
|
673
|
-
if (!machine.id) continue;
|
|
675
|
+
if (!machine.id || leases.wasRemoved(machine.id)) continue;
|
|
674
676
|
if (machine.host_status === "unreachable") {
|
|
675
677
|
observed.push(machine);
|
|
676
678
|
continue;
|
|
@@ -1163,7 +1165,7 @@ const reconcileLeasedReplicas = Effect.fn(function* (
|
|
|
1163
1165
|
);
|
|
1164
1166
|
const volumeId = volume?.id;
|
|
1165
1167
|
if (volumeId === undefined) {
|
|
1166
|
-
return yield* new ReplicaNotCreated({ name, appName: input.appName });
|
|
1168
|
+
return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
|
|
1167
1169
|
}
|
|
1168
1170
|
usedVolumeIds.add(volumeId);
|
|
1169
1171
|
mounts.push({ volume: volumeId, path: group.disk.path });
|
|
@@ -1187,7 +1189,17 @@ const reconcileLeasedReplicas = Effect.fn(function* (
|
|
|
1187
1189
|
skip_launch: input.skipLaunch === true ? true : undefined,
|
|
1188
1190
|
min_secrets_version: input.minSecretsVersion,
|
|
1189
1191
|
})
|
|
1190
|
-
.pipe(
|
|
1192
|
+
.pipe(
|
|
1193
|
+
// A storage host out of capacity may still have made the Machine;
|
|
1194
|
+
// names are unique per App, so a retry that made it before hits
|
|
1195
|
+
// Conflict and the listing below finds it.
|
|
1196
|
+
Effect.retry({
|
|
1197
|
+
while: (error) => error._tag === "StorageHostCapacity",
|
|
1198
|
+
schedule: Schedule.spaced("5 seconds"),
|
|
1199
|
+
times: 6,
|
|
1200
|
+
}),
|
|
1201
|
+
Effect.catchTag("Conflict", () => Effect.undefined),
|
|
1202
|
+
);
|
|
1191
1203
|
current =
|
|
1192
1204
|
created ??
|
|
1193
1205
|
(yield* listMachinesByApp(input.appName).pipe(
|
|
@@ -1200,11 +1212,11 @@ const reconcileLeasedReplicas = Effect.fn(function* (
|
|
|
1200
1212
|
),
|
|
1201
1213
|
));
|
|
1202
1214
|
if (current === undefined || current.id === undefined) {
|
|
1203
|
-
return yield* new ReplicaNotCreated({ name, appName: input.appName });
|
|
1215
|
+
return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
|
|
1204
1216
|
}
|
|
1205
1217
|
const observed = (yield* leaseSnapshot(input.appName, [current], leases))[0];
|
|
1206
1218
|
if (!observed || input.configDrifted(observed, { mounts, metadata })) {
|
|
1207
|
-
return yield* new ReplicaNotCreated({ name, appName: input.appName });
|
|
1219
|
+
return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
|
|
1208
1220
|
}
|
|
1209
1221
|
current = observed;
|
|
1210
1222
|
} else if (
|
|
@@ -1243,7 +1255,7 @@ const reconcileLeasedReplicas = Effect.fn(function* (
|
|
|
1243
1255
|
!sameChecks(observed.config?.checks, config.checks) ||
|
|
1244
1256
|
!sameStopConfig(observed.config?.stop_config, config.stop_config)
|
|
1245
1257
|
)
|
|
1246
|
-
return yield* new ReplicaNotCreated({ appName: input.appName, name });
|
|
1258
|
+
return yield* new ReplicaNotCreated({ appName: input.appName, resourceName: name });
|
|
1247
1259
|
return observed;
|
|
1248
1260
|
}).pipe(
|
|
1249
1261
|
Effect.retry({
|
|
@@ -1478,3 +1490,52 @@ export const observeReplicaSet = Effect.fn(function* (input: {
|
|
|
1478
1490
|
};
|
|
1479
1491
|
return undefined;
|
|
1480
1492
|
});
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* The Machines a lookup names: those whose name (or Alchemy base name) is
|
|
1496
|
+
* `name`, or `name-N` for a replica, or every Machine in the App when
|
|
1497
|
+
* `name` is omitted. Of an Alchemy deployment, only the newest generation
|
|
1498
|
+
* that finished rolling out counts; a Machine without that metadata
|
|
1499
|
+
* counts as it is. `undefined` when nothing in the App matches.
|
|
1500
|
+
*/
|
|
1501
|
+
export const lookupReplicaSet = Effect.fn(function* (input: { appName: string; name?: string }) {
|
|
1502
|
+
const named = (yield* listMachinesByApp(input.appName)).filter((machine) => {
|
|
1503
|
+
if (input.name === undefined) return true;
|
|
1504
|
+
const base = machine.config?.metadata?.[alchemyMetadataKeys.baseName];
|
|
1505
|
+
return (
|
|
1506
|
+
base === input.name ||
|
|
1507
|
+
machine.name === input.name ||
|
|
1508
|
+
(base === undefined && machine.name?.startsWith(`${input.name}-`) === true)
|
|
1509
|
+
);
|
|
1510
|
+
});
|
|
1511
|
+
const active = named.filter((machine) => {
|
|
1512
|
+
const phase = machine.config?.metadata?.[alchemyMetadataKeys.phase];
|
|
1513
|
+
return (phase === undefined || phase === "active") && machine.cordoned !== true;
|
|
1514
|
+
});
|
|
1515
|
+
const sequenceOf = (machine: FlyMachine) =>
|
|
1516
|
+
Number(machine.config?.metadata?.[alchemyMetadataKeys.sequence] ?? 0);
|
|
1517
|
+
const newest = Math.max(0, ...active.map(sequenceOf));
|
|
1518
|
+
const listed = active
|
|
1519
|
+
.filter((machine) => sequenceOf(machine) === newest)
|
|
1520
|
+
.sort((a, b) => replicaIndexOf(a) - replicaIndexOf(b));
|
|
1521
|
+
if (listed.length === 0) return undefined;
|
|
1522
|
+
const volumesById = new Map<string, FlyVolume>();
|
|
1523
|
+
for (const machine of listed) {
|
|
1524
|
+
for (const mount of machine.config?.mounts ?? []) {
|
|
1525
|
+
const volumeId = mount.volume;
|
|
1526
|
+
if (volumeId === undefined || volumesById.has(volumeId)) continue;
|
|
1527
|
+
const volume = yield* getVolumeById(input.appName, volumeId);
|
|
1528
|
+
if (volume !== undefined) volumesById.set(volumeId, volume);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
const replicas = listed.map((machine) => toReplica(machine, volumesById));
|
|
1532
|
+
return toReplicaSet(
|
|
1533
|
+
replicas,
|
|
1534
|
+
input.appName,
|
|
1535
|
+
listed[0]?.config?.metadata?.[alchemyMetadataKeys.baseName] ??
|
|
1536
|
+
input.name ??
|
|
1537
|
+
replicas[0]?.name ??
|
|
1538
|
+
"",
|
|
1539
|
+
listed[0]?.config?.services,
|
|
1540
|
+
);
|
|
1541
|
+
});
|
package/src/rpc.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import type { HttpEffect } from "alchemy/Http";
|
|
3
3
|
import { RPC_PATH_PREFIX, serveRpc } from "alchemy/Rpc";
|
|
4
|
+
import * as Clock from "effect/Clock";
|
|
5
|
+
import * as Config from "effect/Config";
|
|
4
6
|
import * as Effect from "effect/Effect";
|
|
5
7
|
import * as FileSystem from "effect/FileSystem";
|
|
6
8
|
import { HttpServerRequest } from "effect/http/HttpServerRequest";
|
|
7
9
|
import * as HttpServerResponse from "effect/http/HttpServerResponse";
|
|
8
10
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Machine env var holding the Apps allowed to call the Service's methods,
|
|
13
|
+
* comma-separated: the Apps of the Services that bind it.
|
|
14
|
+
*/
|
|
15
|
+
export const RPC_CALLERS_ENV = "ALCHEMY_FLY_RPC_CALLERS";
|
|
11
16
|
/** Machine env var holding the organization callers must belong to. */
|
|
12
17
|
export const RPC_ORG_ENV = "ALCHEMY_FLY_ORG";
|
|
13
18
|
/**
|
|
@@ -15,8 +20,6 @@ export const RPC_ORG_ENV = "ALCHEMY_FLY_ORG";
|
|
|
15
20
|
* Requests on it must come from Fly's private network.
|
|
16
21
|
*/
|
|
17
22
|
export const BINDING_PORT_ENV = "ALCHEMY_FLY_BINDING_PORT";
|
|
18
|
-
/** Request header carrying the caller token. */
|
|
19
|
-
export const RPC_TOKEN_HEADER = "x-alchemy-rpc-token";
|
|
20
23
|
/** Published port Alchemy adds when a Service has no plain-HTTP port. */
|
|
21
24
|
export const DEFAULT_BINDING_PORT = 7780;
|
|
22
25
|
|
|
@@ -26,21 +29,16 @@ const FLY_SRC_SIGNATURE_HEADER = "fly-src-signature";
|
|
|
26
29
|
const FLY_SRC_PUBLIC_KEY = "/.fly/fly-src.pub";
|
|
27
30
|
/** DER prefix that wraps a raw 32-byte ed25519 key as SubjectPublicKeyInfo. */
|
|
28
31
|
const ED25519_SPKI_PREFIX = "302a300506032b6570032100";
|
|
29
|
-
/**
|
|
30
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Oldest accepted `Fly-Src` timestamp, in seconds. The signature covers
|
|
34
|
+
* only the header, so a short window limits replay; Fly suggests 10s, and
|
|
35
|
+
* the rest allows for clock skew between its proxy and the Machine.
|
|
36
|
+
*/
|
|
37
|
+
const MAX_FLY_SRC_AGE_SECONDS = 30;
|
|
31
38
|
|
|
32
39
|
const header = (headers: Record<string, string | undefined>, name: string): string =>
|
|
33
40
|
headers[name] ?? "";
|
|
34
41
|
|
|
35
|
-
const tokensEqual = (left: string, right: string): boolean => {
|
|
36
|
-
if (left.length !== right.length) return false;
|
|
37
|
-
let mismatch = 0;
|
|
38
|
-
for (let i = 0; i < left.length; i++) {
|
|
39
|
-
mismatch |= left.charCodeAt(i) ^ right.charCodeAt(i);
|
|
40
|
-
}
|
|
41
|
-
return mismatch === 0;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
42
|
let publicKey: crypto.KeyObject | undefined;
|
|
45
43
|
|
|
46
44
|
/** Load Fly's `Fly-Src` verification key once per instance. */
|
|
@@ -64,40 +62,42 @@ const loadPublicKey = Effect.gen(function* () {
|
|
|
64
62
|
});
|
|
65
63
|
|
|
66
64
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* over its private network, so a public
|
|
65
|
+
* The calling App named by a valid `Fly-Src` signature from Fly's proxy for
|
|
66
|
+
* a caller in `org`, or `undefined`. Fly adds the header only to requests
|
|
67
|
+
* that arrive over its private network and signs who sent them, so a public
|
|
68
|
+
* request can never pass and no caller can claim another App.
|
|
70
69
|
*/
|
|
71
70
|
export const verifyFlySrc = (headers: Record<string, string | undefined>, org: string) =>
|
|
72
71
|
Effect.gen(function* () {
|
|
73
72
|
const source = header(headers, FLY_SRC_HEADER);
|
|
74
73
|
const signature = header(headers, FLY_SRC_SIGNATURE_HEADER);
|
|
75
|
-
if (source.length === 0 || signature.length === 0 || org.length === 0) return
|
|
74
|
+
if (source.length === 0 || signature.length === 0 || org.length === 0) return undefined;
|
|
76
75
|
const key = yield* loadPublicKey;
|
|
77
|
-
if (key === undefined) return
|
|
76
|
+
if (key === undefined) return undefined;
|
|
78
77
|
const valid = yield* Effect.try(() =>
|
|
79
78
|
crypto.verify(null, Buffer.from(source), key, Buffer.from(signature, "base64")),
|
|
80
79
|
).pipe(Effect.orElseSucceed(() => false));
|
|
81
|
-
if (!valid) return
|
|
80
|
+
if (!valid) return undefined;
|
|
82
81
|
const fields = Object.fromEntries(
|
|
83
82
|
source.split(";").map((pair) => {
|
|
84
83
|
const at = pair.indexOf("=");
|
|
85
84
|
return [pair.slice(0, at), pair.slice(at + 1)];
|
|
86
85
|
}),
|
|
87
86
|
);
|
|
88
|
-
const now = yield*
|
|
87
|
+
const now = (yield* Clock.currentTimeMillis) / 1000;
|
|
89
88
|
const age = now - Number(fields.ts);
|
|
90
|
-
|
|
89
|
+
const fresh = age >= -MAX_FLY_SRC_AGE_SECONDS && age <= MAX_FLY_SRC_AGE_SECONDS;
|
|
90
|
+
return fields.org === org && fresh && fields.app ? fields.app : undefined;
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
const unauthorized = HttpServerResponse.text("Unauthorized", { status: 401 });
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Serve a Service's RPC methods on `/__rpc__/*` and everything else with
|
|
97
|
-
* `fallback`. RPC calls must carry
|
|
98
|
-
*
|
|
99
|
-
* port Alchemy added for bindings must carry the `Fly-Src`
|
|
100
|
-
* that port never answers the public internet.
|
|
97
|
+
* `fallback`. RPC calls must carry a valid `Fly-Src` signature from the
|
|
98
|
+
* Service's organization naming an App that binds the Service. Every
|
|
99
|
+
* request on the port Alchemy added for bindings must carry the `Fly-Src`
|
|
100
|
+
* signature, so that port never answers the public internet.
|
|
101
101
|
*/
|
|
102
102
|
export const serveFlyRpc = <Req = never>(
|
|
103
103
|
shape: Record<string, unknown>,
|
|
@@ -107,23 +107,20 @@ export const serveFlyRpc = <Req = never>(
|
|
|
107
107
|
const request = yield* HttpServerRequest;
|
|
108
108
|
const headers = request.headers as Record<string, string | undefined>;
|
|
109
109
|
const isRpc = request.url.includes(RPC_PATH_PREFIX);
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
const readEnv = (name: string) => Config.String(name).pipe(Effect.orElseSucceed(() => ""));
|
|
111
|
+
const env = {
|
|
112
|
+
bindingPort: yield* readEnv(BINDING_PORT_ENV),
|
|
113
|
+
org: yield* readEnv(RPC_ORG_ENV),
|
|
114
|
+
callers: yield* readEnv(RPC_CALLERS_ENV),
|
|
115
|
+
};
|
|
115
116
|
const bindingPort = env.bindingPort;
|
|
116
117
|
const onBindingPort =
|
|
117
118
|
bindingPort.length > 0 && header(headers, "fly-forwarded-port") === bindingPort;
|
|
118
119
|
if (!isRpc && !onBindingPort) return yield* fallback;
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (expected.length === 0 || provided.length === 0 || !tokensEqual(provided, expected)) {
|
|
124
|
-
return unauthorized;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
120
|
+
const caller = yield* verifyFlySrc(headers, env.org);
|
|
121
|
+
if (caller === undefined) return unauthorized;
|
|
122
|
+
// Methods answer only the Apps of the Services that bind this one.
|
|
123
|
+
if (isRpc && !env.callers.split(",").includes(caller)) return unauthorized;
|
|
127
124
|
return yield* serveRpc(shape, fallback);
|
|
128
125
|
});
|
|
129
126
|
|