Skip to content
Source
Sign inSign up

floci-container.ts

72 lines2.7 KB

1/**
2 * Start the managed Floci container with the settings `test:aws:floci`
3 * suites rely on, before any test file runs.
4 *
5 * `ensureFloci` reuses a running container when its image and requested
6 * ports match; it does not compare environment settings. A container started
7 * earlier without these settings (e.g. by `alchemy dev`) is therefore removed
8 * first, so the suites never run against an emulator configured differently
9 * from the one they were validated on.
10 */
11import * as Floci from "@alchemy.run/aws/Local/Floci";
12import * as Console from "effect/Console";
13import * as Effect from "effect/Effect";
14import { ChildProcess } from "effect/process";
15import { ChildProcessSpawner } from "effect/process/ChildProcessSpawner";
16​
17/** Emulator settings the suites assume, mirroring a real AWS account. */
18export const flociTestEnv: Readonly<Record<string, string>> = {
19 // Real S3 evaluates IAM; the denied-read suites assert that behavior.
20 FLOCI_SERVICES_S3_ENFORCE_AUTH: "true",
21 // Every real AWS account has a primary contact.
22 FLOCI_SERVICES_ACCOUNT_BOOTSTRAP_CONTACT_INFORMATION: JSON.stringify({
23 FullName: "Local AWS account",
24 AddressLine1: "1 Example Street",
25 City: "Seattle",
26 StateOrRegion: "WA",
27 PostalCode: "98101",
28 CountryCode: "US",
29 PhoneNumber: "+12025550100",
30 }),
31};
32​
33/** The running container's environment, or undefined when there is none. */
34const containerEnv = Effect.fn(function* (name: string) {
35 const spawner = yield* ChildProcessSpawner;
36 const output = yield* spawner
37 .string(
38 ChildProcess.make("docker", ["inspect", "--format", "{{json .Config.Env}}", name], {
39 stdin: "ignore",
40 stderr: "ignore",
41 }),
42 )
43 .pipe(Effect.option);
44 if (output._tag === "None") return undefined;
45 return yield* Effect.try(() => new Set(JSON.parse(output.value) as string[])).pipe(
46 Effect.orElseSucceed(() => undefined),
47 );
48});
49​
50export const startFlociForTests = Effect.fn(function* (label: string) {
51 const spawner = yield* ChildProcessSpawner;
52 const name = Floci.DEFAULT_CONTAINER_NAME;
53 const env = yield* containerEnv(name);
54 const missing = Object.entries(flociTestEnv).filter(
55 ([key, value]) => !env?.has(`${key}=${value}`),
56 );
57 if (env !== undefined && missing.length > 0) {
58 yield* Console.log(
59 `${label}: recreating ${name}; it lacks ${missing.map(([key]) => key).join(", ")}`,
60 );
61 yield* spawner
62 .exitCode(
63 ChildProcess.make("docker", ["rm", "-f", name], {
64 stdin: "ignore",
65 stdout: "ignore",
66 stderr: "ignore",
67 }),
68 )
69 .pipe(Effect.ignore);
70 }
71 yield* Floci.ensureFloci({ port: Floci.DEFAULT_FLOCI_PORT, env: { ...flociTestEnv } });
72});