diff --git a/renderers/docker-compose.ts b/renderers/docker-compose.ts index 0bf07e4..a36b435 100644 --- a/renderers/docker-compose.ts +++ b/renderers/docker-compose.ts @@ -1,5 +1,5 @@ import { stringify } from "jsr:@std/yaml@^1.0.5"; -import { lookup, makeCtx } from "../utils/resolve.ts"; +import { buildContainerFor, makeCtx } from "../utils/resolve.ts"; import { portNum, portProtocol } from "../utils/types.ts"; import type { Ctx, @@ -130,11 +130,7 @@ function addPod( volumes: Record, files: RenderResult["files"], ) { - const builds = pod.containers.map((def) => { - const proto = lookup(def.prototype); - if (!proto.buildContainer) throw new Error(`container ${def.name} has no buildContainer()`); - return { def, built: proto.buildContainer(def, ctx) }; - }); + const builds = pod.containers.map((def) => ({ def, built: buildContainerFor(def, ctx) })); const leader = builds[0]; const podVols = new Map(); diff --git a/renderers/k8s.ts b/renderers/k8s.ts index c889ed9..e1d8344 100644 --- a/renderers/k8s.ts +++ b/renderers/k8s.ts @@ -1,6 +1,6 @@ import { stringify } from "jsr:@std/yaml@^1.0.5"; import { imageTag } from "../utils/image-build.ts"; -import { lookup, makeCtx } from "../utils/resolve.ts"; +import { buildContainerFor, makeCtx } from "../utils/resolve.ts"; import { portInService, portNum, portProtocol } from "../utils/types.ts"; import type { ConfigFile, @@ -86,11 +86,7 @@ function podDocs(pod: Pod, ctx: Ctx, imageBuilds: Map): }; const matchLabels = { "app.kubernetes.io/name": pod.name }; - const builds: ContainerBuild[] = pod.containers.map((def) => { - const proto = lookup(def.prototype); - if (!proto.buildContainer) throw new Error(`container ${def.name} has no buildContainer()`); - return { def, built: proto.buildContainer(def, ctx) }; - }); + const builds: ContainerBuild[] = pod.containers.map((def) => ({ def, built: buildContainerFor(def, ctx) })); const volMap = new Map(); for (const { built } of builds) { diff --git a/renderers/podman.ts b/renderers/podman.ts index f6df84b..0a4bc2c 100644 --- a/renderers/podman.ts +++ b/renderers/podman.ts @@ -1,5 +1,5 @@ import { stringify } from "jsr:@std/yaml@^1.0.5"; -import { findComponent, lookup, makeCtx } from "../utils/resolve.ts"; +import { buildContainerFor, findComponent, makeCtx } from "../utils/resolve.ts"; import { portNum, portProtocol } from "../utils/types.ts"; import type { ConfigFile, @@ -118,9 +118,7 @@ function podDocs(pod: Pod, recipe: Recipe, ctx: Ctx, imageBuilds: Map [v.name, v])); diff --git a/utils/resolve.ts b/utils/resolve.ts index 25d8015..478996d 100644 --- a/utils/resolve.ts +++ b/utils/resolve.ts @@ -1,6 +1,6 @@ import { isAbsolute, join } from "jsr:@std/path@^1.0.0"; import { portNum } from "./types.ts"; -import type { ContainerDef, Ctx, HostCtx, Pod, ProcessDef, Prototype, PrototypeOverrides, Recipe } from "./types.ts"; +import type { ContainerDef, ContainerResult, Ctx, HostCtx, Pod, ProcessDef, Prototype, PrototypeOverrides, Recipe } from "./types.ts"; import { DECKER_ROOT } from "./root.ts"; @@ -104,3 +104,19 @@ export function makeHostCtx( ): HostCtx { return { ...makeCtx(recipe, host), artifactsPath, dataPath, configPath, binary }; } + +// Build one container through its prototype, then apply the per-container +// overrides every prototype supports without special-casing them: +// config.image - replace the prototype's pinned image (a version bump, a +// dev build) from the recipe or an `--opt`, without editing +// the container file. Defaults stay in the prototypes. +// The renderers all build containers through here, so this is the one place +// such overrides live. +export function buildContainerFor(def: ContainerDef, ctx: Ctx): ContainerResult { + const proto = lookup(def.prototype); + if (!proto.buildContainer) throw new Error(`container ${def.name} has no buildContainer()`); + const built = proto.buildContainer(def, ctx); + const image = def.config?.image; + if (typeof image === "string" && image !== "") built.container.image = image; + return built; +}