/** * Cloud runner sizes. Each size is its own Container-backed Durable Object class because a Container's * instance type is fixed per class; larger sizes are charged proportionally more Container minutes. * * Every size has a Docker-in-Docker twin on the same instance type, built from `runner/Dockerfile.dind`. Jobs * that use Docker actions move to the twin at compile time (`withDockerRunner`); everything else stays on the * lighter image. A twin bills exactly like its base size: same instance type, same weight, same meter. */ export type RunnerBinding = | "RUNNER" | "RUNNER_LARGE" | "RUNNER_XLARGE" | "RUNNER_DIND" | "RUNNER_LARGE_DIND" | "RUNNER_XLARGE_DIND"; type BaseBinding = "RUNNER" | "RUNNER_LARGE" | "RUNNER_XLARGE"; type RunnerSize = { binding: RunnerBinding; /** The size this one bills as (itself for base sizes). */ base: BaseBinding; labels: string[]; weight: number; docker: boolean; description: string; }; const BASE_SIZES: Array<{ binding: BaseBinding; labels: string[]; weight: number; description: string }> = [ { binding: "RUNNER", labels: ["cloudflare", "ubuntu-latest"], weight: 1, description: "standard-1: 1/2 vCPU, 4 GiB" }, { binding: "RUNNER_LARGE", labels: ["cloudflare-large", "ubuntu-latest-4-cores"], weight: 2, description: "standard-2: 1 vCPU, 6 GiB", }, { binding: "RUNNER_XLARGE", labels: ["cloudflare-xlarge", "ubuntu-latest-8-cores"], weight: 4, description: "standard-4: 4 vCPU, 12 GiB", }, ]; export const DOCKER_LABEL_SUFFIX = "-docker"; export const RUNNER_SIZES: RunnerSize[] = BASE_SIZES.flatMap((size): RunnerSize[] => [ { ...size, base: size.binding, docker: false }, { ...size, binding: `${size.binding}_DIND`, base: size.binding, labels: [`${size.labels[0]}${DOCKER_LABEL_SUFFIX}`], docker: true, description: `${size.description}, with Docker`, }, ]); export function cloudRunnerSize(runsOn: string[]): RunnerSize | null { if (runsOn.length !== 1) return null; return RUNNER_SIZES.find((size) => size.labels.includes(runsOn[0])) ?? null; } /** The runs-on of the Docker-capable twin of a cloud job's size; self-hosted jobs are left alone. */ export function withDockerRunner(runsOn: string[]): string[] { const size = cloudRunnerSize(runsOn); if (!size || size.docker) return runsOn; const twin = RUNNER_SIZES.find((candidate) => candidate.docker && candidate.base === size.base); return twin ? [twin.labels[0]] : runsOn; } /** The serialized `workflow_jobs.runs_on` values that run on cloud Containers. */ export const CLOUD_RUNS_ON_VALUES = RUNNER_SIZES.flatMap((size) => size.labels.map((label) => JSON.stringify([label]))); /** SQL expression weighting a job's Container minutes by its runner size (`column` is the runs_on column). */ export function runnerWeightSql(column: string): string { const cases = RUNNER_SIZES.filter((size) => size.weight !== 1) .flatMap((size) => size.labels.map((label) => `WHEN '${JSON.stringify([label])}' THEN ${size.weight}`)) .join(" "); return `(CASE ${column} ${cases} ELSE 1 END)`; } export function cloudRunsOnPlaceholders(firstIndex: number): string { return CLOUD_RUNS_ON_VALUES.map((_, index) => `?${firstIndex + index}`).join(", "); }