Skip to content

Commit

Permalink
remote_tag can be used as image ref (#103)
Browse files Browse the repository at this point in the history
I would like to suggest some enhancements regarding `oci_push` image
referencing and replacements in chart files.

Sometimes it's more convenient to see meaningful Docker image tag in k8s
specs instead of sha256 (otherwise we didn't add these tags to Docker
images). In our case these tags are available from `oci_push` target
(along with repository file). So we can try to use them in chart files
replacements.

Besides that a lot of existing charts have separated settings for image
repository and image tag.
```
image:
   repository: docker.io/busybox
   tag: v1.2.3
```
So it would be useful to add separate replacements for these parameters
so that people don't need to change structure of theirs charts in order
to make them correctly packaged by `rules_helm`. Moreover, there are
charts exist that use `image.tag` as a value for labels and annotations.

The proposed solution works as follows:
* if there is remote tags file in `oci_push` target, these information
is included into `ImageInfo`
* in a replacement group corresponding to `oci_push` target added next
replacements:
     * `[@@]@<target name>.digest`
     * `[@@]@<target name>.repository`
     * `[@@]@<target name>.tag`
* besides that in case of single `oci_push` target "well-known"
substitution added
     * `bazel.image.url`
     * `bazel.image.repository`
     * `bazel.image.digest`
     * `bazel.image.tag` 

So that it's up to a user what substitution to use. 

Please, have a look at PR and thanks in advance!

---------

Co-authored-by: Dmitrii Shchitinin <[email protected]>
  • Loading branch information
dormidon and Dmitrii Shchitinin authored Oct 15, 2024
1 parent 4118652 commit 11392fa
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 7 deletions.
2 changes: 1 addition & 1 deletion MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions helm/private/helm_package.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ OciPushRepositoryInfo = provider(
doc = "Repository and image information for a given oci_push target",
fields = {
"image_root": "File: The directory containing the image files for the oci_push target",
"remote_tags_file": "File (optional): The file containing remote tags (one per line) used for the oci_push target",
"repository_file": "File: The file containing the repository path for the oci_push target",
},
)
Expand All @@ -31,9 +32,14 @@ def _oci_push_repository_aspect_impl(target, ctx):
target.label,
))

remote_tags_file = None
if hasattr(ctx.rule.file, "remote_tags") and ctx.rule.file.remote_tags:
remote_tags_file = ctx.rule.file.remote_tags

return [OciPushRepositoryInfo(
repository_file = output,
image_root = ctx.rule.file.image,
remote_tags_file = remote_tags_file,
)]

# This aspect exists because rules_oci seems reluctant to create a provider
Expand Down Expand Up @@ -133,13 +139,20 @@ def _helm_package_impl(ctx):
str(image.label).strip("@").replace("/", "_").replace(":", "_") + ".image_manifest",
))
push_info = image[DefaultInfo]

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

ctx.actions.write(
output = single_image_manifest,
content = json.encode_indent(
struct(
label = str(image.label),
repository_path = image[OciPushRepositoryInfo].repository_file.path,
image_root_path = image[OciPushRepositoryInfo].image_root.path,
remote_tags_path = remote_tags_path,
),
),
)
Expand Down
54 changes: 48 additions & 6 deletions helm/private/packager/packager.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ type ImageInfo struct {
Label string
Repository string
Digest string
RemoteTag string
}

type ImageManifest struct {
Label string `json:"label"`
RepositoryPath string `json:"repository_path"`
ImageRootPath string `json:"image_root_path"`
RemoteTagsPath string `json:"remote_tags_path"`
}

type ImageIndexManifest struct {
Expand Down Expand Up @@ -227,6 +229,20 @@ func imageManifestToImageInfo(imageManifest ImageManifest) (ImageInfo, error) {
}

imageInfo.Digest = imageIndex.Manifests[0].Digest

if imageManifest.RemoteTagsPath != "" {
remoteTagsContent, err := os.ReadFile(imageManifest.RemoteTagsPath)
if err != nil {
return imageInfo, fmt.Errorf("read remote tags file %q: %w", imageManifest.RemoteTagsPath, err)
}

remoteTags := strings.Split(strings.Trim(string(remoteTagsContent), "\n"), "\n")
if len(remoteTags) == 1 {
// With many remote tags we can't say for sure which one should be used
imageInfo.RemoteTag = remoteTags[0]
}
}

return imageInfo, nil
}

Expand All @@ -243,17 +259,43 @@ func loadImageStamps(imageManifestPath string) ([]ReplacementGroup, error) {

replacementGroups := []ReplacementGroup{}

isSingleImage := len(imageInfos) == 1

for _, imageInfo := range imageInfos {
repository := imageInfo.Repository
digest := imageInfo.Digest
tag := imageInfo.RemoteTag

workspaceLabel := strings.Replace(imageInfo.Label, "@@", "@", 1)
bzmodLabel := fmt.Sprintf("@%s", imageInfo.Label)
imageUrl := fmt.Sprintf("%s@%s", imageInfo.Repository, imageInfo.Digest)
imageUrl := fmt.Sprintf("%s@%s", repository, digest)

replacements := map[string]string{
workspaceLabel: imageUrl,
workspaceLabel + ".repository": repository,
workspaceLabel + ".digest": digest,
bzmodLabel: imageUrl,
bzmodLabel + ".repository": repository,
bzmodLabel + ".digest": digest,
}
if tag != "" {
replacements[workspaceLabel+".tag"] = tag
replacements[bzmodLabel+".tag"] = tag
}

// in case of single image add well-known replacements for image details
if isSingleImage {
replacements["bazel.image.url"] = imageUrl
replacements["bazel.image.repository"] = repository
replacements["bazel.image.digest"] = digest
if tag != "" {
replacements["bazel.image.tag"] = tag
}
}

replacementGroups = append(replacementGroups, ReplacementGroup{
Name: imageInfo.Label,
Replacements: map[string]string{
workspaceLabel: imageUrl,
bzmodLabel: imageUrl,
},
Name: imageInfo.Label,
Replacements: replacements,
})
}

Expand Down
11 changes: 11 additions & 0 deletions tests/with_image_deps/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ helm_chart(
images = [
":image_a.push",
"//tests/with_image_deps:image_b.push",
"//tests/with_image_deps:image_c.push",
],
target_compatible_with = EXCLUDE_WINDOWS,
values = "values.yaml",
Expand All @@ -36,12 +37,14 @@ helm_package_regex_test(
values_patterns = [
r"image_a:\s+url:\s+\"docker.io/rules_helm/test/image_a@sha256:[a-z0-9]{64}\"",
r"image_b:\s+url:\s+\"docker.io/rules_helm/test/image_b@sha256:[a-z0-9]{64}\"",
r"image_c:\s+url:\s+\"docker.io/rules_helm/test/image_c:1.2.3\"",
],
)

_IMAGES = [
"image_a",
"image_b",
"image_c",
]

[
Expand Down Expand Up @@ -75,3 +78,11 @@ oci_push(
repository_file = ":image_b.repository.txt",
target_compatible_with = EXCLUDE_WINDOWS,
)

oci_push(
name = "image_c.push",
image = ":image_c",
remote_tags = ["1.2.3"],
repository = "docker.io/rules_helm/test/image_c",
target_compatible_with = EXCLUDE_WINDOWS,
)
2 changes: 2 additions & 0 deletions tests/with_image_deps/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ bazel_produced_images:
url: "{@//tests/with_image_deps:image_a.push}"
image_b:
url: "{@//tests/with_image_deps:image_b.push}"
image_c:
url: "{@//tests/with_image_deps:image_c.push.repository}:{@//tests/with_image_deps:image_c.push.tag}"

imagePullSecrets: []
nameOverride: ""
Expand Down

0 comments on commit 11392fa

Please sign in to comment.