|
1 | | -# argocd-values-renderer-plugin |
| 1 | +# argocd-appset-params-template-plugin |
| 2 | + |
| 3 | +ApplicationSet plugin generator for one job: take structured input, merge it, render templates inside it, and return parameter objects for `ApplicationSet.template`. |
| 4 | + |
| 5 | +It does **not** generate `ApplicationSet` manifests. |
| 6 | +It does **not** replace Argo CD multiple sources. |
| 7 | +It stays compatible with both: |
| 8 | + |
| 9 | +- one Helm source with `toYaml .result` |
| 10 | +- multiple Helm sources with `toYaml (.result.<part> | default dict)` |
| 11 | + |
| 12 | +## Why this exists |
| 13 | + |
| 14 | +Plain ApplicationSet templating is good at wiring generators into a template. |
| 15 | +It is not a recursive structured data pipeline. |
| 16 | + |
| 17 | +This plugin fills that gap: |
| 18 | + |
| 19 | +- global `defaults` |
| 20 | +- global `overrides` |
| 21 | +- selector-based overrides by `context.*` or `params.*` |
| 22 | +- optional fanout through `targets` |
| 23 | +- recursive template rendering inside the final result |
| 24 | +- pure-expression type preservation |
| 25 | + |
| 26 | +The main use case is Ansible-like templated defaults for Helm values, without hardcoding chart-specific logic into the plugin. |
| 27 | + |
| 28 | +## Contract |
| 29 | + |
| 30 | +All input lives under: |
| 31 | + |
| 32 | +```yaml |
| 33 | +spec.generators[].plugin.input.parameters |
| 34 | +``` |
| 35 | + |
| 36 | +Supported top-level keys: |
| 37 | + |
| 38 | +```yaml |
| 39 | +context: {} |
| 40 | +params: {} |
| 41 | +defaults: {} |
| 42 | +overrides: {} |
| 43 | +selectorOverrides: |
| 44 | + - path: context.cluster.name |
| 45 | + overrides: |
| 46 | + dev-blabla: |
| 47 | + domain: dev.example.com |
| 48 | + default: |
| 49 | + domain: default.example.com |
| 50 | +targets: |
| 51 | + - context: {} |
| 52 | + params: {} |
| 53 | + overrides: {} |
| 54 | +options: |
| 55 | + maxTemplatePasses: 4 |
| 56 | +``` |
| 57 | +
|
| 58 | +Strict rules: |
| 59 | +
|
| 60 | +- unknown top-level keys are rejected |
| 61 | +- unknown target keys are rejected |
| 62 | +- `selectorOverrides[*].path` must start with `context.` or `params.` |
| 63 | +- `targets[].defaults` is not supported |
| 64 | +- if `targets` is omitted, the plugin returns one result |
| 65 | +- if `targets` is present, the plugin returns one result per target |
| 66 | + |
| 67 | +## Output |
| 68 | + |
| 69 | +Each generated object looks like this: |
| 70 | + |
| 71 | +```yaml |
| 72 | +context: {} |
| 73 | +params: {} |
| 74 | +result: {} |
| 75 | +``` |
| 76 | + |
| 77 | +Use it in `ApplicationSet.template` however you need. |
| 78 | + |
| 79 | +Single Helm source: |
| 80 | + |
| 81 | +```yaml |
| 82 | +helm: |
| 83 | + values: | |
| 84 | + {{- toYaml .result | nindent 12 }} |
| 85 | +``` |
| 86 | + |
| 87 | +Multiple Helm sources inside one `Application`: |
| 88 | + |
| 89 | +```yaml |
| 90 | +sources: |
| 91 | + - repoURL: ... |
| 92 | + helm: |
| 93 | + values: | |
| 94 | + {{- toYaml (.result.operator | default dict) | nindent 12 }} |
| 95 | +
|
| 96 | + - repoURL: ... |
| 97 | + helm: |
| 98 | + values: | |
| 99 | + {{- toYaml (.result.cluster | default dict) | nindent 12 }} |
| 100 | +``` |
| 101 | + |
| 102 | +## Merge order |
| 103 | + |
| 104 | +For one resolved item: |
| 105 | + |
| 106 | +1. root `defaults` |
| 107 | +2. root `overrides` |
| 108 | +3. matching `selectorOverrides` in declaration order |
| 109 | +4. target `overrides` |
| 110 | +5. recursive template rendering until stable |
| 111 | + |
| 112 | +`context` and `params` are merged separately: |
| 113 | + |
| 114 | +1. root `context` + target `context` |
| 115 | +2. root `params` + target `params` |
| 116 | + |
| 117 | +Merge semantics: |
| 118 | + |
| 119 | +- map + map -> recursive merge |
| 120 | +- list + list -> replace |
| 121 | +- scalar + scalar -> replace |
| 122 | +- `null` stays `null` |
| 123 | + |
| 124 | +## Template context |
| 125 | + |
| 126 | +Template strings inside the final result get exactly these namespaces: |
| 127 | + |
| 128 | +```gotemplate |
| 129 | +{{ .result.domain }} |
| 130 | +{{ .context.cluster.name }} |
| 131 | +{{ .params.appName }} |
| 132 | +{{ .target.params.appName }} |
| 133 | +``` |
| 134 | + |
| 135 | +No root aliases like `.name`, `.domain`, or `.appName`. |
| 136 | + |
| 137 | +## Type preservation |
| 138 | + |
| 139 | +There are two rendering modes for strings: |
| 140 | + |
| 141 | +- **pure expression**: the whole string is one template action like `{{ 3 }}` or `{{ dict "tier" "frontend" }}` |
| 142 | +- **mixed string**: anything else like `svc-{{ .result.port }}` |
| 143 | + |
| 144 | +Rules: |
| 145 | + |
| 146 | +- pure expressions preserve type |
| 147 | +- mixed strings always stay strings |
| 148 | + |
| 149 | +Examples: |
| 150 | + |
| 151 | +```gotemplate |
| 152 | +{{ 3 }} -> int |
| 153 | +{{ true }} -> bool |
| 154 | +{{ list 80 443 }} -> []any{80, 443} |
| 155 | +{{ dict "tier" "frontend" }} -> map[string]any |
| 156 | +{{ .result.someString }} -> string |
| 157 | +svc-{{ .result.port }} -> string |
| 158 | +``` |
| 159 | + |
| 160 | +To force a string in a pure expression, use `quote`. |
| 161 | + |
| 162 | +## Template functions |
| 163 | + |
| 164 | +Built-ins plus: |
| 165 | + |
| 166 | +- `default` |
| 167 | +- `lower`, `upper`, `trim`, `trimPrefix`, `trimSuffix` |
| 168 | +- `replace`, `contains`, `hasPrefix`, `hasSuffix` |
| 169 | +- `quote`, `squote` |
| 170 | +- `list`, `dict` |
| 171 | +- `join`, `split` |
| 172 | +- `toJson`, `toPrettyJson`, `toYaml` |
| 173 | +- `indent`, `nindent` |
| 174 | +- `normalize` |
| 175 | +- `required` |
| 176 | + |
| 177 | +## Patterns |
| 178 | + |
| 179 | +### 1. One app per cluster via label |
| 180 | + |
| 181 | +Use `matrix(clusters, plugin)`. |
| 182 | + |
| 183 | +See `manifests/applicationset-label-per-cluster-example.yaml`. |
| 184 | + |
| 185 | +### 2. Multiple instances on one cluster |
| 186 | + |
| 187 | +Use `matrix(list(instances), plugin)` when the instance list is explicit. |
| 188 | + |
| 189 | +See `manifests/applicationset-multi-instance-example.yaml`. |
| 190 | + |
| 191 | +### 3. One `Application`, multiple Helm charts |
| 192 | + |
| 193 | +Let the plugin return one structured `result`, then split it in the template per source. |
| 194 | + |
| 195 | +See `manifests/applicationset-multi-source-compatible-example.yaml`. |
| 196 | + |
| 197 | +## Deploy |
| 198 | + |
| 199 | +Runtime manifests are in `manifests/`. |
| 200 | + |
| 201 | +They assume: |
| 202 | + |
| 203 | +- the controller and the plugin share the same token stored in `argocd-appset-params-template-plugin-token` |
| 204 | +- the plugin is reachable at `http://argocd-appset-params-template-plugin.argocd.svc.cluster.local` |
| 205 | + |
| 206 | +Apply: |
| 207 | + |
| 208 | +```bash |
| 209 | +kubectl apply -k manifests |
| 210 | +``` |
0 commit comments