From 092cf1f41c1e2e08eb3295468edefb17ca4ddf3b Mon Sep 17 00:00:00 2001 From: "Piotr (ptab) Tabor" <132376425+sfc-gh-ptabor@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:16:41 +0100 Subject: [PATCH] Enable: bazel run @rules_apko//apko resolve ./apko.yaml (#36) Adds @rules_apko//apko(:apko) target that is executable and runs apko (resolved by toolchain) in the working directory. This simplify processes as avoids needs to install (and keep in sync) separate instance of apko outside of bazel. Please see the `./examples/resolve.sh` file for the examplar usage. --------- Signed-off-by: Piotr Tabor Signed-off-by: Jason Hall Co-authored-by: Jason Hall --- README.md | 7 ++++-- apko/BUILD.bazel | 9 ++++++++ apko/private/apk.bzl | 43 ++++++++++++++++++----------------- apko/private/apko_run.bzl | 47 +++++++++++++++++++++++++++++++++++++++ examples/resolve.sh | 7 +----- 5 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 apko/private/apko_run.bzl diff --git a/README.md b/README.md index 3558f6d..aa378e3 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,16 @@ using the GitHub-provided source archive like Apko usage begins with an `apko.yaml` configuration file. The `apko resolve` tool will create a corresponding `apko.resolved.json` file, and this is where Bazel will read to fetch external content. +Assuming `apko_rules` are already loaded in your `MODULE.bazel` or `WORKSPACE` file one can call: +`bazel run @rules_apko//apko resolve ./apko.yaml` to lock the dependencies and generate `apko.resolved.json` file. -First you import these base layers into Bazel: +Than you import these base layers into Bazel: - With Bazel 6 and [bzlmod], call `apk.translate_lock` in `MODULE.bazel` - Otherwise, call `translate_apko_lock` in `WORKSPACE` -Then, call `apko resolve path/to/apko.yaml` to generate `apko.resolved.json` and use the `apko_image` rule to build the image, producing an OCI format output. +Periodically one can call `apko resolve path/to/apko.yaml` or `bazel run @rules_apko//apko resolve path/to/apko.yaml` +to regenerate `apko.resolved.json` and use the `apko_image` rule to build the image, producing an OCI format output. Finally, we recommend using as the next step in your Bazel build to add application code from your repo as the next layers of the image. diff --git a/apko/BUILD.bazel b/apko/BUILD.bazel index 070182e..6b8fbfd 100644 --- a/apko/BUILD.bazel +++ b/apko/BUILD.bazel @@ -1,5 +1,6 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load("//apko/private:resolved_toolchain.bzl", "resolved_toolchain") +load("@rules_apko//apko/private:apko_run.bzl", "apko_run") # For stardoc to reference the files exports_files([ @@ -65,3 +66,11 @@ bzl_library( srcs = ["toolchain.bzl"], visibility = ["//visibility:public"], ) + +# Enables calling apko tool directly by bazel. +# To resolve given `./apko.yaml` file into `./apko.resolved.json`, once can call: +# e.g. (cd ./examples/oci; bazel run @rules_apko//apko resolve ./apko.yaml) +apko_run( + name = "apko", + visibility = ["//visibility:public"], +) diff --git a/apko/private/apk.bzl b/apko/private/apk.bzl index 585fa71..7da3127 100644 --- a/apko/private/apk.bzl +++ b/apko/private/apk.bzl @@ -126,31 +126,34 @@ APK_KEYRING_TMPL = """\ # Generated by apk_import. DO NOT EDIT filegroup( name = "keyring", - srcs = glob(["**/*.pub"]), + srcs = ["{public_key}"], visibility = ["//visibility:public"] ) """ -def _apk_keyring_impl(rctx): - scheme = "https" - url = rctx.attr.url - if url.startswith("http://"): - url = url[len("http://"):] - scheme = "http" - if url.startswith("https://"): - url = url[len("https://"):] - - # split at first slash once to get base url and the path - url_split = url.split("/", 1) - - path = url_split[1] - repo = util.url_escape("{}://{}/".format(scheme, url_split[0])) +def _cachePathFromURL(url): + """ + Translates URL to a name of local directory that can be used to represent prefetched content of the URL. + + Mimicks https://github.com/chainguard-dev/go-apk/blob/7b08e8f3b0fcaa0f0a44757aedf23f6778cd8e4f/pkg/apk/cache.go#L326C6-L326C22 + Is interprets URL as following path: {repo}/{arch}/{file} [but also used for keyring files that don't obey {arch} part]. + + Examples: + https://packages.wolfi.dev/os/wolfi-signing.rsa.pub -> https%3A%2F%2Fpackages.wolfi.dev%2F/os/wolfi-signing.rsa.pub + https://packages.wolfi.dev/os/aarch64/sqlite-libs-3.44.0-r0.apk -> https%3A%2F%2Fpackages.wolfi.dev%2Fos/arch64/sqlite-libs-3.44.0-r0.apk + """ + url_split = url.rsplit("/", 2) + repo = url_split[0] + if len(repo.split("/")) <= 3: + # Seems the Apko adds additional "/" if the URL is short. + repo += "/" + repo_escaped = util.url_escape(repo) + return "{}/{}/{}".format(repo_escaped, url_split[1], url_split[2]) - rctx.download( - url = [rctx.attr.url], - output = "{}/{}".format(repo, path), - ) - rctx.file("BUILD.bazel", APK_KEYRING_TMPL) +def _apk_keyring_impl(rctx): + public_key = _cachePathFromURL(rctx.attr.url) + rctx.download(url = [rctx.attr.url], output = public_key) + rctx.file("BUILD.bazel", APK_KEYRING_TMPL.format(public_key = public_key)) apk_keyring = repository_rule( implementation = _apk_keyring_impl, diff --git a/apko/private/apko_run.bzl b/apko/private/apko_run.bzl new file mode 100644 index 0000000..07939d5 --- /dev/null +++ b/apko/private/apko_run.bzl @@ -0,0 +1,47 @@ +"A rule for running apko - convenience layer to stay within consistent versions." + +_ATTRS = { +} + +_DOC = """ +apko_run is used internally to defune @rules_apko//apko target that allows to run an apko tool in the version supplied by Bazel. + +Thanks to this, `bazel run @rules_apko//apko {flags}` can be called, without need to download/install apko outside of Bazel. +The workdir of the running command is the directory from which bazel has been called. +""" + +LAUNCHER_TEMPLATE = """ +#!#!/usr/bin/env sh + +set -e +LAUNCHER_DIR="${PWD}" +if test "${BUILD_WORKING_DIRECTORY+x}"; then + cd $BUILD_WORKING_DIRECTORY +fi + +echo "Workdir: ${PWD}" >&2 +${LAUNCHER_DIR}/{{apko_binary}} "${@:1}" +""" + +def _impl(ctx): + output = ctx.actions.declare_file("_{}_run.sh".format(ctx.label.name)) + apko_info = ctx.toolchains["@rules_apko//apko:toolchain_type"].apko_info + + ctx.actions.write(output = output, content = LAUNCHER_TEMPLATE.replace("{{apko_binary}}", apko_info.binary.path), is_executable = True) + + return DefaultInfo(executable = output, runfiles = ctx.runfiles(files = [apko_info.binary])) + +apko_run_lib = struct( + attrs = _ATTRS, + documentation = _DOC, + implementation = _impl, + toolchains = ["@rules_apko//apko:toolchain_type"], +) + +apko_run = rule( + implementation = apko_run_lib.implementation, + attrs = apko_run_lib.attrs, + toolchains = apko_run_lib.toolchains, + doc = apko_run_lib.documentation, + executable = True, +) diff --git a/examples/resolve.sh b/examples/resolve.sh index f012b98..681cf7c 100755 --- a/examples/resolve.sh +++ b/examples/resolve.sh @@ -2,9 +2,4 @@ set -e -x -for d in $(find . -name apko.yaml); do - ( - cd $(dirname ${d}) - docker run -v "$PWD":/work cgr.dev/chainguard/apko@sha256:d5e219c1ceb2e7d56a5933df54819467e5b3331098ea8bebc996fdd30f974f33 resolve /work/apko.yaml - ) -done +find . -name apko.yaml | xargs bazel run @rules_apko//apko resolve \ No newline at end of file