Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for crds in the packaged chart #85

Merged
merged 2 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ A rule for performing `helm lint` on a helm package
## helm_package

<pre>
helm_package(<a href="#helm_package-name">name</a>, <a href="#helm_package-deps">deps</a>, <a href="#helm_package-chart">chart</a>, <a href="#helm_package-chart_json">chart_json</a>, <a href="#helm_package-images">images</a>, <a href="#helm_package-stamp">stamp</a>, <a href="#helm_package-substitutions">substitutions</a>, <a href="#helm_package-templates">templates</a>, <a href="#helm_package-values">values</a>,
helm_package(<a href="#helm_package-name">name</a>, <a href="#helm_package-deps">deps</a>, <a href="#helm_package-chart">chart</a>, <a href="#helm_package-chart_json">chart_json</a>, <a href="#helm_package-crds">crds</a>, <a href="#helm_package-images">images</a>, <a href="#helm_package-stamp">stamp</a>, <a href="#helm_package-substitutions">substitutions</a>, <a href="#helm_package-templates">templates</a>, <a href="#helm_package-values">values</a>,
<a href="#helm_package-values_json">values_json</a>)
</pre>

Expand All @@ -177,6 +177,7 @@ Rules for creating Helm chart packages.
| <a id="helm_package-deps"></a>deps | Other helm packages this package depends on. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="helm_package-chart"></a>chart | The `Chart.yaml` file of the helm chart | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="helm_package-chart_json"></a>chart_json | The `Chart.yaml` file of the helm chart as a json object | String | optional | `""` |
| <a id="helm_package-crds"></a>crds | All crds associated with the current helm chart. E.g., the `./crds` directory | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="helm_package-images"></a>images | A list of [oci_push](https://github.com/bazel-contrib/rules_oci/blob/main/docs/push.md#oci_push_rule-remote_tags) targets. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="helm_package-stamp"></a>stamp | Whether to encode build information into the helm actions. Possible values:<br><br>- `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.<br><br>- `stamp = 0`: Always replace build information by constant values. This gives good build result caching.<br><br>- `stamp = -1`: Embedding of build information is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.<br><br>Stamped targets are not rebuilt unless their dependencies change. | Integer | optional | `-1` |
| <a id="helm_package-substitutions"></a>substitutions | A dictionary of substitutions to apply to the `values.yaml` file. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | `{}` |
Expand Down
6 changes: 6 additions & 0 deletions helm/private/helm.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def helm_chart(
name,
chart = None,
chart_json = None,
crds = None,
values = None,
values_json = None,
substitutions = {},
Expand Down Expand Up @@ -38,6 +39,7 @@ def helm_chart(
name (str): The name of the [helm_package](#helm_package) target.
chart (str, optional): The path to the chart directory. Defaults to `Chart.yaml`.
chart_json (str, optional): The json encoded contents of `Chart.yaml`.
crds (list, optional): A list of crd files to include in the package.
values (str, optional): The path to the values file. Defaults to `values.yaml`.
values_json (str, optional): The json encoded contents of `values.yaml`.
substitutions (dict, optional): A dictionary of substitutions to apply to `values.yaml`.
Expand All @@ -58,10 +60,14 @@ def helm_chart(
if templates == None:
templates = native.glob(["templates/**"])

if crds == None:
crds = native.glob(["crds/**"], allow_empty = True)

helm_package(
name = name,
chart = chart,
chart_json = chart_json,
crds = crds,
deps = deps,
images = images,
templates = templates,
Expand Down
14 changes: 13 additions & 1 deletion helm/private/helm_package.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ def _helm_package_impl(ctx):
)
args.add("-templates_manifest", templates_manifest)

crds_manifest = ctx.actions.declare_file("{}/crds_manifest.json".format(ctx.label.name))
ctx.actions.write(
output = crds_manifest,
content = json.encode_indent({file.path: file.short_path for file in ctx.files.crds}, indent = " " * 4),
)
args.add("-crds_manifest", crds_manifest)

deps = []
if ctx.attr.deps:
deps.extend([dep[HelmPackageInfo].chart for dep in ctx.attr.deps])
Expand Down Expand Up @@ -159,7 +166,7 @@ def _helm_package_impl(ctx):
executable = ctx.executable._packager,
outputs = [output, metadata_output],
inputs = depset(
ctx.files.templates + stamps + image_inputs + deps + [chart_yaml, values_yaml, templates_manifest, substitutions_file],
ctx.files.templates + ctx.files.crds + stamps + image_inputs + deps + [chart_yaml, values_yaml, templates_manifest, crds_manifest, substitutions_file],
),
tools = depset([toolchain.helm]),
mnemonic = "HelmPackage",
Expand Down Expand Up @@ -192,6 +199,11 @@ helm_package = rule(
"chart_json": attr.string(
doc = "The `Chart.yaml` file of the helm chart as a json object",
),
"crds": attr.label_list(
doc = "All crds associated with the current helm chart. E.g., the `./crds` directory",
default = [],
allow_files = True,
),
"deps": attr.label_list(
doc = "Other helm packages this package depends on.",
providers = [HelmPackageInfo],
Expand Down
105 changes: 101 additions & 4 deletions helm/private/packager/packager.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type ImageIndex struct {
}

type TemplatesManfiest map[string]string
type CrdsManfiest map[string]string
type DepsManfiest []string

type HelmResultMetadata struct {
Expand Down Expand Up @@ -86,6 +87,7 @@ type HelmChart struct {

type Arguments struct {
TemplatesManifest string
CrdsManifest string
Chart string
Values string
Substitutions string
Expand All @@ -103,6 +105,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.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.")
flag.StringVar(&args.Substitutions, "substitutions", "", "A json file containing key value pairs to substitute into the values file")
Expand Down Expand Up @@ -381,7 +384,7 @@ func copyFile(source string, dest string) error {
return nil
}

func installHelmContent(workingDir string, stampedChartContent string, stampedValuesContent string, templatesManifest string, depsManifest string) error {
func installHelmContent(workingDir string, stampedChartContent string, stampedValuesContent string, templatesManifest string, crdsManifest string, depsManifest string) error {
err := os.MkdirAll(workingDir, 0700)
if err != nil {
return fmt.Errorf("Error creating working directory %s: %w", workingDir, err)
Expand All @@ -399,13 +402,13 @@ func installHelmContent(workingDir string, stampedChartContent string, stampedVa
return fmt.Errorf("Error writing values file %s: %w", valuesYaml, err)
}

manifestContent, err := os.ReadFile(templatesManifest)
templatesManifestContent, err := os.ReadFile(templatesManifest)
if err != nil {
return fmt.Errorf("Error reading templates manifest %s: %w", templatesManifest, err)
}

var templates TemplatesManfiest
err = json.Unmarshal(manifestContent, &templates)
err = json.Unmarshal(templatesManifestContent, &templates)
if err != nil {
return fmt.Errorf("Error unmarshalling templates manifest %s: %w", templatesManifest, err)
}
Expand Down Expand Up @@ -494,6 +497,100 @@ func installHelmContent(workingDir string, stampedChartContent string, stampedVa

}

crdsManifestContent, err := os.ReadFile(crdsManifest)
if err != nil {
return fmt.Errorf("Error reading crds manifest %s: %w", crdsManifest, err)
}

var crds CrdsManfiest
err = json.Unmarshal(crdsManifestContent, &crds)
if err != nil {
return fmt.Errorf("Error unmarshalling crds manifest %s: %w", crdsManifest, err)
}

crdsDir := filepath.Join(workingDir, "crds")
crdsRoot := ""

// Copy all templates
for crdPath, crdShortpath := range crds {
fileInfo, err := os.Stat(crdPath)
if err != nil {
return fmt.Errorf("Error getting info for %s: %w", crdPath, err)
}

if fileInfo.IsDir() {
destDirBasePath := filepath.Join(crdsDir) // Destination is the base crds directory

// Walk the source directory and copy each item to the destination
err := filepath.Walk(crdPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("Error during walking the directory %s: %w", path, err)
}

relPath, err := filepath.Rel(crdPath, path)
if err != nil {
return fmt.Errorf("Error calculating relative path from %s to %s: %w", crdPath, path, err)
}

targetPath := filepath.Join(destDirBasePath, relPath)

if info.IsDir() {
return os.MkdirAll(targetPath, 0750)
} else {
if err := os.MkdirAll(filepath.Dir(targetPath), 0750); err != nil {
return fmt.Errorf("Error creating directory %s: %w", targetPath, err)
}
// Copy the file to the target path
return copyFile(path, targetPath)
}
})

if err != nil {
return fmt.Errorf("Error copying directory contents from %s to %s: %w", crdPath, destDirBasePath, err)
}
} else {
// Locate the templates directory so we can start copying files
// into the new templates directory at the right location
if len(crdsRoot) == 0 {
var current = filepath.Clean(crdShortpath)
for {
if len(current) == 0 {
return errors.New("Failed to find crds directory")
}
parent := filepath.Dir(current)
if filepath.Base(parent) == "crds" {
crdsRoot = filepath.Clean(parent)
break
}
current = parent
}
}

if !strings.HasPrefix(filepath.Clean(crdShortpath), crdsRoot) {
return fmt.Errorf(
"Crd path (%s) does not start with crd root (%s)",
filepath.Clean(crdShortpath), crdsRoot)
}

targetFile, err := filepath.Rel(crdsRoot, crdShortpath)
if err != nil {
return err
}

crdDest := filepath.Join(crdsDir, targetFile)
crdDestDir := filepath.Dir(crdDest)
err = os.MkdirAll(crdDestDir, 0700)
if err != nil {
return fmt.Errorf("Error creating crd parent directory %s: %w", crdDestDir, err)
}

err = copyFile(crdPath, crdDest)
if err != nil {
return fmt.Errorf("Error copying crd %s: %w", crdPath, err)
}
}
}

// Copy over any dependency chart files
if len(depsManifest) > 0 {
manifestContent, err := os.ReadFile(depsManifest)
Expand Down Expand Up @@ -660,7 +757,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.DepsManifest)
err = installHelmContent(tmpPath, stampedChartContent, stampedValuesContent, args.TemplatesManifest, args.CrdsManifest, args.DepsManifest)
if err != nil {
log.Fatal(err)
}
Expand Down
26 changes: 26 additions & 0 deletions tests/simple/crds/test.crd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must be in the form: <plural>.<group>
name: myapps.example.com
spec:
# group name to use for REST API: /apis/<group>/<version>
group: example.com
scope: Namespaced
names:
# kind is normally the CamelCased singular type.
kind: MyApp
# singular name to be used as an alias on the CLI
singular: myapp
# plural name in the URL: /apis/<group>/<version>/<plural>
plural: myapps
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
x-kubernetes-preserve-unknown-fields: true
Loading