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

Support apko configs that consist of multiple files. #64

Merged
merged 29 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ac851e4
Expose ApkoConfig
sfc-gh-mhazy Apr 24, 2024
0f74017
Expose ApkoConfig
sfc-gh-mhazy Apr 24, 2024
0eaa072
Add the file with ApkoConfigInfo provider
sfc-gh-mhazy Apr 24, 2024
d0cea3b
Update docs
sfc-gh-mhazy Apr 24, 2024
22cb975
Add test
sfc-gh-mhazy Apr 24, 2024
510f01e
Add test
sfc-gh-mhazy Apr 24, 2024
770ea82
Docs update
sfc-gh-mhazy Apr 24, 2024
6bb22b4
Cleanup
sfc-gh-mhazy Apr 24, 2024
fa71d51
Add test for lock
sfc-gh-mhazy Apr 24, 2024
4e97585
Refresh lockfiles
sfc-gh-mhazy Apr 24, 2024
cd69376
Add apko_lock and apko-config
sfc-gh-mhazy Apr 24, 2024
eb96b35
documentation
sfc-gh-mhazy May 6, 2024
4ac3ec6
Update apko/private/apko_image.bzl
sfc-gh-mhazy May 6, 2024
67fe67f
Fix docs tests
sfc-gh-mhazy May 6, 2024
2412fd7
Move to the deps approach for multifle apko config
sfc-gh-mhazy May 7, 2024
42305df
Move to the deps approach for multifle apko config
sfc-gh-mhazy May 7, 2024
064a1b1
Move to the deps approach for multifle apko config
sfc-gh-mhazy May 8, 2024
0b04633
Move to the deps approach for multifle apko config
sfc-gh-mhazy May 8, 2024
55dcc09
Handle inputs that are directories correctly
sfc-gh-mhazy May 9, 2024
6622443
Address comments
sfc-gh-mhazy May 9, 2024
409299f
Address comments
sfc-gh-mhazy May 9, 2024
fa05b9d
Address comments
sfc-gh-mhazy May 9, 2024
7739582
Address comments
sfc-gh-mhazy May 9, 2024
8f9d7ac
Clarify the test.sh comment
sfc-gh-mhazy May 9, 2024
c513721
Update apko/private/apko_image.bzl
sfc-gh-ptabor May 13, 2024
98bb4ab
Update apko/private/apko_image.bzl
sfc-gh-ptabor May 13, 2024
100993e
Update apko/private/apko_image.bzl
sfc-gh-ptabor May 13, 2024
315baa6
Update apko/private/apko_lock.bzl
sfc-gh-ptabor May 13, 2024
38fd7f7
Update docs
sfc-gh-mhazy May 14, 2024
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
2 changes: 2 additions & 0 deletions apko/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ bzl_library(
srcs = ["defs.bzl"],
visibility = ["//visibility:public"],
deps = [
"//apko/private:apko_config",
"//apko/private:apko_image",
"//apko/private:apko_lock",
"//apko/private/range:bazelrc",
"@aspect_bazel_lib//lib:write_source_files",
],
Expand Down
5 changes: 5 additions & 0 deletions apko/defs.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"Public API re-exports"

load("//apko/private:apko_image.bzl", _apko_image = "apko_image")
load("//apko/private:apko_lock.bzl", _apko_lock = "apko_lock")
load("//apko/private/range:bazelrc.bzl", _apko_bazelrc = "apko_bazelrc")
load("//apko/private:apko_config.bzl", _ApkoConfigInfo = "ApkoConfigInfo", _apko_config = "apko_config")

apko_image = _apko_image
apko_bazelrc = _apko_bazelrc
ApkoConfigInfo = _ApkoConfigInfo
apko_config = _apko_config
apko_lock = _apko_lock
17 changes: 17 additions & 0 deletions apko/private/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

exports_files(srcs = ["apko_config.tmpl.sh"])

bzl_library(
name = "apko_config",
srcs = ["apko_config.bzl"],
visibility = ["//apko:__subpackages__"],
)

bzl_library(
name = "apk",
srcs = ["apk.bzl"],
Expand All @@ -16,6 +24,7 @@ bzl_library(
visibility = ["//apko:__subpackages__"],
deps = [
"apko_run",
":apko_config",
"@bazel_skylib//lib:paths",
"@bazel_skylib//lib:versions",
],
Expand All @@ -25,6 +34,14 @@ bzl_library(
name = "apko_run",
srcs = ["apko_run.bzl"],
visibility = ["//apko:__subpackages__"],
deps = [":apko_config"],
)

bzl_library(
name = "apko_lock",
srcs = ["apko_lock.bzl"],
visibility = ["//apko:__subpackages__"],
deps = [":apko_run"],
)

bzl_library(
Expand Down
63 changes: 63 additions & 0 deletions apko/private/apko_config.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Provider that serves as an API for adding any files needed for apko build"""

_PATH_CONVENTION_DOC = """
When referencing other files in the config yaml file use paths relative to your Bazel workspace root.
For example, if you want to reference source file foo/bar/baz use foo/bar/baz. If you want to reference output file of foo/bar:rule and rule's
output file is rule.out, reference it as foo/bar/rule.out.
"""

ApkoConfigInfo = provider(
doc = """Information about apko config. May be used when generating apko config file instead of using hardcoded ones.
{}
""".format(_PATH_CONVENTION_DOC),
fields = {
"files": "depset of files that will be needed for building. All of them will be added to the execution of apko commands when built with Bazel.",
},
)

def _apko_config_impl(ctx):
config = ctx.file.config
out = ctx.actions.declare_file(ctx.attr.name)
ctx.actions.symlink(
target_file = config,
output = out,
)

apko_depsets = []
for dep in ctx.attr.deps:
if ApkoConfigInfo in dep:
apko_depsets.append(dep[ApkoConfigInfo].files)
direct_deps = [out]
for dep in ctx.files.deps:
direct_deps.append(dep)

return [
DefaultInfo(
files = depset([out]),
),
ApkoConfigInfo(
files = depset(direct_deps, transitive = apko_depsets),
),
]

apko_config = rule(
implementation = _apko_config_impl,
attrs = {
"deps": attr.label_list(
allow_empty = True,
default = [],
allow_files = True,
doc = """
List of all dependencies of the config. Transitive dependencies are included based on
ApkoConfigInfo provider.
""",
),
"config": attr.label(
allow_single_file = True,
doc = """
Config of the image. Either in source directory or generated by Bazel.
{}
""".format(_PATH_CONVENTION_DOC),
),
},
)
76 changes: 65 additions & 11 deletions apko/private/apko_image.bzl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"A rule for running apko with prepopulated cache"

load("//apko/private:apko_run.bzl", "apko_run")
load("//apko/private:apko_config.bzl", "ApkoConfigInfo")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//lib:versions.bzl", "versions")

Expand All @@ -16,6 +17,17 @@ _ATTRS = {
def _impl(ctx):
apko_info = ctx.toolchains["@rules_apko//apko:toolchain_type"].apko_info

# We execute apko build within ctx.bin_dir.path/workdir and make all the files available within the workdir.
# The key part here is that the path to input file in action is:
# - bin_dir/target.short_path for generated targets (example bazel-out/.../path/to/my_config)
# - target.short_path for source files. (example path/to/source_config)
#
# For each input file we create a symlink as workdir/input.short_path
# Since the symlink is a generated target, its path is:
# bin_dir/workspace_root/package/workdir/input.short_path
#
# Then when we move to bin_dir/workspace_root/package the relative path become target.short_path for all kinds of input files.
workdir = "workdir_{}".format(ctx.label.name)
cache_name = "cache_{}".format(ctx.label.name)

if ctx.attr.output == "oci":
Expand All @@ -25,9 +37,10 @@ def _impl(ctx):

args = ctx.actions.args()
args.add("build")
args.add(ctx.file.config.path)
args.add(ctx.file.config.short_path)
args.add(ctx.attr.tag)
args.add(output.path)

args.add("../" + output.basename)

args.add("--vcs=false")

Expand All @@ -38,17 +51,41 @@ def _impl(ctx):
indexes = ctx.attr.contents[OutputGroupInfo].indexes
keyrings = ctx.attr.contents[OutputGroupInfo].keyrings

inputs = [ctx.file.config]
inputs = []
if ApkoConfigInfo in ctx.attr.config:
for f in ctx.attr.config[ApkoConfigInfo].files.to_list():
if f.is_directory:
input_entry = ctx.actions.declare_directory(paths.join(workdir, f.short_path))
else:
input_entry = ctx.actions.declare_file(paths.join(workdir, f.short_path))
ctx.actions.symlink(
target_file = f,
output = input_entry,
)
inputs.append(input_entry)
else:
config_symlink = ctx.actions.declare_file(paths.join(workdir, ctx.file.config.short_path))
ctx.actions.symlink(
target_file = ctx.file.config,
output = config_symlink,
)
inputs.append(config_symlink)

deps = [apks, keyrings]

supports_lockfile = versions.is_at_least("0.13.0", apko_info.version)
if supports_lockfile:
inputs.append(lockfile)
args.add("--lockfile={}".format(lockfile.path))
lockfile_symlink = ctx.actions.declare_file(paths.join(workdir, lockfile.short_path))
ctx.actions.symlink(
target_file = lockfile,
output = lockfile_symlink,
)
inputs.append(lockfile_symlink)
args.add("--lockfile={}".format(lockfile.short_path))
else:
deps.append(indexes)

args.add("--cache-dir={}".format(paths.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, cache_name)))
args.add("--cache-dir={}".format(cache_name))
args.add("--offline")

if ctx.attr.architecture:
Expand All @@ -58,17 +95,25 @@ def _impl(ctx):
for content in depset(transitive = deps).to_list():
content_owner = content.owner.workspace_name
content_cache_entry_key = content.path[content.path.find(content_owner) + len(content_owner) + 1:]
content_entry = ctx.actions.declare_file("/".join([cache_name, content_cache_entry_key]))
content_entry = ctx.actions.declare_file(paths.join(workdir, cache_name, content_cache_entry_key))
ctx.actions.symlink(
target_file = content,
output = content_entry,
)
inputs.append(content_entry)

ctx.actions.run(
executable = apko_info.binary,
apko_binary = ctx.actions.declare_file(paths.join(workdir, apko_info.binary.short_path))
ctx.actions.symlink(
target_file = apko_info.binary,
output = apko_binary,
)
inputs.append(apko_binary)

ctx.actions.run_shell(
command = "cd {} && {} $@".format(paths.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, workdir), apko_info.binary.short_path),
arguments = [args],
inputs = inputs,
tools = [apko_info.binary],
outputs = [output],
)

Expand All @@ -88,7 +133,15 @@ _apko_image = rule(
toolchains = apko_image_lib.toolchains,
)

def apko_image(name, contents, config, tag, output = "oci", architecture = None, args = [], **kwargs):
def apko_image(
name,
contents,
config,
tag,
output = "oci",
architecture = None,
args = [],
**kwargs):
"""Build OCI images from APK packages directly without Dockerfile

This rule creates images using the 'apko.yaml' configuration file and relies on cache contents generated by [translate_lock](./translate_lock.md) to be fast.
Expand Down Expand Up @@ -126,7 +179,8 @@ def apko_image(name, contents, config, tag, output = "oci", architecture = None,
Args:
name: of the target for the generated image.
contents: Label to the contents repository generated by translate_lock. See [apko-cache](./apko-cache.md) documentation.
config: Label to the `apko.yaml` file.
config: Label to the `apko.yaml` file. For more advanced use-cases (multi-file configuration), use target providing `ApkoConfigInfo`
(e.g. output of `apko_config` rule).
output: "oci" of "docker",
architecture: the CPU architecture which this image should be built to run on. See https://github.com/chainguard-dev/apko/blob/main/docs/apko_file.md#archs-top-level-element"),
tag: tag to apply to the resulting docker tarball. only applicable when `output` is `docker`
Expand Down
76 changes: 76 additions & 0 deletions apko/private/apko_lock.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Rule for generating apko locks."""

load("//apko/private:apko_config.bzl", "ApkoConfigInfo")
load("@bazel_skylib//lib:paths.bzl", "paths")

_ATTRS = {
"config": attr.label(allow_single_file = True),
"lockfile_name": attr.string(),
}

_DOC = """
apko_lock generates the lock file based on the provided config.
"""

LAUNCHER_TEMPLATE = """
#!#!/usr/bin/env sh

set -e

config={{config}}
output={{output}}

{{apko_binary}} lock $config --output=${BUILD_WORKSPACE_DIRECTORY}/${output}
"""

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)
.replace("{{config}}", ctx.file.config.short_path)
.replace("{{output}}", ctx.attr.lockfile_name),
is_executable = True,
)

transitive_data = []
if ApkoConfigInfo in ctx.attr.config:
transitive_data.append(ctx.attr.config[ApkoConfigInfo].files)

return DefaultInfo(
executable = output,
runfiles = ctx.runfiles(
files = [apko_info.binary] + depset(ctx.files.config, transitive = transitive_data).to_list(),
),
)

_apko_lock = rule(
implementation = _impl,
attrs = _ATTRS,
doc = _DOC,
executable = True,
toolchains = ["@rules_apko//apko:toolchain_type"],
)

def apko_lock(name, config, lockfile_name):
"""Generates executable rule for producing apko lock files.

When run, the rule will output the lockfile to the lockfile_name in the directory of the package where the rule is defined.
That is, if you define `apko_lock` in `foo/bar/BUILD.bazel` with `lockfile_name="baz.lock.json` the rule will output the lock into
`foo/bar/baz.lock.json`.

Args:
name: name of the rule,
config: label of the apko config. It can be either a source file or generated target. Additionally, if the target provides ApkoConfigInfo provider,
the transitive dependencies listed in ApkoConfigInfo.files will be added to runfiles as well.
lockfile_name: name of the lockfile
"""
config_label = native.package_relative_label(config)
_apko_lock(
name = name,
config = config,
lockfile_name = paths.join(config_label.package, lockfile_name),
)
Loading
Loading