From 98184065e2e38e95faf5a056d4e9b1f651f2896e Mon Sep 17 00:00:00 2001 From: ignormies Date: Wed, 9 Sep 2026 14:08:05 +0200 Subject: [PATCH] Fix 'ocitool push' for `oci_image`s based on other `oci_images` There's been a bug in the `OCILayout` implementation where it did not contain the correct metadata for pushing an `oci_image` where the base is another `oci_image`. The way `rules_oci` works is it build each blob in the bazel build dir, and then creates a blob -> path mapping called `blob_index` that is used at push time. The previous implementation naively assumed that an image would only ever have one `blob_index` map, but this isn't the case. Every image has one for itself and its base(s). This hasn't been an issue because the push operation short-circuits: if a blob is already in the registry, it doesn't try to push the blob again. If it doesn't try to push the blob, it doesn't try to find the blob in the `blob_index`. This was fine because until now all `base`s for images we had been using were "remote" ones (i.e. already pushed to the registry). But I'm trying to create an `oci_image` target that bases itself on another in-repo `oci_image` target. In this case, the base's blobs _aren't_ in the registry, the push operation tries to push them, and then freaks out because the `blob_index` doesn't have the metadata. This PR fixes this so that `blob_index` is turned into `direct_blob_index` (for the current image) and `transitive_blob_indices`, a superset that also includes the index/es for the base. These indices are then _all_ provided to the push tool, we can properly find the blobs at push time. --- oci/config.bzl | 4 ++-- oci/image.bzl | 20 +++++++++++++++----- oci/layout.bzl | 4 +++- oci/oci_image_layout.bzl | 4 ++-- oci/providers.bzl | 6 ++++-- oci/push.bzl | 11 ++++++++--- 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/oci/config.bzl b/oci/config.bzl index 846779c..0a88c1d 100644 --- a/oci/config.bzl +++ b/oci/config.bzl @@ -29,7 +29,7 @@ def generate_config_file_action(ctx, config_file, image, os, arch): ctx.actions.run( executable = toolchain.sdk.ocitool, arguments = [ - "--layout={}".format(base_layout.blob_index.path), + "--layout={}".format(base_layout.direct_blob_index.path), "config", "--base={}".format(base_desc.path), "--os={}".format(os), @@ -39,7 +39,7 @@ def generate_config_file_action(ctx, config_file, image, os, arch): mnemonic = "OCIImageConfig", inputs = [ base_desc, - base_layout.blob_index, + base_layout.direct_blob_index, ] + base_layout.files.to_list(), outputs = [ config_file, diff --git a/oci/image.bzl b/oci/image.bzl index 5f14bc5..d2097d7 100755 --- a/oci/image.bzl +++ b/oci/image.bzl @@ -109,7 +109,7 @@ def _oci_image_index_impl(ctx): ctx.actions.run( executable = toolchain.sdk.ocitool, - arguments = ["--layout={}".format(m[OCILayout].blob_index.path) for m in ctx.attr.manifests] + + arguments = ["--layout={}".format(m[OCILayout].direct_blob_index.path) for m in ctx.attr.manifests] + [ "create-index", "--out-index={}".format(index_file.path), @@ -128,7 +128,12 @@ def _oci_image_index_impl(ctx): descriptor_file = index_desc_file, ), OCILayout( - blob_index = layout_file, + direct_blob_index = layout_file, + index = index_file, + transitive_blob_indices = depset( + direct = [layout_file], + transitive = [m[OCILayout].transitive_blob_indices for m in ctx.attr.manifests], + ), files = depset(direct = [index_file, layout_file], transitive = [layout_files]), ), DefaultInfo( @@ -189,7 +194,7 @@ def _oci_image_impl(ctx): stamp_args.append("--bazel-version-file={}".format(ctx.version_file.path)) arguments = [ - "--layout={}".format(base_layout.blob_index.path), + "--layout={}".format(base_layout.direct_blob_index.path), "append-layers", "--base={}".format(base_desc.path), "--os={}".format(ctx.attr.os), @@ -225,7 +230,7 @@ def _oci_image_impl(ctx): inputs = [ ctx.version_file, base_desc, - base_layout.blob_index, + base_layout.direct_blob_index, ] + ctx.files.layers + layer_descriptor_files + base_layout.files.to_list() + tars if ctx.attr.cmd_override: @@ -272,7 +277,12 @@ def _oci_image_impl(ctx): descriptor_file = manifest_desc_file, ), OCILayout( - blob_index = layout_file, + direct_blob_index = layout_file, + index = None, + transitive_blob_indices = depset( + direct = [layout_file], + transitive = [base_layout.transitive_blob_indices], + ), files = depset( ctx.files.layers + ctx.files.tars + [ manifest_file, diff --git a/oci/layout.bzl b/oci/layout.bzl index 06f62c4..8865702 100755 --- a/oci/layout.bzl +++ b/oci/layout.bzl @@ -23,7 +23,9 @@ def _oci_layout_index_impl(ctx): return [ OCILayout( - blob_index = ctx.outputs.json, + direct_blob_index = ctx.outputs.json, + index = None, + transitive_blob_indices = depset([ctx.outputs.json]), files = depset(all_files), ), ] diff --git a/oci/oci_image_layout.bzl b/oci/oci_image_layout.bzl index 4228d65..40c342b 100644 --- a/oci/oci_image_layout.bzl +++ b/oci/oci_image_layout.bzl @@ -17,7 +17,7 @@ def _oci_image_layout_impl(ctx): ctx.actions.run( executable = toolchain.sdk.ocitool, arguments = [ - "--layout={layout}".format(layout = layout.blob_index.path), + "--layout={layout}".format(layout = layout.direct_blob_index.path), "--debug={debug}".format(debug = str(ctx.attr._debug[DebugInfo].debug)), "create-oci-image-layout", # We need to use the directory one level above bazel-out for the @@ -33,7 +33,7 @@ def _oci_image_layout_impl(ctx): ], inputs = depset( - direct = ctx.files.manifest + [layout.blob_index], + direct = ctx.files.manifest + [layout.direct_blob_index], transitive = [layout.files], ), outputs = [ diff --git a/oci/providers.bzl b/oci/providers.bzl index 7b992cd..a79db56 100755 --- a/oci/providers.bzl +++ b/oci/providers.bzl @@ -15,8 +15,10 @@ OCIReferenceInfo = provider( OCILayout = provider( "OCI Layout", fields = { - "blob_index": "", - "files": "", + "direct_blob_index": "The layout file produced by this rule mapping digest to on-disk path.", + "index": "The index JSON file produced by this rule. None for single-platform images.", + "transitive_blob_indices": "Depset of all layout files in the transitive base chain, including direct_blob_index.", + "files": "Depset of all files (blobs, manifests, configs, layouts) in the transitive base chain.", }, ) diff --git a/oci/push.bzl b/oci/push.bzl index 30eb202..f739ec7 100755 --- a/oci/push.bzl +++ b/oci/push.bzl @@ -89,11 +89,16 @@ done for k, v in ctx.attr.x_meta_headers.items(): xheaders = xheaders + " --x_meta_headers={}={}".format(k, v) + layout_flags = " \\\n ".join([ + "--layout {}".format(f.short_path) + for f in layout.transitive_blob_indices.to_list() + ]) + ctx.actions.write( content = """#!/usr/bin/env bash set -euo pipefail {tool} \\ - --layout {layout} \\ + {layout_flags} \\ --debug={debug} \\ push \\ --layout-relative {root} \\ @@ -107,7 +112,7 @@ done """.format( root = ctx.bin_dir.path, tool = toolchain.sdk.ocitool.short_path, - layout = layout.blob_index.short_path, + layout_flags = layout_flags, desc = ctx.attr.manifest[OCIDescriptor].descriptor_file.short_path, ref = ref, debug = str(ctx.attr._debug[DebugInfo].debug), @@ -124,7 +129,7 @@ done DefaultInfo( runfiles = ctx.runfiles( files = layout.files.to_list() + - [toolchain.sdk.ocitool, ctx.attr.manifest[OCIDescriptor].descriptor_file, layout.blob_index, digest_file, tag_file], + [toolchain.sdk.ocitool, ctx.attr.manifest[OCIDescriptor].descriptor_file, digest_file, tag_file], ), ), OCIReferenceInfo(