Skip to content

3coma3/helm-template-utils

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Helm template utilities

Reusable helper templates for Kubernetes manifests


About

This section explains the terminology we use to for data structures and how the template code deals with type errors. Feel free to skip directly to the helper reference and come back later as needed.

This library contains helpers that simplify common tasks in Helm templating.

While all the code operates on YAML data, it focus on Helm Objects (most frequently Values) - which are YAML trees of key/value mappings represented in Go -and thus Helm- as string-keyed maps. In other words: map[string]interface{}.

To disambiguate these and other entities with similar but not-quite-exactly-equal semantics across the different environments related to Helm, we'll strive for consistency in the words used throughout code and documentation:

helm-template-utils Helm Sprig Go text/template YAML JSON
scalar scalar scalar scalar Scalar Scalar
map map dict struct, map Mapping Object
(map) member k/v pair k/v pair field, element Mapping node Member
(k/v pair) key key key name, key Key Name
list list, slice list slice Block Sequence Array
(list) item item item element Block Sequence node Element
value value value value Value Value
  • With list we refer to Helm lists (which are referred to also as slices in Helm's documentation)

  • With map we refer to Helm maps

  • With key we refer to the key in a Helm key/value pair

  • With value we refer to the universal idea of a value, but often to the value in a Helm key/value pair

Error handling

The takeaway:

"not all valid YAML is valid Helm Values, and not all valid Helm Values are valid Kubernetes manifest data"

Examples:

---
# ✅ YAML: parses this as a single scalar value (string)
# ❌ Helm: the root node has to be a map
# ❓ K8S: can't be a whole manifest, but it can appear as data
"hello from root node"

---
# ❌ YAML: if the root node is a scalar, that's all the data the document will have 
# ❌ Helm: can't parse invalid YAML
# ❌ K8S: same as Helm
"hello from root"
"two root nodes? no way!"

---
# ✅ YAML: the root node can be a Scalar, Sequence or Mapping
# ❌ Helm: the root node has to be an explicit map
# ❓ K8S: can't be a whole manifest, but it can appear as data
- 1
  2
  3

---
# ✅ YAML ✅ Helm ✅ Kubernetes
# Scalar value in Helm (keyed, this is a Go map)
# { key: "configurationKey1", value: true }
configurationKey1: true

---
# ✅ YAML ✅ Helm ✅ Kubernetes
# Nested map, (top-level map with the value being a map itself)
# { key: "aMap", value: { "subKey1": "a string", "subKey2": 42 } }
aMap:
  subKey1: "a string"
  subKey2: 42

---
# ✅ YAML ✅ Helm ❌ Kubernetes
# Mixed list, (top-level map with the value being a list with one map entry and one plain string)
# { key: "aList", value: [ { "subKey1": "a string" }, "other string" ] }
aList:
  - subKey1: "a string"
  - "other string"

We see that some values that are valid YAML won’t work as Helm values. Also, some values that are valid in YAML and Helm may still be considered invalid or unsupported by this library when generating content for Kubernetes manifests.

To avoid surprises keep in mind that the code currently filters out invalid Kubernetes data without warnings or errors. For example, if you process the aList list with toEnv, the plain string "other string" will be silently ignored, because it lacks a key and cannot be mapped to a valid EnvVar.

In future expansions, optional levels of strictness might be added that would have some helpers work more as lightweight static checkers, to help with debugging or enforcing guarantees on the input. In the current state the code is intended to "just work" with common real-world input, and behaves more like a filter than a checker, skipping over data that it can't validate.


Templates

📦 util.SSCase

Normalizes strings into SCREAMING_SNAKE_CASE (useful for EnvVar name formatting)

Usage

{{ include "util.SSCase" string }}

Behaviors

  • Converts all non-alphanumeric characters to underscores (_)
  • Deduplicates and trims leading and trailing underscores
  • Filters the result through snakecase and uppercase

📦 util.toEnv

Maps values to Kubernetes EnvVar fields

Usage

# Process target values, no prefix is added to variable names
{{ include "util.toEnv" targetKey }}

# Process target values and add a custom prefix to variable names
{{ include "util.toEnv" (list targetKey "custom prefix") }}

# Process target values and use target key as prefix 
{{ include "util.toEnv" (list targetParentKey "targetSubkey") }}

Behaviors

Accepted target values
Target values Outcome
scalar renders one EnvVar item
valueFrom checked via util.leafKind and rendered as one EnvVar item
map renders multiple EnvVar items
list renders multiple EnvVar items
improper valueFrom maps ignored
list items other than scalar or valueFrom ignored
map members other than scalar or valueFrom ignored
undefined, null, invalid ignored
Optional prefixing for generated variable names
  • A second string argument becomes a prefix for the generated names
  • If the prefix exists as a member key in the target map, that member becomes the target for processing. This is a shorthand notation to avoid passing the same key twice when wanting to use it as the prefix.
Name and prefix normalization

Example use

Input values:

database:
  host: localhost
  port: 5432

Include:

env: {{ include "util.toEnv" (list .Values.database "DB config") }}

Rendered output:

- name: DB_CONFIG_HOST
  value: "localhost"
- name: DB_CONFIG_PORT
  value: "5432"

More examples can be found in the test chart in this repo.

Future enhancements

  • configurable strictness (extra leafKind typechecks, warn or error instead of discard)
  • regex filters for keys or values

📦 util.leafKind

This is used as a toEnv helper that checks the kind of its context and returns it as string for leaf values (scalars and valueFrom), or null otherwise

Usage

# use as a predicate
{{ if include "util.leafKind" target }} ...

# use as a predicate and save the result in a single pass
{{ if $kind := include "util.leafKind" target }} ...

Behaviors

  • A scalar ( bool, int, int64 ,float64 or string) will render its kind
  • A well-formed valueFrom will render the string "valueFrom"

"Well-formed" means for now a trivial check that we have a map with a single valueFrom member, no comprehensive guarantees are issued.

Future enhancements

  • optional extra checks on valueFrom

📦 util.isKeyValue

A simple predicate testing for maps with a single member

Usage

{{ if include "util.isKeyValue" target }} ...

Behaviors

  • Renders "true" if the input is a map with exactly one key, otherwise it renders nothing

Testing

The templates and assertions in the test chart should cover every possible case, but if you spot omissions be welcome to open an issue about it.

Run the test template with:

make test

To run the helm-unittest templates, install that plugin and run:

make unittest

About

Reusable helper templates for Kubernetes manifests

Resources

License

Stars

Watchers

Forks

Packages

No packages published