Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement oci_image_index #34

Merged
merged 2 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ stardoc_with_diff_test(
bzl_library_target = "//oci/private:tarball",
)

stardoc_with_diff_test(
name = "image_index",
bzl_library_target = "//oci/private:image_index",
)

update_docs(name = "update")
46 changes: 46 additions & 0 deletions docs/image_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->

Implementation details for oci_image_index rule

<a id="#oci_image_index"></a>

## oci_image_index

<pre>
oci_image_index(<a href="#oci_image_index-name">name</a>, <a href="#oci_image_index-images">images</a>)
</pre>

Build a multi-architecture OCI compatible container image.

It takes number of `oci_image`s to create a fat multi-architecture image.

Requires `wc` and `shasum` to be installed on the execution machine.

```starlark
oci_image(
name = "app_linux_amd64"
)

oci_image(
name = "app_linux_arm64"
)

oci_image_index(
name = "app",
images = [
":app_linux_amd64",
":app_linux_arm64"
]
)
```


**ATTRIBUTES**


| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="oci_image_index-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
| <a id="oci_image_index-images"></a>images | List of labels to oci_image targets. | <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a> | required | |


4 changes: 4 additions & 0 deletions example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ genrule(
},
no_match_error = "Please build on a arm64 or amd64 host",
),
message = select({
"@platforms//cpu:arm64": "Pulling base image for linux/arm64",
"@platforms//cpu:x86_64": "Pulling base image for linux/amd64",
}),
output_to_bindir = True,
toolchains = [
"@oci_crane_toolchains//:resolved_toolchain",
Expand Down
37 changes: 37 additions & 0 deletions example/multi_arch/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load("//oci:defs.bzl", "oci_image", "oci_image_index")
load("@rules_pkg//:pkg.bzl", "pkg_tar")
load(":transition.bzl", "multi_arch")

pkg_tar(
name = "app",
srcs = ["test.bash"],
)

oci_image(
name = "image",
architecture = select({
"@platforms//cpu:arm64": "arm64",
"@platforms//cpu:x86_64": "amd64",
}),
base = "//example:base",
cmd = ["test.bash"],
entrypoint = ["bash"],
os = "linux",
tars = ["app.tar"],
)

multi_arch(
name = "images",
image = ":image",
platforms = [
"//example:linux_arm64",
"//example:linux_amd64",
],
)

oci_image_index(
name = "index",
images = [
":images",
],
)
1 change: 1 addition & 0 deletions example/multi_arch/test.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
echo "This is rules_oci!"
27 changes: 27 additions & 0 deletions example/multi_arch/transition.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"a rule transitioning an oci_image to multiple platforms"

def _multiarch_transition(settings, attr):
return [
{"//command_line_option:platforms": str(platform)}
for platform in attr.platforms
]

multiarch_transition = transition(
implementation = _multiarch_transition,
inputs = [],
outputs = ["//command_line_option:platforms"],
)

def _impl(ctx):
return DefaultInfo(files = depset(ctx.files.image))

multi_arch = rule(
implementation = _impl,
attrs = {
"image": attr.label(cfg = multiarch_transition),
"platforms": attr.label_list(),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
)
2 changes: 2 additions & 0 deletions oci/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

load("//oci/private:tarball.bzl", _oci_tarball = "oci_tarball")
load("//oci/private:image.bzl", _oci_image = "oci_image")
load("//oci/private:image_index.bzl", _oci_image_index = "oci_image_index")
load("//oci/private:structure_test.bzl", _structure_test = "structure_test")

oci_tarball = _oci_tarball
oci_image = _oci_image
oci_image_index = _oci_image_index
structure_test = _structure_test
10 changes: 10 additions & 0 deletions oci/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ exports_files(

exports_files([
"image.sh.tpl",
"image_index.sh.tpl",
"tarball.sh.tpl",
])

Expand Down Expand Up @@ -44,6 +45,15 @@ bzl_library(
],
)

bzl_library(
name = "image_index",
srcs = ["image_index.bzl"],
visibility = [
"//docs:__pkg__",
"//oci:__subpackages__",
],
)

bzl_library(
name = "versions",
srcs = ["versions.bzl"],
Expand Down
79 changes: 79 additions & 0 deletions oci/private/image_index.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"Implementation details for oci_image_index rule"

_DOC = """Build a multi-architecture OCI compatible container image.

It takes number of `oci_image`s to create a fat multi-architecture image.

Requires `wc` and `shasum` to be installed on the execution machine.

```starlark
oci_image(
name = "app_linux_amd64"
)

oci_image(
name = "app_linux_arm64"
)

oci_image_index(
name = "app",
images = [
":app_linux_amd64",
":app_linux_arm64"
]
)
```
"""

_attrs = {
"images": attr.label_list(mandatory = True, doc = "List of labels to oci_image targets."),
"_image_index_sh_tpl": attr.label(default = "image_index.sh.tpl", allow_single_file = True),
}

def _expand_image_to_args(image, expander):
args = [
"--image={}".format(image.path),
]
for file in expander.expand(image):
if file.path.find("blobs") != -1:
args.append("--blob={}".format(file.tree_relative_path))
return args

def _oci_image_index_impl(ctx):
yq = ctx.toolchains["@aspect_bazel_lib//lib:yq_toolchain_type"]

launcher = ctx.actions.declare_file("image_index_{}.sh".format(ctx.label.name))
ctx.actions.expand_template(
template = ctx.file._image_index_sh_tpl,
output = launcher,
is_executable = True,
substitutions = {
"{{yq_path}}": yq.yqinfo.bin.path,
},
)

output = ctx.actions.declare_directory(ctx.label.name)

args = ctx.actions.args()
args.add(output.path, format = "--output=%s")
args.add_all(ctx.files.images, map_each = _expand_image_to_args, expand_directories = False)

ctx.actions.run(
inputs = ctx.files.images,
arguments = [args],
outputs = [output],
executable = launcher,
tools = [yq.yqinfo.bin],
progress_message = "OCI Index %{label}",
)

return DefaultInfo(files = depset([output]))

oci_image_index = rule(
implementation = _oci_image_index_impl,
attrs = _attrs,
doc = _DOC,
toolchains = [
"@aspect_bazel_lib//lib:yq_toolchain_type",
],
)
61 changes: 61 additions & 0 deletions oci/private/image_index.sh.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -o pipefail -o errexit -o nounset

readonly YQ="{{yq_path}}"


function add_image() {
local image_path="$1"
local output_path="$2"

local manifests=$("${YQ}" eval '.manifests[]' "${image_path}/index.json")

for manifest in "${manifests}"; do
local manifest_blob_path=$("${YQ}" '.digest | sub(":"; "/")' <<< ${manifest})
local config_blob_path=$("${YQ}" '.config.digest | sub(":"; "/")' "${image_path}/blobs/${manifest_blob_path}")

local platform=$("${YQ}" --output-format=json '{"os": .os, "architecture": .architecture, "variant": .variant, "os.version": .["os.version"], "os.features": .["os.features"]} | with_entries(select( .value != null ))' "${image_path}/blobs/${config_blob_path}")

platform="${platform}" \
manifest="${manifest}" \
"${YQ}" --inplace --output-format=json '.manifests += [env(manifest) + {"platform": env(platform)}]' "${output_path}/manifest_list.json"
done
}

function copy_blob() {
local image_path="$1"
local output_path="$2"
local blob_image_relative_path="$3"
local dest_path="${output_path}/${blob_image_relative_path}"
mkdir -p "$(dirname "${dest_path}")"
cat "${image_path}/${blob_image_relative_path}" > "${dest_path}"
}

function create_oci_layout() {
local path="$1"
mkdir -p "${path}"

echo '{"imageLayoutVersion": "1.0.0"}' > "${path}/oci-layout"
echo '{"schemaVersion": 2, "manifests": []}' > "${path}/index.json"
echo '{"schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": []}' > "${path}/manifest_list.json"
}

CURRENT_IMAGE=""
OUTPUT=""

for ARG in "$@"; do
case "$ARG" in
(--output=*) OUTPUT="${ARG#--output=}"; create_oci_layout "$OUTPUT" ;;
(--image=*) CURRENT_IMAGE="${ARG#--image=}"; add_image "$CURRENT_IMAGE" "$OUTPUT" ;;
(--blob=*) copy_blob "${CURRENT_IMAGE}" "$OUTPUT" "${ARG#--blob=}" ;;
(*) echo "Unknown argument ${ARG}"; exit 1;;
esac
done


export checksum=$(shasum -a 256 "${OUTPUT}/manifest_list.json" | cut -f 1 -d " ")
export size=$(wc -c < "${OUTPUT}/manifest_list.json")

"${YQ}" --inplace --output-format=json '.manifests += [{"mediaType": "application/vnd.oci.image.index.v1+json", "size": env(size), "digest": "sha256:" + env(checksum)}]' "$OUTPUT/index.json"

mv "${OUTPUT}/manifest_list.json" "$OUTPUT/blobs/sha256/${checksum}"
1 change: 0 additions & 1 deletion oci/private/toolchains_repo.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ TOOLCHAIN_TMPL = """\
toolchain(
name = "{platform}_toolchain",
exec_compatible_with = {compatible_with},
target_compatible_with = {compatible_with},
toolchain = "{toolchain}",
toolchain_type = "{toolchain_type}",
)
Expand Down