Skip to content

Commit 3d2abbe

Browse files
committed
Split helm_import_repository into different rules
`helm_import_repository` is for finding a chart in a [HTTP repository](https://helm.sh/docs/topics/chart_repository/), which may provide a OCI source. `helm_import_url` is for directly downloading a chart package from a HTTP URL. `helm_import_registry` is for importing a chart from an [OCI registry](https://helm.sh/docs/topics/registries/). Fixes #189, #195
1 parent f9f505a commit 3d2abbe

File tree

3 files changed

+172
-111
lines changed

3 files changed

+172
-111
lines changed

helm/defs.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ load(
1212
load(
1313
"//helm/private:helm_import.bzl",
1414
_helm_import = "helm_import",
15+
_helm_import_registry = "helm_import_registry",
1516
_helm_import_repository = "helm_import_repository",
17+
_helm_import_url = "helm_import_url",
1618
)
1719
load(
1820
"//helm/private:helm_install.bzl",
@@ -57,6 +59,8 @@ load(
5759
helm_chart = _helm_chart
5860
helm_import = _helm_import
5961
helm_import_repository = _helm_import_repository
62+
helm_import_registry = _helm_import_registry
63+
helm_import_url = _helm_import_url
6064
helm_install = _helm_install
6165
helm_lint_aspect = _helm_lint_aspect
6266
helm_lint_test = _helm_lint_test

helm/private/helm_import.bzl

Lines changed: 160 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ helm_import = rule(
3434
allow_single_file = [".tgz"],
3535
),
3636
"version": attr.string(
37-
doc = "The version fo the helm chart",
37+
doc = "The version of the helm chart",
3838
),
3939
},
4040
)
@@ -60,14 +60,6 @@ def _find_chart_url(repository_ctx, repo_file, chart_name, chart_version):
6060
return url
6161
fail("cannot find {} (version {}) in {}".format(chart_name, chart_version, repository_ctx.attr.repository))
6262

63-
def _get_chart_file_name(chart_url):
64-
if chart_url.startswith("http") and chart_url.endswith(".tgz"):
65-
return chart_url.split("/")[-1]
66-
if chart_url.startswith("oci"):
67-
chart_name, chart_version = chart_url.split("/")[-1].split(":")
68-
return "{}-{}.tgz".format(chart_name, chart_version)
69-
fail("cannot determine chart file name from {}".format(chart_url))
70-
7163
def _find_chart_digest(manifest):
7264
for layer in manifest["layers"]:
7365
# https://helm.sh/docs/topics/registries/#helm-chart-manifest
@@ -83,141 +75,203 @@ helm_import(
8375
chart = "{chart_file}",
8476
visibility = ["//visibility:public"],
8577
)
78+
"""
8679

87-
alias(
88-
name = "{repository_name}",
89-
actual = ":{chart_name}",
90-
visibility = ["//visibility:public"],
80+
def _helm_import_url_impl(repository_ctx):
81+
chart_name = repository_ctx.attr.chart_name
82+
chart_url = repository_ctx.attr.url
83+
84+
chart_file = "{}.tgz".format(chart_name)
85+
86+
repository_ctx.file("BUILD.bazel", content = _HELM_DEP_BUILD_FILE.format(
87+
chart_name = chart_name,
88+
chart_file = chart_file,
89+
))
90+
91+
chart_package = repository_ctx.download(
92+
output = repository_ctx.path(chart_file),
93+
url = chart_url,
94+
sha256 = repository_ctx.attr.sha256,
95+
)
96+
97+
return {
98+
"chart_name": chart_name,
99+
"name": repository_ctx.name,
100+
"sha256": chart_package.sha256,
101+
"url": chart_url,
102+
}
103+
104+
helm_import_url = repository_rule(
105+
implementation = _helm_import_url_impl,
106+
attrs = {
107+
"chart_name": attr.string(
108+
doc = "Chart name to import.",
109+
mandatory = True,
110+
),
111+
"sha256": attr.string(
112+
doc = "The expected SHA-256 hash of the chart imported.",
113+
),
114+
"url": attr.string(
115+
doc = "The URL where the chart can be directly downloaded.",
116+
mandatory = True,
117+
),
118+
},
91119
)
92-
"""
93120

94-
def _helm_import_repository_impl(repository_ctx):
95-
if repository_ctx.attr.url and repository_ctx.attr.repository:
96-
fail("`url` and `repository` are exclusive arguments.")
97-
98-
if repository_ctx.attr.url:
99-
chart_url = repository_ctx.attr.url
100-
if repository_ctx.attr.chart_name:
101-
fail("`url` provided, do not set `chart_name`")
102-
if repository_ctx.attr.version:
103-
fail("`url` provided, do not set `version`")
104-
else:
105-
if not repository_ctx.attr.chart_name:
106-
fail("`chart_name` is required to locate chart")
107-
if not repository_ctx.attr.version:
108-
fail("`version` is required to locate chart")
109-
110-
repo_yaml = "index.yaml"
111-
repository_ctx.download(
112-
output = repo_yaml,
113-
url = "{}/{}".format(
114-
repository_ctx.attr.repository,
115-
repo_yaml,
116-
),
117-
)
121+
def _oci_url_download(repository_ctx, url, chart_file):
122+
url, _, chart_version = url.rpartition(":")
123+
hostname, _, chart_path = url.removeprefix("oci://").partition("/")
124+
125+
au = authn.new(repository_ctx)
126+
token = au.get_token(hostname, chart_path)
127+
128+
manifest_json = "manifest.json"
129+
manifest_url = "https://{}/v2/{}/manifests/{}".format(
130+
hostname,
131+
chart_path,
132+
chart_version,
133+
)
134+
repository_ctx.download(
135+
output = manifest_json,
136+
url = manifest_url,
137+
auth = {
138+
manifest_url: token,
139+
},
140+
# Copied from `helm pull --debug`
141+
headers = {
142+
"Accept": [
143+
"application/vnd.docker.distribution.manifest.v2+json",
144+
"application/vnd.docker.distribution.manifest.list.v2+json",
145+
"application/vnd.oci.image.manifest.v1+json",
146+
"application/vnd.oci.image.index.v1+json",
147+
"*/*",
148+
],
149+
},
150+
)
151+
manifest = json.decode(repository_ctx.read(manifest_json))
118152

119-
chart_url = _find_chart_url(repository_ctx, repo_yaml, repository_ctx.attr.chart_name, repository_ctx.attr.version)
153+
# https://helm.sh/docs/topics/registries/#helm-chart-manifest
154+
if manifest["config"]["mediaType"] != "application/vnd.cncf.helm.config.v1+json":
155+
fail("oci://{}/{} is not a Helm chart package".format(hostname, chart_path))
120156

121-
chart_file = _get_chart_file_name(chart_url)
157+
chart_digest = _find_chart_digest(manifest)
158+
chart_blob_url = "https://{}/v2/{}/blobs/{}".format(
159+
hostname,
160+
chart_path,
161+
chart_digest,
162+
)
163+
164+
# Download the chart package:
165+
return repository_ctx.download(
166+
output = repository_ctx.path(chart_file),
167+
url = chart_blob_url,
168+
sha256 = repository_ctx.attr.sha256,
169+
auth = {
170+
chart_blob_url: token,
171+
},
172+
)
173+
174+
def _helm_import_repository_impl(repository_ctx):
175+
chart_name = repository_ctx.attr.chart_name
176+
chart_version = repository_ctx.attr.version
177+
178+
repo_yaml = "index.yaml"
179+
repository_ctx.download(
180+
output = repo_yaml,
181+
url = "{}/{}".format(
182+
repository_ctx.attr.repository,
183+
repo_yaml,
184+
),
185+
)
186+
chart_url = _find_chart_url(repository_ctx, repo_yaml, chart_name, chart_version)
187+
chart_file = "{}.tgz".format(chart_name)
122188

123189
if chart_url.startswith("http"):
124-
result = repository_ctx.download(
190+
chart_package = repository_ctx.download(
125191
output = repository_ctx.path(chart_file),
126192
url = chart_url,
127193
sha256 = repository_ctx.attr.sha256,
128194
)
129195
elif chart_url.startswith("oci"):
130-
url, _, chart_version = chart_url.rpartition(":")
131-
hostname, _, chart_path = url.removeprefix("oci://").partition("/")
132-
133-
au = authn.new(repository_ctx)
134-
token = au.get_token(hostname, chart_path)
135-
136-
# Find the digest for the layer with the chart package in the image manifest:
137-
manifest_json = "manifest.json"
138-
manifest_url = "https://{}/v2/{}/manifests/{}".format(
139-
hostname,
140-
chart_path,
141-
chart_version,
142-
)
143-
repository_ctx.download(
144-
output = manifest_json,
145-
url = manifest_url,
146-
auth = {
147-
manifest_url: token,
148-
},
149-
# Copied from `helm pull --debug`
150-
headers = {
151-
"Accept": [
152-
"application/vnd.docker.distribution.manifest.v2+json",
153-
"application/vnd.docker.distribution.manifest.list.v2+json",
154-
"application/vnd.oci.image.manifest.v1+json",
155-
"application/vnd.oci.image.index.v1+json",
156-
"*/*",
157-
],
158-
},
159-
)
160-
manifest = json.decode(repository_ctx.read(manifest_json))
161-
162-
# https://helm.sh/docs/topics/registries/#helm-chart-manifest
163-
if manifest["config"]["mediaType"] != "application/vnd.cncf.helm.config.v1+json":
164-
fail("{} is not a Helm chart package".format(chart_url))
165-
166-
chart_digest = _find_chart_digest(manifest)
167-
chart_blob_url = "https://{}/v2/{}/blobs/{}".format(
168-
hostname,
169-
chart_path,
170-
chart_digest,
171-
)
172-
173-
# Download the chart package:
174-
result = repository_ctx.download(
175-
output = repository_ctx.path(chart_file),
176-
url = chart_blob_url,
177-
sha256 = repository_ctx.attr.sha256,
178-
auth = {
179-
chart_blob_url: token,
180-
},
181-
)
182-
196+
chart_package = _oci_url_download(repository_ctx, chart_url, chart_file)
183197
else:
184-
fail("cannot download {} from {}, unsupported scheme".format(repository_ctx.attr.chart_name, chart_url))
185-
186-
chart_name, _, chart_version = chart_file.removesuffix(".tgz").rpartition("-")
198+
fail("cannot download {} from {}, unsupported scheme".format(chart_name, chart_url))
187199

188200
repository_ctx.file("BUILD.bazel", content = _HELM_DEP_BUILD_FILE.format(
189201
chart_name = chart_name,
190202
chart_file = chart_file,
191-
repository_name = repository_ctx.name,
192203
))
193204

194205
return {
195206
"chart_name": chart_name,
196207
"name": repository_ctx.name,
197208
"repository": repository_ctx.attr.repository,
198-
"sha256": result.sha256,
199-
"url": chart_url,
209+
"sha256": chart_package.sha256,
200210
"version": chart_version,
201211
}
202212

203213
helm_import_repository = repository_rule(
204214
implementation = _helm_import_repository_impl,
205-
doc = "A rule for fetching external Helm charts from an arbitrary URL or repository.",
215+
doc = "A rule for fetching external Helm chart from a HTTP repository.",
206216
attrs = {
207217
"chart_name": attr.string(
208-
doc = "Chart name to import. Must be set if `repository` is specified",
218+
doc = "Chart name to import.",
219+
mandatory = True,
209220
),
210221
"repository": attr.string(
211-
doc = "Chart repository url where to locate the requested chart. Mutually exclusive with `url`.",
222+
doc = "Repository URL where to locate the specified chart.",
223+
mandatory = True,
212224
),
213225
"sha256": attr.string(
214226
doc = "The expected SHA-256 hash of the chart imported.",
215227
),
216-
"url": attr.string(
217-
doc = "The url where a chart can be directly downloaded. Mutually exclusive with `chart_name`, `repository`, and `version`",
228+
"version": attr.string(
229+
doc = "Chart version to import.",
230+
mandatory = True,
231+
),
232+
},
233+
)
234+
235+
def _helm_import_registry_impl(repository_ctx):
236+
registry = repository_ctx.attr.registry
237+
chart_name = repository_ctx.attr.chart_name
238+
version = repository_ctx.attr.version
239+
240+
chart_url = "{}/{}:{}".format(registry, chart_name, version)
241+
chart_file = "{}.tgz".format(chart_name)
242+
chart_package = _oci_url_download(repository_ctx, chart_url, chart_file)
243+
244+
repository_ctx.file("BUILD.bazel", content = _HELM_DEP_BUILD_FILE.format(
245+
chart_name = chart_name,
246+
chart_file = chart_file,
247+
))
248+
249+
return {
250+
"chart_name": chart_name,
251+
"name": repository_ctx.name,
252+
"registry": repository_ctx.attr.registry,
253+
"sha256": chart_package.sha256,
254+
"version": repository_ctx.attr.version,
255+
}
256+
257+
helm_import_registry = repository_rule(
258+
implementation = _helm_import_registry_impl,
259+
doc = "A rule for fetching an external Helm chart from a OCI registry.",
260+
attrs = {
261+
"chart_name": attr.string(
262+
doc = "Chart name to import.",
263+
mandatory = True,
264+
),
265+
"registry": attr.string(
266+
doc = "OCI registry URL where to locate the specified chart.",
267+
mandatory = True,
268+
),
269+
"sha256": attr.string(
270+
doc = "The expected SHA-256 hash of the chart imported.",
218271
),
219272
"version": attr.string(
220-
doc = "Specify a version constraint for the chart version to use. Must be set if `repository` is specified.",
273+
doc = "Chart version to import.",
274+
mandatory = True,
221275
),
222276
},
223277
)

tests/test_deps.bzl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
44
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
55
load("@rules_oci//oci:pull.bzl", "oci_pull")
6-
load("//helm:defs.bzl", "helm_import_repository")
6+
load("//helm:defs.bzl", "helm_import_registry", "helm_import_repository", "helm_import_url")
77

88
_CM_HELM_PUSH_BUILD_CONTENT = """\
99
package(default_visibility = ["//visibility:public"])
@@ -31,17 +31,20 @@ def helm_test_deps():
3131

3232
# Directly download this chart from a HTTP URL:
3333
maybe(
34-
helm_import_repository,
34+
helm_import_url,
3535
name = "helm_test_deps__with_chart_deps_postgresql",
36+
chart_name = "postgresql",
3637
url = "https://charts.bitnami.com/bitnami/postgresql-14.0.5.tgz",
3738
sha256 = "38d9b6657aa3b0cc16d190570dbaf96796e997d03a1665264dac9966343e4d1b",
3839
)
3940

40-
# Directly download this chart from an OCI URL:
41+
# Download this chart from an OCI registry:
4142
maybe(
42-
helm_import_repository,
43+
helm_import_registry,
4344
name = "helm_test_deps__with_chart_deps_grafana",
44-
url = "oci://registry-1.docker.io/bitnamicharts/grafana:12.1.4",
45+
registry = "oci://registry-1.docker.io/bitnamicharts",
46+
chart_name = "grafana",
47+
version = "12.1.4",
4548
sha256 = "015f66a231a809557ab368d903f6762ba31ba2f7b3d0f890445be6e8f213cff1",
4649
)
4750

0 commit comments

Comments
 (0)