Skip to content

Commit

Permalink
Add support for crds in the packaged chart
Browse files Browse the repository at this point in the history
  • Loading branch information
BabisK committed May 24, 2024
1 parent 55fa8f1 commit 8c9ee14
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 6 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.

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

0 comments on commit 8c9ee14

Please sign in to comment.