Skip to content

Commit 14fb1f8

Browse files
Allow images produced by img_pull to be used
The img_pull repository rule from rules_img generates image_import targets. This change allows these targets to be used as an input to the images list of a Helm chart
1 parent 0e9115e commit 14fb1f8

7 files changed

Lines changed: 120 additions & 35 deletions

File tree

helm/private/image_utils.bzl

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,86 @@
22

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

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

15-
def _image_push_repository_aspect_img(target, ctx):
15+
def _image_repository_aspect_img_pull(target, ctx):
16+
repository_file = None
17+
remote_tags_file = None
18+
manifest_file = None
19+
20+
if hasattr(ctx.rule.attr, "digest") and ctx.rule.attr.digest:
21+
digest = ctx.rule.attr.digest
22+
23+
if not hasattr(ctx.rule.attr, "data") or not ctx.rule.attr.data:
24+
fail("image_pull target {} must have a `data` attribute".format(target.label))
25+
26+
data = ctx.rule.attr.data
27+
28+
if not digest in data or not data[digest]:
29+
fail("manifest file not found in `data` of image_pull target {}".format(target.label))
30+
31+
manifest_file = ctx.actions.declare_file("{}.rules_helm.pull_manifest.txt".format(target.label.name))
32+
ctx.actions.write(
33+
output = manifest_file,
34+
content = data[digest],
35+
)
36+
else:
37+
fail("image_pull target {} must have a `digest` attribute".format(target.label))
38+
39+
registry = None
40+
41+
# The primary registry from which to pull the image
42+
if hasattr(ctx.rule.attr, "registry") and ctx.rule.attr.registry:
43+
registry = ctx.rule.attr.registry
44+
45+
# Additional registries can be provided. When rules_img pulls the image,
46+
# these are tried in order, before the primary registry. To emulate this
47+
# behaviour, if the list is set, the first element will be used as the
48+
# registry to pull from
49+
if hasattr(ctx.rule.attr, "registries") and ctx.rule.attr.registries:
50+
registries = ctx.rule.attr.registries
51+
if len(registries) > 0:
52+
registry = registries[0]
53+
54+
if registry == None:
55+
fail("image_pull target {} must have either a `registry` or `registries` attribute".format(target.label))
56+
57+
repository = None
58+
if hasattr(ctx.rule.attr, "repository") and ctx.rule.attr.repository:
59+
repository = ctx.rule.attr.repository
60+
else:
61+
fail("image_pull target {} must have a `repository` attribute".format(target.label))
62+
63+
registry_clean = registry.replace("https://", "").replace("http://", "")
64+
repository_file = ctx.actions.declare_file("{}.rules_helm.repository.txt".format(target.label.name))
65+
ctx.actions.write(
66+
output = repository_file,
67+
content = "{}/{}".format(registry_clean, repository),
68+
)
69+
70+
if hasattr(ctx.rule.attr, "tag") and ctx.rule.attr.tag:
71+
remote_tags_file = ctx.actions.declare_file("{}.rules_helm.tags.txt".format(target.label.name))
72+
ctx.actions.write(
73+
output = remote_tags_file,
74+
content = ctx.rule.attr.tag,
75+
)
76+
77+
return [ImageRepositoryInfo(
78+
repository_file = repository_file,
79+
manifest_file = manifest_file,
80+
oci_layout = None,
81+
remote_tags_file = remote_tags_file,
82+
)]
83+
84+
def _image_repository_aspect_img_push(target, ctx):
1685
repository_file = None
1786
remote_tags_file = None
1887
manifest_file = None
@@ -118,14 +187,14 @@ def _image_push_repository_aspect_img(target, ctx):
118187
# it
119188
remote_tags_file = ctx.rule.file.tag_file
120189

121-
return [ImagePushRepositoryInfo(
190+
return [ImageRepositoryInfo(
122191
repository_file = repository_file,
123192
manifest_file = manifest_file,
124193
oci_layout = None,
125194
remote_tags_file = remote_tags_file,
126195
)]
127196

128-
def _image_push_repository_aspect_oci(target, ctx):
197+
def _image_repository_aspect_oci_push(target, ctx):
129198
repository_file = None
130199
remote_tags_file = None
131200

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

151-
return [ImagePushRepositoryInfo(
220+
return [ImageRepositoryInfo(
152221
repository_file = repository_file,
153222
oci_layout = ctx.rule.file.image,
154223
manifest_file = None,
155224
remote_tags_file = remote_tags_file,
156225
)]
157226

158-
def _image_push_repository_aspect_impl(target, ctx):
227+
def _image_repository_aspect_impl(target, ctx):
228+
if ctx.rule.kind == "image_import":
229+
return _image_repository_aspect_img_pull(target, ctx)
230+
159231
if ctx.rule.kind == "image_push" or hasattr(ctx.rule.attr, "registry"):
160-
return _image_push_repository_aspect_img(target, ctx)
232+
return _image_repository_aspect_img_push(target, ctx)
161233

162-
return _image_push_repository_aspect_oci(target, ctx)
234+
return _image_repository_aspect_oci_push(target, ctx)
163235

164236
# This aspect exists because rules_oci and rules_img don't provide a provider
165237
# that cleanly publishes this information but for the helm rules, it's
166238
# absolutely necessary that an image's repository and digest are knowable.
167239
# If rules_oci/rules_img decide to define their own provider for this (which they should)
168240
# then this should be deleted in favor of that.
169-
image_push_repository_aspect = aspect(
170-
doc = "Provides the repository and image_root for a given oci_push or image_push target",
171-
implementation = _image_push_repository_aspect_impl,
241+
image_repository_aspect = aspect(
242+
doc = "Provides the repository and image_root for a given oci_{push,pull} or image_{push-pull} target",
243+
implementation = _image_repository_aspect_impl,
172244
attrs = {
173245
"_json_extractor": attr.label(
174246
doc = "Tool for extracting data from json files",

helm/private/install.bzl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ def _helm_install_impl(ctx, subcommand = "install"):
7878
image_pushers = []
7979
image_runfiles = []
8080
for image in pkg_info.images:
81-
image_pushers.append(image[DefaultInfo].files_to_run.executable)
82-
image_runfiles.append(image[DefaultInfo].default_runfiles)
81+
executable = image[DefaultInfo].files_to_run.executable
82+
if executable:
83+
image_pushers.append(executable)
84+
image_runfiles.append(image[DefaultInfo].default_runfiles)
8385

8486
args = ctx.actions.args()
8587
args.add_all(_expand_opts(ctx, ctx.attr.helm_opts, ctx.attr.data))

helm/private/package.bzl

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Helm rules"""
22

33
load(":helm_utils.bzl", "is_stamping_enabled")
4-
load(":image_utils.bzl", "ImagePushRepositoryInfo", "image_push_repository_aspect")
4+
load(":image_utils.bzl", "ImageRepositoryInfo", "image_repository_aspect")
55
load(":json_to_yaml.bzl", "json_to_yaml")
66
load(":providers.bzl", "HelmPackageInfo")
77

@@ -105,31 +105,31 @@ def _helm_package_impl(ctx):
105105
image_inputs = []
106106
single_image_manifests = []
107107
for image in ctx.attr.images:
108-
image_inputs.append(image[ImagePushRepositoryInfo].repository_file)
108+
image_inputs.append(image[ImageRepositoryInfo].repository_file)
109109

110110
# Add the appropriate image file based on format
111-
if image[ImagePushRepositoryInfo].oci_layout:
112-
image_inputs.append(image[ImagePushRepositoryInfo].oci_layout)
113-
elif image[ImagePushRepositoryInfo].manifest_file:
114-
image_inputs.append(image[ImagePushRepositoryInfo].manifest_file)
111+
if image[ImageRepositoryInfo].oci_layout:
112+
image_inputs.append(image[ImageRepositoryInfo].oci_layout)
113+
elif image[ImageRepositoryInfo].manifest_file:
114+
image_inputs.append(image[ImageRepositoryInfo].manifest_file)
115115
single_image_manifest = ctx.actions.declare_file("{}/{}".format(
116116
ctx.label.name,
117117
str(image.label).strip("@").replace("/", "_").replace(":", "_") + ".image_manifest",
118118
))
119119
push_info = image[DefaultInfo]
120120

121121
remote_tags_path = None
122-
if image[ImagePushRepositoryInfo].remote_tags_file:
123-
remote_tags_path = image[ImagePushRepositoryInfo].remote_tags_file.path
124-
image_inputs.append(image[ImagePushRepositoryInfo].remote_tags_file)
122+
if image[ImageRepositoryInfo].remote_tags_file:
123+
remote_tags_path = image[ImageRepositoryInfo].remote_tags_file.path
124+
image_inputs.append(image[ImageRepositoryInfo].remote_tags_file)
125125

126126
# Set mutually exclusive fields based on image format
127127
oci_layout_dir = None
128128
manifest_file = None
129-
if image[ImagePushRepositoryInfo].oci_layout:
130-
oci_layout_dir = image[ImagePushRepositoryInfo].oci_layout.path
131-
elif image[ImagePushRepositoryInfo].manifest_file:
132-
manifest_file = image[ImagePushRepositoryInfo].manifest_file.path
129+
if image[ImageRepositoryInfo].oci_layout:
130+
oci_layout_dir = image[ImageRepositoryInfo].oci_layout.path
131+
elif image[ImageRepositoryInfo].manifest_file:
132+
manifest_file = image[ImageRepositoryInfo].manifest_file.path
133133
else:
134134
fail("Unable to determine repository info for {}".format(image.label))
135135

@@ -138,7 +138,7 @@ def _helm_package_impl(ctx):
138138
content = json.encode_indent(
139139
struct(
140140
label = str(image.label),
141-
repository_path = image[ImagePushRepositoryInfo].repository_file.path,
141+
repository_path = image[ImageRepositoryInfo].repository_file.path,
142142
oci_layout_dir = oci_layout_dir,
143143
manifest_file = manifest_file,
144144
remote_tags_path = remote_tags_path,
@@ -232,10 +232,11 @@ helm_package = rule(
232232
"images": attr.label_list(
233233
doc = """\
234234
A list of \
235-
[oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) or \
236-
[image_push](https://github.com/bazel-contrib/rules_img) \
235+
[oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags), \
236+
[image_push](https://github.com/bazel-contrib/rules_img) or \
237+
[image_import](https://github.com/bazel-contrib/rules_img) \
237238
targets.""",
238-
aspects = [image_push_repository_aspect],
239+
aspects = [image_repository_aspect],
239240
),
240241
"opts": attr.string_list(
241242
doc = "Additional arguments to pass to `helm package` commands.",

helm/private/registry.bzl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ def _get_image_push_commands(ctx, pkg_info):
77
image_pushers = []
88
image_runfiles = []
99
for image in pkg_info.images:
10-
image_pushers.append(image[DefaultInfo].files_to_run.executable)
11-
image_runfiles.append(image[DefaultInfo].default_runfiles)
10+
executable = image[DefaultInfo].files_to_run.executable
11+
if executable:
12+
image_pushers.append(executable)
13+
image_runfiles.append(image[DefaultInfo].default_runfiles)
1214

1315
runfiles = ctx.runfiles(files = image_pushers)
1416
for ir in image_runfiles:

tests/test_deps.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def helm_test_deps():
7575
img_pull,
7676
name = "rules_helm_test_img_container_base",
7777
digest = "sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659",
78+
tag = "3.23.3",
7879
registry = "index.docker.io",
7980
repository = "library/alpine",
8081
)

tests/with_image_deps_img/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ helm_chart(
2121
":image_c.push",
2222
":image_d.push",
2323
":image_e.push",
24+
"@rules_helm_test_img_container_base",
2425
],
2526
target_compatible_with = EXCLUDE_WINDOWS,
2627
)
@@ -47,6 +48,8 @@ helm_package_regex_test(
4748
r"image_c:\s+url:\s+\"docker.io/rules_helm/test_img/image_c:1.2.3\"",
4849
r"image_d:\s+url:\s+\"docker.io/rules_helm/test_img/image_d:4.5.6\"",
4950
r"image_e:\s+url:\s+\"docker.io/rules_helm/test_img/image_e:7.8.9\"",
51+
r"image_pull:\s+url:\s+\"index.docker.io/library/alpine@sha256:[a-z0-9]{64}\"",
52+
r"image_pull_tag:\s+url:\s+\"index.docker.io/library/alpine:3.23.3\"",
5053
],
5154
)
5255

tests/with_image_deps_img/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ bazel_produced_images:
2121
url: "{@//tests/with_image_deps_img:image_d.push.repository}:{@//tests/with_image_deps_img:image_d.push.tag}"
2222
image_e:
2323
url: "{@//tests/with_image_deps_img:image_e.push.repository}:{@//tests/with_image_deps_img:image_e.push.tag}"
24+
image_pull:
25+
url: "{@+helm_test+rules_helm_test_img_container_base//:image}"
26+
image_pull_tag:
27+
url: "{@+helm_test+rules_helm_test_img_container_base//:image.repository}:{@+helm_test+rules_helm_test_img_container_base//:image.tag}"
2428

2529
imagePullSecrets: []
2630
nameOverride: ""

0 commit comments

Comments
 (0)