Skip to content

Commit

Permalink
Added support for helm plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
abrisco committed Oct 30, 2024
1 parent ea63d24 commit f0b4fa3
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 40 deletions.
38 changes: 37 additions & 1 deletion MODULE.bazel.lock

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

10 changes: 6 additions & 4 deletions helm/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ load(
_helm_push = "helm_push",
_helm_push_images = "helm_push_images",
)
load(
"//helm/private:helm_toolchain.bzl",
_helm_plugin = "helm_plugin",
_helm_toolchain = "helm_toolchain",
)
load(
":providers.bzl",
_HelmPackageInfo = "HelmPackageInfo",
Expand All @@ -114,10 +119,6 @@ load(
_helm_register_toolchains = "helm_register_toolchains",
_rules_helm_dependencies = "rules_helm_dependencies",
)
load(
":toolchain.bzl",
_helm_toolchain = "helm_toolchain",
)

helm_chart = _helm_chart
helm_import = _helm_import
Expand All @@ -126,6 +127,7 @@ helm_install = _helm_install
helm_lint_aspect = _helm_lint_aspect
helm_lint_test = _helm_lint_test
helm_package = _helm_package
helm_plugin = _helm_plugin
helm_push = _helm_push
helm_push_images = _helm_push_images
helm_push_registry = _helm_push
Expand Down
2 changes: 1 addition & 1 deletion helm/private/current_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def _current_helm_toolchain_impl(ctx):
toolchain_info = ctx.toolchains["@rules_helm//helm:toolchain_type"]
return [
toolchain_info,
toolchain_info.default,
toolchain_info.default_info,
toolchain_info.template_variables,
]

Expand Down
8 changes: 7 additions & 1 deletion helm/private/helm_registry.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def _helm_push_impl(ctx):
template = ctx.file._registrar,
output = registrar,
substitutions = {
"{helm_plugins}": toolchain.plugins_dir.short_path,
"{chart}": pkg_info.chart.short_path,
"{helm}": toolchain.helm.short_path,
"{image_pushers}": image_commands,
Expand All @@ -50,7 +51,12 @@ def _helm_push_impl(ctx):
is_executable = True,
)

runfiles = ctx.runfiles([registrar, toolchain.helm, pkg_info.chart]).merge(image_runfiles)
runfiles = ctx.runfiles([
registrar,
toolchain.helm,
pkg_info.chart,
toolchain.plugins_dir,
]).merge(image_runfiles)

return [
DefaultInfo(
Expand Down
131 changes: 131 additions & 0 deletions helm/private/helm_toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""rules_helm toolchain implementation"""

HelmPluginInfo = provider(
doc = "Info about a Helm plugin.",
fields = {
"data": "Depset[File]: TODO",
"name": "String: TODO",
"yaml": "File: TODO",
},
)

def _helm_plugin_impl(ctx):
name = ctx.attr.plugin_name
if not name:
name = ctx.label.name

return [
HelmPluginInfo(
yaml = ctx.file.yaml,
name = name,
data = depset(ctx.files.data),
),
]

helm_plugin = rule(
doc = "TODO",
implementation = _helm_plugin_impl,
attrs = {
"data": attr.label_list(
doc = "TODO",
allow_files = True,
),
"plugin_name": attr.string(
doc = "TODO",
),
"yaml": attr.label(
doc = "TODO",
allow_single_file = True,
),
},
)

def _create_plugins_dir(*, ctx, plugins, output):
manifest = ctx.actions.declare_file("{}.manifest".format(output.basename), sibling = output)
manifest_data = {}
inputs = [depset([manifest])]
for plugin in plugins:
info = plugin[HelmPluginInfo]
inputs.append(depset([info.yaml], transitive = [info.data]))

if info.name in manifest_data:
fail("Two plugins sharing the name {} were provided. Please update {}".format(
info.name,
ctx.label,
))

manifest_data[info.name] = {
"yaml": info.yaml.path,
"data": [f.path for f in info.data.to_list()],
}

ctx.actions.write(
output = manifest,
content = json.encode_indent(manifest_data, indent = " " * 4) + "\n",
)

args = ctx.actions.args()
args.add("-manifest", manifest)
args.add("-output", output.path)

ctx.actions.run(
mnemonic = "HelmPluginsDir",
progress_message = "HelmPluginsDir %{label}",
executable = ctx.executable._plugins_builder,
arguments = [args],
inputs = depset(transitive = inputs),
outputs = [output],
)

return output

def _helm_toolchain_impl(ctx):
binary = ctx.file.helm

plugins_dir = _create_plugins_dir(
ctx = ctx,
plugins = ctx.attr.plugins,
output = ctx.actions.declare_directory("{}.plugins".format(ctx.label.name)),
)

template_variables = platform_common.TemplateVariableInfo({
"HELM_BIN": binary.path,
"HELM_PLUGINS": plugins_dir.path,
})

default_info = DefaultInfo(
files = depset([binary]),
runfiles = ctx.runfiles(files = [binary, plugins_dir]),
)

toolchain_info = platform_common.ToolchainInfo(
helm = binary,
default_info = default_info,
plugins_dir = plugins_dir,
template_variables = template_variables,
)

return [default_info, toolchain_info, template_variables]

helm_toolchain = rule(
implementation = _helm_toolchain_impl,
doc = "A helm toolchain.",
attrs = {
"helm": attr.label(
doc = "A helm binary",
allow_single_file = True,
mandatory = True,
cfg = "exec",
),
"plugins": attr.label_list(
doc = "TODO",
cfg = "exec",
providers = [HelmPluginInfo],
),
"_plugins_builder": attr.label(
default = Label("//helm/private/plugin:plugin_builder"),
cfg = "exec",
executable = True,
),
},
)
7 changes: 7 additions & 0 deletions helm/private/plugin/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary")

go_binary(
name = "plugin_builder",
srcs = ["plugin_builder.go"],
visibility = ["//visibility:public"],
)
110 changes: 110 additions & 0 deletions helm/private/plugin/plugin_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)

// ManifestEntry represents the structure of each entry in the manifest
type ManifestEntry struct {
YAML string `json:"yaml"`
Data []string `json:"data"`
}

// Manifest represents the entire manifest map
type Manifest map[string]ManifestEntry

func main() {
// Parse command-line flags
outputDir := flag.String("output", "", "Path to the output directory")
manifestPath := flag.String("manifest", "", "Path to the manifest JSON file")
flag.Parse()

// Validate the flags
if *outputDir == "" || *manifestPath == "" {
fmt.Println("Both -output and -manifest are required")
flag.Usage()
os.Exit(1)
}

// Read and parse the manifest JSON
manifestFile, err := os.Open(*manifestPath)
if err != nil {
log.Fatalf("Failed to open manifest file: %v\n", err)
}
defer manifestFile.Close()

var manifest Manifest
decoder := json.NewDecoder(manifestFile)
if err := decoder.Decode(&manifest); err != nil {
log.Fatalf("Failed to parse manifest JSON: %v\n", err)
}

// Process each entry in the manifest
for key, entry := range manifest {
// Validate that all data files have the same parent directory as the YAML file
yamlDir := filepath.Dir(entry.YAML)

for _, dataFile := range entry.Data {
if !strings.HasPrefix(filepath.Dir(dataFile), yamlDir) {
log.Fatalf("Error: Data file %s does not have the same parent directory as YAML file %s\n", dataFile, entry.YAML)
}
}

// Create the directory for this key in the output path
targetDir := filepath.Join(*outputDir, key)
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
log.Fatalf("Failed to create directory %s: %v\n", targetDir, err)
}

// Copy the YAML file
if err := copyFile(entry.YAML, filepath.Join(targetDir, filepath.Base(entry.YAML))); err != nil {
log.Fatalf("Failed to copy YAML file for %s: %v\n", key, err)
}

// Copy each data file and preserve the relative directory structure
for _, dataFile := range entry.Data {
relativePath, err := filepath.Rel(yamlDir, dataFile)
if err != nil {
log.Fatalf("Failed to determine relative path for %s: %v\n", dataFile, err)
}

dataTargetPath := filepath.Join(targetDir, relativePath)
if err := os.MkdirAll(filepath.Dir(dataTargetPath), os.ModePerm); err != nil {
log.Fatalf("Failed to create directory %s: %v\n", filepath.Dir(dataTargetPath), err)
}

if err := copyFile(dataFile, dataTargetPath); err != nil {
log.Fatalf("Failed to copy data file %s for %s: %v\n", dataFile, key, err)
}
}
}
}

// copyFile copies a file from src to dst
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("unable to open source file: %w", err)
}
defer sourceFile.Close()

destinationFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("unable to create destination file: %w", err)
}
defer destinationFile.Close()

_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
return fmt.Errorf("failed to copy data: %w", err)
}

return nil
}
2 changes: 2 additions & 0 deletions helm/private/registrar/helm_registry.bat.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
ECHO OFF

SET "HELM_PLUGINS={helm_plugins}"

{image_pushers}
{helm} push {chart} {registry_url}
2 changes: 2 additions & 0 deletions helm/private/registrar/helm_registry.sh.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@

set -euo pipefail

export HELM_PLUGINS="{helm_plugins}"

{image_pushers}
eval exec {helm} push {chart} {registry_url}
Loading

0 comments on commit f0b4fa3

Please sign in to comment.