281 lines · 12.8 KB
import { env } from "cloudflare:workers";
import { toHex } from "../../crypto";
import { guardedFetch } from "../../git/relay";
import { ActionSourceError } from "./github";

/**
 * Registry images for Docker actions. A tag is resolved to a manifest digest when the run is scheduled, so a
 * moving tag can't change what a run executes. Blobs are pulled through the forge on first use and kept in
 * R2 under `ci-images/` (content-addressed, sha256-checked by R2 on write, swept 30 days after upload), shared
 * by every account like action archives: public image bytes are identical for everyone and a digest can only
 * ever name one content.
 */
export const IMAGE_PREFIX = "ci-images/blobs/sha256/";
export const IMAGE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
const MAX_IMAGE_BYTES = 2 * 1024 ** 3;
const MAX_LAYERS = 128;
const MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
const TAG_CACHE_SECONDS = 300;
const TOKEN_CACHE_SECONDS = 240;
const FETCH_TIMEOUT_MS = 30_000;
const DOCKER_HUB = "registry-1.docker.io";
const HTTP_UNAUTHORIZED = 401;
const DIGEST = /^sha256:([0-9a-f]{64})$/;
const REPOSITORY = /^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*$/;
const TAG = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
const HOST = /^[A-Za-z0-9.-]+(?::\d{1,5})?$/;
const INDEX_TYPES = new Set([
  "application/vnd.oci.image.index.v1+json",
  "application/vnd.docker.distribution.manifest.list.v2+json",
]);
const MANIFEST_TYPES = new Set([
  "application/vnd.oci.image.manifest.v1+json",
  "application/vnd.docker.distribution.manifest.v2+json",
]);
const ACCEPT = [...INDEX_TYPES, ...MANIFEST_TYPES].join(", ");

export type ImageRef = { registry: string; repository: string; reference: string };
export type BlobDescriptor = { digest: string; size: number; mediaType: string };

/** A registry image pinned to one linux/amd64 manifest. */