Resolve container tags to immutable @sha256 digests via OCI API in ho… (#2749)
…st mode
When execution mode is 'host', container image tags (e.g. :latest) are
resolved to immutable @sha256 digests by querying the container
registry's OCI V2 manifest API (HTTP header docker-content-digest). This
prevents remote build failures on RBE execution servers that reject
unpinned container image tags.
diff --git a/rules/rbe_config.bzl b/rules/rbe_config.bzl
index 24935ad..3122830 100644
--- a/rules/rbe_config.bzl
+++ b/rules/rbe_config.bzl
@@ -103,6 +103,47 @@
repository_ctx.extract(archive = output_tarball)
+def _resolve_container_digest(repository_ctx, container_image):
+ """Resolves a container image tag (e.g. :latest) to an immutable @sha256 digest via OCI manifest API."""
+ if "@sha256:" in container_image:
+ return container_image
+
+ parts = container_image.split(":")
+ if len(parts) != 2:
+ fail("rbe_config: Host mode requires container image '{}' to be a valid tag or @sha256 digest, but could not parse tag.".format(container_image))
+
+ image_name = parts[0]
+ tag = parts[1]
+ slash_idx = image_name.find("/")
+ if slash_idx == -1:
+ fail("rbe_config: Host mode requires container image '{}' to include registry and repository (e.g. gcr.io/repo/image:tag).".format(container_image))
+
+ registry = image_name[:slash_idx]
+ repo = image_name[slash_idx + 1:]
+ url = "https://{}/v2/{}/manifests/{}".format(registry, repo, tag)
+
+ res = repository_ctx.execute([
+ "curl", "-sI",
+ "-H", "Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json",
+ url,
+ ])
+ if res.return_code == 0:
+ for line in res.stdout.splitlines():
+ if line.lower().startswith("docker-content-digest:"):
+ digest = line.split(":", 1)[1].strip()
+ if digest.startswith("sha256:"):
+ resolved = "{}@{}".format(image_name, digest)
+ print("rbe_config: Resolved host container '{}' to digest '{}' via OCI API.".format(container_image, resolved))
+ return resolved
+
+ fail("rbe_config: Failed to resolve '@sha256' digest for container image '{}' via OCI Manifest API ({}):\nStdout: {}\nStderr: {}".format(
+ container_image,
+ url,
+ res.stdout,
+ res.stderr,
+ ))
+
+
# --- Private Repository Rule Entrypoint ---
def _rbe_config_impl(repository_ctx):
# 1. Resolve presets/custom image container and environment
@@ -117,7 +158,7 @@
host_container = repository_ctx.os.environ.get("RBE_CONFIG_CONTAINER")
if host_container:
exec_mode = "host"
- container_image = host_container
+ container_image = _resolve_container_digest(repository_ctx, host_container)
if preset_container and preset_container != host_container:
print("rbe_config: RBE_CONFIG_CONTAINER ('{}') overrides requested preset container ('{}') in host mode.".format(host_container, preset_container))
else: