| 1 | /** |
| 2 | * Inline Dockerfile content for container platforms. |
| 3 | * |
| 4 | * Container platform props accept `dockerfile: string | { content: Input<string> }`: |
| 5 | * a plain string is always a **path** (relative to `context`); an object with a |
| 6 | * `content` key is **inline Dockerfile content**. `Dockerfile.inline` is the |
| 7 | * ergonomic way to produce the latter — a tagged template whose interpolations |
| 8 | * ride the normal {@link Output} machinery, so referencing another resource's |
| 9 | * attributes (e.g. an {@link ECR.Image}'s `imageUri`) creates a real dependency |
| 10 | * edge and re-resolves on change. |
| 11 | * |
| 12 | * The returned value is a plain JSON-serializable object (no symbols or |
| 13 | * brands) so it round-trips through persisted state unchanged; the structural |
| 14 | * `content` key is the discriminant. |
| 15 | * |
| 16 | * @example Base plus system packages, still bundling your Effect program |
| 17 | * ```typescript |
| 18 | * import * as Dockerfile from "alchemy/Docker/Dockerfile"; |
| 19 | * |
| 20 | * const worker = yield* AWS.ECS.Task( |
| 21 | * "Transcoder", |
| 22 | * { |
| 23 | * cluster, |
| 24 | * main: import.meta.url, |
| 25 | * dockerfile: Dockerfile.inline` |
| 26 | * FROM oven/bun:1 |
| 27 | * RUN apt-get update && apt-get install -y ffmpeg |
| 28 | * `, |
| 29 | * }, |
| 30 | * impl, |
| 31 | * ); |
| 32 | * ``` |
| 33 | * |
| 34 | * @example Deriving from another resource's image (dependency edge for free) |
| 35 | * ```typescript |
| 36 | * const base = yield* AWS.ECR.Image("MlBase", { context: "./ml-base" }); |
| 37 | * |
| 38 | * const trainer = yield* AWS.ECS.Task( |
| 39 | * "Trainer", |
| 40 | * { |
| 41 | * cluster, |
| 42 | * main: import.meta.url, |
| 43 | * dockerfile: Dockerfile.inline` |
| 44 | * FROM ${base.imageUri} |
| 45 | * RUN pip install -r /opt/requirements.txt |
| 46 | * `, |
| 47 | * }, |
| 48 | * impl, |
| 49 | * ); |
| 50 | * ``` |
| 51 | */ |
| 52 | import type { Input } from "../Input.ts"; |
| 53 | import * as Output from "../Output.ts"; |
| 54 | |
| 55 | /** |
| 56 | * Inline Dockerfile content. A plain, state-serializable object; the |
| 57 | * structural `content` key distinguishes it from a path string wherever |
| 58 | * `dockerfile` props accept `string | InlineDockerfile`. |
| 59 | * |
| 60 | * Never interpolate secrets: anything in Dockerfile content is baked into |
| 61 | * image layers. `Input<string>` excludes `Redacted` values at the type level. |
| 62 | */ |
| 63 | export interface InlineDockerfile { |
| 64 | readonly content: Input<string>; |
| 65 | } |
| 66 | |
| 67 | /** Structural guard for {@link InlineDockerfile} vs a path string. */ |
| 68 | export const isInlineDockerfile = (value: unknown): value is InlineDockerfile => |
| 69 | typeof value === "object" && value !== null && "content" in value; |
| 70 | |
| 71 | /** |
| 72 | * Tagged template producing {@link InlineDockerfile}. Interpolations may be |
| 73 | * plain strings or `Output<string>`s (resolved at deploy time via |
| 74 | * `Output.interpolate`); with no interpolations the content is a plain string. |
| 75 | */ |
| 76 | export const inline = ( |
| 77 | template: TemplateStringsArray, |
| 78 | ...args: Array<Input<string>> |
| 79 | ): InlineDockerfile => ({ |
| 80 | content: |
| 81 | args.length === 0 |
| 82 | ? template.raw.join("") |
| 83 | : Output.interpolate(template, ...args), |
| 84 | }); |