Skip to content

Commit 00358fd

Browse files
authored
feat(config): migrate to Talos v1.12 multi-document config format (#116)
* feat(config): migrate to Talos v1.12 multi-document config format Add support for the Talos v1.12 multi-document configuration format. When TalosVersion is set to v1.12 or later (via Chart.yaml or --talos-version CLI flag), templates generate separate YAML documents instead of the deprecated monolithic machine.network and machine.registries fields. Closes #100 Engine: - Pass TalosVersion through the Helm values map into the template rendering context (available as .TalosVersion), avoiding global mutable state - Remove unused ctx and chrt parameters from applyPatchesAndRenderConfig Chart templates (cozystack + generic): - Split talos.config into shared sub-templates to eliminate duplication: talos.config.machine.common, talos.config.cluster, talos.config.network.legacy, talos.config.network.multidoc - Version dispatcher selects legacy or multi-doc format based on semverCompare - New v1.12 document types generated: HostnameConfig, ResolverConfig, LinkConfig, BondConfig, VLANConfig, RegistryMirrorConfig, Layer2VIPConfig Bug fix: - Fix nr_hugepages rendering inside with block (was producing empty string for non-zero values due to .Values being inaccessible when dot is rebound) Backward compatibility: - Legacy format is fully preserved when TalosVersion is empty or < v1.12 - All legacy regression tests pass unchanged Tests: - isTalosConfigPatch and extractExtraDocuments tests for all 7 new document types - Offline rendering tests for both charts x both roles x both formats - Version edge cases: pre-release, two-component version strings - Concurrent render test with race detector - nr_hugepages rendering test for both legacy and multi-doc paths - Regression test from #119 for offline lookup producing empty interface Code style: - Replace interface{} with any across engine package - Use maps.Copy instead of range+assign loops - Use range over int for simple counting loops - Use strings.Repeat instead of += concatenation Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(engine): validate TalosVersion before template rendering Call ParseContractFromVersion early in Render() so malformed values (e.g. "latest", "foobar") surface a user-friendly "invalid talos-version" error instead of the opaque "semverCompare: invalid semantic version" that otherwise escapes from deep inside the Helm engine template evaluation. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * test(engine): cover bond/vlan topologies, invalid version, Layer2VIPConfig - TestRenderInvalidTalosVersion: exercises the new early validation. - TestMultiDocCozystack_BondTopology / TestMultiDocGeneric_BondTopology: assert BondConfig emission with mock links/routes/addresses lookups. - TestMultiDocCozystack_VlanOnBondTopology / TestMultiDocGeneric_VlanOnBondTopology: assert BondConfig + VLANConfig combined emission when the default route goes through a VLAN. - TestMultiDocCozystack_ControlPlane: add positive assert for Layer2VIPConfig since cozystack defaults set floatingIP. - TestLegacy/MultiDocCozystack_NrHugepages: restore the package-level helmEngine.LookupFunc via t.Cleanup for test isolation. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * docs: refine multi-doc README note and machine.common helper comment - README: clarify that not all multi-doc types are always present — HostnameConfig/ResolverConfig and one network interface document are always emitted, Layer2VIPConfig depends on floatingIP on controlplane, and RegistryMirrorConfig is cozystack-only. - charts/cozystack _helpers.tpl: the talos.config.machine.common helper also renders nodeLabels for controlplane nodes — update the comment. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(helpers): attach Layer2VIP to VLAN interface in multi-doc path In the VLAN branch of talos.config.network.multidoc, $interfaceName is rewritten to the parent link, while addresses/routes are emitted on the VLAN link ($defaultLinkName). Using $interfaceName for Layer2VIPConfig.link moved the VIP off the configured VLAN. Select $defaultLinkName when the default link is a VLAN so the VIP stays attached to the VLAN interface, matching the legacy path behavior. Address review feedback from coderabbitai on charts/cozystack/templates/_helpers.tpl:229. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(helpers): attach Layer2VIP to VLAN interface in generic multi-doc path Mirror of the cozystack fix: when the default link is a VLAN, $interfaceName is rewritten to the parent link while the VLAN addresses/routes are emitted on $defaultLinkName. Use $defaultLinkName for Layer2VIPConfig.link in that branch so the VIP stays on the VLAN. Address review feedback from coderabbitai on charts/generic/templates/_helpers.tpl:152. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> --------- Signed-off-by: Aleksei Sviridkin <f@lex.la>
1 parent 397f32f commit 00358fd

8 files changed

Lines changed: 1321 additions & 87 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ cluster:
116116
endpoint: https://192.168.0.1:6443
117117
```
118118
119+
> **Note:** The output format depends on the Talos version configured in `Chart.yaml` (`templateOptions.talosVersion`) or via the `--talos-version` CLI flag.
120+
> For Talos < v1.12, the output is a single YAML document with `machine.network` and `machine.registries` sections (as shown above).
121+
> For Talos >= v1.12, the output uses the multi-document format with separate typed documents instead of the deprecated monolithic fields. `HostnameConfig`, `ResolverConfig` and a network interface document (`LinkConfig`, `BondConfig`, or `VLANConfig` — depending on topology) are always emitted; `Layer2VIPConfig` appears on controlplane nodes when `floatingIP` is set; `RegistryMirrorConfig` is emitted only by the cozystack chart.
122+
119123
Apply config:
120124
```bash
121125
talm apply -f nodes/node1.yaml -i

charts/cozystack/templates/_helpers.tpl

Lines changed: 200 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
{{- define "talos.config" }}
2+
{{- if and .TalosVersion (not (semverCompare "<1.12.0-0" .TalosVersion)) }}
3+
{{- include "talos.config.multidoc" . }}
4+
{{- else }}
5+
{{- include "talos.config.legacy" . }}
6+
{{- end }}
7+
{{- end }}
8+
9+
{{- /* Shared machine section: type, nodeLabels (controlplane), kubelet, sysctls, kernel, certSANs, files, install */ -}}
10+
{{- define "talos.config.machine.common" }}
211
machine:
312
{{- if eq .MachineType "controlplane" }}
413
nodeLabels:
@@ -14,8 +23,8 @@ machine:
1423
cpuManagerPolicy: static
1524
maxPods: 512
1625
sysctls:
17-
{{- with .Values.nr_hugepages }}
18-
vm.nr_hugepages: {{ .Values.nr_hugepages | quote }}
26+
{{- with $.Values.nr_hugepages }}
27+
vm.nr_hugepages: {{ . | quote }}
1928
{{- end }}
2029
net.ipv4.neigh.default.gc_thresh1: "4096"
2130
net.ipv4.neigh.default.gc_thresh2: "8192"
@@ -35,11 +44,6 @@ machine:
3544
{{- with .Values.certSANs }}
3645
{{- toYaml . | nindent 2 }}
3746
{{- end }}
38-
registries:
39-
mirrors:
40-
docker.io:
41-
endpoints:
42-
- https://mirror.gcr.io
4347
files:
4448
- content: |
4549
[plugins]
@@ -66,53 +70,10 @@ machine:
6670
{{- end }}
6771
{{- (include "talm.discovered.disks_info" .) | nindent 4 }}
6872
disk: {{ include "talm.discovered.system_disk_name" . | quote }}
69-
network:
70-
hostname: {{ include "talm.discovered.hostname" . | quote }}
71-
nameservers: {{ include "talm.discovered.default_resolvers" . }}
72-
{{- (include "talm.discovered.physical_links_info" .) | nindent 4 }}
73-
interfaces:
74-
{{- $existingInterfacesConfiguration := include "talm.discovered.existing_interfaces_configuration" . }}
75-
{{- if $existingInterfacesConfiguration }}
76-
{{- $existingInterfacesConfiguration | nindent 4 }}
77-
{{- else }}
78-
{{- $defaultLinkName := include "talm.discovered.default_link_name_by_gateway" . }}
79-
{{- $isVlan := include "talm.discovered.is_vlan" $defaultLinkName }}
80-
{{- $parentLinkName := "" }}
81-
{{- if $isVlan }}
82-
{{- $parentLinkName = include "talm.discovered.parent_link_name" $defaultLinkName }}
83-
{{- end }}
84-
{{- $interfaceName := $defaultLinkName }}
85-
{{- if and $isVlan $parentLinkName }}
86-
{{- $interfaceName = $parentLinkName }}
87-
{{- end }}
88-
- interface: {{ $interfaceName }}
89-
{{- $bondConfig := include "talm.discovered.bond_config" $interfaceName }}
90-
{{- if $bondConfig }}
91-
{{- $bondConfig | nindent 6 }}
92-
{{- end }}
93-
{{- if $isVlan }}
94-
vlans:
95-
- vlanId: {{ include "talm.discovered.vlan_id" $defaultLinkName }}
96-
addresses: {{ include "talm.discovered.default_addresses_by_gateway" . }}
97-
routes:
98-
- network: 0.0.0.0/0
99-
gateway: {{ include "talm.discovered.default_gateway" . }}
100-
{{- if and .Values.floatingIP (eq .MachineType "controlplane") }}
101-
vip:
102-
ip: {{ .Values.floatingIP }}
103-
{{- end }}
104-
{{- else }}
105-
addresses: {{ include "talm.discovered.default_addresses_by_gateway" . }}
106-
routes:
107-
- network: 0.0.0.0/0
108-
gateway: {{ include "talm.discovered.default_gateway" . }}
109-
{{- if and .Values.floatingIP (eq .MachineType "controlplane") }}
110-
vip:
111-
ip: {{ .Values.floatingIP }}
112-
{{- end }}
113-
{{- end }}
114-
{{- end }}
73+
{{- end }}
11574

75+
{{- /* Shared cluster section */ -}}
76+
{{- define "talos.config.cluster" }}
11677
cluster:
11778
network:
11879
cni:
@@ -161,3 +122,189 @@ cluster:
161122
{{- toYaml .Values.advertisedSubnets | nindent 6 }}
162123
{{- end }}
163124
{{- end }}
125+
126+
{{- /* Shared network document generation for v1.12+ multi-doc format */ -}}
127+
{{- define "talos.config.network.multidoc" }}
128+
{{- /* Multi-doc format always reconstructs network config from discovery resources.
129+
existing_interfaces_configuration is not used here because v1.12 nodes store
130+
network config in separate documents (LinkConfig, BondConfig, etc.), not in
131+
the legacy machine.network.interfaces field. */ -}}
132+
{{- (include "talm.discovered.physical_links_info" .) }}
133+
---
134+
apiVersion: v1alpha1
135+
kind: HostnameConfig
136+
hostname: {{ include "talm.discovered.hostname" . | quote }}
137+
---
138+
apiVersion: v1alpha1
139+
kind: ResolverConfig
140+
nameservers:
141+
{{- $resolvers := include "talm.discovered.default_resolvers" . }}
142+
{{- if $resolvers }}
143+
{{- range fromJsonArray $resolvers }}
144+
- address: {{ . | quote }}
145+
{{- end }}
146+
{{- else }}
147+
[]
148+
{{- end }}
149+
{{- $defaultLinkName := include "talm.discovered.default_link_name_by_gateway" . }}
150+
{{- $isVlan := include "talm.discovered.is_vlan" $defaultLinkName }}
151+
{{- $parentLinkName := "" }}
152+
{{- if $isVlan }}
153+
{{- $parentLinkName = include "talm.discovered.parent_link_name" $defaultLinkName }}
154+
{{- end }}
155+
{{- $interfaceName := $defaultLinkName }}
156+
{{- if and $isVlan $parentLinkName }}
157+
{{- $interfaceName = $parentLinkName }}
158+
{{- end }}
159+
{{- $isBondInterface := include "talm.discovered.is_bond" $interfaceName }}
160+
{{- if $isBondInterface }}
161+
{{- $link := lookup "links" "" $interfaceName }}
162+
{{- if $link }}
163+
{{- $bondMaster := $link.spec.bondMaster }}
164+
{{- $slaves := fromJsonArray (include "talm.discovered.bond_slaves" $link.spec.index) }}
165+
---
166+
apiVersion: v1alpha1
167+
kind: BondConfig
168+
name: {{ $interfaceName }}
169+
links:
170+
{{- range $slaves }}
171+
- {{ . }}
172+
{{- end }}
173+
bondMode: {{ $bondMaster.mode }}
174+
{{- if $bondMaster.xmitHashPolicy }}
175+
xmitHashPolicy: {{ $bondMaster.xmitHashPolicy }}
176+
{{- end }}
177+
{{- if $bondMaster.lacpRate }}
178+
lacpRate: {{ $bondMaster.lacpRate }}
179+
{{- end }}
180+
{{- if $bondMaster.miimon }}
181+
miimon: {{ $bondMaster.miimon }}
182+
{{- end }}
183+
{{- if $bondMaster.updelay }}
184+
updelay: {{ $bondMaster.updelay }}
185+
{{- end }}
186+
{{- if $bondMaster.downdelay }}
187+
downdelay: {{ $bondMaster.downdelay }}
188+
{{- end }}
189+
{{- if not $isVlan }}
190+
addresses:
191+
{{- range fromJsonArray (include "talm.discovered.default_addresses_by_gateway" .) }}
192+
- address: {{ . }}
193+
{{- end }}
194+
routes:
195+
- gateway: {{ include "talm.discovered.default_gateway" . }}
196+
{{- end }}
197+
{{- end }}
198+
{{- end }}
199+
{{- if $isVlan }}
200+
---
201+
apiVersion: v1alpha1
202+
kind: VLANConfig
203+
name: {{ $defaultLinkName }}
204+
vlanID: {{ include "talm.discovered.vlan_id" $defaultLinkName }}
205+
parent: {{ $interfaceName }}
206+
addresses:
207+
{{- range fromJsonArray (include "talm.discovered.default_addresses_by_gateway" .) }}
208+
- address: {{ . }}
209+
{{- end }}
210+
routes:
211+
- gateway: {{ include "talm.discovered.default_gateway" . }}
212+
{{- else if not $isBondInterface }}
213+
---
214+
apiVersion: v1alpha1
215+
kind: LinkConfig
216+
name: {{ $interfaceName }}
217+
addresses:
218+
{{- range fromJsonArray (include "talm.discovered.default_addresses_by_gateway" .) }}
219+
- address: {{ . }}
220+
{{- end }}
221+
routes:
222+
- gateway: {{ include "talm.discovered.default_gateway" . }}
223+
{{- end }}
224+
{{- $vipLinkName := $interfaceName }}
225+
{{- if $isVlan }}
226+
{{- $vipLinkName = $defaultLinkName }}
227+
{{- end }}
228+
{{- if and .Values.floatingIP (eq .MachineType "controlplane") }}
229+
---
230+
apiVersion: v1alpha1
231+
kind: Layer2VIPConfig
232+
name: {{ .Values.floatingIP | quote }}
233+
link: {{ $vipLinkName }}
234+
{{- end }}
235+
{{- end }}
236+
237+
{{- /* Shared legacy network section for machine.network */ -}}
238+
{{- define "talos.config.network.legacy" }}
239+
network:
240+
hostname: {{ include "talm.discovered.hostname" . | quote }}
241+
nameservers: {{ include "talm.discovered.default_resolvers" . }}
242+
{{- (include "talm.discovered.physical_links_info" .) | nindent 4 }}
243+
interfaces:
244+
{{- $existingInterfacesConfiguration := include "talm.discovered.existing_interfaces_configuration" . }}
245+
{{- if $existingInterfacesConfiguration }}
246+
{{- $existingInterfacesConfiguration | nindent 4 }}
247+
{{- else }}
248+
{{- $defaultLinkName := include "talm.discovered.default_link_name_by_gateway" . }}
249+
{{- $isVlan := include "talm.discovered.is_vlan" $defaultLinkName }}
250+
{{- $parentLinkName := "" }}
251+
{{- if $isVlan }}
252+
{{- $parentLinkName = include "talm.discovered.parent_link_name" $defaultLinkName }}
253+
{{- end }}
254+
{{- $interfaceName := $defaultLinkName }}
255+
{{- if and $isVlan $parentLinkName }}
256+
{{- $interfaceName = $parentLinkName }}
257+
{{- end }}
258+
- interface: {{ $interfaceName }}
259+
{{- $bondConfig := include "talm.discovered.bond_config" $interfaceName }}
260+
{{- if $bondConfig }}
261+
{{- $bondConfig | nindent 6 }}
262+
{{- end }}
263+
{{- if $isVlan }}
264+
vlans:
265+
- vlanId: {{ include "talm.discovered.vlan_id" $defaultLinkName }}
266+
addresses: {{ include "talm.discovered.default_addresses_by_gateway" . }}
267+
routes:
268+
- network: 0.0.0.0/0
269+
gateway: {{ include "talm.discovered.default_gateway" . }}
270+
{{- if and .Values.floatingIP (eq .MachineType "controlplane") }}
271+
vip:
272+
ip: {{ .Values.floatingIP }}
273+
{{- end }}
274+
{{- else }}
275+
addresses: {{ include "talm.discovered.default_addresses_by_gateway" . }}
276+
routes:
277+
- network: 0.0.0.0/0
278+
gateway: {{ include "talm.discovered.default_gateway" . }}
279+
{{- if and .Values.floatingIP (eq .MachineType "controlplane") }}
280+
vip:
281+
ip: {{ .Values.floatingIP }}
282+
{{- end }}
283+
{{- end }}
284+
{{- end }}
285+
{{- end }}
286+
287+
{{- define "talos.config.legacy" }}
288+
{{- include "talos.config.machine.common" . }}
289+
registries:
290+
mirrors:
291+
docker.io:
292+
endpoints:
293+
- https://mirror.gcr.io
294+
{{- include "talos.config.network.legacy" . }}
295+
296+
{{- include "talos.config.cluster" . }}
297+
{{- end }}
298+
299+
{{- define "talos.config.multidoc" }}
300+
{{- include "talos.config.machine.common" . }}
301+
302+
{{- include "talos.config.cluster" . }}
303+
---
304+
apiVersion: v1alpha1
305+
kind: RegistryMirrorConfig
306+
name: docker.io
307+
endpoints:
308+
- url: https://mirror.gcr.io
309+
{{- include "talos.config.network.multidoc" . }}
310+
{{- end }}

0 commit comments

Comments
 (0)