diff --git a/README.md b/README.md index b3a9eac..6ddba69 100755 --- a/README.md +++ b/README.md @@ -165,37 +165,6 @@ A rule for performing `helm lint` on a helm package | chart | The helm package to run linting on. | Label | required | | - - -## helm_package - -
-load("@rules_helm//helm:defs.bzl", "helm_package")
-
-helm_package(name, deps, chart, chart_json, crds, images, stamp, substitutions, templates, values,
-             values_json)
-
- -Rules for creating Helm chart packages. - -**ATTRIBUTES** - - -| Name | Description | Type | Mandatory | Default | -| :------------- | :------------- | :------------- | :------------- | :------------- | -| name | A unique name for this target. | Name | required | | -| deps | Other helm packages this package depends on. | List of labels | optional | `[]` | -| chart | The `Chart.yaml` file of the helm chart | Label | optional | `None` | -| chart_json | The `Chart.yaml` file of the helm chart as a json object | String | optional | `""` | -| crds | All crds associated with the current helm chart. E.g., the `./crds` directory | List of labels | optional | `[]` | -| images | A list of [oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) targets. | List of labels | optional | `[]` | -| stamp | Whether to encode build information into the helm actions. Possible values:

- `stamp = 1`: Always stamp the build information into the helm actions, even in [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. This setting should be avoided, since it potentially kills remote caching for the target and any downstream actions that depend on it.

- `stamp = 0`: Always replace build information by constant values. This gives good build result caching.

- `stamp = -1`: Embedding of build information is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.

Stamped targets are not rebuilt unless their dependencies change. | Integer | optional | `-1` | -| substitutions | A dictionary of substitutions to apply to the `values.yaml` file. | Dictionary: String -> String | optional | `{}` | -| templates | All templates associated with the current helm chart. E.g., the `./templates` directory | List of labels | optional | `[]` | -| values | The `values.yaml` file for the current package. | Label | optional | `None` | -| values_json | The `values.yaml` file for the current package as a json object. | String | optional | `""` | - - ## helm_plugin @@ -451,8 +420,8 @@ str: A json encoded string which represents `Chart.yaml` contents.
 load("@rules_helm//helm:defs.bzl", "helm_chart")
 
-helm_chart(name, chart, chart_json, crds, values, values_json, substitutions, templates, images,
-           deps, install_name, registry_url, login_url, helm_opts, install_opts, upgrade_opts,
+helm_chart(name, chart, chart_json, crds, values, values_json, substitutions, templates, files,
+           images, deps, install_name, registry_url, login_url, helm_opts, install_opts, upgrade_opts,
            uninstall_opts, data, stamp, **kwargs)
 
@@ -482,6 +451,7 @@ Rules for producing a helm package and some convenience targets. | values_json | The json encoded contents of `values.yaml`. | `None` | | substitutions | A dictionary of substitutions to apply to `values.yaml`. | `{}` | | templates | A list of template files to include in the package. | `None` | +| files | A a map with lists of files to be copied to their respective folders within the helm chart. | `{}` | | images | A list of [oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) targets | `[]` | | deps | A list of helm package dependencies. | `None` | | install_name | The `helm install` name to use. `name` will be used if unset. | `None` | @@ -496,6 +466,39 @@ Rules for producing a helm package and some convenience targets. | kwargs | Additional keyword arguments for `helm_package`. | none | + + +## helm_package + +
+load("@rules_helm//helm:defs.bzl", "helm_package")
+
+helm_package(name, chart, chart_json, crds, values, values_json, substitutions, templates, files,
+             images, deps, stamp, **kwargs)
+
+ +Rules for creating Helm chart packages. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | The name of the target | none | +| chart | "The `Chart.yaml` file of the helm chart" | `None` | +| chart_json | "The `Chart.yaml` file of the helm chart as a json object" | `None` | +| crds | All crds associated with the current helm chart. E.g., the `./crds` directory | `None` | +| values | The `values.yaml` file for the current package. | `None` | +| values_json | The `values.yaml` file for the current package as a json object. | `None` | +| substitutions | A dictionary of substitutions to apply to the `values.yaml` file. | `{}` | +| templates | All templates associated with the current helm chart. E.g., the `./templates` directory | `None` | +| files | Additional files to be added to the chart specified as a map from string to list of labels. | `{}` | +| images | A list of [oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) targets | `[]` | +| deps | Other helm packages this package depends on. | `None` | +| stamp | Whether to encode build information into the helm actions. Possible values:

- `stamp = 1`: Always stamp the build information into the helm actions, even in [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. This setting should be avoided, since it potentially kills remote caching for the target and any downstream actions that depend on it.

- `stamp = 0`: Always replace build information by constant values. This gives good build result caching.

- `stamp = -1`: Embedding of build information is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.

Stamped targets are not rebuilt unless their dependencies change. | `None` | +| kwargs | Additional keyword arguments. | none | + + ## helm_register_toolchains diff --git a/helm/private/helm.bzl b/helm/private/helm.bzl index 926d072..82dd743 100644 --- a/helm/private/helm.bzl +++ b/helm/private/helm.bzl @@ -13,6 +13,7 @@ def helm_chart( values_json = None, substitutions = {}, templates = None, + files = {}, images = [], deps = None, install_name = None, @@ -46,6 +47,7 @@ def helm_chart( values_json (str, optional): The json encoded contents of `values.yaml`. substitutions (dict, optional): A dictionary of substitutions to apply to `values.yaml`. templates (list, optional): A list of template files to include in the package. + files (map[string, list[label]], optional): A a map with lists of files to be copied to their respective folders within the helm chart. images (list, optional): A list of [oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) targets deps (list, optional): A list of helm package dependencies. install_name (str, optional): The `helm install` name to use. `name` will be used if unset. @@ -74,6 +76,7 @@ def helm_chart( deps = deps, images = images, templates = templates, + files = files, values = values, values_json = values_json, substitutions = substitutions, diff --git a/helm/private/helm_package.bzl b/helm/private/helm_package.bzl index 6b3995a..1e4cd8f 100644 --- a/helm/private/helm_package.bzl +++ b/helm/private/helm_package.bzl @@ -109,6 +109,10 @@ def _helm_package_impl(ctx): ) args.add("-templates_manifest", templates_manifest) + for ln in ctx.attr.files: + fmt = "%s=" + ctx.attr.files[ln] + args.add_all(ln.files, before_each = "-add_files", format_each = fmt, uniquify = True) + crds_manifest = ctx.actions.declare_file("{}/crds_manifest.json".format(ctx.label.name)) ctx.actions.write( output = crds_manifest, @@ -180,7 +184,7 @@ def _helm_package_impl(ctx): executable = ctx.executable._packager, outputs = [output, metadata_output], inputs = depset( - ctx.files.templates + ctx.files.crds + stamps + image_inputs + deps + [ + ctx.files.templates + ctx.files.files + ctx.files.crds + stamps + image_inputs + deps + [ chart_yaml, values_yaml, templates_manifest, @@ -208,7 +212,77 @@ def _helm_package_impl(ctx): ), ] -helm_package = rule( +def helm_package( + name, + chart = None, + chart_json = None, + crds = None, + values = None, + values_json = None, + substitutions = {}, + templates = None, + files = {}, + images = [], + deps = None, + stamp = None, + **kwargs): + """Rules for creating Helm chart packages. + + Args: + name: The name of the target + chart: "The `Chart.yaml` file of the helm chart" + chart_json: "The `Chart.yaml` file of the helm chart as a json object" + crds: All crds associated with the current helm chart. E.g., the `./crds` directory + values (str, optional): The `values.yaml` file for the current package. + values_json: The `values.yaml` file for the current package as a json object. + substitutions: A dictionary of substitutions to apply to the `values.yaml` file. + templates: All templates associated with the current helm chart. E.g., the `./templates` directory + files: Additional files to be added to the chart specified as a map from string to list of labels. + images: A list of [oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) targets + deps: Other helm packages this package depends on. + stamp: Whether to encode build information into the helm actions. Possible values: + + - `stamp = 1`: Always stamp the build information into the helm actions, even in \ + [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. \ + This setting should be avoided, since it potentially kills remote caching for the target and \ + any downstream actions that depend on it. + + - `stamp = 0`: Always replace build information by constant values. This gives good build result caching. + + - `stamp = -1`: Embedding of build information is controlled by the \ + [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag. + + Stamped targets are not rebuilt unless their dependencies change. + **kwargs (dict): Additional keyword arguments. + """ + + # We wrap this in a macro so that we can provide a better API for the `files` attribute + # If https://github.com/bazelbuild/bazel/issues/7989 ever gets implemented we can remove the macro + filegroups = {} + for folder, folder_files in files.items(): + native.filegroup( + name = "{}_filegroup".format(folder), + srcs = folder_files, + ) + filegroups[":{}_filegroup".format(folder)] = folder + + _helm_package( + name = name, + chart = chart, + chart_json = chart_json, + crds = crds, + deps = deps, + images = images, + templates = templates, + files = filegroups, + values = values, + values_json = values_json, + substitutions = substitutions, + stamp = stamp, + **kwargs + ) + +_helm_package = rule( implementation = _helm_package_impl, doc = "Rules for creating Helm chart packages.", attrs = { @@ -228,6 +302,11 @@ helm_package = rule( doc = "Other helm packages this package depends on.", providers = [HelmPackageInfo], ), + "files": attr.label_keyed_string_dict( + doc = "Additional files to be added to the chart specified as a map from string to list of labels.", + allow_empty = True, + allow_files = True, + ), "images": attr.label_list( doc = """\ A list of \ diff --git a/helm/private/packager/packager.go b/helm/private/packager/packager.go index 871a2d4..a7574c6 100644 --- a/helm/private/packager/packager.go +++ b/helm/private/packager/packager.go @@ -69,6 +69,35 @@ type HelmDependency struct { Alias string `yaml:"alias,omitempty"` } +type addFile struct { + src string + dst string +} + +func (r addFile) String() string { + return fmt.Sprintf("%s=%s", r.src, r.dst) +} + +type addFiles []addFile + +func (c *addFiles) String() string { + return fmt.Sprintf("%v", *c) +} + +func (c *addFiles) Set(s string) error { + split := strings.SplitN(s, "=", 2) + if len(split) != 2 { + return fmt.Errorf("parse addFile from %q", s) + } + src := split[0] + if len(src) == 0 { + return fmt.Errorf("empty src in %q", s) + } + dst := split[1] + *c = append(*c, addFile{src: src, dst: dst}) + return nil +} + type HelmChart struct { ApiVersion string `yaml:"apiVersion"` Name string `yaml:"name"` @@ -89,6 +118,7 @@ type HelmChart struct { type Arguments struct { TemplatesManifest string + AddFiles addFiles CrdsManifest string Chart string Values string @@ -108,6 +138,7 @@ func parseArgs() Arguments { var args Arguments flag.StringVar(&args.TemplatesManifest, "templates_manifest", "", "A helm file containing a list of all helm template files.") + flag.Var(&args.AddFiles, "add_files", "Additional files to be added to the chart") flag.StringVar(&args.CrdsManifest, "crds_manifest", "", "A helm file containing a list of all helm crd files.") flag.StringVar(&args.Chart, "chart", "", "The helm `chart.yaml` file.") flag.StringVar(&args.Values, "values", "", "The helm `values.yaml` file.") @@ -428,7 +459,67 @@ func copyFile(source string, dest string) error { return nil } -func installHelmContent(workingDir string, stampedChartContent string, stampedValuesContent string, templatesManifest string, crdsManifest string, depsManifest string) error { +func copyFileToDir(src string, dir string) error { + srcFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("open %s: %w", src, err) + } + defer srcFile.Close() + + err = os.MkdirAll(dir, 0700) + if err != nil && !os.IsExist(err) { + return fmt.Errorf("make dirs %s: %w", dir, err) + } + + dst := filepath.Join(dir, filepath.Base(src)) + + dstFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("create %s: %w", dst, err) + } + + _, err = io.Copy(dstFile, srcFile) + if errClose := dstFile.Close(); errClose != nil && err == nil { + err = errClose + } + return err +} + +func copyDir(src string, dst string) error { + var err error + var fds []os.DirEntry + + if _, err = os.Stat(src); err != nil { + return fmt.Errorf("file stat: %w", err) + } + + if err = os.MkdirAll(dst, 0700); err != nil { + if !os.IsExist(err) { + return fmt.Errorf("make dirs: %w", err) + } + } + + if fds, err = os.ReadDir(src); err != nil { + return fmt.Errorf("read dir: %w", err) + } + for _, fd := range fds { + srcfp := filepath.Join(src, fd.Name()) + dstfp := filepath.Join(dst, fd.Name()) + + if fd.IsDir() { + if err = copyDir(srcfp, dstfp); err != nil { + return fmt.Errorf("copy dir: %w", err) + } + } else { + if err = copyFile(srcfp, dstfp); err != nil { + return fmt.Errorf("copy file: %w", err) + } + } + } + return nil +} + +func installHelmContent(workingDir string, stampedChartContent string, stampedValuesContent string, templatesManifest string, addFiles addFiles, crdsManifest string, depsManifest string) error { err := os.MkdirAll(workingDir, 0700) if err != nil { return fmt.Errorf("Error creating working directory %s: %w", workingDir, err) @@ -635,6 +726,23 @@ func installHelmContent(workingDir string, stampedChartContent string, stampedVa } } + // Copy all additional files + for _, addFile := range addFiles { + dst := filepath.Join(workingDir, addFile.dst) + srcStat, err := os.Stat(addFile.src) + if err != nil { + return fmt.Errorf("stat: %w", err) + } + if srcStat.IsDir() { + err = copyDir(addFile.src, dst) + } else { + err = copyFileToDir(addFile.src, dst) + } + if err != nil { + return fmt.Errorf("copy %s to %s: %w", addFile.src, dst, err) + } + } + // Copy over any dependency chart files if len(depsManifest) > 0 { manifestContent, err := os.ReadFile(depsManifest) @@ -801,7 +909,7 @@ func main() { // Create a directory in which to run helm package tmpPath := filepath.Join(dir, chart.Name) - err = installHelmContent(tmpPath, stampedChartContent, stampedValuesContent, args.TemplatesManifest, args.CrdsManifest, args.DepsManifest) + err = installHelmContent(tmpPath, stampedChartContent, stampedValuesContent, args.TemplatesManifest, args.AddFiles, args.CrdsManifest, args.DepsManifest) if err != nil { log.Fatal(err) } diff --git a/tests/simple/BUILD.bazel b/tests/simple/BUILD.bazel index 6c584c6..88b61ac 100644 --- a/tests/simple/BUILD.bazel +++ b/tests/simple/BUILD.bazel @@ -1,8 +1,26 @@ load("//helm:defs.bzl", "helm_chart", "helm_lint_test", "helm_template_test") +genrule( + name = "generated", + outs = ["generated_file"], + cmd = "echo 'generated file' > $@", +) + helm_chart( name = "simple", chart = "Chart.yaml", + files = { + "": [ + "other_files/file1", + "other_files/file2", + ":generated", + ], + "folder1": [ + "other_files/file1", + "other_files/file2", + ":generated", + ], + }, registry_url = "oci://localhost/helm-registry", values = "values.yaml", ) diff --git a/tests/simple/other_files/file1 b/tests/simple/other_files/file1 new file mode 100644 index 0000000..f81fce0 --- /dev/null +++ b/tests/simple/other_files/file1 @@ -0,0 +1 @@ +this is a file diff --git a/tests/simple/other_files/file2 b/tests/simple/other_files/file2 new file mode 100644 index 0000000..77d8517 --- /dev/null +++ b/tests/simple/other_files/file2 @@ -0,0 +1 @@ +This is another file