Skip to content
Open
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
100 changes: 86 additions & 14 deletions helm/private/image_utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,86 @@

load("//helm/private:json_extractor.bzl", "json_extractor")

ImagePushRepositoryInfo = provider(
doc = "Repository and image information for a given oci_push or image_push target",
ImageRepositoryInfo = provider(
doc = "Repository and image information for a given rules_oci or rules_img image target",
fields = {
"manifest_file": "File (optional): The manifest JSON file for rules_img images",
"oci_layout": "File (optional): The OCI layout directory for rules_oci images (contains index.json)",
"remote_tags_file": "File (optional): The file containing remote tags (one per line) used for the push target",
"repository_file": "File: The file containing the repository path for the push target",
"remote_tags_file": "File (optional): The file containing remote tags (one per line) used for the target",
"repository_file": "File: The file containing the repository path for the target",
},
)

def _image_push_repository_aspect_img(target, ctx):
def _image_repository_aspect_img_pull(target, ctx):
repository_file = None
remote_tags_file = None
manifest_file = None

if hasattr(ctx.rule.attr, "digest") and ctx.rule.attr.digest:
digest = ctx.rule.attr.digest

if not hasattr(ctx.rule.attr, "data") or not ctx.rule.attr.data:
fail("image_pull target {} must have a `data` attribute".format(target.label))

data = ctx.rule.attr.data

if not digest in data or not data[digest]:
fail("manifest file not found in `data` of image_pull target {}".format(target.label))

manifest_file = ctx.actions.declare_file("{}.rules_helm.pull_manifest.txt".format(target.label.name))
ctx.actions.write(
output = manifest_file,
content = data[digest],
)
else:
fail("image_pull target {} must have a `digest` attribute".format(target.label))

registry = None

# The primary registry from which to pull the image
if hasattr(ctx.rule.attr, "registry") and ctx.rule.attr.registry:
registry = ctx.rule.attr.registry

# Additional registries can be provided. When rules_img pulls the image,
# these are tried in order, before the primary registry. To emulate this
# behaviour, if the list is set, the first element will be used as the
# registry to pull from
if hasattr(ctx.rule.attr, "registries") and ctx.rule.attr.registries:
registries = ctx.rule.attr.registries
if len(registries) > 0:
registry = registries[0]

if registry == None:
fail("image_pull target {} must have either a `registry` or `registries` attribute".format(target.label))

repository = None
if hasattr(ctx.rule.attr, "repository") and ctx.rule.attr.repository:
repository = ctx.rule.attr.repository
else:
fail("image_pull target {} must have a `repository` attribute".format(target.label))

registry_clean = registry.replace("https://", "").replace("http://", "")
repository_file = ctx.actions.declare_file("{}.rules_helm.repository.txt".format(target.label.name))
ctx.actions.write(
output = repository_file,
content = "{}/{}".format(registry_clean, repository),
)

if hasattr(ctx.rule.attr, "tag") and ctx.rule.attr.tag:
remote_tags_file = ctx.actions.declare_file("{}.rules_helm.tags.txt".format(target.label.name))
ctx.actions.write(
output = remote_tags_file,
content = ctx.rule.attr.tag,
)

return [ImageRepositoryInfo(
repository_file = repository_file,
manifest_file = manifest_file,
oci_layout = None,
remote_tags_file = remote_tags_file,
)]

def _image_repository_aspect_img_push(target, ctx):
repository_file = None
remote_tags_file = None
manifest_file = None
Expand Down Expand Up @@ -118,14 +187,14 @@ def _image_push_repository_aspect_img(target, ctx):
# it
remote_tags_file = ctx.rule.file.tag_file

return [ImagePushRepositoryInfo(
return [ImageRepositoryInfo(
repository_file = repository_file,
manifest_file = manifest_file,
oci_layout = None,
remote_tags_file = remote_tags_file,
)]

def _image_push_repository_aspect_oci(target, ctx):
def _image_repository_aspect_oci_push(target, ctx):
repository_file = None
remote_tags_file = None

Expand All @@ -148,27 +217,30 @@ def _image_push_repository_aspect_oci(target, ctx):
if not hasattr(ctx.rule.file, "image"):
fail("oci_push target {} must have an `image` attribute".format(target.label))

return [ImagePushRepositoryInfo(
return [ImageRepositoryInfo(
repository_file = repository_file,
oci_layout = ctx.rule.file.image,
manifest_file = None,
remote_tags_file = remote_tags_file,
)]

def _image_push_repository_aspect_impl(target, ctx):
def _image_repository_aspect_impl(target, ctx):
if ctx.rule.kind == "image_import":
return _image_repository_aspect_img_pull(target, ctx)

if ctx.rule.kind == "image_push" or hasattr(ctx.rule.attr, "registry"):
return _image_push_repository_aspect_img(target, ctx)
return _image_repository_aspect_img_push(target, ctx)

return _image_push_repository_aspect_oci(target, ctx)
return _image_repository_aspect_oci_push(target, ctx)

# This aspect exists because rules_oci and rules_img don't provide a provider
# that cleanly publishes this information but for the helm rules, it's
# absolutely necessary that an image's repository and digest are knowable.
# If rules_oci/rules_img decide to define their own provider for this (which they should)
# then this should be deleted in favor of that.
image_push_repository_aspect = aspect(
doc = "Provides the repository and image_root for a given oci_push or image_push target",
implementation = _image_push_repository_aspect_impl,
image_repository_aspect = aspect(
doc = "Provides the repository and image_root for a given oci_push, image_push or image_import target",
implementation = _image_repository_aspect_impl,
attrs = {
"_json_extractor": attr.label(
doc = "Tool for extracting data from json files",
Expand Down
6 changes: 4 additions & 2 deletions helm/private/install.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ def _helm_install_impl(ctx, subcommand = "install"):
image_pushers = []
image_runfiles = []
for image in pkg_info.images:
image_pushers.append(image[DefaultInfo].files_to_run.executable)
image_runfiles.append(image[DefaultInfo].default_runfiles)
executable = image[DefaultInfo].files_to_run.executable
if executable:
image_pushers.append(executable)
image_runfiles.append(image[DefaultInfo].default_runfiles)

args = ctx.actions.args()
args.add_all(_expand_opts(ctx, ctx.attr.helm_opts, ctx.attr.data))
Expand Down
35 changes: 18 additions & 17 deletions helm/private/package.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Helm rules"""

load(":helm_utils.bzl", "is_stamping_enabled")
load(":image_utils.bzl", "ImagePushRepositoryInfo", "image_push_repository_aspect")
load(":image_utils.bzl", "ImageRepositoryInfo", "image_repository_aspect")
load(":json_to_yaml.bzl", "json_to_yaml")
load(":providers.bzl", "HelmPackageInfo")

Expand Down Expand Up @@ -105,31 +105,31 @@ def _helm_package_impl(ctx):
image_inputs = []
single_image_manifests = []
for image in ctx.attr.images:
image_inputs.append(image[ImagePushRepositoryInfo].repository_file)
image_inputs.append(image[ImageRepositoryInfo].repository_file)

# Add the appropriate image file based on format
if image[ImagePushRepositoryInfo].oci_layout:
image_inputs.append(image[ImagePushRepositoryInfo].oci_layout)
elif image[ImagePushRepositoryInfo].manifest_file:
image_inputs.append(image[ImagePushRepositoryInfo].manifest_file)
if image[ImageRepositoryInfo].oci_layout:
image_inputs.append(image[ImageRepositoryInfo].oci_layout)
elif image[ImageRepositoryInfo].manifest_file:
image_inputs.append(image[ImageRepositoryInfo].manifest_file)
single_image_manifest = ctx.actions.declare_file("{}/{}".format(
ctx.label.name,
str(image.label).strip("@").replace("/", "_").replace(":", "_") + ".image_manifest",
))
push_info = image[DefaultInfo]

remote_tags_path = None
if image[ImagePushRepositoryInfo].remote_tags_file:
remote_tags_path = image[ImagePushRepositoryInfo].remote_tags_file.path
image_inputs.append(image[ImagePushRepositoryInfo].remote_tags_file)
if image[ImageRepositoryInfo].remote_tags_file:
remote_tags_path = image[ImageRepositoryInfo].remote_tags_file.path
image_inputs.append(image[ImageRepositoryInfo].remote_tags_file)

# Set mutually exclusive fields based on image format
oci_layout_dir = None
manifest_file = None
if image[ImagePushRepositoryInfo].oci_layout:
oci_layout_dir = image[ImagePushRepositoryInfo].oci_layout.path
elif image[ImagePushRepositoryInfo].manifest_file:
manifest_file = image[ImagePushRepositoryInfo].manifest_file.path
if image[ImageRepositoryInfo].oci_layout:
oci_layout_dir = image[ImageRepositoryInfo].oci_layout.path
elif image[ImageRepositoryInfo].manifest_file:
manifest_file = image[ImageRepositoryInfo].manifest_file.path
else:
fail("Unable to determine repository info for {}".format(image.label))

Expand All @@ -138,7 +138,7 @@ def _helm_package_impl(ctx):
content = json.encode_indent(
struct(
label = str(image.label),
repository_path = image[ImagePushRepositoryInfo].repository_file.path,
repository_path = image[ImageRepositoryInfo].repository_file.path,
oci_layout_dir = oci_layout_dir,
manifest_file = manifest_file,
remote_tags_path = remote_tags_path,
Expand Down Expand Up @@ -232,10 +232,11 @@ helm_package = rule(
"images": attr.label_list(
doc = """\
A list of \
[oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) or \
[image_push](https://github.com/bazel-contrib/rules_img) \
[oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags), \
[image_push](https://github.com/bazel-contrib/rules_img) or \
[image_import](https://github.com/bazel-contrib/rules_img) \
targets.""",
aspects = [image_push_repository_aspect],
aspects = [image_repository_aspect],
),
"opts": attr.string_list(
doc = "Additional arguments to pass to `helm package` commands.",
Expand Down
6 changes: 4 additions & 2 deletions helm/private/registry.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ def _get_image_push_commands(ctx, pkg_info):
image_pushers = []
image_runfiles = []
for image in pkg_info.images:
image_pushers.append(image[DefaultInfo].files_to_run.executable)
image_runfiles.append(image[DefaultInfo].default_runfiles)
executable = image[DefaultInfo].files_to_run.executable
if executable:
image_pushers.append(executable)
image_runfiles.append(image[DefaultInfo].default_runfiles)

runfiles = ctx.runfiles(files = image_pushers)
for ir in image_runfiles:
Expand Down
3 changes: 3 additions & 0 deletions tests/test_defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,6 @@ helm_package_regex_test = rule(
},
test = True,
)

def label_to_str(label):
return str(Label(label))
1 change: 1 addition & 0 deletions tests/test_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def helm_test_deps():
img_pull,
name = "rules_helm_test_img_container_base",
digest = "sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659",
tag = "3.23.3",
registry = "index.docker.io",
repository = "library/alpine",
)
Expand Down
13 changes: 12 additions & 1 deletion tests/with_image_deps_img/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ load("@rules_img//img:image.bzl", "image_manifest")
load("@rules_img//img:layer.bzl", "image_layer")
load("@rules_img//img:push.bzl", "image_push")
load("//helm:defs.bzl", "helm_chart", "helm_lint_test", "helm_template_test")
load("//tests:test_defs.bzl", "helm_package_regex_test")
load("//tests:test_defs.bzl", "helm_package_regex_test", "label_to_str")

exports_files(["Chart.lock"])

Expand All @@ -21,7 +21,16 @@ helm_chart(
":image_c.push",
":image_d.push",
":image_e.push",
"@rules_helm_test_img_container_base",
],
substitutions = {
# This goofy workaround is needed because Bazel 7 has '~' characters in
# the label string, while newer versions have '+'. Relying on the fact
# that the substitutions are handled before image stamps, we can do a
# double replacement, and thus avoid hardcoding either character into
# the values file ensuring compatibility with both versions
"base_container_label": label_to_str("@rules_helm_test_img_container_base//:image"),
},
target_compatible_with = EXCLUDE_WINDOWS,
)

Expand All @@ -47,6 +56,8 @@ helm_package_regex_test(
r"image_c:\s+url:\s+\"docker.io/rules_helm/test_img/image_c:1.2.3\"",
r"image_d:\s+url:\s+\"docker.io/rules_helm/test_img/image_d:4.5.6\"",
r"image_e:\s+url:\s+\"docker.io/rules_helm/test_img/image_e:7.8.9\"",
r"image_pull:\s+url:\s+\"index.docker.io/library/alpine@sha256:[a-z0-9]{64}\"",
r"image_pull_tag:\s+url:\s+\"index.docker.io/library/alpine:3.23.3\"",
],
)

Expand Down
4 changes: 4 additions & 0 deletions tests/with_image_deps_img/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ bazel_produced_images:
url: "{@//tests/with_image_deps_img:image_d.push.repository}:{@//tests/with_image_deps_img:image_d.push.tag}"
image_e:
url: "{@//tests/with_image_deps_img:image_e.push.repository}:{@//tests/with_image_deps_img:image_e.push.tag}"
image_pull:
url: "{@{base_container_label}}"
image_pull_tag:
url: "{@{base_container_label}.repository}:{@{base_container_label}.tag}"

imagePullSecrets: []
nameOverride: ""
Expand Down
Loading