Skip to content

feat(aws-lambda): push a prebuilt image to ECR without Docker - #6063

Open
miguel-heygen wants to merge 4 commits into
mainfrom
feat/lambda-ecr-push
Open

miguel-heygen wants to merge 4 commits into
mainfrom
feat/lambda-ecr-push

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

Adds two functions to @hyperframes/aws-lambda/sdk:

  • ensureEcrRepository({ ecr, repositoryName, tags? }) creates the ECR repository, or returns the existing one's URI when it already exists.
  • pushImageToEcr({ ecr, layoutDir, repositoryName, repositoryUri, tag, log? }) pushes a prebuilt single-platform OCI image layout (a directory) into that repository and returns { imageUri, digest, architecture, os }, where imageUri is <repositoryUri>@sha256:<manifest digest>.

Adds the @aws-sdk/client-ecr dependency on the same ^3.1146.0 line as the other AWS SDK clients. No CLI wiring yet; callers come in a follow-up.

Why

The arm64 video function will ship as a container image, and Lambda only runs images from the account's own private ECR. Users of the released CLI should not need Docker to get it there. The release will publish the image as an OCI layout; this function moves that layout into the user's ECR with the ECR API alone (no Docker, no registry HTTP calls).

Related work

First piece of the container-image deploy path. Nothing else touches ECR yet.

How

  • index.json must list exactly one image manifest (OCI or Docker v2 schema 2). An image index, attestation manifests, or zero or several manifests throw an error naming what was found.
  • Every blob (manifest, config, layers) is checked for size and streamed sha256 against its descriptor before any ECR call. A corrupt layout fails with nothing written.
  • The image config must name architecture and os; both are returned so the caller can assert the platform it expects.
  • BatchCheckLayerAvailability in batches of at most 100 digests; only blobs not AVAILABLE are uploaded: InitiateLayerUpload, parts of exactly partSize bytes read from disk one part at a time (inclusive partFirstByte/partLastByte), then CompleteLayerUpload. LayerAlreadyExistsException counts as uploaded.
  • PutImage sends the manifest's exact bytes, its media type, the tag and the digest. ImageAlreadyExistsException counts as success, so a redeploy of the same image is a no-op.
  • Uploads are sequential for now; bounded parallel layer uploads are the upgrade if push time on large layers matters.
  • The Lambda handler bundle does not import the SDK subpath, so the new dependency stays out of the handler zip.
  • bun.lock: besides the new entry, bun install refreshed the stale workspace version fields and dropped an unused date-fns entry.

Test plan

  • Unit tests added: pushImageToEcr.test.ts runs the real code path against an in-memory ECR that behaves like ECR where it matters. It tracks uploads per upload id, rejects out-of-order or non-contiguous parts, a wrong partLastByte, or a short non-final part, re-hashes the assembled bytes on CompleteLayerUpload, rejects PutImage when a referenced blob is missing, and caps availability checks at 100 digests. The fixture layout is built in a temp dir: one config, a 21-byte layer and a 17-byte layer, with a 7-byte part size.
    • bun test src/sdk/pushImageToEcr.test.ts: exit 0, 11 pass / 0 fail, three runs in a row.
    • bun test src/handler.test.ts: exit 0, 31 pass / 0 fail.
    • tsc --noEmit in packages/aws-lambda: exit 0. oxlint: 0 warnings, 0 errors. oxfmt --check: clean.
    • Each guarded behaviour was broken once to check that its test catches it (then reverted):
      • partLastByte off by one: the 5 tests that upload went red.
      • Skipping the pre-upload digest check: the tampered-blob test went red.
      • Raising the check batch size past 100: the batching test went red.
      • Rethrowing LayerAlreadyExists, ImageAlreadyExists or RepositoryAlreadyExists: in each case only the matching test went red.
      • Dropping the one-manifest check: the two-manifest test went red.
      • Dropping the architecture/os check: the config test went red.
      • Ignoring availability: the skip and redeploy tests went red.
  • Manual testing performed. Not exercised: no call to a real ECR, and Lambda pulling the pushed image is proven in a later PR.
  • Comments follow CONTRIBUTING.md "Comments"

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 159 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (1)

  • seqplainkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

Adds pushImageToEcr and ensureEcrRepository to the SDK subpath. The push
reads a single-platform OCI image layout, verifies every blob against its
descriptor before any ECR call, uploads only the blobs ECR lacks through
the layer-upload API, and puts the manifest under a tag, returning the
digest-pinned image URI.
Like the other AWS SDK clients, @aws-sdk/client-ecr is a dependency the consumer
resolves. Bundled into the ESM SDK it hits a dynamic require of node:https and
the packed package fails to import.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant