Skip to content

Commit 049fbd4

Browse files
authored
Merge pull request #55 from otaviof/RHTAP-6544
RHTAP-6544: Foundation & Agent-Ready Documentation
2 parents 73a197b + 96b2765 commit 049fbd4

6 files changed

Lines changed: 402 additions & 21 deletions

File tree

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ The result is a `Topology` representing the sorted installation order. See [topo
100100

101101
### 3. Render Values Template
102102

103-
The `engine.Engine` processes `values.yaml.tpl` using Go's `text/template` with:
103+
The `engine.Engine` processes `values.yaml.tpl` using Go's `html/template` with:
104104

105105
- **Sprig Functions**: Full Sprig library
106106
- **Custom Functions**: `toYaml`, `fromYaml`, `fromYamlArray`, `toJson`, `fromJson`, `fromJsonArray`, `required`, `lookup`

docs/configuration.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ Examples:
7272
- `Product A` → `Product_A`
7373
- `my-product` → `my_product`
7474

75+
### The Triad
76+
77+
Every product chart creates three linked identifiers that must stay consistent
78+
across config.yaml, Chart.yaml, and values.yaml.tpl:
79+
80+
| Identifier | Source | Appears In |
81+
|---|---|---|
82+
| product name | config.yaml `name` field | Chart.yaml `product-name` annotation |
83+
| keyName | Sanitized product name (see [KeyName conversion](#product-name-and-keyname) above) | `.Installer.Products.<keyName>` in values.yaml.tpl |
84+
| root-key | Chart's values.yaml primary root attribute | values.yaml.tpl section header |
85+
86+
The root-key is conventionally derived from the chart name (not the keyName).
87+
The naming style is project-level — use camelCase, snake_case, or lowercase
88+
consistently within a project. What matters is uniqueness across all charts:
89+
the framework passes the full rendered values to every chart, so each chart's
90+
templates access only their root key via `.Values.rootKey`.
91+
92+
Infrastructure charts (no `product-name` annotation) are outside the triad —
93+
they have a root-key and a values.yaml.tpl section but no config.yaml entry.
94+
See [Topology: Infrastructure Charts](topology.md#infrastructure-charts--derived-products).
95+
96+
When adding or modifying a product, update all three artifacts together:
97+
config.yaml, Chart.yaml annotation, and values.yaml.tpl section header.
98+
7599
### Namespace Resolution
76100

77101
Products use the following namespace resolution order:
@@ -249,7 +273,7 @@ The `ApplyDefaults()` method propagates the installer namespace to products with
249273

250274
## Cross-References
251275

252-
- [Topology](topology.md) — chart annotations, dependency resolution, and installation order
253-
- [Templating](templating.md) — values.yaml.tpl syntax and template functions
276+
- [Topology](topology.md) — chart annotations, dependency resolution, installation order, [infrastructure charts](topology.md#infrastructure-charts--derived-products)
277+
- [Templating](templating.md) — values.yaml.tpl syntax, template functions, [production patterns](templating.md#production-patterns)
254278
- [MCP Server](mcp.md) — configuration tools for AI-assisted workflows
255279
- [CLI Reference](cli-reference.md) — `config` command usage

docs/installer-structure.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,82 @@ func NewAppFromTarball(
202202
3. Wrap in `ChartFS`: `chartfs.New(ofs)`
203203
4. Pass to `NewApp()` with all options applied
204204

205+
## Build Pipeline
206+
207+
The installer build process has a strict dependency: the tarball must be created before the Go binary is compiled, because `go:embed` requires the tarball file to exist at build time.
208+
209+
### Makefile Targets
210+
211+
| Target | Purpose | Notes |
212+
|---|---|---|
213+
| `installer-tarball` | Package installer directory into `installer.tar` | Must run before `build` |
214+
| `build` | Compile Go binary with version injection | Depends on `installer-tarball` |
215+
| `image` | Build container image | Multi-stage Containerfile |
216+
| `lint` | Run linters (`golangci-lint`) | Code quality checks |
217+
| `security` | Run security scanners (`govulncheck`) | Vulnerability detection |
218+
219+
### Tarball-Before-Build Dependency
220+
221+
The `installer-tarball` target must run before `go build`. The `build` target should declare this dependency explicitly:
222+
223+
```makefile
224+
build: installer-tarball
225+
go build -ldflags "-X main.version=$(VERSION) -X main.commitID=$(COMMIT_ID)" -o bin/my-app ./cmd
226+
```
227+
228+
If `installer.tar` does not exist when `go build` runs, the `go:embed` directive in `embed.go` fails with a compilation error.
229+
230+
### GNU tar on macOS
231+
232+
On macOS, use GNU tar (`gtar`) instead of the system BSD tar for consistent behavior. BSD tar and GNU tar handle symlinks, permissions, and extended attributes differently. Install via `brew install gnu-tar`.
233+
234+
### Container Image
235+
236+
The Containerfile uses a multi-stage build:
237+
238+
1. **Builder stage**: Compiles the Go binary with build args for `BUILD_VERSION` and `COMMIT_ID` injected via ldflags
239+
2. **Runtime stage**: Copies the compiled binary and required tools (`kubectl`, `oc`, `jq`) into a minimal base image
240+
241+
Build args enable version tracking in the final image without carrying the full build toolchain.
242+
243+
## Chart Provenance
244+
245+
A Helmet installer is a **composition of Helm charts**. Most installers combine external charts (for products with existing Helm charts) and authored charts (for infrastructure and integration glue). The framework is chart-agnostic — it loads whatever is in the `charts/` directory.
246+
247+
### Bringing External Charts
248+
249+
The primary workflow for building an installer:
250+
251+
1. **Copy** the chart directory into `charts/<name>/`
252+
2. **Add Helmet annotations** to `Chart.yaml` — `product-name`, `depends-on`, and integration annotations as needed
253+
3. **Set the root key** in `values.yaml` — conventionally derived from the chart name, must be unique across all charts
254+
4. **Add `__OVERWRITE_ME__` placeholders** in `values.yaml` for values that should be dynamically rendered by `values.yaml.tpl`
255+
5. **Wire up** a corresponding section in `values.yaml.tpl` and a product entry in `config.yaml` (if the chart maps to a product)
256+
257+
**Helm `dependencies:` handling**: Upstream charts may use `dependencies:` in `Chart.yaml` for sub-chart resolution. The framework ignores this field for topology resolution — it reads `depends-on` annotations instead. However, Helm itself still processes `dependencies:` during install (pulling and rendering sub-charts with values merging). Only remove `dependencies:` entries when you intend to surface the sub-chart as a separate Helmet-managed topology node; otherwise, keep the upstream `dependencies:` so Helm continues to handle sub-chart rendering and values merging. Express inter-chart relationships via `depends-on` annotations regardless.
258+
259+
### Authoring New Charts
260+
261+
A secondary workflow for installer-specific charts that don't exist upstream:
262+
263+
- **Namespace charts**: Create OpenShift projects, set labels and annotations
264+
- **Operator subscription charts**: Deploy OLM Subscriptions for operators the installer manages
265+
- **Integration aggregation charts**: Collect and validate integration Secrets
266+
- **Companion and test charts**: Validation or post-deploy checks for a product
267+
268+
These charts are typically thin — a few templates, minimal values — and are authored from scratch. See [topology.md](topology.md#infrastructure-charts--derived-products) for infrastructure chart patterns.
269+
270+
### Composition Model
271+
272+
The filesystem is the composition mechanism. Copy charts into `charts/`, add annotations, wire up `config.yaml` and `values.yaml.tpl`. No catalog or registry feature is needed — the directory structure is the source of truth.
273+
274+
A realistic installer combines both paths. Plan the composition by considering:
275+
276+
- **Inventory**: What products does the installer deploy? Which have existing Helm charts?
277+
- **Gap analysis**: What infrastructure charts are needed (namespaces, operators, shared databases)?
278+
- **Dependency design**: How do the charts relate? What deployment order is required?
279+
- **Integration mapping**: Which charts provide or require integrations?
280+
205281
## The `instructions.md` File
206282

207283
The optional `instructions.md` provides context to AI assistants when the installer runs as an MCP server. The MCP client (e.g., Claude Desktop, Cursor) receives this content, helping the AI understand available products, workflow phases, and tool usage.
@@ -212,7 +288,8 @@ See [mcp.md](mcp.md) for MCP server implementation details.
212288

213289
## Cross-References
214290

215-
- [Configuration](configuration.md) — config.yaml schema and values rendering
216-
- [Topology](topology.md) — chart dependency resolution and deployment ordering
291+
- [Configuration](configuration.md) — config.yaml schema, values rendering, [the triad](configuration.md#the-triad)
292+
- [Topology](topology.md) — chart dependency resolution, deployment ordering, [infrastructure charts](topology.md#infrastructure-charts--derived-products)
293+
- [Templating](templating.md) — values.yaml.tpl syntax and [production patterns](templating.md#production-patterns)
217294
- [MCP Server](mcp.md) — Model Context Protocol server implementation
218295
- [Getting Started](getting-started.md) — building your first installer

docs/templating.md

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Templating
22

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.
44

55
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).
66

@@ -310,11 +310,136 @@ foundation:
310310
{{- end }}
311311
```
312312

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+
313438
## Scope Boundaries
314439

315440
This document covers template rendering mechanics and the `values.yaml.tpl` syntax. Related topics:
316441

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
318443
- **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)
320445
- **OpenShift introspection**: Implemented in `internal/k8s/openshift.go`

0 commit comments

Comments
 (0)