@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/lib/Service.js
CHANGED
|
@@ -5,9 +5,9 @@ import * as Bundle from "alchemy/Bundle/Bundle";
|
|
|
5
5
|
import { deepEqual, isResolved } from "alchemy/Diff";
|
|
6
6
|
import { Platform } from "alchemy/Platform";
|
|
7
7
|
import * as Provider from "alchemy/Provider";
|
|
8
|
-
import { makeRandom } from "alchemy/Random";
|
|
9
8
|
import { packEnvValue } from "alchemy/RuntimeContext";
|
|
10
9
|
import { Stack } from "alchemy/Stack";
|
|
10
|
+
import { sha256 } from "alchemy/Util/sha256";
|
|
11
11
|
import * as Data from "effect/Data";
|
|
12
12
|
import * as Effect from "effect/Effect";
|
|
13
13
|
import * as Layer from "effect/Layer";
|
|
@@ -19,13 +19,13 @@ import { deploymentPolicy, validateDeployment, } from "./Deployment.js";
|
|
|
19
19
|
import { resolveOrgSlug } from "./Environment.js";
|
|
20
20
|
import { collectBindingState, createFlyHostedSupport, createFlyHostRuntimeContext, defaultHttpServices, DEFAULT_PORT, toEnvRecord, } from "./hosted.js";
|
|
21
21
|
import { ensureFlycastAddress, hasPublicAddress, syncOwnedAppAddresses } from "./IpAssignment.js";
|
|
22
|
-
import { alchemyMetadataKeys, createFlyAppName, createFlyResourceName, diffMachineMetadata, sanitizeFlyAppName, } from "./Metadata.js";
|
|
22
|
+
import { alchemyMetadataKeys, createFlyAppName, createFlyResourceName, diffMachineMetadata, matchesAlchemyPhysicalName, sanitizeFlyAppName, } from "./Metadata.js";
|
|
23
23
|
import { bindingPortOf, findPortConflict, portsOfFly } from "./ports.js";
|
|
24
24
|
import { attachPostgresSecrets } from "./Postgres.js";
|
|
25
25
|
import { attachRedisSecrets } from "./Redis.js";
|
|
26
26
|
import { regionList } from "./Region.js";
|
|
27
|
-
import { deleteReplicaSet, hasPublishedService, listReplicaSets, observeReplicaSet, reconcileReplicas, resolveCount, sameServices, toFlyService, volumeIdsOf, } from "./replicas.js";
|
|
28
|
-
import { BINDING_PORT_ENV, DEFAULT_BINDING_PORT,
|
|
27
|
+
import { deleteReplicaSet, hasPublishedService, listReplicaSets, lookupReplicaSet, observeReplicaSet, reconcileReplicas, resolveCount, sameServices, toFlyService, volumeIdsOf, } from "./replicas.js";
|
|
28
|
+
import { BINDING_PORT_ENV, DEFAULT_BINDING_PORT, RPC_CALLERS_ENV, RPC_ORG_ENV } from "./rpc.js";
|
|
29
29
|
const DEFAULT_REGION = "iad";
|
|
30
30
|
const DEFAULT_CPU_KIND = "shared";
|
|
31
31
|
const DEFAULT_CPUS = 1;
|
|
@@ -212,9 +212,9 @@ export const isService = (value) => typeof value === "object" &&
|
|
|
212
212
|
* ### Call another Service
|
|
213
213
|
* A Service can return methods next to `fetch`. Another Service calls
|
|
214
214
|
* them by binding it with {@link bindService}, which returns a typed
|
|
215
|
-
* client. Binding makes the caller deploy after the Service
|
|
216
|
-
* callers that bind a Service
|
|
217
|
-
*
|
|
215
|
+
* client. Binding makes the caller deploy after the Service and records
|
|
216
|
+
* the caller's App on it, so only callers that bind a Service can call
|
|
217
|
+
* its methods.
|
|
218
218
|
*
|
|
219
219
|
* **Example:** A private Service with methods
|
|
220
220
|
* ```typescript
|
|
@@ -248,9 +248,10 @@ export const isService = (value) => typeof value === "object" &&
|
|
|
248
248
|
* ```
|
|
249
249
|
*
|
|
250
250
|
* Calls use plain HTTP to `privateUrl` inside Fly's WireGuard-encrypted
|
|
251
|
-
* private network. The Service accepts a method call only with
|
|
252
|
-
*
|
|
253
|
-
*
|
|
251
|
+
* private network. The Service accepts a method call only with a
|
|
252
|
+
* `Fly-Src` signature from Fly's proxy naming the App of a Service that
|
|
253
|
+
* binds it, so neither the public internet nor any other App can call
|
|
254
|
+
* it, and no caller holds a key. A public
|
|
254
255
|
* Service publishes an extra plain-HTTP port for bound callers,
|
|
255
256
|
* `bindingPort` (default 7780), since its own ports are an HTTPS
|
|
256
257
|
* redirect and HTTPS. `client.fetch` sends a request to the Service's
|
|
@@ -401,8 +402,18 @@ export const isService = (value) => typeof value === "object" &&
|
|
|
401
402
|
*
|
|
402
403
|
* ### Config
|
|
403
404
|
* Yield `Config` in init. Alchemy reads the value from the env of
|
|
404
|
-
* whoever deploys and
|
|
405
|
-
* to copy it into `env
|
|
405
|
+
* whoever deploys and stores it as a secret on the Service's App, so
|
|
406
|
+
* there is no need to copy it into `env` and it never appears in the
|
|
407
|
+
* Machine config. Redacted outputs of bound resources are stored the
|
|
408
|
+
* same way. The secret's name is scoped to the Service
|
|
409
|
+
* (`ALCHEMY_<hash>_API_KEY`) and the Machine maps it back to `API_KEY`,
|
|
410
|
+
* so it never collides with `Fly.Secret`, `fly secrets set` or an
|
|
411
|
+
* attachment. The bound value wins over an App secret already named
|
|
412
|
+
* `API_KEY`, which stays in the App untouched. The Machine sees the
|
|
413
|
+
* App's other secrets as they were at deploy; one set later with
|
|
414
|
+
* `fly secrets set` reaches it on the next deploy. A Service in a shared
|
|
415
|
+
* App (`app`) writes the values into the Machine env instead, because
|
|
416
|
+
* Fly gives every App secret to every Machine in the App.
|
|
406
417
|
*
|
|
407
418
|
* `Config.Redacted("API_KEY")` is `Redacted<string>`. Unwrap with
|
|
408
419
|
* `Redacted.value` only where you need the raw string.
|
|
@@ -540,6 +551,20 @@ export const isService = (value) => typeof value === "object" &&
|
|
|
540
551
|
* ) {}
|
|
541
552
|
* ```
|
|
542
553
|
*
|
|
554
|
+
* ### Run your own image
|
|
555
|
+
* `context` builds a directory's `Dockerfile` as it is, on a Fly remote
|
|
556
|
+
* builder, and runs the image's own `CMD`. A change to any file in the
|
|
557
|
+
* directory updates the Machines.
|
|
558
|
+
*
|
|
559
|
+
* **Example:** A Dockerfile build
|
|
560
|
+
* ```typescript
|
|
561
|
+
* const worker = yield* Fly.Service("Worker", {
|
|
562
|
+
* context: "./worker",
|
|
563
|
+
* public: false,
|
|
564
|
+
* services: [],
|
|
565
|
+
* });
|
|
566
|
+
* ```
|
|
567
|
+
*
|
|
543
568
|
* ### Custom proxy services
|
|
544
569
|
* `services` defaults to HTTP 80 (redirecting to HTTPS) + HTTPS 443
|
|
545
570
|
* toward `port`, or plain HTTP 80 for a private Service. Pass a custom
|
|
@@ -602,28 +627,23 @@ export const isService = (value) => typeof value === "object" &&
|
|
|
602
627
|
* ) {}
|
|
603
628
|
* ```
|
|
604
629
|
*
|
|
605
|
-
* **Example:** Install
|
|
606
|
-
* `
|
|
607
|
-
*
|
|
608
|
-
*
|
|
630
|
+
* **Example:** Install a native package unbundled
|
|
631
|
+
* `build.install` runs `npm install` in the image for packages that
|
|
632
|
+
* cannot be bundled, such as native addons — same `build.install` as
|
|
633
|
+
* Lambda.
|
|
609
634
|
* ```typescript
|
|
610
635
|
* export default class Api extends Fly.Service<Api>()(
|
|
611
636
|
* "Api",
|
|
612
637
|
* {
|
|
613
638
|
* main: import.meta.url,
|
|
614
639
|
* port: 3000,
|
|
615
|
-
* build: { install: ["
|
|
640
|
+
* build: { install: ["sharp"] },
|
|
616
641
|
* },
|
|
617
642
|
* Effect.gen(function* () {
|
|
618
|
-
* const conn = yield* Fly.ConnectPostgres(Db);
|
|
619
|
-
* const db = yield* Drizzle.Postgres(conn.connectionString);
|
|
620
643
|
* return {
|
|
621
|
-
* fetch: Effect.
|
|
622
|
-
* const rows = yield* db.execute("select 1 as ok");
|
|
623
|
-
* return HttpServerResponse.json({ rows });
|
|
624
|
-
* }),
|
|
644
|
+
* fetch: Effect.succeed(HttpServerResponse.text("hello")),
|
|
625
645
|
* };
|
|
626
|
-
* })
|
|
646
|
+
* }),
|
|
627
647
|
* ) {}
|
|
628
648
|
* ```
|
|
629
649
|
*
|
|
@@ -707,6 +727,18 @@ export const isService = (value) => typeof value === "object" &&
|
|
|
707
727
|
* See the [deployment guide](/fly/compute/deployments) for recovery,
|
|
708
728
|
* idle capacity, application responsibilities, and verification limits.
|
|
709
729
|
*
|
|
730
|
+
* ### Looking up an existing Service
|
|
731
|
+
* `Service.lookup` references a Service Alchemy doesn't manage (deployed
|
|
732
|
+
* with `fly deploy` or by another stack). It is read during plan/deploy,
|
|
733
|
+
* never created, updated or deleted, and `url`, `endpoints` and
|
|
734
|
+
* `privateUrl` come from the Machines and addresses Fly reports.
|
|
735
|
+
*
|
|
736
|
+
* **Example:** Call a Service another stack deployed
|
|
737
|
+
* ```typescript
|
|
738
|
+
* const api = Fly.Service.lookup({ appName: "my-api" });
|
|
739
|
+
* const url = api.privateUrl;
|
|
740
|
+
* ```
|
|
741
|
+
*
|
|
710
742
|
* @resource
|
|
711
743
|
* @product Service
|
|
712
744
|
*/
|
|
@@ -721,8 +753,6 @@ export const Service = Platform("Fly.Service", {
|
|
|
721
753
|
const app = Effect.isEffect(props.app)
|
|
722
754
|
? yield* props.app
|
|
723
755
|
: props.app;
|
|
724
|
-
// Only callers that bind this Service receive the token.
|
|
725
|
-
const rpcToken = yield* makeRandom(`${id}RpcToken`);
|
|
726
756
|
if (app !== undefined) {
|
|
727
757
|
// Report the ports this Service publishes so the App can reject
|
|
728
758
|
// two Services on the same port before either one is deployed.
|
|
@@ -732,7 +762,7 @@ export const Service = Platform("Fly.Service", {
|
|
|
732
762
|
bindingPort: props.bindingPort,
|
|
733
763
|
});
|
|
734
764
|
}
|
|
735
|
-
return { ...props, app
|
|
765
|
+
return { ...props, app };
|
|
736
766
|
}),
|
|
737
767
|
});
|
|
738
768
|
export class ServiceNotCreated extends Data.TaggedError("Fly.ServiceNotCreated") {
|
|
@@ -768,6 +798,104 @@ const toFlyGuest = (guest) => {
|
|
|
768
798
|
fly.gpus = guest.gpus;
|
|
769
799
|
return fly;
|
|
770
800
|
};
|
|
801
|
+
/**
|
|
802
|
+
* Prefix of the App secrets a Service writes. Scoped to its FQN so they
|
|
803
|
+
* never share a name with `Fly.Secret`, `fly secrets set`, the
|
|
804
|
+
* Redis/Postgres/Bucket attachments, or another Service.
|
|
805
|
+
*/
|
|
806
|
+
const boundSecretPrefix = Effect.fn(function* (fqn) {
|
|
807
|
+
const hash = yield* sha256(fqn);
|
|
808
|
+
return `ALCHEMY_${hash.slice(0, 8).toUpperCase()}_`;
|
|
809
|
+
});
|
|
810
|
+
/** A secret some Service wrote under its {@link boundSecretPrefix}. */
|
|
811
|
+
const SCOPED_SECRET = /^ALCHEMY_[0-9A-F]{8}_/;
|
|
812
|
+
/**
|
|
813
|
+
* Store bound `Config` and Redacted values as App secrets under names
|
|
814
|
+
* scoped to the Service; the Machine config maps each back to its env var
|
|
815
|
+
* (see {@link secretMappings}). Fly's digest is not a hash Alchemy can
|
|
816
|
+
* compute, so a value is unchanged only when its sha256 matches the last
|
|
817
|
+
* write and Fly still reports that write's digest. Deletes secrets under
|
|
818
|
+
* the Service's prefix that are no longer bound, including ones a crash
|
|
819
|
+
* left out of state; never touches any other secret. Returns what it now
|
|
820
|
+
* owns, the App's other secrets the Machine should still see, and the
|
|
821
|
+
* App-secrets version Machines must reach.
|
|
822
|
+
*/
|
|
823
|
+
const syncBoundSecrets = Effect.fn(function* (appName, fqn, desired, previous) {
|
|
824
|
+
if (Object.keys(desired).length === 0 && Object.keys(previous.secrets).length === 0) {
|
|
825
|
+
return { secrets: {}, others: [], version: previous.version };
|
|
826
|
+
}
|
|
827
|
+
const prefix = yield* boundSecretPrefix(fqn);
|
|
828
|
+
const listDigests = machines
|
|
829
|
+
.listSecrets({ app_name: appName, show_secrets: false })
|
|
830
|
+
.pipe(Effect.map((listed) => new Map((listed.secrets ?? []).flatMap((secret) => secret.name === undefined ? [] : [[secret.name, secret.digest ?? ""]]))));
|
|
831
|
+
const observed = yield* listDigests;
|
|
832
|
+
const planned = yield* Effect.forEach(Object.entries(desired), ([envVar, value]) =>
|
|
833
|
+
// Salted with the scoped name, so equal values on different Services
|
|
834
|
+
// or Apps never share a hash in state.
|
|
835
|
+
sha256(`${prefix}${envVar}:${Redacted.value(value)}`).pipe(Effect.map((hash) => ({ envVar, name: `${prefix}${envVar}`, value, hash }))), { concurrency: "unbounded" });
|
|
836
|
+
const isUnchanged = ({ envVar, name, hash }) => {
|
|
837
|
+
const last = previous.secrets[envVar];
|
|
838
|
+
return (last !== undefined &&
|
|
839
|
+
last.name === name &&
|
|
840
|
+
last.sha256 === hash &&
|
|
841
|
+
observed.get(name) === last.digest);
|
|
842
|
+
};
|
|
843
|
+
const kept = planned.filter(isUnchanged);
|
|
844
|
+
const changed = planned.filter((entry) => !isUnchanged(entry));
|
|
845
|
+
const unbound = [...observed.keys()].filter((name) => name.startsWith(prefix) && !(name.slice(prefix.length) in desired));
|
|
846
|
+
const write = changed.length === 0
|
|
847
|
+
? Effect.succeed({ version: undefined, written: [] })
|
|
848
|
+
: Effect.gen(function* () {
|
|
849
|
+
const response = yield* machines.updateSecrets({
|
|
850
|
+
app_name: appName,
|
|
851
|
+
values: Object.fromEntries(changed.map(({ name, value }) => [name, value])),
|
|
852
|
+
});
|
|
853
|
+
const digests = yield* listDigests;
|
|
854
|
+
return {
|
|
855
|
+
version: response.version ?? response.Version,
|
|
856
|
+
written: changed.map(({ envVar, name, hash }) => [
|
|
857
|
+
envVar,
|
|
858
|
+
{ name, digest: digests.get(name) ?? "", sha256: hash },
|
|
859
|
+
]),
|
|
860
|
+
};
|
|
861
|
+
});
|
|
862
|
+
const remove = Effect.forEach(unbound, (name) => machines.deleteSecret({ app_name: appName, secret_name: name }).pipe(Effect.map((response) => response.version ?? response.Version), Effect.catchTag("NotFound", () => Effect.succeed(undefined))), { concurrency: "unbounded" });
|
|
863
|
+
const [{ version: writeVersion, written }, deleteVersions] = yield* Effect.all([write, remove], {
|
|
864
|
+
concurrency: "unbounded",
|
|
865
|
+
});
|
|
866
|
+
const secrets = Object.fromEntries([
|
|
867
|
+
...kept.map(({ envVar }) => [envVar, previous.secrets[envVar]]),
|
|
868
|
+
...written,
|
|
869
|
+
]);
|
|
870
|
+
const versions = [writeVersion, ...deleteVersions].filter((version) => version !== undefined && Number.isSafeInteger(version));
|
|
871
|
+
// A bound value wins over an App secret with the same env var name, as
|
|
872
|
+
// a Machine env var did; that secret stays in the App untouched.
|
|
873
|
+
const others = [...observed.keys()]
|
|
874
|
+
.filter((name) => !SCOPED_SECRET.test(name) && !(name in desired))
|
|
875
|
+
.sort();
|
|
876
|
+
return {
|
|
877
|
+
secrets,
|
|
878
|
+
others,
|
|
879
|
+
version: versions.length > 0 ? Math.max(...versions) : previous.version,
|
|
880
|
+
};
|
|
881
|
+
});
|
|
882
|
+
/**
|
|
883
|
+
* Fly maps a process's `secrets` entries from a scoped App secret to the
|
|
884
|
+
* env var the program reads, but only with `ignore_app_secrets`, which
|
|
885
|
+
* hides every App secret the process does not list. So the App's other
|
|
886
|
+
* secrets (attachments, `fly secrets set`) are listed under their own
|
|
887
|
+
* names, as they were at deploy.
|
|
888
|
+
*/
|
|
889
|
+
const secretMappings = (bound) => {
|
|
890
|
+
const mapped = Object.entries(bound.secrets).map(([envVar, secret]) => ({
|
|
891
|
+
env_var: envVar,
|
|
892
|
+
name: secret.name,
|
|
893
|
+
}));
|
|
894
|
+
if (mapped.length === 0)
|
|
895
|
+
return undefined;
|
|
896
|
+
const entries = [...mapped, ...bound.others.map((name) => ({ env_var: name, name }))].sort((left, right) => left.env_var.localeCompare(right.env_var));
|
|
897
|
+
return [{ secrets: entries, ignore_app_secrets: true }];
|
|
898
|
+
};
|
|
771
899
|
const desiredEnv = (props, bindingEnv, alchemyEnv, port) => ({
|
|
772
900
|
...toEnv(bindingEnv),
|
|
773
901
|
...alchemyEnv,
|
|
@@ -792,6 +920,7 @@ const buildConfig = (input) => ({
|
|
|
792
920
|
mounts: input.mounts.length > 0 ? input.mounts : undefined,
|
|
793
921
|
metadata: input.metadata,
|
|
794
922
|
statics: input.statics !== undefined && input.statics.length > 0 ? input.statics : undefined,
|
|
923
|
+
processes: input.processes,
|
|
795
924
|
});
|
|
796
925
|
const sameImage = (machine, image) => {
|
|
797
926
|
const configImage = machine.config?.image;
|
|
@@ -832,6 +961,17 @@ const sameStatics = (observed, desired) => {
|
|
|
832
961
|
}));
|
|
833
962
|
return deepEqual(normalize(observed), normalize(desired), { stripNullish: true });
|
|
834
963
|
};
|
|
964
|
+
/** Fly fills an omitted secret `name` with its `env_var`. */
|
|
965
|
+
const sameProcesses = (observed, desired) => {
|
|
966
|
+
const normalize = (processes) => (processes ?? []).map((process) => ({
|
|
967
|
+
...process,
|
|
968
|
+
secrets: process.secrets?.map((secret) => ({
|
|
969
|
+
env_var: secret.env_var,
|
|
970
|
+
name: secret.name || secret.env_var,
|
|
971
|
+
})),
|
|
972
|
+
}));
|
|
973
|
+
return deepEqual(normalize(observed), normalize(desired), { stripNullish: true });
|
|
974
|
+
};
|
|
835
975
|
const configDrifted = (machine, desired) => {
|
|
836
976
|
const config = machine.config;
|
|
837
977
|
return (!sameImage(machine, desired.image) ||
|
|
@@ -840,9 +980,9 @@ const configDrifted = (machine, desired) => {
|
|
|
840
980
|
!sameServices(config?.services, desired.services) ||
|
|
841
981
|
!sameMounts(config?.mounts, desired.mounts) ||
|
|
842
982
|
metadataChanged(config?.metadata, desired.metadata) ||
|
|
843
|
-
!sameStatics(config?.statics, desired.statics)
|
|
983
|
+
!sameStatics(config?.statics, desired.statics) ||
|
|
984
|
+
!sameProcesses(config?.processes, desired.processes));
|
|
844
985
|
};
|
|
845
|
-
const portsOf = (services) => (services ?? []).flatMap((service) => service.ports ?? []);
|
|
846
986
|
const withPort = (base, port, standard) => port === standard ? base : `${base}:${port}`;
|
|
847
987
|
const isRedirect = (handlers, forceHttps) => forceHttps === true && handlers.includes("http") && !handlers.includes("tls");
|
|
848
988
|
/** Every published port on `host`, excluding HTTPS redirects and ranges. */
|
|
@@ -1038,7 +1178,9 @@ const toAttrs = (set, codeHash, access) => ({
|
|
|
1038
1178
|
appName: set.appName,
|
|
1039
1179
|
ownsApp: access.ownsApp,
|
|
1040
1180
|
network: normalizeNetwork(access.network),
|
|
1041
|
-
|
|
1181
|
+
secrets: access.secrets,
|
|
1182
|
+
secretsVersion: access.secretsVersion,
|
|
1183
|
+
orgSlug: access.orgSlug,
|
|
1042
1184
|
...endpointsOf(set, access),
|
|
1043
1185
|
rolloutPending: set.rolloutPending,
|
|
1044
1186
|
machineId: set.machineId,
|
|
@@ -1054,6 +1196,25 @@ const toAttrs = (set, codeHash, access) => ({
|
|
|
1054
1196
|
replicas: set.replicas,
|
|
1055
1197
|
code: { hash: codeHash },
|
|
1056
1198
|
});
|
|
1199
|
+
/** The replica set of a Service whose App exists but runs no Machines yet. */
|
|
1200
|
+
const emptyReplicaSet = (appName, name) => ({
|
|
1201
|
+
appName,
|
|
1202
|
+
machineId: "",
|
|
1203
|
+
machineIds: [],
|
|
1204
|
+
name,
|
|
1205
|
+
baseName: name,
|
|
1206
|
+
region: "",
|
|
1207
|
+
regions: [],
|
|
1208
|
+
state: "created",
|
|
1209
|
+
instanceId: undefined,
|
|
1210
|
+
privateIp: undefined,
|
|
1211
|
+
imageRef: undefined,
|
|
1212
|
+
guest: undefined,
|
|
1213
|
+
services: undefined,
|
|
1214
|
+
count: 0,
|
|
1215
|
+
mounts: [],
|
|
1216
|
+
replicas: [],
|
|
1217
|
+
});
|
|
1057
1218
|
const machineIdsOf = (output) => output?.machineIds ??
|
|
1058
1219
|
(output?.machineId !== undefined && output.machineId.length > 0 ? [output.machineId] : []);
|
|
1059
1220
|
/** The private network the Service's Machines join. */
|
|
@@ -1107,7 +1268,7 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1107
1268
|
return Service.Provider.of({
|
|
1108
1269
|
stables: ["appName"],
|
|
1109
1270
|
nuke: { dependsOn: ["Fly.App"] },
|
|
1110
|
-
diff: Effect.fn(function* ({
|
|
1271
|
+
diff: Effect.fn(function* ({ news, output }) {
|
|
1111
1272
|
if (news === undefined)
|
|
1112
1273
|
return;
|
|
1113
1274
|
const shape = news;
|
|
@@ -1172,6 +1333,11 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1172
1333
|
// effect-config form has been evaluated, so the object view is
|
|
1173
1334
|
// safe to read.
|
|
1174
1335
|
const statics = news;
|
|
1336
|
+
if (typeof statics.context === "string") {
|
|
1337
|
+
const hash = yield* hosted.contextHash(statics.context);
|
|
1338
|
+
if (hash !== output.code.hash)
|
|
1339
|
+
return { action: "update" };
|
|
1340
|
+
}
|
|
1175
1341
|
if (isResolved({
|
|
1176
1342
|
main: statics.main,
|
|
1177
1343
|
build: statics.build,
|
|
@@ -1188,6 +1354,24 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1188
1354
|
}
|
|
1189
1355
|
return output.rolloutPending ? { action: "update" } : undefined;
|
|
1190
1356
|
}),
|
|
1357
|
+
lookup: Effect.fn(function* ({ appName, name }) {
|
|
1358
|
+
const found = yield* lookupReplicaSet({ appName, name });
|
|
1359
|
+
if (found === undefined)
|
|
1360
|
+
return undefined;
|
|
1361
|
+
const app = yield* machines
|
|
1362
|
+
.getApp({ app_name: appName })
|
|
1363
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
1364
|
+
const attrs = toAttrs(found, "", {
|
|
1365
|
+
// Reported as Alchemy-owned only so the endpoints follow the App's
|
|
1366
|
+
// observed addresses, as a declared Service's own App's do.
|
|
1367
|
+
ownsApp: true,
|
|
1368
|
+
isPublic: yield* hasPublicAddress(appName),
|
|
1369
|
+
network: app?.network,
|
|
1370
|
+
bindingPort: DEFAULT_BINDING_PORT,
|
|
1371
|
+
orgSlug: app?.organization?.slug,
|
|
1372
|
+
});
|
|
1373
|
+
return { ...attrs, ownsApp: false };
|
|
1374
|
+
}),
|
|
1191
1375
|
read: Effect.fn(function* ({ id, fqn, instanceId, olds, output }) {
|
|
1192
1376
|
const ownsApp = output?.ownsApp ?? (olds !== undefined && olds.app === undefined);
|
|
1193
1377
|
const appName = ownsApp
|
|
@@ -1206,14 +1390,36 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1206
1390
|
machineIds: machineIdsOf(output),
|
|
1207
1391
|
baseName: name,
|
|
1208
1392
|
});
|
|
1209
|
-
if (found === undefined)
|
|
1210
|
-
|
|
1393
|
+
if (found === undefined) {
|
|
1394
|
+
// A create interrupted between creating the Service's own App and
|
|
1395
|
+
// starting its Machines (an image build in between can take
|
|
1396
|
+
// minutes) leaves the App with nothing in it. Report it, so the
|
|
1397
|
+
// engine's recovery of the interrupted create deletes it instead
|
|
1398
|
+
// of leaking it. The App's name is derived from this resource's
|
|
1399
|
+
// instance, so an App with that name is this resource's.
|
|
1400
|
+
if (!ownsApp || appName === undefined)
|
|
1401
|
+
return undefined;
|
|
1402
|
+
const app = yield* machines
|
|
1403
|
+
.getApp({ app_name: appName })
|
|
1404
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
1405
|
+
if (app === undefined || !matchesAlchemyPhysicalName(app.name))
|
|
1406
|
+
return undefined;
|
|
1407
|
+
return toAttrs(emptyReplicaSet(appName, name), output?.code.hash ?? "", {
|
|
1408
|
+
ownsApp,
|
|
1409
|
+
isPublic: false,
|
|
1410
|
+
network: output?.network ?? olds?.network,
|
|
1411
|
+
bindingPort: olds?.bindingPort ?? DEFAULT_BINDING_PORT,
|
|
1412
|
+
orgSlug: output?.orgSlug ?? (yield* resolveOrgSlug()),
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1211
1415
|
return toAttrs(found, output?.code.hash ?? "", {
|
|
1212
1416
|
ownsApp,
|
|
1213
1417
|
isPublic: ownsApp && (yield* hasPublicAddress(found.appName)),
|
|
1214
1418
|
network: output?.network ?? (ownsApp ? olds?.network : undefined),
|
|
1215
1419
|
bindingPort: olds?.bindingPort ?? DEFAULT_BINDING_PORT,
|
|
1216
|
-
|
|
1420
|
+
secrets: output?.secrets,
|
|
1421
|
+
secretsVersion: output?.secretsVersion,
|
|
1422
|
+
orgSlug: output?.orgSlug ?? (yield* resolveOrgSlug()),
|
|
1217
1423
|
});
|
|
1218
1424
|
}),
|
|
1219
1425
|
list: Effect.fn(function* () {
|
|
@@ -1224,7 +1430,7 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1224
1430
|
isPublic: true,
|
|
1225
1431
|
network: undefined,
|
|
1226
1432
|
bindingPort: DEFAULT_BINDING_PORT,
|
|
1227
|
-
|
|
1433
|
+
orgSlug: undefined,
|
|
1228
1434
|
}));
|
|
1229
1435
|
}),
|
|
1230
1436
|
reconcile: Effect.fn(function* ({ id, fqn, instanceId, news, output, bindings, session }) {
|
|
@@ -1255,8 +1461,28 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1255
1461
|
if (version !== undefined)
|
|
1256
1462
|
secretVersions.push(version);
|
|
1257
1463
|
}
|
|
1258
|
-
const minSecretsVersion = secretVersions.length > 0 ? Math.max(...secretVersions) : undefined;
|
|
1259
1464
|
const env = desiredEnv(props, bound.env, hosted.alchemyEnv, port);
|
|
1465
|
+
// Redacted values (every bound `Config`) become App secrets, but
|
|
1466
|
+
// only in an App the Service owns: Fly gives every App secret to
|
|
1467
|
+
// every Machine in the App. A plain `env` entry wins over a binding.
|
|
1468
|
+
const redacted = Object.keys(env).filter((key) => props.env?.[key] !== undefined
|
|
1469
|
+
? Redacted.isRedacted(props.env[key])
|
|
1470
|
+
: bound.secretNames.includes(key));
|
|
1471
|
+
// Move them out of the plain env record before anything else reads it.
|
|
1472
|
+
const desiredSecrets = {};
|
|
1473
|
+
if (ownsApp) {
|
|
1474
|
+
for (const key of redacted) {
|
|
1475
|
+
desiredSecrets[key] = Redacted.make(env[key]);
|
|
1476
|
+
delete env[key];
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
const boundSecrets = yield* syncBoundSecrets(appName, fqn, desiredSecrets, output?.appName === appName
|
|
1480
|
+
? { secrets: output.secrets ?? {}, version: output.secretsVersion }
|
|
1481
|
+
: { secrets: {}, version: undefined });
|
|
1482
|
+
if (boundSecrets.version !== undefined)
|
|
1483
|
+
secretVersions.push(boundSecrets.version);
|
|
1484
|
+
const minSecretsVersion = secretVersions.length > 0 ? Math.max(...secretVersions) : undefined;
|
|
1485
|
+
const processes = secretMappings(boundSecrets);
|
|
1260
1486
|
if (policy.shutdown && !props.isExternal) {
|
|
1261
1487
|
env.ALCHEMY_FLY_SHUTDOWN_TIMEOUT_MS = String(policy.shutdown.timeoutMs);
|
|
1262
1488
|
}
|
|
@@ -1267,27 +1493,36 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1267
1493
|
const services = published.services;
|
|
1268
1494
|
if (!ownsApp)
|
|
1269
1495
|
yield* validateObservedPorts(appName, fqn, id, services);
|
|
1270
|
-
|
|
1271
|
-
|
|
1496
|
+
// The Apps of the Services that bind this one; only they may call
|
|
1497
|
+
// its methods (see `serveFlyRpc`).
|
|
1498
|
+
if (bound.callers.length > 0)
|
|
1499
|
+
env[RPC_CALLERS_ENV] = bound.callers.join(",");
|
|
1272
1500
|
for (const target of bound.targets) {
|
|
1273
1501
|
const keys = boundTargetEnvKeys(target.service);
|
|
1274
1502
|
env[keys.appName] = packEnvValue(target.appName);
|
|
1275
1503
|
if (target.privateUrl !== undefined)
|
|
1276
1504
|
env[keys.url] = packEnvValue(target.privateUrl);
|
|
1277
|
-
if (target.rpcToken !== undefined)
|
|
1278
|
-
env[keys.token] = packEnvValue(target.rpcToken);
|
|
1279
1505
|
}
|
|
1280
1506
|
env[RPC_ORG_ENV] = yield* resolveOrgSlug();
|
|
1281
1507
|
if (published.added)
|
|
1282
1508
|
env[BINDING_PORT_ENV] = String(bindingPort);
|
|
1283
1509
|
const statics = toFlyStatics(props.statics);
|
|
1284
|
-
const { imageRef, codeHash } =
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1510
|
+
const { imageRef, codeHash } = props.context !== undefined
|
|
1511
|
+
? yield* hosted.resolveContextImage({
|
|
1512
|
+
id,
|
|
1513
|
+
appName,
|
|
1514
|
+
context: props.context,
|
|
1515
|
+
remote: props.build?.remote,
|
|
1516
|
+
previousHash: output?.code.hash,
|
|
1517
|
+
session,
|
|
1518
|
+
})
|
|
1519
|
+
: yield* hosted.resolveImage({
|
|
1520
|
+
id,
|
|
1521
|
+
appName,
|
|
1522
|
+
props: props,
|
|
1523
|
+
previousHash: output?.code.hash,
|
|
1524
|
+
session,
|
|
1525
|
+
});
|
|
1291
1526
|
const set = yield* reconcileReplicas({
|
|
1292
1527
|
id,
|
|
1293
1528
|
type: "Fly.Service",
|
|
@@ -1312,20 +1547,32 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1312
1547
|
mounts: desired.mounts,
|
|
1313
1548
|
metadata: desired.metadata,
|
|
1314
1549
|
statics,
|
|
1550
|
+
processes,
|
|
1551
|
+
}),
|
|
1552
|
+
buildConfig: ({ mounts, metadata }) => buildConfig({
|
|
1553
|
+
image: imageRef,
|
|
1554
|
+
guest,
|
|
1555
|
+
env,
|
|
1556
|
+
services,
|
|
1557
|
+
mounts,
|
|
1558
|
+
metadata,
|
|
1559
|
+
statics,
|
|
1560
|
+
processes,
|
|
1315
1561
|
}),
|
|
1316
|
-
|
|
1317
|
-
}).pipe(Effect.catchTag("Fly.ReplicaNotCreated", (error) => Effect.fail(new ServiceNotCreated({ name: error.name, appName: error.appName }))));
|
|
1562
|
+
}).pipe(Effect.catchTag("Fly.ReplicaNotCreated", (error) => Effect.fail(new ServiceNotCreated({ resourceName: error.resourceName, appName: error.appName }))));
|
|
1318
1563
|
return toAttrs(set, codeHash, {
|
|
1319
1564
|
ownsApp,
|
|
1320
1565
|
isPublic,
|
|
1321
1566
|
network,
|
|
1322
1567
|
bindingPort,
|
|
1323
|
-
|
|
1568
|
+
secrets: boundSecrets.secrets,
|
|
1569
|
+
secretsVersion: boundSecrets.version,
|
|
1570
|
+
orgSlug: yield* resolveOrgSlug(),
|
|
1324
1571
|
});
|
|
1325
1572
|
}),
|
|
1326
|
-
// Create the App
|
|
1327
|
-
//
|
|
1328
|
-
//
|
|
1573
|
+
// Create the App and its addresses before any Machine, so two
|
|
1574
|
+
// Services that bind each other can both resolve the other's App
|
|
1575
|
+
// and private address.
|
|
1329
1576
|
precreate: Effect.fn(function* ({ id, news }) {
|
|
1330
1577
|
const props = news;
|
|
1331
1578
|
// Precreate runs before dependencies resolve. A Service placed in
|
|
@@ -1371,7 +1618,7 @@ export const ServiceProvider = () => Provider.effect(Service, Effect.gen(functio
|
|
|
1371
1618
|
isPublic,
|
|
1372
1619
|
network,
|
|
1373
1620
|
bindingPort,
|
|
1374
|
-
|
|
1621
|
+
orgSlug: yield* resolveOrgSlug(),
|
|
1375
1622
|
});
|
|
1376
1623
|
}),
|
|
1377
1624
|
delete: Effect.fn(function* ({ id, fqn, instanceId, olds, output, force }) {
|