diff --git a/oci/BUILD.bazel b/oci/BUILD.bazel index 6443160..eff474d 100755 --- a/oci/BUILD.bazel +++ b/oci/BUILD.bazel @@ -38,6 +38,7 @@ bzl_library( visibility = ["//visibility:public"], deps = [ ":image", + ":oci_image_layout", ":pull", ":push", ], @@ -64,6 +65,16 @@ bzl_library( deps = ["@com_github_datadog_rules_oci//oci:providers"], ) +bzl_library( + name = "oci_image_layout", + srcs = ["oci_image_layout.bzl"], + visibility = ["//visibility:public"], + deps = [ + "@com_github_datadog_rules_oci//oci:debug_flag", + "@com_github_datadog_rules_oci//oci:providers", + ], +) + bzl_library( name = "push", srcs = ["push.bzl"], diff --git a/oci/defs.bzl b/oci/defs.bzl index 306f5eb..a57d314 100755 --- a/oci/defs.bzl +++ b/oci/defs.bzl @@ -1,6 +1,7 @@ load(":pull.bzl", _oci_pull = "oci_pull") load(":push.bzl", _oci_push = "oci_push") load(":image.bzl", _oci_image = "oci_image", _oci_image_index = "oci_image_index", _oci_image_layer = "oci_image_layer") +load(":oci_image_layout.bzl", _oci_image_layout = "oci_image_layout") oci_pull = _oci_pull oci_push = _oci_push @@ -8,3 +9,4 @@ oci_push = _oci_push oci_image = _oci_image oci_image_index = _oci_image_index oci_image_layer = _oci_image_layer +oci_image_layout = _oci_image_layout diff --git a/oci/image.bzl b/oci/image.bzl index 1d2febf..846fa43 100755 --- a/oci/image.bzl +++ b/oci/image.bzl @@ -1,4 +1,4 @@ -load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCILayout") +load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCIImageLayoutInfo", "OCILayout") def get_descriptor_file(ctx, desc): if hasattr(desc, "descriptor_file"): @@ -45,6 +45,7 @@ def _oci_image_layer_impl(ctx): return [ OCIDescriptor( descriptor_file = descriptor_file, + file = ctx.outputs.layer, ), ] @@ -107,6 +108,8 @@ def _oci_image_index_impl(ctx): outputs = outputs, ) + oci_layouts = [m[OCIImageLayoutInfo].oci_image_layout_dirs for m in ctx.attr.manifests] + return [ OCIDescriptor( descriptor_file = index_desc_file, @@ -115,6 +118,10 @@ def _oci_image_index_impl(ctx): blob_index = layout_file, files = depset(direct = [index_file, layout_file], transitive = [layout_files]), ), + # Pass through any OCIImageLayoutInfo data from the manifests. + OCIImageLayoutInfo( + oci_image_layout_dirs = depset(transitive = oci_layouts) + ), DefaultInfo( files = depset(outputs), ), @@ -213,6 +220,9 @@ def _oci_image_impl(ctx): blob_index = layout_file, files = depset(ctx.files.layers + [manifest_file, config_file, layout_file]), ), + OCIImageLayoutInfo( + oci_image_layout_dirs = depset(ctx.files.pulled_base) if ctx.attr.pulled_base != None else [], + ), DefaultInfo( files = depset([ entrypoint_config_file, @@ -238,6 +248,13 @@ oci_image = rule( OCILayout, ], ), + "pulled_base": attr.label( + doc = """A directory that contains the base image in OCI Image Layout format. + See https://github.com/opencontainers/image-spec/blob/main/image-layout.md for a description + of the OCI Image Layout format. This is optional, and if present, is passed through as an output of oci_image, + by the OCIImageLayoutInfo provider.""", + allow_single_file = True, + ), "entrypoint": attr.string_list( doc = """A list of entrypoints for the image; these will be inserted into the generated OCI image config""", diff --git a/oci/oci_image_layout.bzl b/oci/oci_image_layout.bzl new file mode 100644 index 0000000..3b9e96f --- /dev/null +++ b/oci/oci_image_layout.bzl @@ -0,0 +1,85 @@ +load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCIImageLayoutInfo", "OCILayout") +load("@com_github_datadog_rules_oci//oci:debug_flag.bzl", "DebugInfo") + +def _oci_image_layout_impl(ctx): + toolchain = ctx.toolchains["@com_github_datadog_rules_oci//oci:toolchain"] + + layout = ctx.attr.manifest[OCILayout] + base_layouts = ctx.attr.manifest[OCIImageLayoutInfo] + base_layout_dirs = "" + if base_layouts != None: + base_layouts = base_layouts.oci_image_layout_dirs.to_list() + base_layout_dirs = ",".join([p.path for p in base_layouts]) + + + descriptor = ctx.attr.manifest[OCIDescriptor] + out_dir = ctx.actions.declare_directory(ctx.label.name) + + ctx.actions.run( + executable = toolchain.sdk.ocitool, + arguments = [ + "--layout={layout}".format(layout = layout.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 + # layout-relative directory. This is because the paths in + # oci_image_index's index.layout.json are of the form: + # "bazel-out/os_arch-fastbuild/bin/...". Unfortunately, bazel + # provides no direct way to access this directory, so here we traverse + # up 3 levels from the bin directory. + "--layout-relative={root}".format(root = ctx.bin_dir.path+"/../../../"), + "--desc={desc}".format(desc = descriptor.descriptor_file.path), + "--base-image-layouts={base_layouts}".format(base_layouts = base_layout_dirs), + "--out-dir={out_dir}".format(out_dir = out_dir.path), + ], + inputs = ctx.files.manifest, + outputs = [ + out_dir, + ], + use_default_shell_env = True, + ) + + return DefaultInfo(files = depset([out_dir])) + +oci_image_layout = rule( + doc = """ + Writes an OCI Image Index and related blobs to an OCI Image Format + directory. See https://github.com/opencontainers/image-spec/blob/main/image-layout.md + for the specification of the OCI Image Format directory. Local blobs are + used where available, and if a referenced blob is not present, it is + fetched from the provided OCI repository and placed in the output. + + In order for this rule to work correctly in its current state, the + following flags must be provided to bazel: + --spawn_strategy=local + + The spawn_strategy flag must be set to local because currently, + oci_image_index is only declaring the new JSON files it creates as + outputs; it's not declaring any manifests or layers from the images as + outputs. By default, Bazel only permits rules to access specifically + declared outputs of the rule's direct dependencies. In order for this + rule to access the transitive set of outputs of all dependencies, we + must disable bazel's sandboxing by setting spawn_strategy=local. + """, + # TODO(kim.mason): Fix oci_image/oci_image_index so they explicitly declare + # outputs that include everything needed to build the image. + # TODO(kim.mason): Make it so that Docker credential helpers are available + # to oci_image_layout without making the system PATH available. + implementation = _oci_image_layout_impl, + attrs = { + "manifest": attr.label( + doc = """ + An OCILayout index to be written to the OCI Image Format directory. + """, + providers = [OCILayout, OCIImageLayoutInfo], + ), + "_debug": attr.label( + default = "//oci:debug", + providers = [DebugInfo], + ), + }, + provides = [ + DefaultInfo, + ], + toolchains = ["@com_github_datadog_rules_oci//oci:toolchain"], +) \ No newline at end of file diff --git a/oci/providers.bzl b/oci/providers.bzl index 8057d9a..35411ec 100755 --- a/oci/providers.bzl +++ b/oci/providers.bzl @@ -41,7 +41,7 @@ OCIImageManifest = provider( OCIImageIndexManifest = provider( doc = "", fields = { - "manifests": "List of desciptors", + "manifests": "List of descriptors", "annotations": "String map of arbitrary metadata", }, ) @@ -56,3 +56,11 @@ OCIPlatform = provider( "variant": "Variant is an optional field specifying a variant of the CPU", }, ) + +OCIImageLayoutInfo = provider( + doc = "This provider represents a list of directories that each contain an OCI Image Layout." + + "See https://github.com/opencontainers/image-spec/blob/main/image-layout.md for a description of the format.", + fields = { + "oci_image_layout_dirs": "A list of directories, each containing an OCI Image Layout." + }, +)