|
1 | 1 | # Templating |
2 | 2 |
|
3 | | -The framework uses Go's `text/template` engine to render `values.yaml.tpl` into Helm values before chart installation. This enables dynamic value generation based on installer configuration, cluster introspection, and live Kubernetes resources. |
| 3 | +The framework uses Go's `html/template` engine to render `values.yaml.tpl` into Helm values before chart installation. This enables dynamic value generation based on installer configuration, cluster introspection, and live Kubernetes resources. |
4 | 4 |
|
5 | 5 | This page covers the template context structure, available functions, rendering lifecycle, and common patterns. For configuration schema and ConfigMap storage, see [configuration.md](configuration.md). For installer directory layout and `values.yaml.tpl` placement, see [installer-structure.md](installer-structure.md). |
6 | 6 |
|
@@ -310,11 +310,136 @@ foundation: |
310 | 310 | {{- end }} |
311 | 311 | ``` |
312 | 312 |
|
| 313 | +## Production Patterns |
| 314 | + |
| 315 | +The patterns above show individual template features in isolation. This section covers how to structure a multi-product `values.yaml.tpl` at production scale — preambles, derived variables, YAML anchors, and cross-product logic. |
| 316 | + |
| 317 | +### Preamble Structure |
| 318 | + |
| 319 | +Production templates start with a preamble that extracts products and cluster state using `required()`, then derives shared variables: |
| 320 | + |
| 321 | +```yaml |
| 322 | +--- |
| 323 | +{{- $productA := required "Product_A" .Installer.Products.Product_A -}} |
| 324 | +{{- $productB := required "Product_B" .Installer.Products.Product_B -}} |
| 325 | +{{- $domain := required "OpenShift domain" .OpenShift.Ingress.Domain -}} |
| 326 | +{{- $routerCA := required "OpenShift router CA" .OpenShift.Ingress.RouterCA -}} |
| 327 | +
|
| 328 | +{{- $crc := dig "crc" false .Installer.Settings -}} |
| 329 | +{{- $protocol := "https" -}} |
| 330 | +{{- if $crc }}{{ $protocol = "http" }}{{ end -}} |
| 331 | +
|
| 332 | +{{- $keycloakEnabled := or $productA.Enabled $productB.Enabled -}} |
| 333 | +``` |
| 334 | + |
| 335 | +Use `required()` for values that must exist — template rendering fails early with a descriptive error instead of producing broken YAML. |
| 336 | + |
| 337 | +### Mid-File Variables |
| 338 | + |
| 339 | +Not all variables belong in the preamble. Variables scoped to a single section can be defined immediately before that section: |
| 340 | + |
| 341 | +```yaml |
| 342 | +{{- $databaseSecretName := dig "databaseSecret" "my-db-secret" $productA.Properties -}} |
| 343 | +
|
| 344 | +# |
| 345 | +# product-a |
| 346 | +# |
| 347 | +productA: |
| 348 | + database: |
| 349 | + secretName: {{ $databaseSecretName }} |
| 350 | +``` |
| 351 | + |
| 352 | +This is a pragmatic alternative to a monolithic preamble — variables stay near their usage, making maintenance easier. |
| 353 | + |
| 354 | +### Section Structure |
| 355 | + |
| 356 | +Each chart gets a comment block and root-key section. Fields are rendered from preamble or section-local variables: |
| 357 | + |
| 358 | +```yaml |
| 359 | +# |
| 360 | +# chart-name |
| 361 | +# |
| 362 | +rootKey: |
| 363 | + enabled: {{ $productA.Enabled }} |
| 364 | + namespace: {{ $productA.Namespace }} |
| 365 | + url: {{ $protocol }}://app.{{ $domain }} |
| 366 | +``` |
| 367 | + |
| 368 | +### YAML Anchors |
| 369 | + |
| 370 | +Use YAML anchors (`&name` / `*name`) for shared configuration blocks — database credentials, OIDC settings, ingress config. Anchors also bridge values across root keys for multi-component charts: |
| 371 | + |
| 372 | +```yaml |
| 373 | +trustedProfileAnalyzer: |
| 374 | + database: &tpaDatabase |
| 375 | + name: |
| 376 | + valueFrom: |
| 377 | + secretKeyRef: |
| 378 | + name: {{ $databaseSecretName }} |
| 379 | + key: dbname |
| 380 | + createDatabase: *tpaDatabase |
| 381 | + migrateDatabase: *tpaDatabase |
| 382 | + storage: &tpaStorage |
| 383 | + type: filesystem |
| 384 | + size: 32Gi |
| 385 | + oidc: &tpaOIDC |
| 386 | + issuerUrl: {{ $oidcIssuerURL }} |
| 387 | +
|
| 388 | +trustification: |
| 389 | + storage: *tpaStorage |
| 390 | + oidc: *tpaOIDC |
| 391 | +``` |
| 392 | + |
| 393 | +Companion charts can inherit their parent's entire configuration via anchors (e.g., `acsTest: *acs`). |
| 394 | + |
| 395 | +### URL Composition |
| 396 | + |
| 397 | +Always compose URLs from `$protocol` + `$domain`. Never hardcode protocol or domain: |
| 398 | + |
| 399 | +```yaml |
| 400 | +appUrl: {{ $protocol }}://app.{{ $domain }} |
| 401 | +``` |
| 402 | + |
| 403 | +This ensures CRC/development-mode toggles and domain changes propagate everywhere. |
| 404 | + |
| 405 | +### Cross-Product Conditionals |
| 406 | + |
| 407 | +Extract composite booleans to named variables instead of writing inline logic: |
| 408 | + |
| 409 | +```yaml |
| 410 | +{{- $keycloakEnabled := or $tpa.Enabled $tas.Enabled (and $rhdh.Enabled (eq $authProvider "oidc")) -}} |
| 411 | +``` |
| 412 | + |
| 413 | +Use `and`/`or` (variadic Sprig functions), not `&&`/`||`. |
| 414 | + |
| 415 | +### Core Sprig Subset |
| 416 | + |
| 417 | +The following functions cover the vast majority of production templates: `required`, `dig`, `default`, `printf`, `indent`, `toYaml`, `and`, `or`, `not`, `eq`, `list`, `range`. Start with these and add others only when the pattern demands it. The full Sprig library is available — see [Sprig documentation](https://masterminds.github.io/sprig/) for edge cases. |
| 418 | + |
| 419 | +## Anti-Patterns |
| 420 | + |
| 421 | +Common mistakes to avoid in `values.yaml.tpl` authoring: |
| 422 | + |
| 423 | +| Anti-Pattern | Why It Breaks | Fix | |
| 424 | +|---|---|---| |
| 425 | +| Hardcoded URLs | Breaks CRC/dev mode, ignores domain config | Compose from `$protocol` + `$domain` | |
| 426 | +| Duplicated values across sections | Drift between copies, maintenance burden | Derive once in preamble or section-local variable | |
| 427 | +| Inline boolean logic | Unreadable, error-prone in complex cases | Extract to named variables (e.g., `$keycloakEnabled`) | |
| 428 | +| Missing `required()` for mandatory paths | Silent nil renders as `<nil>` in YAML | Wrap product and cluster extractions with `required()` | |
| 429 | +| Unfulfilled `__OVERWRITE_ME__` placeholders | Chart receives placeholder string at deploy time | Every placeholder in values.yaml must have a corresponding template expression | |
| 430 | +| `nindent` not last in pipeline | Indentation applied before serialization | Always: `toYaml \| nindent N` (serialization before indentation) | |
| 431 | +| `index` instead of `dig` | Errors or returns zero value on missing intermediate keys; no default value support | Use `dig` with a default value for safe deep access | |
| 432 | +| Unintentional multiple root keys | Chart receives values from wrong root | One primary root key per chart; secondary keys only for sub-components or debug flags | |
| 433 | + |
| 434 | +### Engine Caveat |
| 435 | + |
| 436 | +Helmet uses `html/template` (not `text/template`). HTML entities (`<`, `>`, `&`) are auto-escaped in all pipeline output — including strings returned by `toYaml` and `toJson`. In practice, this only affects values containing `<`, `>`, or `&` literals; most Kubernetes configuration values do not include these characters. |
| 437 | + |
313 | 438 | ## Scope Boundaries |
314 | 439 |
|
315 | 440 | This document covers template rendering mechanics and the `values.yaml.tpl` syntax. Related topics: |
316 | 441 |
|
317 | | -- **Configuration schema**: See [configuration.md](configuration.md) for `config.yaml` structure, product specifications, and ConfigMap persistence |
| 442 | +- **Configuration schema**: See [configuration.md](configuration.md) for `config.yaml` structure, product specifications, [the triad](configuration.md#the-triad), and ConfigMap persistence |
318 | 443 | - **Installer packaging**: See [installer-structure.md](installer-structure.md) for directory layout, embedding, and the overlay filesystem |
319 | | -- **Dependency resolution**: See [topology.md](topology.md) for chart ordering and annotation-based dependencies |
| 444 | +- **Dependency resolution**: See [topology.md](topology.md) for chart ordering, annotation-based dependencies, and [companion chart patterns](topology.md#companion-charts--multi-root-key-patterns) |
320 | 445 | - **OpenShift introspection**: Implemented in `internal/k8s/openshift.go` |
0 commit comments