Files
Adam CarhedenandHugo Shaka e27404bd9d Factor teleport-proxy-lib library chart out of teleport-cluster (#67296)
* Factor teleport-proxy-lib library chart out of teleport-cluster helm chart

* Remove 'FQDN' suffix from variable name and clean up comments

* Restored $proxy in config templates to minimize differences in review.

* Restored unnecessary changes to ingress template

* Fix unnecessary change in predeploy job

* used deep copy for .Values

* Explain renter-time _validation in comment

* Set domain for proxy config test

* Fixed snapshot and test differences

* Cleaned up comments

* Replace mistakenly removed asserts

* Added forceHAReplicas tests

* Fixed edge case of replicas=0, preserving previous behavior

* Eliminated intermediate version/majorVersion templates and used util lib templates directly

* Update examples/chart/lib/teleport-proxy-lib/templates/_ingress.tpl

Co-authored-by: Hugo Shaka <hugo.hervieux@goteleport.com>

* Removed unneeded alias for teleport-proxy-lib

* Corrected teleport-cluster schema bugs and removed teleport-proxy-lib validation not also done by schema

---------

Co-authored-by: Hugo Shaka <hugo.hervieux@goteleport.com>
2026-06-10 22:22:00 +00:00

7.0 KiB

Library Helm Charts

This directory contains Helm Library charts used by multiple Application (i.e. "normal") charts. Library charts are kept here to distinguish them from those meant to be installed by end users.

What is a library chart?

A Library Chart is one that has type: library in Chart.yaml. Such charts behave differently in two ways:

  1. Helm will actively refuse to install them.

    $ helm install my-release .
    Error: INSTALLATION FAILED: library charts are not installable
    
  2. They render no templates

    Helm still registers the chart's named templates ({{ define ... }}) into the parent chart's global scope, but it does not render the library chart's own manifest templates. Files that don't start with _ are ignored.

Interface Contract

Library charts defined here should define Named Templates designed to take a copy of the calling chart's root context.

{{- include "my-lib-chart.my-template" . -}}

This follows patterns established by:

This means:

  • Parent charts should define top-level values the library chart's templates consume

    # parent/values.yaml
    
    # Correct!
    myLibChartValue: foo
    
    ## WRONG! Don't Copy ME
    my-lib-chart:
      myLibChartValue: bar
    
  • Library chart templates should consume them at:

    • .Values.myLibChartValue

    NOT:

    • .myLibChartValue # WRONG, DON'T COPY
    • .Values.my-lib-chart.myLibChartValue # WRONG, DON'T COPY

Value Defaults / Schema Validation

Library charts' default values and schema validation are processed just like subchart dependencies. This means that if a library chart has a values.yaml and/or values.schema.json, these will apply to the parent chart at .Values.<subkey>.*, not at the root level. This doesn't follow the pattern above, nor does it allow the parent chart to deviate from the dependent library chart when it needs different validation (i.e. if the parent chart manipulates the context passed to the library chart's named templates, thereby making the library chart's validation invalid). Library charts should therefore generally not define values or schema.

This does mean that multiple charts that depend on the same library chart will likely duplicate values and schema for the library chart. Helm has no great way to move that responsibility to library charts without limiting how the parent chart can define its required values.

Library charts can instead validate their inputs at render-time by having all public named templates call an internal validation template.

{{- define "my-lib-chart.my-template" -}}
{{- include "my-lib-chart.internal.validate" . -}}
...
{{- end -}}

{{- define "my-lib-chart.internal.validate" -}}
{{- if not (and (hasKey .Values "myRequiredValue") (kindIs "string" .Values.myRequiredValue)) -}}
{{- fail "'myRequiredValue' is required" -}}
{{- end -}}
...
{{- end -}}

Value Merging

It's possible to merge library chart values from the parent chart's root values and the library chart's subkey with an init helper called by library chart templates (example) to define values at multiple levels. However, including values and schema in the library chart still only applies defaults and validation to values defined under the library chart's subkey (.Values.my-lib-chart), so something like .Values.myLibChartValue still needs independent validation by the parent's schema.

Documentation

A library chart's public interface is its public (non-internal) named templates and the .Values keys they read. Document that interface in a README.md at the chart root (e.g. lib/teleport-proxy-lib/README.md). It should list each public template, how to call it, and the values it requires — plus any values the consuming chart must compute and inject.

Do not rely on values.yaml or values.schema.json for this. As described under "Value Defaults / Schema Validation" above, Helm applies a library chart's values.yaml/schema at .Values.<subkey>, not where the templates read, so they cannot serve as the interface contract.

Testing

Because they don't render templates, library charts can't be tested using helm unittest. To work around this, library charts should define an application chart as a test fixture/helper in the test-chart/ directory under their root. This chart should include the library chart with a symlink (my-lib-chart/test-chart/charts/my-lib-chart -> ../../). It should define templates and unit tests that call the library chart templates. Don't forget to add both the library chart AND test-chart to Helm Janitor (teleport/build.assets/tooling/cmd/helm-janitor/main.go).

Scoping & Naming

All named templates defined in a library chart are exported into the parent's global namespace. All named templates should therefore be prefixed with the library chart's name to avoid naming collisions:

{{- define "my-lib-chart.my-template" -}}

Kebab-case vs. camelCase

Use kebab-case for chart names and named-template names, and camelCase for values/field keys. The two never collide:

  • Named-template names are always quoted strings in define/include ({{ include "my-lib-chart.my-template" . }}), so a - is just a character in a string — it is never parsed as an identifier.
  • Value keys are read with dot notation (.Values.myValue). Go templates parse - as subtraction, so a dashed key like .Values.my-value won't work — you'd have to write index .Values "my-value". camelCase keeps dot access working.

Abstraction (if you must)

Helm does allow naming collisions, in which case the parent chart's version will be rendered. This can be used as a form of abstraction:

# my-lib-chart/templates/_helpers.tpl
{{- define "my-lib-chart.my-template" -}}
{{ fail "REDEFINE ME IN THE CALLING CHART PLEASE." }}
{{- end -}}
# parent/templates/_helpers.tpl
{{- define "my-lib-chart.my-template" -}}
A concrete implementation of my-template in parent chart.
{{- end -}}

This is acceptable, but should be well documented.

Internal / Private Named Templates

Helm has no way to protect / privatize some subset of a library chart's templates. To distinguish the library chart's public interface from templates not intended for consumption, "private" templates should have an .internal component to their name:

{{- define "my-lib-chart.internal.my-internal-template" -}}