@alchemy.run/fly 0.0.0 → 2.0.0-beta.85
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/LICENSE +201 -0
- package/README.md +57 -4
- package/lib/App.d.ts +230 -0
- package/lib/App.d.ts.map +1 -0
- package/lib/App.js +338 -0
- package/lib/App.js.map +1 -0
- package/lib/AuthProvider.d.ts +22 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +48 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/BindService.d.ts +159 -0
- package/lib/BindService.d.ts.map +1 -0
- package/lib/BindService.js +222 -0
- package/lib/BindService.js.map +1 -0
- package/lib/Bootstrap.d.ts +8 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +41 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/Bucket.d.ts +278 -0
- package/lib/Bucket.d.ts.map +1 -0
- package/lib/Bucket.js +658 -0
- package/lib/Bucket.js.map +1 -0
- package/lib/BucketBinding.d.ts +33 -0
- package/lib/BucketBinding.d.ts.map +1 -0
- package/lib/BucketBinding.js +70 -0
- package/lib/BucketBinding.js.map +1 -0
- package/lib/Catalog.d.ts +28 -0
- package/lib/Catalog.d.ts.map +1 -0
- package/lib/Catalog.js +31 -0
- package/lib/Catalog.js.map +1 -0
- package/lib/Certificate.d.ts +189 -0
- package/lib/Certificate.d.ts.map +1 -0
- package/lib/Certificate.js +380 -0
- package/lib/Certificate.js.map +1 -0
- package/lib/Checkpoint.d.ts +55 -0
- package/lib/Checkpoint.d.ts.map +1 -0
- package/lib/Checkpoint.js +3 -0
- package/lib/Checkpoint.js.map +1 -0
- package/lib/CheckpointHttp.d.ts +22 -0
- package/lib/CheckpointHttp.d.ts.map +1 -0
- package/lib/CheckpointHttp.js +53 -0
- package/lib/CheckpointHttp.js.map +1 -0
- package/lib/ConnectPostgres.d.ts +58 -0
- package/lib/ConnectPostgres.d.ts.map +1 -0
- package/lib/ConnectPostgres.js +13 -0
- package/lib/ConnectPostgres.js.map +1 -0
- package/lib/ConnectPostgresHttp.d.ts +26 -0
- package/lib/ConnectPostgresHttp.d.ts.map +1 -0
- package/lib/ConnectPostgresHttp.js +57 -0
- package/lib/ConnectPostgresHttp.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/Credentials.d.ts +33 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +55 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Decrypt.d.ts +52 -0
- package/lib/Decrypt.d.ts.map +1 -0
- package/lib/Decrypt.js +3 -0
- package/lib/Decrypt.js.map +1 -0
- package/lib/DecryptHttp.d.ts +22 -0
- package/lib/DecryptHttp.d.ts.map +1 -0
- package/lib/DecryptHttp.js +43 -0
- package/lib/DecryptHttp.js.map +1 -0
- package/lib/DeleteObject.d.ts +29 -0
- package/lib/DeleteObject.d.ts.map +1 -0
- package/lib/DeleteObject.js +3 -0
- package/lib/DeleteObject.js.map +1 -0
- package/lib/DeleteObjectHttp.d.ts +12 -0
- package/lib/DeleteObjectHttp.d.ts.map +1 -0
- package/lib/DeleteObjectHttp.js +17 -0
- package/lib/DeleteObjectHttp.js.map +1 -0
- package/lib/Deployment.d.ts +51 -0
- package/lib/Deployment.d.ts.map +1 -0
- package/lib/Deployment.js +62 -0
- package/lib/Deployment.js.map +1 -0
- package/lib/DeploymentImages.d.ts +18 -0
- package/lib/DeploymentImages.d.ts.map +1 -0
- package/lib/DeploymentImages.js +61 -0
- package/lib/DeploymentImages.js.map +1 -0
- package/lib/DeploymentState.d.ts +17 -0
- package/lib/DeploymentState.d.ts.map +1 -0
- package/lib/DeploymentState.js +87 -0
- package/lib/DeploymentState.js.map +1 -0
- package/lib/Encrypt.d.ts +50 -0
- package/lib/Encrypt.d.ts.map +1 -0
- package/lib/Encrypt.js +3 -0
- package/lib/Encrypt.js.map +1 -0
- package/lib/EncryptHttp.d.ts +22 -0
- package/lib/EncryptHttp.d.ts.map +1 -0
- package/lib/EncryptHttp.js +39 -0
- package/lib/EncryptHttp.js.map +1 -0
- package/lib/Environment.d.ts +45 -0
- package/lib/Environment.d.ts.map +1 -0
- package/lib/Environment.js +43 -0
- package/lib/Environment.js.map +1 -0
- package/lib/Errors.d.ts +18 -0
- package/lib/Errors.d.ts.map +1 -0
- package/lib/Errors.js +13 -0
- package/lib/Errors.js.map +1 -0
- package/lib/Exec.d.ts +41 -0
- package/lib/Exec.d.ts.map +1 -0
- package/lib/Exec.js +3 -0
- package/lib/Exec.js.map +1 -0
- package/lib/ExecHttp.d.ts +22 -0
- package/lib/ExecHttp.d.ts.map +1 -0
- package/lib/ExecHttp.js +39 -0
- package/lib/ExecHttp.js.map +1 -0
- package/lib/GetObject.d.ts +34 -0
- package/lib/GetObject.d.ts.map +1 -0
- package/lib/GetObject.js +3 -0
- package/lib/GetObject.js.map +1 -0
- package/lib/GetObjectHttp.d.ts +12 -0
- package/lib/GetObjectHttp.d.ts.map +1 -0
- package/lib/GetObjectHttp.js +17 -0
- package/lib/GetObjectHttp.js.map +1 -0
- package/lib/GetSecret.d.ts +44 -0
- package/lib/GetSecret.d.ts.map +1 -0
- package/lib/GetSecret.js +3 -0
- package/lib/GetSecret.js.map +1 -0
- package/lib/GetSecretHttp.d.ts +22 -0
- package/lib/GetSecretHttp.d.ts.map +1 -0
- package/lib/GetSecretHttp.js +50 -0
- package/lib/GetSecretHttp.js.map +1 -0
- package/lib/HeadObject.d.ts +29 -0
- package/lib/HeadObject.d.ts.map +1 -0
- package/lib/HeadObject.js +3 -0
- package/lib/HeadObject.js.map +1 -0
- package/lib/HeadObjectHttp.d.ts +12 -0
- package/lib/HeadObjectHttp.d.ts.map +1 -0
- package/lib/HeadObjectHttp.js +17 -0
- package/lib/HeadObjectHttp.js.map +1 -0
- package/lib/IpAssignment.d.ts +258 -0
- package/lib/IpAssignment.d.ts.map +1 -0
- package/lib/IpAssignment.js +376 -0
- package/lib/IpAssignment.js.map +1 -0
- package/lib/ListObjectsV2.d.ts +29 -0
- package/lib/ListObjectsV2.d.ts.map +1 -0
- package/lib/ListObjectsV2.js +3 -0
- package/lib/ListObjectsV2.js.map +1 -0
- package/lib/ListObjectsV2Http.d.ts +12 -0
- package/lib/ListObjectsV2Http.d.ts.map +1 -0
- package/lib/ListObjectsV2Http.js +17 -0
- package/lib/ListObjectsV2Http.js.map +1 -0
- package/lib/ListSecrets.d.ts +53 -0
- package/lib/ListSecrets.d.ts.map +1 -0
- package/lib/ListSecrets.js +3 -0
- package/lib/ListSecrets.js.map +1 -0
- package/lib/ListSecretsHttp.d.ts +22 -0
- package/lib/ListSecretsHttp.d.ts.map +1 -0
- package/lib/ListSecretsHttp.js +35 -0
- package/lib/ListSecretsHttp.js.map +1 -0
- package/lib/Machine.d.ts +802 -0
- package/lib/Machine.d.ts.map +1 -0
- package/lib/Machine.js +773 -0
- package/lib/Machine.js.map +1 -0
- package/lib/MachineContainers.d.ts +44 -0
- package/lib/MachineContainers.d.ts.map +1 -0
- package/lib/MachineContainers.js +115 -0
- package/lib/MachineContainers.js.map +1 -0
- package/lib/Metadata.d.ts +109 -0
- package/lib/Metadata.d.ts.map +1 -0
- package/lib/Metadata.js +159 -0
- package/lib/Metadata.js.map +1 -0
- package/lib/MountVolume.d.ts +295 -0
- package/lib/MountVolume.d.ts.map +1 -0
- package/lib/MountVolume.js +20 -0
- package/lib/MountVolume.js.map +1 -0
- package/lib/Postgres.d.ts +298 -0
- package/lib/Postgres.d.ts.map +1 -0
- package/lib/Postgres.js +508 -0
- package/lib/Postgres.js.map +1 -0
- package/lib/PostgresMigrations.d.ts +20 -0
- package/lib/PostgresMigrations.d.ts.map +1 -0
- package/lib/PostgresMigrations.js +58 -0
- package/lib/PostgresMigrations.js.map +1 -0
- package/lib/Providers.d.ts +38 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +95 -0
- package/lib/Providers.js.map +1 -0
- package/lib/PutObject.d.ts +35 -0
- package/lib/PutObject.d.ts.map +1 -0
- package/lib/PutObject.js +3 -0
- package/lib/PutObject.js.map +1 -0
- package/lib/PutObjectHttp.d.ts +12 -0
- package/lib/PutObjectHttp.d.ts.map +1 -0
- package/lib/PutObjectHttp.js +17 -0
- package/lib/PutObjectHttp.js.map +1 -0
- package/lib/ReadRedis.d.ts +26 -0
- package/lib/ReadRedis.d.ts.map +1 -0
- package/lib/ReadRedis.js +3 -0
- package/lib/ReadRedis.js.map +1 -0
- package/lib/ReadRedisHttp.d.ts +11 -0
- package/lib/ReadRedisHttp.d.ts.map +1 -0
- package/lib/ReadRedisHttp.js +15 -0
- package/lib/ReadRedisHttp.js.map +1 -0
- package/lib/ReadWriteRedis.d.ts +29 -0
- package/lib/ReadWriteRedis.d.ts.map +1 -0
- package/lib/ReadWriteRedis.js +3 -0
- package/lib/ReadWriteRedis.js.map +1 -0
- package/lib/ReadWriteRedisHttp.d.ts +11 -0
- package/lib/ReadWriteRedisHttp.d.ts.map +1 -0
- package/lib/ReadWriteRedisHttp.js +15 -0
- package/lib/ReadWriteRedisHttp.js.map +1 -0
- package/lib/Redis.d.ts +252 -0
- package/lib/Redis.d.ts.map +1 -0
- package/lib/Redis.js +611 -0
- package/lib/Redis.js.map +1 -0
- package/lib/RedisBinding.d.ts +7 -0
- package/lib/RedisBinding.d.ts.map +1 -0
- package/lib/RedisBinding.js +53 -0
- package/lib/RedisBinding.js.map +1 -0
- package/lib/RedisHttp.d.ts +8 -0
- package/lib/RedisHttp.d.ts.map +1 -0
- package/lib/RedisHttp.js +11 -0
- package/lib/RedisHttp.js.map +1 -0
- package/lib/Region.d.ts +16 -0
- package/lib/Region.d.ts.map +1 -0
- package/lib/Region.js +13 -0
- package/lib/Region.js.map +1 -0
- package/lib/Secret.d.ts +190 -0
- package/lib/Secret.d.ts.map +1 -0
- package/lib/Secret.js +310 -0
- package/lib/Secret.js.map +1 -0
- package/lib/SecretHttp.d.ts +54 -0
- package/lib/SecretHttp.d.ts.map +1 -0
- package/lib/SecretHttp.js +66 -0
- package/lib/SecretHttp.js.map +1 -0
- package/lib/SecretKey.d.ts +164 -0
- package/lib/SecretKey.d.ts.map +1 -0
- package/lib/SecretKey.js +277 -0
- package/lib/SecretKey.js.map +1 -0
- package/lib/SecretKeyHttp.d.ts +14 -0
- package/lib/SecretKeyHttp.d.ts.map +1 -0
- package/lib/SecretKeyHttp.js +5 -0
- package/lib/SecretKeyHttp.js.map +1 -0
- package/lib/Service.d.ts +1017 -0
- package/lib/Service.d.ts.map +1 -0
- package/lib/Service.js +1430 -0
- package/lib/Service.js.map +1 -0
- package/lib/Sign.d.ts +36 -0
- package/lib/Sign.d.ts.map +1 -0
- package/lib/Sign.js +3 -0
- package/lib/Sign.js.map +1 -0
- package/lib/SignHttp.d.ts +22 -0
- package/lib/SignHttp.d.ts.map +1 -0
- package/lib/SignHttp.js +36 -0
- package/lib/SignHttp.js.map +1 -0
- package/lib/Sprite.d.ts +283 -0
- package/lib/Sprite.d.ts.map +1 -0
- package/lib/Sprite.js +471 -0
- package/lib/Sprite.js.map +1 -0
- package/lib/SpriteHttp.d.ts +25 -0
- package/lib/SpriteHttp.d.ts.map +1 -0
- package/lib/SpriteHttp.js +33 -0
- package/lib/SpriteHttp.js.map +1 -0
- package/lib/Verify.d.ts +37 -0
- package/lib/Verify.d.ts.map +1 -0
- package/lib/Verify.js +3 -0
- package/lib/Verify.js.map +1 -0
- package/lib/VerifyHttp.d.ts +22 -0
- package/lib/VerifyHttp.d.ts.map +1 -0
- package/lib/VerifyHttp.js +38 -0
- package/lib/VerifyHttp.js.map +1 -0
- package/lib/Volume.d.ts +53 -0
- package/lib/Volume.d.ts.map +1 -0
- package/lib/Volume.js +188 -0
- package/lib/Volume.js.map +1 -0
- package/lib/VolumeSnapshot.d.ts +135 -0
- package/lib/VolumeSnapshot.d.ts.map +1 -0
- package/lib/VolumeSnapshot.js +213 -0
- package/lib/VolumeSnapshot.js.map +1 -0
- package/lib/Website/AssetDeployment.d.ts +43 -0
- package/lib/Website/AssetDeployment.d.ts.map +1 -0
- package/lib/Website/AssetDeployment.js +162 -0
- package/lib/Website/AssetDeployment.js.map +1 -0
- package/lib/Website/Astro.d.ts +89 -0
- package/lib/Website/Astro.d.ts.map +1 -0
- package/lib/Website/Astro.js +54 -0
- package/lib/Website/Astro.js.map +1 -0
- package/lib/Website/Foldkit.d.ts +52 -0
- package/lib/Website/Foldkit.d.ts.map +1 -0
- package/lib/Website/Foldkit.js +45 -0
- package/lib/Website/Foldkit.js.map +1 -0
- package/lib/Website/FrameworkSite.d.ts +212 -0
- package/lib/Website/FrameworkSite.d.ts.map +1 -0
- package/lib/Website/FrameworkSite.js +205 -0
- package/lib/Website/FrameworkSite.js.map +1 -0
- package/lib/Website/Nextjs.d.ts +54 -0
- package/lib/Website/Nextjs.d.ts.map +1 -0
- package/lib/Website/Nextjs.js +46 -0
- package/lib/Website/Nextjs.js.map +1 -0
- package/lib/Website/Nuxt.d.ts +51 -0
- package/lib/Website/Nuxt.d.ts.map +1 -0
- package/lib/Website/Nuxt.js +36 -0
- package/lib/Website/Nuxt.js.map +1 -0
- package/lib/Website/Octane.d.ts +49 -0
- package/lib/Website/Octane.d.ts.map +1 -0
- package/lib/Website/Octane.js +39 -0
- package/lib/Website/Octane.js.map +1 -0
- package/lib/Website/ReactRouter.d.ts +80 -0
- package/lib/Website/ReactRouter.d.ts.map +1 -0
- package/lib/Website/ReactRouter.js +70 -0
- package/lib/Website/ReactRouter.js.map +1 -0
- package/lib/Website/SolidStart.d.ts +93 -0
- package/lib/Website/SolidStart.d.ts.map +1 -0
- package/lib/Website/SolidStart.js +71 -0
- package/lib/Website/SolidStart.js.map +1 -0
- package/lib/Website/StaticSite.d.ts +162 -0
- package/lib/Website/StaticSite.d.ts.map +1 -0
- package/lib/Website/StaticSite.js +180 -0
- package/lib/Website/StaticSite.js.map +1 -0
- package/lib/Website/SvelteKit.d.ts +52 -0
- package/lib/Website/SvelteKit.d.ts.map +1 -0
- package/lib/Website/SvelteKit.js +37 -0
- package/lib/Website/SvelteKit.js.map +1 -0
- package/lib/Website/TanStackStart.d.ts +76 -0
- package/lib/Website/TanStackStart.d.ts.map +1 -0
- package/lib/Website/TanStackStart.js +66 -0
- package/lib/Website/TanStackStart.js.map +1 -0
- package/lib/Website/Vinext.d.ts +73 -0
- package/lib/Website/Vinext.d.ts.map +1 -0
- package/lib/Website/Vinext.js +67 -0
- package/lib/Website/Vinext.js.map +1 -0
- package/lib/Website/Vite.d.ts +112 -0
- package/lib/Website/Vite.d.ts.map +1 -0
- package/lib/Website/Vite.js +100 -0
- package/lib/Website/Vite.js.map +1 -0
- package/lib/Website/Vocs.d.ts +52 -0
- package/lib/Website/Vocs.d.ts.map +1 -0
- package/lib/Website/Vocs.js +38 -0
- package/lib/Website/Vocs.js.map +1 -0
- package/lib/Website/Waku.d.ts +64 -0
- package/lib/Website/Waku.d.ts.map +1 -0
- package/lib/Website/Waku.js +39 -0
- package/lib/Website/Waku.js.map +1 -0
- package/lib/Website/index.d.ts +17 -0
- package/lib/Website/index.d.ts.map +1 -0
- package/lib/Website/index.js +17 -0
- package/lib/Website/index.js.map +1 -0
- package/lib/WriteCertificates.d.ts +63 -0
- package/lib/WriteCertificates.d.ts.map +1 -0
- package/lib/WriteCertificates.js +3 -0
- package/lib/WriteCertificates.js.map +1 -0
- package/lib/WriteCertificatesHttp.d.ts +25 -0
- package/lib/WriteCertificatesHttp.d.ts.map +1 -0
- package/lib/WriteCertificatesHttp.js +67 -0
- package/lib/WriteCertificatesHttp.js.map +1 -0
- package/lib/WriteRedis.d.ts +26 -0
- package/lib/WriteRedis.d.ts.map +1 -0
- package/lib/WriteRedis.js +3 -0
- package/lib/WriteRedis.js.map +1 -0
- package/lib/WriteRedisHttp.d.ts +11 -0
- package/lib/WriteRedisHttp.d.ts.map +1 -0
- package/lib/WriteRedisHttp.js +15 -0
- package/lib/WriteRedisHttp.js.map +1 -0
- package/lib/WriteSecret.d.ts +68 -0
- package/lib/WriteSecret.d.ts.map +1 -0
- package/lib/WriteSecret.js +4 -0
- package/lib/WriteSecret.js.map +1 -0
- package/lib/WriteSecretHttp.d.ts +26 -0
- package/lib/WriteSecretHttp.d.ts.map +1 -0
- package/lib/WriteSecretHttp.js +54 -0
- package/lib/WriteSecretHttp.js.map +1 -0
- package/lib/bluegreen.d.ts +36 -0
- package/lib/bluegreen.d.ts.map +1 -0
- package/lib/bluegreen.js +460 -0
- package/lib/bluegreen.js.map +1 -0
- package/lib/hosted.d.ts +171 -0
- package/lib/hosted.d.ts.map +1 -0
- package/lib/hosted.js +458 -0
- package/lib/hosted.js.map +1 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +63 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/RemoteBuilder.d.ts +28 -0
- package/lib/internal/RemoteBuilder.d.ts.map +1 -0
- package/lib/internal/RemoteBuilder.js +158 -0
- package/lib/internal/RemoteBuilder.js.map +1 -0
- package/lib/leases.d.ts +53 -0
- package/lib/leases.d.ts.map +1 -0
- package/lib/leases.js +279 -0
- package/lib/leases.js.map +1 -0
- package/lib/ports.d.ts +51 -0
- package/lib/ports.d.ts.map +1 -0
- package/lib/ports.js +123 -0
- package/lib/ports.js.map +1 -0
- package/lib/replicas.d.ts +270 -0
- package/lib/replicas.d.ts.map +1 -0
- package/lib/replicas.js +969 -0
- package/lib/replicas.js.map +1 -0
- package/lib/rpc.d.ts +35 -0
- package/lib/rpc.d.ts.map +1 -0
- package/lib/rpc.js +113 -0
- package/lib/rpc.js.map +1 -0
- package/package.json +83 -3
- package/src/App.ts +514 -0
- package/src/AuthProvider.ts +68 -0
- package/src/BindService.ts +303 -0
- package/src/Bootstrap.ts +66 -0
- package/src/Bucket.ts +918 -0
- package/src/BucketBinding.ts +128 -0
- package/src/Catalog.ts +43 -0
- package/src/Certificate.ts +548 -0
- package/src/Checkpoint.ts +69 -0
- package/src/CheckpointHttp.ts +66 -0
- package/src/ConnectPostgres.ts +68 -0
- package/src/ConnectPostgresHttp.ts +77 -0
- package/src/Contributions.ts +9 -0
- package/src/Credentials.ts +75 -0
- package/src/Decrypt.ts +62 -0
- package/src/DecryptHttp.ts +52 -0
- package/src/DeleteObject.ts +43 -0
- package/src/DeleteObjectHttp.ts +20 -0
- package/src/Deployment.ts +121 -0
- package/src/DeploymentImages.ts +84 -0
- package/src/DeploymentState.ts +104 -0
- package/src/Encrypt.ts +60 -0
- package/src/EncryptHttp.ts +48 -0
- package/src/Environment.ts +81 -0
- package/src/Errors.ts +14 -0
- package/src/Exec.ts +50 -0
- package/src/ExecHttp.ts +47 -0
- package/src/GetObject.ts +48 -0
- package/src/GetObjectHttp.ts +20 -0
- package/src/GetSecret.ts +48 -0
- package/src/GetSecretHttp.ts +60 -0
- package/src/HeadObject.ts +43 -0
- package/src/HeadObjectHttp.ts +20 -0
- package/src/IpAssignment.ts +522 -0
- package/src/ListObjectsV2.ts +43 -0
- package/src/ListObjectsV2Http.ts +20 -0
- package/src/ListSecrets.ts +57 -0
- package/src/ListSecretsHttp.ts +43 -0
- package/src/Machine.ts +1270 -0
- package/src/MachineContainers.ts +152 -0
- package/src/Metadata.ts +195 -0
- package/src/MountVolume.ts +315 -0
- package/src/Postgres.ts +777 -0
- package/src/PostgresMigrations.ts +93 -0
- package/src/Providers.ts +155 -0
- package/src/PutObject.ts +49 -0
- package/src/PutObjectHttp.ts +20 -0
- package/src/ReadRedis.ts +31 -0
- package/src/ReadRedisHttp.ts +18 -0
- package/src/ReadWriteRedis.ts +33 -0
- package/src/ReadWriteRedisHttp.ts +18 -0
- package/src/Redis.ts +823 -0
- package/src/RedisBinding.ts +64 -0
- package/src/RedisHttp.ts +14 -0
- package/src/Region.ts +44 -0
- package/src/Secret.ts +405 -0
- package/src/SecretHttp.ts +149 -0
- package/src/SecretKey.ts +389 -0
- package/src/SecretKeyHttp.ts +23 -0
- package/src/Service.ts +1968 -0
- package/src/Sign.ts +46 -0
- package/src/SignHttp.ts +44 -0
- package/src/Sprite.ts +673 -0
- package/src/SpriteHttp.ts +50 -0
- package/src/Verify.ts +47 -0
- package/src/VerifyHttp.ts +46 -0
- package/src/Volume.ts +264 -0
- package/src/VolumeSnapshot.ts +321 -0
- package/src/Website/AssetDeployment.ts +292 -0
- package/src/Website/Astro.ts +92 -0
- package/src/Website/Foldkit.ts +54 -0
- package/src/Website/FrameworkSite.ts +449 -0
- package/src/Website/Nextjs.ts +50 -0
- package/src/Website/Nuxt.ts +48 -0
- package/src/Website/Octane.ts +44 -0
- package/src/Website/ReactRouter.ts +76 -0
- package/src/Website/SolidStart.ts +90 -0
- package/src/Website/StaticSite.ts +295 -0
- package/src/Website/SvelteKit.ts +49 -0
- package/src/Website/TanStackStart.ts +72 -0
- package/src/Website/Vinext.ts +79 -0
- package/src/Website/Vite.ts +121 -0
- package/src/Website/Vocs.ts +50 -0
- package/src/Website/Waku.ts +65 -0
- package/src/Website/index.ts +16 -0
- package/src/WriteCertificates.ts +90 -0
- package/src/WriteCertificatesHttp.ts +93 -0
- package/src/WriteRedis.ts +31 -0
- package/src/WriteRedisHttp.ts +18 -0
- package/src/WriteSecret.ts +86 -0
- package/src/WriteSecretHttp.ts +71 -0
- package/src/bluegreen.ts +711 -0
- package/src/globals.d.ts +2 -0
- package/src/hosted.ts +667 -0
- package/src/index.ts +64 -0
- package/src/internal/RemoteBuilder.ts +242 -0
- package/src/leases.ts +437 -0
- package/src/ports.ts +177 -0
- package/src/replicas.ts +1480 -0
- package/src/rpc.ts +136 -0
package/lib/replicas.js
ADDED
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
import * as machines from "@distilled.cloud/fly-io/machines";
|
|
2
|
+
import * as Retry from "@distilled.cloud/fly-io/Retry";
|
|
3
|
+
import { deepEqual } from "alchemy/Diff";
|
|
4
|
+
import * as Data from "effect/Data";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Result from "effect/Result";
|
|
7
|
+
import * as Schedule from "effect/Schedule";
|
|
8
|
+
import { listOwnedApps } from "./App.js";
|
|
9
|
+
import { reconcileBlueGreen, setRouting } from "./bluegreen.js";
|
|
10
|
+
import { validateDeployment } from "./Deployment.js";
|
|
11
|
+
import { classifyDeploymentState, validProtocol2Generation } from "./DeploymentState.js";
|
|
12
|
+
import { usingMachineLeases } from "./leases.js";
|
|
13
|
+
import { alchemyMetadataKeys, createMachineMetadata, isAlchemyOwnedMetadata, sanitizeFlyAppName, } from "./Metadata.js";
|
|
14
|
+
import { regionOfReplica } from "./Region.js";
|
|
15
|
+
import { deleteVolume, ensureVolumeGroup, getVolumeById, volumeGroupName } from "./Volume.js";
|
|
16
|
+
const WAIT_TIMEOUT_SECONDS = 8;
|
|
17
|
+
const waitBackoff = Schedule.exponential("500 millis");
|
|
18
|
+
const SERVICE_CHECK_NAME_PREFIX = "servicecheck-";
|
|
19
|
+
export class ReplicaNotCreated extends Data.TaggedError("Fly.ReplicaNotCreated") {
|
|
20
|
+
}
|
|
21
|
+
export class ReplicaChecksNotPassing extends Data.TaggedError("Fly.ReplicaChecksNotPassing") {
|
|
22
|
+
get message() {
|
|
23
|
+
const checks = this.checks.map((check) => `${check.name ?? "unnamed"}: ${check.status ?? "unknown"}${check.output ? ` (${check.output})` : ""}`);
|
|
24
|
+
return `Service checks did not pass for ${this.appName}/${this.machineId}: ${checks.join("; ") || "no service check results"}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const compactRecord = (record) => Object.fromEntries(Object.entries(record ?? {}).flatMap(([key, value]) => value === undefined ? [] : [[key, value]]));
|
|
28
|
+
export const gone = (machine) => machine === undefined || machine.state === "destroyed";
|
|
29
|
+
export const getMachineById = (appName, machineId) => machines.getMachine({ app_name: appName, machine_id: machineId }).pipe(Effect.map((machine) => (gone(machine) ? undefined : machine)), Effect.catchTag("NotFound", () => Effect.undefined));
|
|
30
|
+
/**
|
|
31
|
+
* Reads a Machine we hold a lease on. Fly's Machines API is not
|
|
32
|
+
* read-after-write consistent: a freshly created Machine can accept a lease
|
|
33
|
+
* (201) and then answer `GET /machines/{id}` with 404 for several seconds.
|
|
34
|
+
* Holding the lease blocks other writers, so a 404 here is lag rather than
|
|
35
|
+
* deletion; retry it (bounded) before surfacing `NotFound`.
|
|
36
|
+
*/
|
|
37
|
+
const getLeasedMachine = (appName, machineId) => machines.getMachine({ app_name: appName, machine_id: machineId }).pipe(Effect.retry({
|
|
38
|
+
times: 10,
|
|
39
|
+
schedule: Schedule.spaced("1 second"),
|
|
40
|
+
while: (error) => error._tag === "NotFound",
|
|
41
|
+
}));
|
|
42
|
+
const getLeasedMachineById = (appName, machineId) => getLeasedMachine(appName, machineId).pipe(Effect.map((machine) => (gone(machine) ? undefined : machine)), Effect.catchTag("NotFound", () => Effect.undefined));
|
|
43
|
+
export const listMachinesByApp = (appName) => machines.listMachines({ app_name: appName }).pipe(
|
|
44
|
+
// A just-created App can answer 500 "failed to get app: sql: no rows in
|
|
45
|
+
// result set" until the Machines API sees it; safe to replay a read.
|
|
46
|
+
Effect.retry({
|
|
47
|
+
while: (error) => error._tag === "InternalServerError",
|
|
48
|
+
schedule: Schedule.spaced("2 seconds"),
|
|
49
|
+
times: 8,
|
|
50
|
+
}), Effect.map((machines) => machines.filter((machine) => !gone(machine))), Effect.catchTag("NotFound", () => Effect.succeed([])));
|
|
51
|
+
export const resolveCount = (count) => Math.max(1, Math.floor(count ?? 1));
|
|
52
|
+
export const replicaMachineName = (base, index, count) => {
|
|
53
|
+
if (count <= 1 && index === 0)
|
|
54
|
+
return base;
|
|
55
|
+
const suffix = `-${index}`;
|
|
56
|
+
const room = 30 - suffix.length;
|
|
57
|
+
const clipped = base.slice(0, Math.max(1, room)).replace(/-+$/g, "");
|
|
58
|
+
return sanitizeFlyAppName(`${clipped}${suffix}`);
|
|
59
|
+
};
|
|
60
|
+
export const replicaIndexOf = (machine) => {
|
|
61
|
+
const raw = compactRecord(machine.config?.metadata)[alchemyMetadataKeys.replica];
|
|
62
|
+
const parsed = raw === undefined ? 0 : Number(raw);
|
|
63
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
64
|
+
};
|
|
65
|
+
export const alchemyIdOf = (machine) => {
|
|
66
|
+
const id = compactRecord(machine.config?.metadata)[alchemyMetadataKeys.id];
|
|
67
|
+
return id !== undefined && id.length > 0 ? id : undefined;
|
|
68
|
+
};
|
|
69
|
+
export const isOwnedType = (machine, type) => {
|
|
70
|
+
const metadata = compactRecord(machine.config?.metadata);
|
|
71
|
+
return isAlchemyOwnedMetadata(metadata) && metadata[alchemyMetadataKeys.type] === type;
|
|
72
|
+
};
|
|
73
|
+
export const toImageRef = (ref) => {
|
|
74
|
+
if (ref === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const imageRef = {
|
|
77
|
+
registry: ref.registry,
|
|
78
|
+
repository: ref.repository,
|
|
79
|
+
tag: ref.tag,
|
|
80
|
+
digest: ref.digest,
|
|
81
|
+
};
|
|
82
|
+
return imageRef.registry === undefined &&
|
|
83
|
+
imageRef.repository === undefined &&
|
|
84
|
+
imageRef.tag === undefined &&
|
|
85
|
+
imageRef.digest === undefined
|
|
86
|
+
? undefined
|
|
87
|
+
: imageRef;
|
|
88
|
+
};
|
|
89
|
+
export const toGuestAttrs = (guest) => {
|
|
90
|
+
if (guest === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
return {
|
|
93
|
+
cpuKind: guest.cpu_kind,
|
|
94
|
+
cpus: guest.cpus,
|
|
95
|
+
memoryMb: guest.memory_mb,
|
|
96
|
+
gpuKind: guest.gpu_kind,
|
|
97
|
+
gpus: guest.gpus,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
export const toFlyServiceCheck = (check) => ({
|
|
101
|
+
type: check.type,
|
|
102
|
+
port: check.port,
|
|
103
|
+
interval: check.interval,
|
|
104
|
+
timeout: check.timeout,
|
|
105
|
+
grace_period: check.gracePeriod,
|
|
106
|
+
method: check.method,
|
|
107
|
+
path: check.path,
|
|
108
|
+
protocol: check.protocol,
|
|
109
|
+
headers: check.headers?.map((header) => ({ name: header.name, values: header.values })),
|
|
110
|
+
tls_server_name: check.tlsServerName,
|
|
111
|
+
tls_skip_verify: check.tlsSkipVerify,
|
|
112
|
+
});
|
|
113
|
+
export const toFlyService = (service) => ({
|
|
114
|
+
protocol: service.protocol,
|
|
115
|
+
internal_port: service.internalPort,
|
|
116
|
+
autostart: service.autostart,
|
|
117
|
+
autostop: typeof service.autostop === "boolean" ? (service.autostop ? "stop" : "off") : service.autostop,
|
|
118
|
+
min_machines_running: service.minMachinesRunning,
|
|
119
|
+
ports: service.ports?.map((port) => ({
|
|
120
|
+
port: port.port,
|
|
121
|
+
handlers: port.handlers,
|
|
122
|
+
force_https: port.forceHttps,
|
|
123
|
+
start_port: port.startPort,
|
|
124
|
+
end_port: port.endPort,
|
|
125
|
+
})),
|
|
126
|
+
checks: service.checks?.map((check) => ({
|
|
127
|
+
...toFlyServiceCheck(check),
|
|
128
|
+
// Fly clamps service-check intervals; named Machine checks retain longer intervals.
|
|
129
|
+
interval: (durationNanoseconds(check.interval) ?? 0n) > 60000000000n ? "60s" : check.interval,
|
|
130
|
+
})),
|
|
131
|
+
});
|
|
132
|
+
export const autostopMode = (value) => value === true ? "stop" : value === false || value === undefined ? "off" : value;
|
|
133
|
+
const normalizedCheckDuration = (value) => {
|
|
134
|
+
const nanos = durationNanoseconds(value);
|
|
135
|
+
return nanos === undefined ? value : `${nanos}ns`;
|
|
136
|
+
};
|
|
137
|
+
const normalizedCheck = (check) => check === undefined
|
|
138
|
+
? undefined
|
|
139
|
+
: {
|
|
140
|
+
...check,
|
|
141
|
+
grace_period: normalizedCheckDuration(check.grace_period),
|
|
142
|
+
interval: normalizedCheckDuration(check.interval),
|
|
143
|
+
timeout: normalizedCheckDuration(check.timeout),
|
|
144
|
+
};
|
|
145
|
+
export const sameChecks = (observed, desired) => {
|
|
146
|
+
const normalize = (checks) => Object.fromEntries(Object.entries(checks ?? {}).map(([name, check]) => [name, normalizedCheck(check)]));
|
|
147
|
+
return deepEqual(normalize(observed), normalize(desired), { stripNullish: true });
|
|
148
|
+
};
|
|
149
|
+
export const normalizedServices = (services) => (services ?? []).map((service) => ({
|
|
150
|
+
...service,
|
|
151
|
+
autostop: autostopMode(service.autostop),
|
|
152
|
+
checks: service.checks?.map(normalizedCheck),
|
|
153
|
+
}));
|
|
154
|
+
export const sameServices = (observed, desired) => deepEqual(normalizedServices(observed), normalizedServices(desired), { stripNullish: true });
|
|
155
|
+
export const hasPublishedService = (services) => (services ?? []).some((service) => (service.ports ?? []).some((port) => port.port !== undefined || port.start_port !== undefined));
|
|
156
|
+
export const waitStarted = (appName, machineId) => machines
|
|
157
|
+
.waitMachine({
|
|
158
|
+
app_name: appName,
|
|
159
|
+
machine_id: machineId,
|
|
160
|
+
state: "started",
|
|
161
|
+
timeout: WAIT_TIMEOUT_SECONDS,
|
|
162
|
+
})
|
|
163
|
+
.pipe(Retry.none, Effect.retry({
|
|
164
|
+
times: 6,
|
|
165
|
+
schedule: waitBackoff,
|
|
166
|
+
while: (e) => e._tag === "GatewayTimeout" || e._tag === "MachineWaitTimeout",
|
|
167
|
+
}), Effect.timeout("50 seconds"));
|
|
168
|
+
export const configuredCheckNames = (config) => [
|
|
169
|
+
...Object.keys(config?.checks ?? {}),
|
|
170
|
+
...(config?.services ?? []).flatMap((service) => (service.checks ?? []).map((check, checkIndex) => `${SERVICE_CHECK_NAME_PREFIX}${String(checkIndex).padStart(2, "0")}-${check.type ?? "tcp"}-${service.internal_port}`)),
|
|
171
|
+
];
|
|
172
|
+
export const checksPassing = (machine, config) => {
|
|
173
|
+
const expected = configuredCheckNames(config);
|
|
174
|
+
const checks = machine.checks ?? [];
|
|
175
|
+
// Fly may also report compatibility mirrors; they do not replace service reports.
|
|
176
|
+
return (machine.state === "started" &&
|
|
177
|
+
new Set(expected).size === expected.length &&
|
|
178
|
+
checks.every((check) => check.name !== undefined &&
|
|
179
|
+
check.status === "passing" &&
|
|
180
|
+
(expected.includes(check.name) ||
|
|
181
|
+
expected.some((name) => name.startsWith(SERVICE_CHECK_NAME_PREFIX) &&
|
|
182
|
+
check.name === name.replace(SERVICE_CHECK_NAME_PREFIX, "bg_deployments_compat-")))) &&
|
|
183
|
+
new Set(checks.map((check) => check.name)).size === checks.length &&
|
|
184
|
+
expected.every((name) => {
|
|
185
|
+
const reports = checks.filter((check) => check.name === name);
|
|
186
|
+
return reports.length === 1 && reports[0]?.status === "passing";
|
|
187
|
+
}));
|
|
188
|
+
};
|
|
189
|
+
const TRANSIENT_GET_TAGS = [
|
|
190
|
+
"TooManyRequests",
|
|
191
|
+
"InternalServerError",
|
|
192
|
+
"BadGateway",
|
|
193
|
+
"ServiceUnavailable",
|
|
194
|
+
"GatewayTimeout",
|
|
195
|
+
];
|
|
196
|
+
/**
|
|
197
|
+
* After the Machine is started, wait until Fly reports every service
|
|
198
|
+
* check as passing. No configured checks is a no-op. Empty live
|
|
199
|
+
* `servicecheck-*` results keep polling — they are not success.
|
|
200
|
+
* `warning` / `unknown` during grace keep polling.
|
|
201
|
+
*/
|
|
202
|
+
export const waitHealthy = Effect.fn(function* (appName, machine, healthTimeoutMs = 60_000, config = machine.config) {
|
|
203
|
+
const machineId = machine.id;
|
|
204
|
+
const named = Object.keys(config?.checks ?? {});
|
|
205
|
+
const expected = (config?.services ?? []).flatMap((service) => service.checks ?? []).length;
|
|
206
|
+
if (machineId === undefined || (expected === 0 && named.length === 0))
|
|
207
|
+
return machine;
|
|
208
|
+
let observed = machine;
|
|
209
|
+
const notPassing = () => new ReplicaChecksNotPassing({
|
|
210
|
+
appName,
|
|
211
|
+
machineId,
|
|
212
|
+
checks: (observed.checks ?? []).map((check) => ({
|
|
213
|
+
name: check.name,
|
|
214
|
+
status: check.status,
|
|
215
|
+
output: undefined,
|
|
216
|
+
})),
|
|
217
|
+
});
|
|
218
|
+
const passing = yield* getMachineById(appName, machineId).pipe(Retry.none, Effect.map((current) => {
|
|
219
|
+
if (current === undefined)
|
|
220
|
+
return false;
|
|
221
|
+
observed = current;
|
|
222
|
+
return (current.instance_id !== undefined &&
|
|
223
|
+
current.instance_id === machine.instance_id &&
|
|
224
|
+
checksPassing(current, config));
|
|
225
|
+
}), Effect.catchTag(TRANSIENT_GET_TAGS, () => Effect.succeed(false)), Effect.catchReason("HttpClientError", "TransportError", () => Effect.succeed(false)), Effect.repeat({
|
|
226
|
+
schedule: Schedule.spaced(healthTimeoutMs / 10),
|
|
227
|
+
until: (passing) => passing,
|
|
228
|
+
times: 10,
|
|
229
|
+
}), Effect.timeoutOrElse({ duration: healthTimeoutMs, orElse: () => Effect.fail(notPassing()) }));
|
|
230
|
+
if (!passing)
|
|
231
|
+
return yield* notPassing();
|
|
232
|
+
return observed;
|
|
233
|
+
});
|
|
234
|
+
export const waitDestroyed = (appName, machineId) => machines
|
|
235
|
+
.waitMachine({
|
|
236
|
+
app_name: appName,
|
|
237
|
+
machine_id: machineId,
|
|
238
|
+
state: "destroyed",
|
|
239
|
+
timeout: WAIT_TIMEOUT_SECONDS,
|
|
240
|
+
})
|
|
241
|
+
.pipe(Retry.none, Effect.as(undefined), Effect.catchTag("NotFound", () => Effect.void), Effect.retry({
|
|
242
|
+
times: 6,
|
|
243
|
+
schedule: waitBackoff,
|
|
244
|
+
while: (e) => e._tag === "GatewayTimeout" || e._tag === "MachineWaitTimeout",
|
|
245
|
+
}), Effect.timeout("50 seconds"));
|
|
246
|
+
export const ensureStarted = (appName, machine, skipLaunch, healthTimeoutMs = 60_000, expectedConfig, existingLeases) => usingMachineLeases(appName, existingLeases, (leases) => ensureLeasedStarted(appName, machine, skipLaunch, healthTimeoutMs, expectedConfig, leases));
|
|
247
|
+
const ensureLeasedStarted = Effect.fn(function* (appName, machine, skipLaunch, healthTimeoutMs, expectedConfig, leases) {
|
|
248
|
+
const machineId = machine.id;
|
|
249
|
+
if (machineId === undefined || skipLaunch)
|
|
250
|
+
return machine;
|
|
251
|
+
yield* leases.acquire([machineId]);
|
|
252
|
+
const started = yield* Effect.gen(function* () {
|
|
253
|
+
// Create/update responses can lag Fly's automatic launch.
|
|
254
|
+
const current = yield* getLeasedMachine(appName, machineId);
|
|
255
|
+
if (!sameOwnership(current, machine)) {
|
|
256
|
+
return yield* new ReplicaOwnershipChanged({ appName, machineId });
|
|
257
|
+
}
|
|
258
|
+
yield* Effect.logDebug("Fly machine startup", { appName, machineId, state: current.state });
|
|
259
|
+
if (current.state === "stopped" ||
|
|
260
|
+
current.state === "suspended" ||
|
|
261
|
+
current.state === "failed") {
|
|
262
|
+
yield* leases.mutate(machineId, (lease_nonce) => machines.startMachine({ app_name: appName, machine_id: machineId, lease_nonce }));
|
|
263
|
+
}
|
|
264
|
+
// Re-observe state between waits instead of retrying the wait in the SDK.
|
|
265
|
+
yield* machines
|
|
266
|
+
.waitMachine({
|
|
267
|
+
app_name: appName,
|
|
268
|
+
machine_id: machineId,
|
|
269
|
+
state: "started",
|
|
270
|
+
instance_id: current.instance_id,
|
|
271
|
+
timeout: WAIT_TIMEOUT_SECONDS,
|
|
272
|
+
})
|
|
273
|
+
.pipe(Retry.none);
|
|
274
|
+
return yield* getLeasedMachine(appName, machineId);
|
|
275
|
+
}).pipe(Effect.retry({
|
|
276
|
+
times: 6,
|
|
277
|
+
schedule: waitBackoff,
|
|
278
|
+
while: (error) => error._tag === "MachineStartFromCreatedState" ||
|
|
279
|
+
error._tag === "MachineReplacing" ||
|
|
280
|
+
error._tag === "MachineWaitTimeout" ||
|
|
281
|
+
error._tag === "Conflict" ||
|
|
282
|
+
error._tag === "GatewayTimeout",
|
|
283
|
+
}), Effect.timeout("180 seconds"));
|
|
284
|
+
return yield* waitHealthy(appName, started, healthTimeoutMs, expectedConfig ?? machine.config);
|
|
285
|
+
});
|
|
286
|
+
export const deleteMachine = (appName, machineId, existingLeases) => usingMachineLeases(appName, existingLeases, (leases) => Effect.gen(function* () {
|
|
287
|
+
if (appName.length === 0 || machineId.length === 0)
|
|
288
|
+
return;
|
|
289
|
+
yield* leases.acquire([machineId]);
|
|
290
|
+
yield* leases.remove(machineId, (lease_nonce) => machines
|
|
291
|
+
.deleteMachine({ app_name: appName, machine_id: machineId, force: true, lease_nonce })
|
|
292
|
+
.pipe(Effect.timeout("30 seconds")));
|
|
293
|
+
yield* waitDestroyed(appName, machineId);
|
|
294
|
+
}).pipe(Effect.catchTag("NotFound", () => leases.forget(machineId))));
|
|
295
|
+
const durationNanoseconds = (value) => {
|
|
296
|
+
if (value === undefined || value === "")
|
|
297
|
+
return undefined;
|
|
298
|
+
const negative = value.startsWith("-");
|
|
299
|
+
const duration = /^[+-]/.test(value) ? value.slice(1) : value;
|
|
300
|
+
if (duration === "0")
|
|
301
|
+
return 0n;
|
|
302
|
+
const limit = negative ? 1n << 63n : (1n << 63n) - 1n;
|
|
303
|
+
const units = {
|
|
304
|
+
ns: 1n,
|
|
305
|
+
us: 1000n,
|
|
306
|
+
µs: 1000n,
|
|
307
|
+
μs: 1000n,
|
|
308
|
+
ms: 1000000n,
|
|
309
|
+
s: 1000000000n,
|
|
310
|
+
m: 60000000000n,
|
|
311
|
+
h: 3600000000000n,
|
|
312
|
+
};
|
|
313
|
+
let total = 0n;
|
|
314
|
+
let consumed = 0;
|
|
315
|
+
for (const part of duration.matchAll(/(\d+(?:\.\d*)?|\.\d+)(ns|us|µs|μs|ms|s|m|h)/gy)) {
|
|
316
|
+
const [integer = "", fraction = ""] = part[1].split(".");
|
|
317
|
+
const whole = integer.replace(/^0+/, "") || "0";
|
|
318
|
+
if (whole.length > 19)
|
|
319
|
+
return undefined;
|
|
320
|
+
const unit = units[part[2]];
|
|
321
|
+
let fractionalNanos = 0n;
|
|
322
|
+
// Truncate each component to nanoseconds without unbounded BigInt operands.
|
|
323
|
+
for (let index = fraction.length - 1; index >= 0; index--) {
|
|
324
|
+
fractionalNanos = (BigInt(fraction[index]) * unit + fractionalNanos) / 10n;
|
|
325
|
+
}
|
|
326
|
+
total += BigInt(whole) * unit + fractionalNanos;
|
|
327
|
+
if (total > limit)
|
|
328
|
+
return undefined;
|
|
329
|
+
consumed += part[0].length;
|
|
330
|
+
}
|
|
331
|
+
if (consumed === 0 || consumed !== duration.length)
|
|
332
|
+
return undefined;
|
|
333
|
+
return negative ? -total : total;
|
|
334
|
+
};
|
|
335
|
+
const stopTimeoutMillis = (timeout) => {
|
|
336
|
+
if (timeout === "")
|
|
337
|
+
return 0;
|
|
338
|
+
// Shutdown policies retain their unsigned millisecond-or-larger syntax.
|
|
339
|
+
if (timeout === undefined || !/^(?:\d+(?:\.\d+)?(?:ms|s|m|h))+$/.test(timeout)) {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
const nanos = durationNanoseconds(timeout);
|
|
343
|
+
return nanos === undefined ? undefined : Number(nanos) / 1_000_000;
|
|
344
|
+
};
|
|
345
|
+
export const sameStopConfig = (observed, desired) => observed?.signal === desired?.signal &&
|
|
346
|
+
stopTimeoutMillis(observed?.timeout) === stopTimeoutMillis(desired?.timeout);
|
|
347
|
+
export class ShutdownPolicyMismatch extends Data.TaggedError("Fly.ShutdownPolicyMismatch") {
|
|
348
|
+
}
|
|
349
|
+
export const predecessorShutdown = Effect.fn(function* (machine) {
|
|
350
|
+
const persisted = machine.config?.stop_config;
|
|
351
|
+
const injected = machine.config?.env?.ALCHEMY_FLY_SHUTDOWN_TIMEOUT_MS;
|
|
352
|
+
const timeoutMs = stopTimeoutMillis(persisted?.timeout);
|
|
353
|
+
if (injected !== undefined) {
|
|
354
|
+
const managedTimeout = Number(injected);
|
|
355
|
+
const signal = persisted?.signal ?? "SIGTERM";
|
|
356
|
+
if (!/^\d+$/.test(injected) ||
|
|
357
|
+
!Number.isSafeInteger(managedTimeout) ||
|
|
358
|
+
managedTimeout <= 0 ||
|
|
359
|
+
managedTimeout > 300_000 ||
|
|
360
|
+
(signal !== "SIGTERM" && signal !== "SIGINT") ||
|
|
361
|
+
(persisted?.timeout !== undefined && timeoutMs !== managedTimeout)) {
|
|
362
|
+
return yield* new ShutdownPolicyMismatch({
|
|
363
|
+
machineId: machine.id ?? "",
|
|
364
|
+
message: "The predecessor's managed shutdown environment and stop_config disagree; reconcile its shutdown policy before migration.",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
signal,
|
|
369
|
+
timeout: persisted?.timeout ?? `${managedTimeout}ms`,
|
|
370
|
+
timeoutMs: managedTimeout,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (persisted?.timeout !== undefined && (!timeoutMs || timeoutMs > 300_000)) {
|
|
374
|
+
return yield* new ShutdownPolicyMismatch({
|
|
375
|
+
machineId: machine.id ?? "",
|
|
376
|
+
message: "The predecessor has an invalid stop_config timeout; refusing to shorten its shutdown window.",
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
// Unspecified raw-image overrides must remain unspecified for Fly's defaults.
|
|
380
|
+
return {
|
|
381
|
+
signal: persisted?.signal,
|
|
382
|
+
timeout: persisted?.timeout,
|
|
383
|
+
timeoutMs: timeoutMs ?? 300_000,
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
export class ReplicaOwnershipChanged extends Data.TaggedError("Fly.ReplicaOwnershipChanged") {
|
|
387
|
+
}
|
|
388
|
+
export class ReplicaRetirementIncomplete extends Data.TaggedError("Fly.ReplicaRetirementIncomplete") {
|
|
389
|
+
}
|
|
390
|
+
const retirementStep = (appName, machineId, stage) => (effect) => effect.pipe(Effect.mapError((error) => error._tag === "NotFound"
|
|
391
|
+
? error
|
|
392
|
+
: new ReplicaRetirementIncomplete({
|
|
393
|
+
appName,
|
|
394
|
+
residuals: [{ machineId, stage: `${stage}: ${error._tag}` }],
|
|
395
|
+
})));
|
|
396
|
+
const sameOwnership = (observed, snapshot) => observed.id === snapshot.id &&
|
|
397
|
+
[
|
|
398
|
+
alchemyMetadataKeys.stack,
|
|
399
|
+
alchemyMetadataKeys.stage,
|
|
400
|
+
alchemyMetadataKeys.id,
|
|
401
|
+
alchemyMetadataKeys.type,
|
|
402
|
+
alchemyMetadataKeys.instance,
|
|
403
|
+
alchemyMetadataKeys.fqn,
|
|
404
|
+
alchemyMetadataKeys.generation,
|
|
405
|
+
].every((key) => observed.config?.metadata?.[key] === snapshot.config?.metadata?.[key]);
|
|
406
|
+
export const leaseSnapshot = Effect.fn(function* (appName, snapshot, leases) {
|
|
407
|
+
const ordered = [...snapshot].sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
|
|
408
|
+
const leased = new Set();
|
|
409
|
+
yield* Effect.forEach(ordered, (machine) => Effect.gen(function* () {
|
|
410
|
+
if (!machine.id || machine.host_status === "unreachable")
|
|
411
|
+
return;
|
|
412
|
+
const id = machine.id;
|
|
413
|
+
yield* leases.acquire([id]).pipe(Effect.tap(() => Effect.sync(() => leased.add(id))),
|
|
414
|
+
// A just-created Machine can 404 on the lease API while it already
|
|
415
|
+
// lists; a Machine that is really gone is dropped by the GET below.
|
|
416
|
+
Effect.retry({
|
|
417
|
+
while: (error) => error._tag === "NotFound",
|
|
418
|
+
schedule: Schedule.spaced("1 second"),
|
|
419
|
+
times: 5,
|
|
420
|
+
}), Effect.catchTag("NotFound", () => Effect.void));
|
|
421
|
+
}), { concurrency: 1 }).pipe(Effect.timeout("30 seconds"));
|
|
422
|
+
const observed = [];
|
|
423
|
+
for (const machine of ordered) {
|
|
424
|
+
if (!machine.id)
|
|
425
|
+
continue;
|
|
426
|
+
if (machine.host_status === "unreachable") {
|
|
427
|
+
observed.push(machine);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const current = yield* leased.has(machine.id)
|
|
431
|
+
? getLeasedMachineById(appName, machine.id)
|
|
432
|
+
: getMachineById(appName, machine.id);
|
|
433
|
+
if (!current) {
|
|
434
|
+
yield* leases.forget(machine.id);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (!sameOwnership(current, machine))
|
|
438
|
+
return yield* new ReplicaOwnershipChanged({ appName, machineId: machine.id });
|
|
439
|
+
yield* leases.checkTarget(machine.id);
|
|
440
|
+
observed.push(current);
|
|
441
|
+
}
|
|
442
|
+
yield* leases.check;
|
|
443
|
+
return observed;
|
|
444
|
+
});
|
|
445
|
+
export const retireMachines = (appName, snapshot, replacementReady = false, existingLeases) => usingMachineLeases(appName, existingLeases, (leases) => retireLeasedMachines(appName, snapshot, replacementReady, leases));
|
|
446
|
+
const retireLeasedMachines = Effect.fn(function* (appName, snapshot, replacementReady, leases) {
|
|
447
|
+
const targets = yield* leaseSnapshot(appName, snapshot, leases);
|
|
448
|
+
const results = yield* Effect.forEach(targets, (machine) => Effect.gen(function* () {
|
|
449
|
+
yield* leases.check;
|
|
450
|
+
const result = yield* Effect.gen(function* () {
|
|
451
|
+
if (!machine.id)
|
|
452
|
+
return;
|
|
453
|
+
if (replacementReady && machine.host_status === "unreachable") {
|
|
454
|
+
const metadata = machine.config?.metadata;
|
|
455
|
+
if (!machine.config ||
|
|
456
|
+
machine.incomplete_config ||
|
|
457
|
+
(machine.config.mounts?.length ?? 0) > 0 ||
|
|
458
|
+
!metadata?.[alchemyMetadataKeys.instance] ||
|
|
459
|
+
!metadata[alchemyMetadataKeys.fqn]) {
|
|
460
|
+
return yield* new ReplicaOwnershipChanged({ appName, machineId: machine.id });
|
|
461
|
+
}
|
|
462
|
+
yield* Effect.logWarning("Force-retiring owned stateless Fly Machine on unreachable host; work was not proven drained", { appName, machineId: machine.id });
|
|
463
|
+
const machineId = machine.id;
|
|
464
|
+
const heldNonce = yield* leases.nonceIfHeld(machineId);
|
|
465
|
+
yield* Effect.gen(function* () {
|
|
466
|
+
const remove = (lease_nonce) => machines
|
|
467
|
+
.deleteMachine({
|
|
468
|
+
app_name: appName,
|
|
469
|
+
machine_id: machineId,
|
|
470
|
+
force: true,
|
|
471
|
+
lease_nonce,
|
|
472
|
+
})
|
|
473
|
+
.pipe(Retry.none, Effect.timeout("30 seconds"));
|
|
474
|
+
if (heldNonce !== undefined)
|
|
475
|
+
yield* leases.remove(machineId, remove);
|
|
476
|
+
else
|
|
477
|
+
yield* leases.guard(remove());
|
|
478
|
+
}).pipe(Effect.catchTag("NotFound", () => Effect.void), retirementStep(appName, machineId, "unreachable force-delete"));
|
|
479
|
+
yield* leases.forget(machine.id);
|
|
480
|
+
return yield* waitDestroyed(appName, machine.id);
|
|
481
|
+
}
|
|
482
|
+
yield* retireMachine(appName, machine.id, machine, leases);
|
|
483
|
+
}).pipe(Effect.result);
|
|
484
|
+
yield* leases.check;
|
|
485
|
+
return Result.isFailure(result)
|
|
486
|
+
? result.failure._tag === "Fly.ReplicaRetirementIncomplete"
|
|
487
|
+
? result.failure.residuals
|
|
488
|
+
: [{ machineId: machine.id ?? "", stage: result.failure._tag }]
|
|
489
|
+
: [];
|
|
490
|
+
}), { concurrency: 4 });
|
|
491
|
+
const residuals = results.flat();
|
|
492
|
+
if (residuals.length)
|
|
493
|
+
return yield* new ReplicaRetirementIncomplete({ appName, residuals });
|
|
494
|
+
});
|
|
495
|
+
export const retireMachine = (appName, machineId, snapshot, existingLeases) => usingMachineLeases(appName, existingLeases, (leases) => retireLeasedMachine(appName, machineId, snapshot, leases).pipe(Effect.tap(() => leases.forget(machineId))));
|
|
496
|
+
const retireLeasedMachine = Effect.fn(function* (appName, machineId, snapshot, leases) {
|
|
497
|
+
yield* leases.acquire([machineId]);
|
|
498
|
+
const current = yield* getMachineById(appName, machineId);
|
|
499
|
+
if (current === undefined)
|
|
500
|
+
return;
|
|
501
|
+
if (snapshot && !sameOwnership(current, snapshot)) {
|
|
502
|
+
return yield* new ReplicaOwnershipChanged({ appName, machineId });
|
|
503
|
+
}
|
|
504
|
+
const stop = yield* predecessorShutdown(current);
|
|
505
|
+
if (current.cordoned === true &&
|
|
506
|
+
current.config?.metadata?.[alchemyMetadataKeys.phase] === "candidate")
|
|
507
|
+
return yield* deleteMachine(appName, machineId, leases);
|
|
508
|
+
if (current.state !== "stopped" && current.state !== "created") {
|
|
509
|
+
yield* leases
|
|
510
|
+
.mutate(machineId, (lease_nonce) => machines
|
|
511
|
+
.cordonMachine({ app_name: appName, machine_id: machineId, lease_nonce })
|
|
512
|
+
.pipe(Effect.timeout("30 seconds")), { idempotent: true })
|
|
513
|
+
.pipe(retirementStep(appName, machineId, "cordon"));
|
|
514
|
+
if (hasPublishedService(current.config?.services))
|
|
515
|
+
yield* Effect.sleep("10 seconds");
|
|
516
|
+
yield* leases
|
|
517
|
+
.mutate(machineId, (lease_nonce) => machines
|
|
518
|
+
.stopMachine({
|
|
519
|
+
app_name: appName,
|
|
520
|
+
machine_id: machineId,
|
|
521
|
+
lease_nonce,
|
|
522
|
+
signal: stop.signal,
|
|
523
|
+
timeout: stop.timeout,
|
|
524
|
+
})
|
|
525
|
+
.pipe(Effect.timeout(stop.timeoutMs + 15_000)), { timeoutMs: stop.timeoutMs + 120_000 })
|
|
526
|
+
.pipe(retirementStep(appName, machineId, "stop"));
|
|
527
|
+
yield* machines
|
|
528
|
+
.waitMachine({
|
|
529
|
+
app_name: appName,
|
|
530
|
+
machine_id: machineId,
|
|
531
|
+
state: "stopped",
|
|
532
|
+
instance_id: current.instance_id,
|
|
533
|
+
timeout: WAIT_TIMEOUT_SECONDS,
|
|
534
|
+
})
|
|
535
|
+
.pipe(Retry.none, Effect.retry({
|
|
536
|
+
times: 10,
|
|
537
|
+
schedule: Schedule.spaced(Math.max(500, stop.timeoutMs / 10)),
|
|
538
|
+
while: (error) => error._tag === "MachineWaitTimeout" || error._tag === "GatewayTimeout",
|
|
539
|
+
}), Effect.timeout(stop.timeoutMs + 15_000), retirementStep(appName, machineId, "wait-stopped"));
|
|
540
|
+
}
|
|
541
|
+
yield* leases
|
|
542
|
+
.remove(machineId, (lease_nonce) => machines
|
|
543
|
+
.deleteMachine({ app_name: appName, machine_id: machineId, force: false, lease_nonce })
|
|
544
|
+
.pipe(Effect.timeout("30 seconds")))
|
|
545
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.void), retirementStep(appName, machineId, "delete"));
|
|
546
|
+
yield* waitDestroyed(appName, machineId).pipe(retirementStep(appName, machineId, "verify-absent"));
|
|
547
|
+
}, Effect.catchTag("NotFound", () => Effect.void));
|
|
548
|
+
const mountedDisksOf = (machine, volumesById) => (machine.config?.mounts ?? []).flatMap((mount) => {
|
|
549
|
+
const volumeId = mount.volume;
|
|
550
|
+
const path = mount.path;
|
|
551
|
+
if (volumeId === undefined || path === undefined)
|
|
552
|
+
return [];
|
|
553
|
+
const volume = volumesById.get(volumeId);
|
|
554
|
+
return [{ path, volumeId, sizeGb: volume?.size_gb ?? 0, name: volume?.name ?? "" }];
|
|
555
|
+
});
|
|
556
|
+
export const toReplica = (machine, volumesById) => ({
|
|
557
|
+
machineId: machine.id ?? "",
|
|
558
|
+
name: machine.name ?? "",
|
|
559
|
+
region: machine.region ?? "",
|
|
560
|
+
state: machine.state ?? "",
|
|
561
|
+
instanceId: machine.instance_id,
|
|
562
|
+
privateIp: machine.private_ip,
|
|
563
|
+
imageRef: toImageRef(machine.image_ref),
|
|
564
|
+
guest: toGuestAttrs(machine.config?.guest),
|
|
565
|
+
mounts: mountedDisksOf(machine, volumesById),
|
|
566
|
+
});
|
|
567
|
+
export const toReplicaSet = (replicas, appName, baseName, services) => {
|
|
568
|
+
const primary = replicas[0];
|
|
569
|
+
return {
|
|
570
|
+
appName,
|
|
571
|
+
machineId: primary?.machineId ?? "",
|
|
572
|
+
machineIds: replicas.map((replica) => replica.machineId),
|
|
573
|
+
name: primary?.name ?? baseName,
|
|
574
|
+
baseName,
|
|
575
|
+
region: primary?.region ?? "",
|
|
576
|
+
regions: [
|
|
577
|
+
...new Set(replicas.map((replica) => replica.region).filter((region) => region.length > 0)),
|
|
578
|
+
],
|
|
579
|
+
state: primary?.state ?? "",
|
|
580
|
+
instanceId: primary?.instanceId,
|
|
581
|
+
privateIp: primary?.privateIp,
|
|
582
|
+
imageRef: primary?.imageRef,
|
|
583
|
+
guest: primary?.guest,
|
|
584
|
+
services,
|
|
585
|
+
count: replicas.length,
|
|
586
|
+
mounts: primary?.mounts ?? [],
|
|
587
|
+
replicas,
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
export const ownedReplicas = (listed, input) => listed.filter((machine) => {
|
|
591
|
+
const metadata = machine.config?.metadata ?? {};
|
|
592
|
+
if (!Object.entries(input.metadata).every(([key, value]) => metadata[key] === value))
|
|
593
|
+
return false;
|
|
594
|
+
if (metadata[alchemyMetadataKeys.instance] !== undefined) {
|
|
595
|
+
return (metadata[alchemyMetadataKeys.instance] === input.resourceInstanceId &&
|
|
596
|
+
metadata[alchemyMetadataKeys.fqn] === input.fqn);
|
|
597
|
+
}
|
|
598
|
+
return ((machine.id !== undefined && input.machineIds?.includes(machine.id)) ||
|
|
599
|
+
(input.baseName !== undefined &&
|
|
600
|
+
(machine.name === input.baseName ||
|
|
601
|
+
machine.name === replicaMachineName(input.baseName, replicaIndexOf(machine), 2))));
|
|
602
|
+
});
|
|
603
|
+
export const listReplicas = Effect.fn(function* (input) {
|
|
604
|
+
const machines = yield* listMachinesByApp(input.appName);
|
|
605
|
+
return machines
|
|
606
|
+
.filter((machine) => isOwnedType(machine, input.type) && alchemyIdOf(machine) === input.id)
|
|
607
|
+
.sort((left, right) => replicaIndexOf(left) - replicaIndexOf(right));
|
|
608
|
+
});
|
|
609
|
+
export const listReplicaSets = Effect.fn(function* (type) {
|
|
610
|
+
const apps = yield* listOwnedApps();
|
|
611
|
+
const groups = yield* Effect.forEach(apps, (app) => listMachinesByApp(app.appName).pipe(Effect.map((machines) => {
|
|
612
|
+
const owned = machines.filter((machine) => isOwnedType(machine, type));
|
|
613
|
+
const byId = new Map();
|
|
614
|
+
for (const machine of owned) {
|
|
615
|
+
const id = alchemyIdOf(machine);
|
|
616
|
+
if (id === undefined)
|
|
617
|
+
continue;
|
|
618
|
+
const metadata = machine.config?.metadata;
|
|
619
|
+
const key = JSON.stringify([
|
|
620
|
+
metadata?.[alchemyMetadataKeys.stack],
|
|
621
|
+
metadata?.[alchemyMetadataKeys.stage],
|
|
622
|
+
metadata?.[alchemyMetadataKeys.fqn] ?? id,
|
|
623
|
+
metadata?.[alchemyMetadataKeys.instance],
|
|
624
|
+
metadata?.[alchemyMetadataKeys.generation],
|
|
625
|
+
]);
|
|
626
|
+
const group = byId.get(key) ?? [];
|
|
627
|
+
group.push(machine);
|
|
628
|
+
byId.set(key, group);
|
|
629
|
+
}
|
|
630
|
+
return [...byId.values()].map((group) => {
|
|
631
|
+
const sorted = [...group].sort((left, right) => replicaIndexOf(left) - replicaIndexOf(right));
|
|
632
|
+
const replicas = sorted.map((machine) => toReplica(machine, new Map()));
|
|
633
|
+
return toReplicaSet(replicas, app.appName, replicas[0]?.name ?? "", sorted[0]?.config?.services);
|
|
634
|
+
});
|
|
635
|
+
})), { concurrency: 8 });
|
|
636
|
+
return groups.flat();
|
|
637
|
+
});
|
|
638
|
+
const pickVolume = (group, used, preferId) => {
|
|
639
|
+
if (preferId !== undefined && !used.has(preferId)) {
|
|
640
|
+
const preferred = group.find((volume) => volume.id === preferId);
|
|
641
|
+
if (preferred !== undefined)
|
|
642
|
+
return preferred;
|
|
643
|
+
}
|
|
644
|
+
return group.find((volume) => volume.id !== undefined && !used.has(volume.id));
|
|
645
|
+
};
|
|
646
|
+
export const reconcileReplicas = (input) => usingMachineLeases(input.appName, undefined, (leases) => reconcileLeasedReplicas(input, leases));
|
|
647
|
+
const reconcileLeasedReplicas = Effect.fn(function* (input, leases) {
|
|
648
|
+
const ownership = yield* createMachineMetadata(input.id, input.type);
|
|
649
|
+
const alchemy = {
|
|
650
|
+
...ownership,
|
|
651
|
+
[alchemyMetadataKeys.instance]: input.resourceInstanceId,
|
|
652
|
+
[alchemyMetadataKeys.fqn]: input.fqn,
|
|
653
|
+
[alchemyMetadataKeys.baseName]: input.baseName,
|
|
654
|
+
};
|
|
655
|
+
const buildConfig = (replica) => ({
|
|
656
|
+
...input.buildConfig(replica),
|
|
657
|
+
checks: input.checks === undefined
|
|
658
|
+
? undefined
|
|
659
|
+
: Object.fromEntries(Object.entries(input.checks).map(([name, check]) => [name, toFlyServiceCheck(check)])),
|
|
660
|
+
stop_config: input.policy.shutdown === undefined
|
|
661
|
+
? undefined
|
|
662
|
+
: { signal: input.policy.shutdown.signal, timeout: input.policy.shutdown.timeout },
|
|
663
|
+
});
|
|
664
|
+
const config = buildConfig({ index: 0, mounts: [], metadata: alchemy });
|
|
665
|
+
yield* validateDeployment(input.policy, config, input.disks.length > 0, input.skipLaunch);
|
|
666
|
+
if (input.policy.bluegreen)
|
|
667
|
+
return yield* reconcileBlueGreen({ ...input, buildConfig }, ownership, alchemy, leases);
|
|
668
|
+
const desiredNames = new Set(Array.from({ length: input.count }, (_, index) => replicaMachineName(input.baseName, index, input.count)));
|
|
669
|
+
const listed = yield* listMachinesByApp(input.appName);
|
|
670
|
+
const owned = yield* leaseSnapshot(input.appName, ownedReplicas(listed, { ...input, metadata: ownership, machineIds: input.outputMachineIds }), leases);
|
|
671
|
+
const byIndex = new Map();
|
|
672
|
+
const preferIds = new Set((input.outputMachineIds ?? []).filter((id) => id.length > 0));
|
|
673
|
+
for (const machine of owned) {
|
|
674
|
+
const id = machine.id;
|
|
675
|
+
if (id !== undefined && preferIds.has(id)) {
|
|
676
|
+
byIndex.set(replicaIndexOf(machine), machine);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
for (const machine of owned) {
|
|
680
|
+
const name = machine.name;
|
|
681
|
+
if (name === undefined ||
|
|
682
|
+
(!desiredNames.has(name) &&
|
|
683
|
+
machine.config?.metadata?.[alchemyMetadataKeys.instance] !== input.resourceInstanceId))
|
|
684
|
+
continue;
|
|
685
|
+
const index = replicaIndexOf(machine);
|
|
686
|
+
if (!byIndex.has(index))
|
|
687
|
+
byIndex.set(index, machine);
|
|
688
|
+
}
|
|
689
|
+
const scaleDown = [...byIndex].filter(([index]) => index >= input.count);
|
|
690
|
+
yield* retireMachines(input.appName, scaleDown.map(([, machine]) => machine), false, leases);
|
|
691
|
+
for (const [index] of scaleDown)
|
|
692
|
+
byIndex.delete(index);
|
|
693
|
+
// Volumes are regional: one group per disk and region, sized to the
|
|
694
|
+
// replicas that run there.
|
|
695
|
+
const replicasIn = (region) => Array.from({ length: input.count }, (_, index) => index).filter((index) => regionOfReplica(input.regions, index) === region).length;
|
|
696
|
+
const groups = [];
|
|
697
|
+
for (const [diskIndex, disk] of input.disks.entries()) {
|
|
698
|
+
const name = yield* volumeGroupName(input.id, disk);
|
|
699
|
+
const preferIds = (input.preferVolumeIds ?? [])
|
|
700
|
+
.map((replica) => replica[diskIndex])
|
|
701
|
+
.filter((id) => id !== undefined && id.length > 0);
|
|
702
|
+
const byRegion = new Map();
|
|
703
|
+
for (const region of input.regions) {
|
|
704
|
+
byRegion.set(region, yield* ensureVolumeGroup({
|
|
705
|
+
appName: input.appName,
|
|
706
|
+
name,
|
|
707
|
+
region,
|
|
708
|
+
count: replicasIn(region),
|
|
709
|
+
disk,
|
|
710
|
+
preferIds,
|
|
711
|
+
}));
|
|
712
|
+
}
|
|
713
|
+
groups.push({ disk, name, byRegion });
|
|
714
|
+
}
|
|
715
|
+
const usedVolumeIds = new Set();
|
|
716
|
+
const live = [];
|
|
717
|
+
for (let index = 0; index < input.count; index++) {
|
|
718
|
+
const name = replicaMachineName(input.baseName, index, input.count);
|
|
719
|
+
const region = regionOfReplica(input.regions, index);
|
|
720
|
+
const metadata = {
|
|
721
|
+
...alchemy,
|
|
722
|
+
[alchemyMetadataKeys.replica]: String(index),
|
|
723
|
+
...(input.minSecretsVersion === undefined
|
|
724
|
+
? {}
|
|
725
|
+
: { [alchemyMetadataKeys.secretsVersion]: String(input.minSecretsVersion) }),
|
|
726
|
+
};
|
|
727
|
+
const prefer = input.preferVolumeIds?.[index] ?? [];
|
|
728
|
+
const mounts = [];
|
|
729
|
+
for (const [diskIndex, group] of groups.entries()) {
|
|
730
|
+
const volume = pickVolume(group.byRegion.get(region)?.volumes ?? [], usedVolumeIds, prefer[diskIndex]);
|
|
731
|
+
const volumeId = volume?.id;
|
|
732
|
+
if (volumeId === undefined) {
|
|
733
|
+
return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
|
|
734
|
+
}
|
|
735
|
+
usedVolumeIds.add(volumeId);
|
|
736
|
+
mounts.push({ volume: volumeId, path: group.disk.path });
|
|
737
|
+
}
|
|
738
|
+
const config = buildConfig({ index, mounts, metadata });
|
|
739
|
+
let current = byIndex.get(index);
|
|
740
|
+
// Fly cannot move a Machine between regions. Machine names are unique
|
|
741
|
+
// per App, so retire the old replica before creating it in its region.
|
|
742
|
+
if (current !== undefined && current.region !== region) {
|
|
743
|
+
yield* retireMachines(input.appName, [current], false, leases);
|
|
744
|
+
byIndex.delete(index);
|
|
745
|
+
current = undefined;
|
|
746
|
+
}
|
|
747
|
+
if (current === undefined) {
|
|
748
|
+
const created = yield* machines
|
|
749
|
+
.createMachine({
|
|
750
|
+
app_name: input.appName,
|
|
751
|
+
name,
|
|
752
|
+
region,
|
|
753
|
+
config,
|
|
754
|
+
skip_launch: input.skipLaunch === true ? true : undefined,
|
|
755
|
+
min_secrets_version: input.minSecretsVersion,
|
|
756
|
+
})
|
|
757
|
+
.pipe(Effect.catchTag("Conflict", () => Effect.undefined));
|
|
758
|
+
current =
|
|
759
|
+
created ??
|
|
760
|
+
(yield* listMachinesByApp(input.appName).pipe(Effect.map((machines) => ownedReplicas(machines, {
|
|
761
|
+
...input,
|
|
762
|
+
metadata: ownership,
|
|
763
|
+
machineIds: input.outputMachineIds,
|
|
764
|
+
}).find((machine) => machine.name === name))));
|
|
765
|
+
if (current === undefined || current.id === undefined) {
|
|
766
|
+
return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
|
|
767
|
+
}
|
|
768
|
+
const observed = (yield* leaseSnapshot(input.appName, [current], leases))[0];
|
|
769
|
+
if (!observed || input.configDrifted(observed, { mounts, metadata })) {
|
|
770
|
+
return yield* new ReplicaNotCreated({ resourceName: name, appName: input.appName });
|
|
771
|
+
}
|
|
772
|
+
current = observed;
|
|
773
|
+
}
|
|
774
|
+
else if (!sameChecks(current.config?.checks, config.checks) ||
|
|
775
|
+
!sameStopConfig(current.config?.stop_config, config.stop_config) ||
|
|
776
|
+
input.configDrifted(current, { mounts, metadata })) {
|
|
777
|
+
const machineId = current.id;
|
|
778
|
+
const currentVersion = current.instance_id;
|
|
779
|
+
const previous = current;
|
|
780
|
+
const expected = { ...previous, config };
|
|
781
|
+
yield* leases.mutate(machineId, (lease_nonce) => machines
|
|
782
|
+
.updateMachine({
|
|
783
|
+
app_name: input.appName,
|
|
784
|
+
machine_id: machineId,
|
|
785
|
+
current_version: currentVersion,
|
|
786
|
+
config,
|
|
787
|
+
skip_launch: input.skipLaunch === true ? true : undefined,
|
|
788
|
+
min_secrets_version: input.minSecretsVersion,
|
|
789
|
+
lease_nonce,
|
|
790
|
+
})
|
|
791
|
+
.pipe(Effect.timeout("30 seconds")));
|
|
792
|
+
// Rolling opt-out removes generation metadata; observe that transition before startup.
|
|
793
|
+
current = yield* leases.guard(Effect.gen(function* () {
|
|
794
|
+
const observed = yield* machines
|
|
795
|
+
.getMachine({ app_name: input.appName, machine_id: machineId })
|
|
796
|
+
.pipe(Retry.none);
|
|
797
|
+
if (!sameOwnership(observed, expected) && !sameOwnership(observed, previous))
|
|
798
|
+
return yield* new ReplicaOwnershipChanged({ appName: input.appName, machineId });
|
|
799
|
+
if (!sameOwnership(observed, expected) ||
|
|
800
|
+
input.configDrifted(observed, { mounts, metadata }) ||
|
|
801
|
+
!sameChecks(observed.config?.checks, config.checks) ||
|
|
802
|
+
!sameStopConfig(observed.config?.stop_config, config.stop_config))
|
|
803
|
+
return yield* new ReplicaNotCreated({ appName: input.appName, resourceName: name });
|
|
804
|
+
return observed;
|
|
805
|
+
}).pipe(Effect.retry({
|
|
806
|
+
times: 8,
|
|
807
|
+
schedule: waitBackoff,
|
|
808
|
+
while: (error) => error._tag === "Fly.ReplicaNotCreated" ||
|
|
809
|
+
TRANSIENT_GET_TAGS.some((tag) => tag === error._tag),
|
|
810
|
+
}), Effect.timeout("60 seconds")));
|
|
811
|
+
}
|
|
812
|
+
current = yield* ensureStarted(input.appName, current, input.skipLaunch === true, input.policy.healthTimeoutMs, config, leases);
|
|
813
|
+
if (current.cordoned === true && !input.skipLaunch) {
|
|
814
|
+
yield* setRouting(input.appName, current.id, false, leases);
|
|
815
|
+
if (hasPublishedService(config.services))
|
|
816
|
+
yield* Effect.sleep("10 seconds");
|
|
817
|
+
current = yield* waitHealthy(input.appName, current, input.policy.healthTimeoutMs, config);
|
|
818
|
+
}
|
|
819
|
+
live.push(current);
|
|
820
|
+
}
|
|
821
|
+
const liveIds = new Set(live.map((machine) => machine.id));
|
|
822
|
+
yield* retireMachines(input.appName, owned.filter((machine) => !liveIds.has(machine.id)), false, leases);
|
|
823
|
+
for (const group of groups) {
|
|
824
|
+
for (const extra of [...group.byRegion.values()].flatMap((placed) => placed.extras)) {
|
|
825
|
+
const volumeId = extra.id;
|
|
826
|
+
if (volumeId === undefined || usedVolumeIds.has(volumeId))
|
|
827
|
+
continue;
|
|
828
|
+
yield* deleteVolume(input.appName, volumeId);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const volumesById = new Map();
|
|
832
|
+
for (const group of groups) {
|
|
833
|
+
for (const volume of [...group.byRegion.values()].flatMap((placed) => placed.volumes)) {
|
|
834
|
+
if (volume.id !== undefined)
|
|
835
|
+
volumesById.set(volume.id, volume);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
const fresh = yield* Effect.forEach(live, (machine) => machine.id === undefined
|
|
839
|
+
? Effect.succeed(machine)
|
|
840
|
+
: getMachineById(input.appName, machine.id).pipe(Effect.map((next) => next ?? machine)), { concurrency: 4 });
|
|
841
|
+
const replicas = fresh.map((machine) => toReplica(machine, volumesById));
|
|
842
|
+
return toReplicaSet(replicas, input.appName, input.baseName, fresh[0]?.config?.services);
|
|
843
|
+
});
|
|
844
|
+
export const deleteReplicaSet = Effect.fn(function* (input) {
|
|
845
|
+
return yield* usingMachineLeases(input.appName, undefined, (leases) => Effect.gen(function* () {
|
|
846
|
+
const owned = yield* leaseSnapshot(input.appName, ownedReplicas(yield* listMachinesByApp(input.appName), {
|
|
847
|
+
...input,
|
|
848
|
+
metadata: yield* createMachineMetadata(input.id, input.type),
|
|
849
|
+
}), leases);
|
|
850
|
+
if (input.force) {
|
|
851
|
+
yield* Effect.forEach(owned, (machine) => Effect.gen(function* () {
|
|
852
|
+
if (!machine.id)
|
|
853
|
+
return;
|
|
854
|
+
const current = yield* getMachineById(input.appName, machine.id);
|
|
855
|
+
if (!current)
|
|
856
|
+
return;
|
|
857
|
+
if (!sameOwnership(current, machine))
|
|
858
|
+
return yield* new ReplicaOwnershipChanged({
|
|
859
|
+
appName: input.appName,
|
|
860
|
+
machineId: machine.id,
|
|
861
|
+
});
|
|
862
|
+
yield* deleteMachine(input.appName, machine.id, leases);
|
|
863
|
+
}), { concurrency: 4 });
|
|
864
|
+
}
|
|
865
|
+
else
|
|
866
|
+
yield* retireMachines(input.appName, owned, false, leases);
|
|
867
|
+
yield* Effect.forEach([...new Set(input.volumeIds)].filter((id) => id.length > 0), (volumeId) => deleteVolume(input.appName, volumeId), { concurrency: 4 });
|
|
868
|
+
}));
|
|
869
|
+
});
|
|
870
|
+
export const volumeIdsOf = (set) => {
|
|
871
|
+
const ids = new Set();
|
|
872
|
+
for (const mount of set.mounts ?? [])
|
|
873
|
+
ids.add(mount.volumeId);
|
|
874
|
+
for (const replica of set.replicas ?? []) {
|
|
875
|
+
for (const mount of replica.mounts)
|
|
876
|
+
ids.add(mount.volumeId);
|
|
877
|
+
}
|
|
878
|
+
return [...ids];
|
|
879
|
+
};
|
|
880
|
+
export const groupGenerations = (machines) => {
|
|
881
|
+
const groups = new Map();
|
|
882
|
+
for (const machine of machines) {
|
|
883
|
+
const generation = machine.config?.metadata?.[alchemyMetadataKeys.generation];
|
|
884
|
+
const group = groups.get(generation) ?? [];
|
|
885
|
+
group.push(machine);
|
|
886
|
+
groups.set(generation, group);
|
|
887
|
+
}
|
|
888
|
+
return groups;
|
|
889
|
+
};
|
|
890
|
+
export const observeReplicaSet = Effect.fn(function* (input) {
|
|
891
|
+
if (input.appName === undefined)
|
|
892
|
+
return undefined;
|
|
893
|
+
const owned = ownedReplicas(yield* listMachinesByApp(input.appName), {
|
|
894
|
+
...input,
|
|
895
|
+
metadata: yield* createMachineMetadata(input.id, input.type),
|
|
896
|
+
});
|
|
897
|
+
const groups = groupGenerations(owned);
|
|
898
|
+
const committed = [...groups.entries()]
|
|
899
|
+
.filter(([generation, group]) => {
|
|
900
|
+
if (generation === undefined)
|
|
901
|
+
return false;
|
|
902
|
+
const count = Number(group[0]?.config?.metadata?.[alchemyMetadataKeys.count]);
|
|
903
|
+
if (group.some((machine) => classifyDeploymentState(machine).protocol === "invalid"))
|
|
904
|
+
return false;
|
|
905
|
+
const protocol2 = group.some((machine) => machine.config?.metadata?.[alchemyMetadataKeys.protocol] === "2");
|
|
906
|
+
if (protocol2 && !validProtocol2Generation(group))
|
|
907
|
+
return false;
|
|
908
|
+
return (Number.isSafeInteger(count) &&
|
|
909
|
+
count > 0 &&
|
|
910
|
+
group.length === count &&
|
|
911
|
+
new Set(group.map(replicaIndexOf)).size === count &&
|
|
912
|
+
group.every((machine) => {
|
|
913
|
+
const metadata = machine.config?.metadata;
|
|
914
|
+
const index = replicaIndexOf(machine);
|
|
915
|
+
return (index >= 0 &&
|
|
916
|
+
index < count &&
|
|
917
|
+
Number(metadata?.[alchemyMetadataKeys.count]) === count &&
|
|
918
|
+
metadata?.[alchemyMetadataKeys.phase] === "active" &&
|
|
919
|
+
machine.cordoned === false &&
|
|
920
|
+
(metadata[alchemyMetadataKeys.protocol] === undefined ||
|
|
921
|
+
((metadata[alchemyMetadataKeys.protocol] === "1" ||
|
|
922
|
+
metadata[alchemyMetadataKeys.protocol] === "2") &&
|
|
923
|
+
metadata[alchemyMetadataKeys.restored] === "true" &&
|
|
924
|
+
(metadata[alchemyMetadataKeys.role] === "idle" ||
|
|
925
|
+
(machine.instance_id !== undefined &&
|
|
926
|
+
metadata[alchemyMetadataKeys.checkedInstance] === machine.instance_id)))));
|
|
927
|
+
}));
|
|
928
|
+
})
|
|
929
|
+
.sort(([, a], [, b]) => Number(b[0]?.config?.metadata?.[alchemyMetadataKeys.sequence] ?? 0) -
|
|
930
|
+
Number(a[0]?.config?.metadata?.[alchemyMetadataKeys.sequence] ?? 0));
|
|
931
|
+
const legacy = (groups.get(undefined) ?? []).filter((machine) => {
|
|
932
|
+
const { protocol } = classifyDeploymentState(machine);
|
|
933
|
+
return protocol === "legacy" || protocol === "1";
|
|
934
|
+
});
|
|
935
|
+
const listed = (committed[0]?.[1] ?? legacy).sort((a, b) => replicaIndexOf(a) - replicaIndexOf(b));
|
|
936
|
+
const rolloutPending = owned.some((machine) => !listed.includes(machine));
|
|
937
|
+
if (listed.length > 0) {
|
|
938
|
+
const volumesById = new Map();
|
|
939
|
+
for (const machine of listed) {
|
|
940
|
+
for (const mount of machine.config?.mounts ?? []) {
|
|
941
|
+
const volumeId = mount.volume;
|
|
942
|
+
if (volumeId === undefined || volumesById.has(volumeId))
|
|
943
|
+
continue;
|
|
944
|
+
const volume = yield* getVolumeById(input.appName, volumeId);
|
|
945
|
+
if (volume !== undefined)
|
|
946
|
+
volumesById.set(volumeId, volume);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const replicas = listed.map((machine) => toReplica(machine, volumesById));
|
|
950
|
+
return {
|
|
951
|
+
...toReplicaSet(replicas, input.appName, listed[0]?.config?.metadata?.[alchemyMetadataKeys.baseName] ??
|
|
952
|
+
input.baseName ??
|
|
953
|
+
replicas[0]?.name ??
|
|
954
|
+
"", listed[0]?.config?.services),
|
|
955
|
+
rolloutPending,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
if (owned.length)
|
|
959
|
+
return {
|
|
960
|
+
...toReplicaSet([], input.appName, input.baseName ?? ""),
|
|
961
|
+
region: owned[0]?.region ?? "",
|
|
962
|
+
regions: [
|
|
963
|
+
...new Set(owned.map((machine) => machine.region ?? "").filter((region) => region.length > 0)),
|
|
964
|
+
],
|
|
965
|
+
rolloutPending: true,
|
|
966
|
+
};
|
|
967
|
+
return undefined;
|
|
968
|
+
});
|
|
969
|
+
//# sourceMappingURL=replicas.js.map
|