Skip to content

feat(charts): add preset value knobs (#226-#230) - #232

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
feat/preset-values-knobs
Jul 29, 2026
Merged

feat(charts): add preset value knobs (#226-#230)#232
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
feat/preset-values-knobs

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

New values so a node's Talos config can come from values.yaml instead of a template fork:

  • timeServers sets machine.time.servers
  • extraApiServerArgs / extraControllerManagerArgs / extraSchedulerArgs pass through control-plane component args
  • registryMirrors declares machine registry mirrors
  • registryTLS sets TLS posture per registry endpoint host (a private CA, or skipping verification for a self-signed cache)
  • vips gives multiple Layer2 VIPs, each pinned to a link
  • network.preserveExisting keeps the running interfaces verbatim
  • network.extraLinks declares bonds, VLANs, and extra addresses, with bond tuning, mtu and routes

Covers #226, #227, #228, #229, #230.

Every knob defaults to empty, so a stock render is byte-identical to before. TestGoldenRender pins that across both presets, control-plane and worker, legacy and multi-doc. Nothing changes for existing users until they opt in.

Anything Talos would reject fails fast at render with a hinted message instead of shipping a broken config: an extraArgs key colliding with a preset built-in, a duplicate or dangling VIP, a VIP on a link already in the running interfaces, a VLAN on a missing parent, an empty or mode-less bond, a route without a gateway, a malformed registry entry. Component args are map[string]string on the Talos side, so values are quoted to keep an unquoted numeric from becoming a YAML int.

The apply gate learned that a link this config creates (a bond or VLAN from extraLinks) satisfies references to it from sibling documents, so an extraLinks bond with a VLAN and VIP on top passes on first apply instead of blocking.

Contract tests cover the accepted and rejected path for every knob, on both schemas and both presets. docs/manual-test-plan.md gets a matching section.

One follow-up, not in this PR: operator docs on cozystack/website for the new values.

Summary by CodeRabbit

  • New Features

    • Added chart knobs: timeServers, component extra*Args, registryMirrors, and registryTLS.
    • Added multi-VIP support via vips, and improved Talos v1.12+ multi-document network output.
    • Added network.preserveExisting and network.extraLinks for richer declarative topology (including bonds/VLANs/addresses/routes).
  • Bug Fixes

    • Stricter VIP/IP canonicalization and collision checks; VIPs are removed from discovered link addresses.
    • Apply-time reference validation now accounts for links created within the same rendered apply (with WireGuard-specific behavior).
  • Documentation

    • Updated README and manual test plan for the new knobs and safety gates.
  • Tests

    • Expanded contract and apply validation coverage for legacy and multidoc paths.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds time servers, registry settings, control-plane arguments, multiple VIPs, preserved interfaces, declarative extra links, and stricter validation across both chart variants. Apply-time reference checking now recognizes links created within the same configuration.

Changes

Configuration rendering

Layer / File(s) Summary
Machine, registry, and control-plane rendering
charts/*/values.yaml, charts/*/templates/_helpers.tpl, pkg/engine/helm/engine.go, pkg/engine/contract_machine_test.go, pkg/engine/contract_cluster_test.go
Adds time servers, registry mirrors/TLS, and component extra arguments with schema-specific rendering, type validation, collision detection, and contract coverage.

Network rendering

Layer / File(s) Summary
VIP and topology rendering
charts/*/values.yaml, charts/*/templates/_helpers.tpl, charts/talm/templates/_helpers.tpl, pkg/engine/contract_network_*, pkg/engine/contract_errors_test.go, pkg/engine/render_test.go
Adds multi-VIP output, canonical address handling, preserved interfaces, declarative bonds/VLANs/links, legacy guards, collision checks, and route/VLAN/MTU/bond validation.

Apply-time validation

Layer / File(s) Summary
Apply-time created-link validation
pkg/applycheck/refs.go, pkg/applycheck/validate.go, pkg/applycheck/validate_test.go
Tracks links created by configuration documents so later references can resolve, while missing links and Wireguard-created links retain blocker behavior.

Documentation and validation plan

Layer / File(s) Summary
Documented contracts and validation scenarios
README.md, docs/manual-test-plan.md
Describes multidoc output, version compatibility, safety gates, new values, registry behavior, VIP handling, and topology validation scenarios.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Values
  participant HelmTemplates
  participant TalosDiscovery
  participant ApplyValidation
  Values->>HelmTemplates: Provide registry, VIP, and network settings
  TalosDiscovery->>HelmTemplates: Supply existing links and addresses
  HelmTemplates->>HelmTemplates: Render typed network documents
  HelmTemplates->>ApplyValidation: Emit created-link and link references
  ApplyValidation->>TalosDiscovery: Compare references with snapshot links
  ApplyValidation-->>HelmTemplates: Accept created links or report blockers
Loading

Possibly related PRs

  • cozystack/talm#154: Adds contract tests covering related multidoc, legacy networking, Layer2 VIP, and topology rendering behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding preset value knobs across the charts.
Docstring Coverage ✅ Passed Docstring coverage is 95.17% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/preset-values-knobs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lexfrei
Aleksei Sviridkin (lexfrei) changed the base branch from chore/helm-4-migration to main July 23, 2026 09:33
@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as ready for review July 23, 2026 17:21
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
pkg/engine/render_test.go (1)

6866-6898: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: parameterize the returned template to avoid duplicating renderCozystackWith.

renderCozystackWorkerWith is a near-verbatim copy of renderCozystackWith, differing only in the returned map key. A shared helper taking the template path would remove the drift risk between the two.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/engine/render_test.go` around lines 6866 - 6898, The duplicated rendering
logic in renderCozystackWorkerWith should be consolidated with
renderCozystackWith. Extract or update a shared helper to accept the desired
rendered template key, have both functions reuse it, and preserve each
function’s existing return key and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@charts/generic/templates/_helpers.tpl`:
- Around line 123-132: Prevent empty apiServer keys from rendering when no
arguments exist: in charts/generic/templates/_helpers.tpl lines 123-132, place
apiServer: inside the existing extraApiServerArgs with block; in
charts/cozystack/templates/_helpers.tpl lines 256-278, move apiServer: inside
the existing if or $oidcSet .Values.extraApiServerArgs guard. Preserve rendering
when the guarded content is present.

In `@docs/manual-test-plan.md`:
- Around line 639-646: Update the collision scenario in the manual test plan so
the selected preserved interface device is returned by discovery and reliably
triggers the expected “collides with a discovered link” error; alternatively,
replace the expected error with the actual preserved-link collision message.
Keep the non-collision and other refusal scenarios unchanged.

In `@README.md`:
- Around line 177-181: Clarify the README statement about VIP removal to specify
that stripping applies only during typed per-link document rebuilds. Update the
nearby preserveExisting explanation consistently, noting that verbatim interface
preservation bypasses this stripping while VIP and registry documents remain
unaffected.
- Around line 181-184: Remove the blank lines between the contiguous blockquote
paragraphs in README.md, keeping the version compatibility text directly
adjacent to the preceding network behavior text so markdownlint MD028 passes.

---

Nitpick comments:
In `@pkg/engine/render_test.go`:
- Around line 6866-6898: The duplicated rendering logic in
renderCozystackWorkerWith should be consolidated with renderCozystackWith.
Extract or update a shared helper to accept the desired rendered template key,
have both functions reuse it, and preserve each function’s existing return key
and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 48bd51c0-60a4-4154-a78d-3280c84252ef

📥 Commits

Reviewing files that changed from the base of the PR and between c2d0f05 and e3477ff.

📒 Files selected for processing (16)
  • README.md
  • charts/cozystack/templates/_helpers.tpl
  • charts/cozystack/values.yaml
  • charts/generic/templates/_helpers.tpl
  • charts/generic/values.yaml
  • charts/talm/templates/_helpers.tpl
  • docs/manual-test-plan.md
  • pkg/applycheck/refs.go
  • pkg/applycheck/validate.go
  • pkg/applycheck/validate_test.go
  • pkg/engine/contract_cluster_test.go
  • pkg/engine/contract_errors_test.go
  • pkg/engine/contract_machine_test.go
  • pkg/engine/contract_network_legacy_test.go
  • pkg/engine/contract_network_multidoc_test.go
  • pkg/engine/render_test.go

Comment on lines 123 to +132
apiServer:
{{- with .Values.extraApiServerArgs }}
extraArgs:
{{- range $k, $v := . }}
{{- if kindIs "invalid" $v }}
{{- fail (printf "values.yaml: extraApiServerArgs.%s has no value. A bare `key:` renders as the literal string \"<nil>\" onto the component command line; give it a value or drop the key." $k) }}
{{- end }}
{{ $k }}: {{ $v | toString | quote }}
{{- end }}
{{- end }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty apiServer: key emitted for control-plane renders without extra args (both charts). Shared root cause: the apiServer: map key is rendered unconditionally, so with the default empty extraApiServerArgs (and no OIDC on cozystack) a bare apiServer: line is produced, adding output that wasn't there before and breaking the byte-identical-for-defaults invariant.

  • charts/generic/templates/_helpers.tpl#L123-L132: wrap apiServer: in {{- with .Values.extraApiServerArgs }} ... {{- end }}, matching the guarded controllerManager (L100) and scheduler (L113) blocks.
  • charts/cozystack/templates/_helpers.tpl#L256-L278: move the apiServer: line inside the {{- if or $oidcSet .Values.extraApiServerArgs }} guard on L258 so the key only appears when it has content.
📍 Affects 2 files
  • charts/generic/templates/_helpers.tpl#L123-L132 (this comment)
  • charts/cozystack/templates/_helpers.tpl#L256-L278
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/generic/templates/_helpers.tpl` around lines 123 - 132, Prevent empty
apiServer keys from rendering when no arguments exist: in
charts/generic/templates/_helpers.tpl lines 123-132, place apiServer: inside the
existing extraApiServerArgs with block; in
charts/cozystack/templates/_helpers.tpl lines 256-278, move apiServer: inside
the existing if or $oidcSet .Values.extraApiServerArgs guard. Preserve rendering
when the guarded content is present.

Comment thread docs/manual-test-plan.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated

@kvaps Andrei Kvapil (kvaps) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by checking out the branch and verifying each claim in the description independently rather than relying on the added tests.

Verification

"a stock render is byte-identical to before" — confirmed, with room to spare. I rendered a 240-cell matrix (3 discovery fixtures × 2 charts × 2 templates × 4 Talos versions × 5 value sets) on both the merge base and the branch head and diffed them: identical. The fixtures included a rich topology (physical primary + bond with two slaves + VLAN child + bridge with a port, a link-local address, a non-default route, jumbo MTU) and the value sets covered floatingIP, vipLink, oidcIssuerUrl and allocateNodeCIDRs: false. The committed golden files are untouched and TestGoldenRender passes across both presets × controlplane/worker × legacy/multi-doc. Moving the hardcoded docker.io mirror into values.yaml is handled carefully, and the deep-merge caveat ({} keeps the default, null clears it) is both documented and true — I checked that registryMirrors: null emits no registry documents at all.

Component-arg quoting — confirmed. Integers, floats and booleans all come out quoted; legitimate values survive intact (A=true,B=false stays whole, an empty string does not become null, yes does not become a boolean). Key ordering is deterministic.

Apply-gate relaxation — confirmed as bounded. I exercised WalkRefs + ValidateRefs directly. A bond → VLAN → VIP chain created entirely by the config validates clean, while a typoed VLANConfig.parent, a dangling Layer2VIPConfig.link, a bond slave absent from the node, and a VIP pointing at a near-miss VLAN name all still block. Because the created-link union runs in its own pass before validation, document order does not matter. LinkConfig still validates its own name, and the WireguardConfig exception is described accurately in the README.

"Anything Talos would reject fails fast" — mostly. Every case listed in the description does fail with a clear message on both schemas, and each has a contract test. A few gaps are below.

go build, go vet, gofmt -l, go test ./... and golangci-lint run are all clean. The 97 new contract tests assert on literal rendered output rather than restating the template expression, and cover the negative path for every guard — no skips, no TODOs.

Please fix

registryTLS keys are emitted unquoted, unlike registryMirrors. charts/talm/templates/_helpers.tpl:366 (multi-doc) and :399 (legacy) interpolate the key raw, while the mirror name goes through | toYaml at :356 and :389 for exactly this reason. An IPv6 endpoint host produces invalid YAML on both schemas:

# registryTLS: { "[::1]:5000": { insecureSkipVerify: true } }
kind: RegistryTLSConfig
name: [::1]:5000          # yaml: did not find expected node content
      [::1]:5000:          # same failure in machine.registries.config
        tls:

Neither document parses. A DNS host:port key survives only because it happens to contain no ": ". The same | toYaml treatment in both places fixes it.

Worth considering

An extraLinks bond can enslave a link the chart is also emitting a config for. At charts/talm/templates/_helpers.tpl:1375-1400, bond.interfaces are not checked against $configurableLinks, $existingLinkNames or $emittedNames — only the entry's own interface name is. Given a node whose discovered primary is eth0:

network:
  extraLinks:
    - interface: bond1
      bond: { interfaces: [eth0], mode: 802.3ad }
      addresses: [203.0.113.10/24]

the render emits both:

kind: LinkConfig
name: eth0
addresses:
  - address: 192.168.201.10/24
routes:
  - gateway: 192.168.201.1
---
kind: BondConfig
name: bond1
links:
  - eth0

The discovery path deliberately filters this shape out — configurable_link_names (:736-747) excludes anything with a slaveKind so a slave never gets a standalone document alongside its master. The declarative path has no equivalent check, and "move the primary NIC into a bond" is one of the main reasons to reach for extraLinks. The same applies under preserveExisting: a preserved interface emitted verbatim in machine.network.interfaces can simultaneously become a bond slave. Given how thorough the rest of the guard set is, this reads more like an oversight than a decision — but if it is intentional, a note in the extraLinks values documentation would help.

Prefix length is not validated in address and route guards. talm.guard.addresses_not_vip (:428-434) and talm.render.link_routes_mtu (:452-459) check for the presence of / and validate the IP portion, but not the prefix itself. 203.0.113.10/33, 203.0.113.10/abc and 203.0.113.10/ all render successfully and reach the node, where Talos fails at decode with a message that does not name the document — the exact failure mode these guards exist to prevent. cidrPrefixLen is already registered and used elsewhere in the same file (:644, :779) and returns -1 on a parse failure, so ge (cidrPrefixLen $address) 0 would close both.

mtu is not validated at all (:470-472). mtu: "jumbo", mtu: -5 and mtu: 99999 are emitted verbatim; mtu: 0 is silently dropped as falsy.

Non-scalar extraArgs values leak a Go string. The guard catches only kindIs "invalid", so a nested map renders as foo: "map[a:b]" onto the component command line. Affects charts/cozystack/templates/_helpers.tpl:238,254,277 and charts/generic/templates/_helpers.tpl:110,120,130.

Nits

  • pkg/applycheck/refs.go:249-252 — the handleListOnly docstring still says it emits "only the list-valued slaves/ports of the doc, not the doc's own .name", but the function now calls appendCreatedRef first. Stale by exactly the change this PR makes.
  • charts/talm/templates/_helpers.tpl:610-613 — the comment justifies the dict-mutation pattern by stating that range introduces a new scope so $var = ... does not propagate. In Go text/template, = assigns to the existing variable and does survive iterations; the same file relies on that at :781, :1005 and :1374. Either drop the rationale or bring $best in line with the surrounding style.
  • Duplicate addresses across two extraLinks entries, and an extraLinks address matching another link's discovered address, are not caught. Probably harmless, but they sit just outside the "anything Talos would reject" line.
  • Layer2VIPConfig for a VIP on an extraLinks bond is emitted before the BondConfig that creates it. Documents are a set, so this is fine today.
  • timeServers accepts a map and renders it as-is under servers:.

Size

The +3105 is 1936 lines of tests plus 156 of docs; the logic itself is around 740 lines. Splitting would create artificial ordering between PRs since the guards are shared across knobs ($vipIPs is needed by both vips and extraLinks; talm.guard.registries serves both schemas), so I would keep it as one change.

@myasnikovdaniil myasnikovdaniil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT LGTM — two input-dependent rendering paths in the new knobs emit a config that is either unparseable YAML or double-declares a VIP.

Business context: Five preset knobs (#226#230) so a node's Talos config comes from values.yaml instead of a forked template — NTP, control-plane extraArgs, registry mirrors/TLS, multiple VIPs, and declarative network topology.

Blockers

B1: registryTLS host rendered unquoted → invalid YAML

File: charts/talm/templates/_helpers.tpl:366 (multidoc), :399 (legacy)

Issue: A bracketed IPv6 endpoint host emits name: [fd00::1]:5000, where [ opens a YAML flow sequence.

Evidence: Rendered both schemas with registryTLS: {"[fd00::1]:5000": {insecureSkipVerify: true}} and parsed the result:

kind: RegistryTLSConfig
name: [fd00::1]:5000        -> yaml: line 99: did not find expected key
    config:
      [fd00::1]:5000:       -> yaml: line 65: did not find expected key
        tls:
          insecureSkipVerify: true

The sibling registryMirrors path already solved exactly this at :354 with name: {{ $name | toYaml }}, and its comment explains why. registryTLS does not. A plain host:port (no brackets) parses fine — only the bracketed form breaks.

Impact: talm template emits a document that neither talm nor the node can parse — worse than the fail-fast this PR promises elsewhere.

Fix: {{ $host | toYaml }} at both sites, matching :354.

B2: Non-canonical IPv6 VIP emitted as both a static address and a Layer2VIPConfig

File: charts/talm/templates/_helpers.tpl:1073

Issue: The strip compares hasPrefix (printf "%s/" .) $addr on raw strings. Talos reports addresses canonically (netip.Prefix.String()), so a VIP spelled 2001:0DB8::5 never matches the discovered 2001:db8::5/64.

Evidence: With a fixture carrying 2001:db8::5/64 on eth0 and vips: [{link: eth0, ip: "2001:0DB8::5"}], the render contains both 2001:db8::5/64 under LinkConfig.addresses and kind: Layer2VIPConfig. A control render with the canonical spelling strips correctly, so the mechanism itself works — only the string comparison is wrong. The same gap pre-exists for floatingIP on main (:164, :807); this PR rewrites that loop and extends it to vips, so one fix covers both.

Impact: Exactly the failure the code's own comment says the strip prevents — "leaves the leader and the followers disagreeing about who owns it". Breaks VIP failover.

Fix: Canonicalize every VIP (and the discovered address) once, before the dedup check, the addresses_not_vip guard, and this strip.

Non-blocking follow-ups

  1. CIDR prefix length unvalidated_helpers.tpl:432 and :457 check contains "/" and then run ipIsValid on the text before the slash only. 203.0.113.10/999 as an extraLinks address, and 198.51.100.0/foo as a route destination, both render straight through with no error. The cidrPrefixLen helper already exists (pkg/engine/helm/engine.go:265) and would close both paths.

  2. Fractional vlanId_helpers.tpl:1449 range-checks $vlan.vlanId | int, truncating 7.5 to 7 and passing, but :1469 emits the original value: the rendered output is vlanID: 7.5 with document name eth0.7.5. Talos rejects that at decode time. Worth checking the value is an integer before the range check.

  3. LinkAliasConfig %d aliasespkg/applycheck/refs.go:211 registers .name literally. Machinery v1.13.7 config/types/network/link_alias.go:59-63 documents net%d expanding to net0, net1, … per matched link. So the literal net%d enters linkSet while the names that will actually exist do not: a net%d reference passes the gate, and a legitimate net0 reference still blocks. This also makes the README's new claim that WireguardConfig is the sole exemption slightly overstated.

  4. Bare apiServer:charts/generic/templates/_helpers.tpl:123 emits a valueless key when certSANs is [] (the default) and extraApiServerArgs is empty. This is pre-existing (apiServer: is unconditional on main at line 76), so it is not a regression from this PR and the golden tests correctly show defaults unchanged — but the block is open here and it is a one-line fix.

  5. Commit granularity — five independent features land in one 3,105-line commit. Split per issue (#226#230) would make git bisect and a per-feature revert possible.

  6. Operator docs — the PR notes site docs as a follow-up; there is no open PR on the docs repo yet. Worth a tracking issue so the values reference (legacy vs v1.12+ split for vips/registryTLS, the multi-doc-only restriction on network.extraLinks, and the preserveExisting escape hatch) does not get lost.

Bot findings triaged

  • Empty apiServer: breaking byte-identical defaults — not a regression; apiServer: is already unconditional on main at line 76. Retained as follow-up 4 on its cosmetic merits only.
  • docs/manual-test-plan.md:646 setup/error-message mismatch — valid.
  • README VIP-stripping wording contradicting itself — valid, and B2 makes the wording moot until fixed.
  • README MD028 blank lines inside the blockquote — valid, lint-only.

Verified as sound

go test ./... passes locally across all packages, matching CI. Golden renders confirm defaults are byte-identical over both presets × controlplane/worker × legacy/multi-doc. The two applycheck dispatch maps are genuinely disjoint — WireguardConfig appears only in multidocNetAddrHandlers, as the comment claims. mergeMaps (pkg/engine/engine.go:1795) does assign nil, so the "null the key out" instruction in values.yaml is accurate. The 228 test functions across the five contract files do cover accept and reject paths; B1 and B2 are cases those tests never reach, not a testing-convention gap.

Comment thread charts/talm/templates/_helpers.tpl Outdated
---
apiVersion: v1alpha1
kind: RegistryTLSConfig
name: {{ $host }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

B1 (blocker). A bracketed IPv6 endpoint host renders as name: [fd00::1]:5000, and [ opens a YAML flow sequence — the document no longer parses (yaml: did not find expected key). Verified by rendering registryTLS: {"[fd00::1]:5000": {insecureSkipVerify: true}} and feeding the output to a YAML decoder.

registryMirrors at line 354 already handles this with name: {{ $name | toYaml }} and a comment about the * fallback key. Same treatment needed here: {{ $host | toYaml }}.

(Plain host:port without brackets parses fine, so only the bracketed form is affected.)

Comment thread charts/talm/templates/_helpers.tpl Outdated
{{- with .Values.registryTLS }}
config:
{{- range $host, $cfg := . }}
{{ $host }}:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

B1 (blocker), legacy half. Same unquoted $host as line 366, this time as a mapping key:

    config:
      [fd00::1]:5000:
        tls:
          insecureSkipVerify: true

Parsing this fails with yaml: line 65: did not find expected key. {{ $host | toYaml }} fixes it here too.

Comment thread charts/talm/templates/_helpers.tpl Outdated
{{- $addr := . }}
{{- $isVip := false }}
{{- range $vipIPs }}
{{- if hasPrefix (printf "%s/" .) $addr }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

B2 (blocker). This compares raw strings, but Talos reports discovered addresses canonically via netip.Prefix.String(). A VIP spelled 2001:0DB8::5 therefore never matches the discovered 2001:db8::5/64, so the strip misses it.

Verified: fixture with 2001:db8::5/64 on eth0 plus vips: [{link: eth0, ip: "2001:0DB8::5"}] renders the address both under LinkConfig.addresses and as a Layer2VIPConfig. A control render using the canonical spelling strips correctly, so the mechanism works — only the comparison is spelling-sensitive.

That is the exact condition the comment above warns about ("leaves the leader and the followers disagreeing about who owns it"). The same gap pre-exists for floatingIP on main (lines 164 and 807), so canonicalizing once — before the dedup check, the addresses_not_vip guard, and this strip — covers both values.

{{- fail (printf "talm: address %q on network.extraLinks %q has no prefix length. Talos parses addresses as CIDR, so write it as 203.0.113.10/24." (. | toString) $name) }}
{{- end }}
{{- $ip := (splitList "/" (. | toString)) | first }}
{{- if not (ipIsValid $ip) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ipIsValid runs on the text before the slash only, so the prefix length is never checked. 203.0.113.10/999 renders straight through to the emitted document with no error — Talos then rejects it at decode time with a message that does not name this document, which is the outcome the surrounding guards exist to avoid.

cidrPrefixLen already exists (pkg/engine/helm/engine.go:265) and validates the whole CIDR. The route-destination check at line 457 has the same partial-validation shape (198.51.100.0/foo also passes).

{{- fail (printf "talm: network.extraLinks entry %q declares a VLAN with no vlanId. VLANConfig requires vlanID; add vlanId to the vlans entry." ($extra.interface | toString)) }}
{{- end }}
{{- if or (lt ($vlan.vlanId | int) 1) (gt ($vlan.vlanId | int) 4094) }}
{{- fail (printf "talm: network.extraLinks entry %q declares vlanId %v, outside the valid range. Talos requires vlanID between 1 and 4094." ($extra.interface | toString) $vlan.vlanId) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$vlan.vlanId | int truncates, so vlanId: 7.5 becomes 7 and passes this range check — but line 1469 emits the original value. The render output is vlanID: 7.5 with document name eth0.7.5, which Talos rejects while decoding. Worth asserting the value is an integer before range-checking it.

Comment thread pkg/applycheck/refs.go
// absent here: it belongs to the parallel net-addr walker, and the
// two dispatch maps must stay disjoint or a kind gets double-walked.
"DummyLinkConfig": handleCreatorOnly,
"LinkAliasConfig": handleCreatorOnly,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LinkAliasConfig.name supports a format verb: machinery v1.13.7 config/types/network/link_alias.go:59-63 documents net%d expanding to net0, net1, … one per matched link, with the selector allowed to match multiple links in that case.

So appendCreatedRef registers the literal net%d into linkSet while the names that will actually exist are never registered. Net effect is inverted from the intent: a VLANConfig.parent: net%d reference passes the gate even though no such link will ever exist, and a legitimate parent: net0 still blocks on first apply.

This also makes the README's new sentence — that WireguardConfig is the one exception which does not register its name — narrower than stated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
pkg/engine/render_test.go (1)

7040-7072: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: fold into renderCozystackWith via a template-name parameter. The two helpers differ only in the returned output key, so the endpoint defaulting and lookup save/restore logic is now duplicated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/engine/render_test.go` around lines 7040 - 7072, Refactor
renderCozystackWith and renderCozystackWorkerWith to share their common setup
and rendering logic, parameterizing only the template output key (or otherwise
reusing one helper). Preserve each helper’s existing returned template while
removing duplicated endpoint defaulting and helmEngine.LookupFunc save/restore
behavior.
pkg/engine/contract_cluster_test.go (1)

728-744: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Name the chart in the failure path. Both presets run through the same anonymous loop, so a failure doesn't say which chart broke.

♻️ Optional: table with names
-	for _, render := range []func(*testing.T, func(string, string, string) (map[string]any, error), map[string]any, ...string) error{
-		renderCozystackExpectError,
-		renderGenericExpectError,
-	} {
-		err := render(t, simpleNicLookup(), map[string]any{
-			"timeServers": map[string]any{"primary": "time.cloudflare.com"},
-		})
-		if err == nil {
-			t.Fatal("expected a fail-fast for a timeServers mapping")
-		}
-
-		if !strings.Contains(err.Error(), "timeServers") {
-			t.Errorf("error should name the field, got %v", err)
-		}
-	}
+	for name, render := range map[string]func(*testing.T, func(string, string, string) (map[string]any, error), map[string]any, ...string) error{
+		"cozystack": renderCozystackExpectError,
+		"generic":   renderGenericExpectError,
+	} {
+		t.Run(name, func(t *testing.T) {
+			err := render(t, simpleNicLookup(), map[string]any{
+				"timeServers": map[string]any{"primary": "time.cloudflare.com"},
+			})
+			if err == nil {
+				t.Fatal("expected a fail-fast for a timeServers mapping")
+			}
+
+			if !strings.Contains(err.Error(), "timeServers") {
+				t.Errorf("error should name the field, got %v", err)
+			}
+		})
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/engine/contract_cluster_test.go` around lines 728 - 744, Update
TestContract_Cluster_TimeServersMapping_Fails to associate each renderer with
its chart/preset name and include that name in failure messages, so failures
identify whether renderCozystackExpectError or renderGenericExpectError failed
while preserving the existing timeServers assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/engine/contract_cluster_test.go`:
- Around line 728-744: Update TestContract_Cluster_TimeServersMapping_Fails to
associate each renderer with its chart/preset name and include that name in
failure messages, so failures identify whether renderCozystackExpectError or
renderGenericExpectError failed while preserving the existing timeServers
assertion.

In `@pkg/engine/render_test.go`:
- Around line 7040-7072: Refactor renderCozystackWith and
renderCozystackWorkerWith to share their common setup and rendering logic,
parameterizing only the template output key (or otherwise reusing one helper).
Preserve each helper’s existing returned template while removing duplicated
endpoint defaulting and helmEngine.LookupFunc save/restore behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ac9f87a-4030-445c-9453-df97d242a38f

📥 Commits

Reviewing files that changed from the base of the PR and between e3477ff and 171afd8.

📒 Files selected for processing (17)
  • README.md
  • charts/cozystack/templates/_helpers.tpl
  • charts/cozystack/values.yaml
  • charts/generic/templates/_helpers.tpl
  • charts/generic/values.yaml
  • charts/talm/templates/_helpers.tpl
  • docs/manual-test-plan.md
  • pkg/applycheck/refs.go
  • pkg/applycheck/validate.go
  • pkg/applycheck/validate_test.go
  • pkg/engine/contract_cluster_test.go
  • pkg/engine/contract_errors_test.go
  • pkg/engine/contract_machine_test.go
  • pkg/engine/contract_network_legacy_test.go
  • pkg/engine/contract_network_multidoc_test.go
  • pkg/engine/helm/engine.go
  • pkg/engine/render_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • charts/cozystack/values.yaml
  • pkg/applycheck/refs.go
  • pkg/engine/contract_errors_test.go
  • docs/manual-test-plan.md
  • pkg/engine/contract_network_legacy_test.go
  • charts/generic/values.yaml
  • charts/talm/templates/_helpers.tpl
  • pkg/engine/contract_machine_test.go

@myasnikovdaniil myasnikovdaniil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT LGTM — both original blockers are fixed; one new blocker came in with the fix round.

Previous findings — all resolved

  • registryTLS host unquoted{{ $host | toYaml }} now applied at _helpers.tpl:405 and :442. Verified: a bracketed IPv6 endpoint host renders as name: '[fd00::1]:5000' and both schemas parse.
  • Non-canonical IPv6 VIP — the new ipCanonical template helper normalises both sides. Verified: vips[].ip: 2001:0DB8::5 against a discovered 2001:db8::5/64 now strips correctly and emits a single Layer2VIPConfig; two spellings of one address are also caught as a duplicate.
  • CIDR prefix lengthcidrPrefixLen < 0 now checked on extraLinks addresses and on route destinations. 203.0.113.10/999 and 198.51.100.0/foo are both refused, with the document named.
  • Fractional vlanId — digits-only check now runs before the range check; vlanId: 7.5 is refused.
  • LinkAliasConfig %d patternsunionCreatedLinks contributes the pattern's prefix and admits references matching prefix+digits. aliasPatternPrefix mirrors machinery's own Validate exactly (strings.Cut on %, rejecting suffix != "d" and an empty prefix), so the accepted shape matches what the node will accept.
  • Bare apiServer: — declining this with a rationale comment and a pinning test is a fine answer for a cosmetic pre-existing artifact.

The additional hardening in this round (mtu digits + kernel 68-65535 bounds, the timeServers shape guard, and folding six copied extraArgs loops into talm.render.component_args with map/slice rejection) all render and refuse as intended in local checks.

Blocker

B3: bond-slave guard prescribes a remediation that cannot work

File: charts/talm/templates/_helpers.tpl:1167-1168

Issue: The guard refuses to enslave a link that discovery reports carrying addresses or the default route, and its message offers two ways out:

Move those addresses and routes onto the bond entry, or enslave a link that carries neither.

The condition at :1167 tests only $slaveAddresses and $defaultLinkName — both derived from discovery. It never reads $extra.addresses. The first remediation therefore has no effect.

Evidence: With eth0 carrying 192.168.201.10/24 and the default route via 192.168.201.1, and an extraLinks entry declaring bond0 over [eth0, eth1] with that same address and gateway moved onto the bond entry, the render still fails with the identical message. Bonding an idle NIC (no addresses, not the default-route link) succeeds, so the guard is otherwise working as designed.

The guard's own comment states the intended contract — "They have to say so by putting the addresses on the bond" — so this is the implementation diverging from the stated design rather than a deliberate restriction.

TestContract_NetworkMultidoc_ExtraLinksBondSlaveWithAddresses_Fails (pkg/engine/contract_network_multidoc_test.go:2605) declares addresses on the bond, i.e. follows the prescribed remediation, and asserts failure — so the suite currently pins the misleading guidance instead of catching it.

Impact: enslaving an already-addressed NIC is unreachable through network.extraLinks. That is the headline scenario in #226bond0 as 802.3ad over two NICs carrying the default route — so the most common bonding migration cannot be expressed, and the error text sends the operator down a path that does not resolve it.

Fix: either accept the case when the bond entry declares addresses (matches the comment's stated intent), or drop the first clause from the message and narrow the test to pin only the genuine restriction. A test covering the accepted path — slave addresses moved onto the bond, render succeeds — would close the gap either way.

Comment thread charts/talm/templates/_helpers.tpl Outdated
{{- if has $slaveName $configurableLinks }}
{{- $slaveAddresses := fromJsonArray (include "talm.discovered.addresses_by_link" $slaveName) }}
{{- if or $slaveAddresses (eq $slaveName $defaultLinkName) }}
{{- fail (printf "talm: network.extraLinks bond %q enslaves %q, which discovery reports carrying its own addresses or the default route. A bond slave holds no addressing of its own, so this apply would take the node's connectivity away without saying where it goes. Move those addresses and routes onto the bond entry, or enslave a link that carries neither." ($extra.interface | toString) $slaveName) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

B3 (blocker). The message offers "Move those addresses and routes onto the bond entry" as a way out, but the condition on line 1167 tests only $slaveAddresses and $defaultLinkName — both from discovery. It never reads $extra.addresses, so that remediation cannot unblock the render.

Verified: eth0 carrying 192.168.201.10/24 plus the default route via 192.168.201.1, and an extraLinks entry declaring bond0 over [eth0, eth1] with that same address and gateway moved onto the bond entry — the render still fails with this exact message. Bonding an idle NIC works, so the rest of the guard behaves as designed.

The comment above states the intent ("They have to say so by putting the addresses on the bond"), so the code is diverging from the stated design. This currently makes enslaving an already-addressed NIC unreachable — the bond0-over-two-NICs-carrying-the-default-route case from #226.

Either honour the intent (accept the case when the bond declares addresses), or drop the first clause so the message only offers the remediation that works.

// fails instead of silently dropping them. Filtering the slave's document
// would take the node's addressing away without saying where it went, and
// the render cannot know whether the operator meant to move it.
func TestContract_NetworkMultidoc_ExtraLinksBondSlaveWithAddresses_Fails(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test declares addresses: [203.0.113.10/24] on the bond — following the remediation the guard's error message prescribes — and asserts that it still fails. So the contract being pinned here is the misleading guidance rather than the genuine restriction.

Worth pairing with a positive case: slave addresses moved onto the bond entry, render succeeds. That test would have surfaced the mismatch, and it is the coverage that decides which way B3 gets fixed.

@myasnikovdaniil myasnikovdaniil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — every blocker from the previous rounds is resolved and independently verified; two narrow edges in the new bond-slave guard are left as follow-ups.

B3 resolved

The guard is now two independent checks (_helpers.tpl:1168 and :1171) instead of one, and both messages interpolate the discovered values so the operator is told which addresses and which gateway to restate. talm.discovered.gateway_by_link, used by the second message, is the pre-existing helper at :870.

Verified against a fixture where eth0 carries 192.168.201.10/24 plus the default route via 192.168.201.1:

  • Declaring bond0 over [eth0, eth1] with that address and gateway restated on the bond entry now renders, and neither slave gets a LinkConfig of its own.
  • Omitting them still refuses, now naming carrying its own addresses (192.168.201.10/24).
  • Bonding an idle NIC continues to work.

TestContract_NetworkMultidoc_ExtraLinksBondSlaveWithAddresses_Fails now enslaves eth1 — which carries 10.0.0.5/24 and is not the default-route link — so it exercises the addresses branch rather than pinning the old contradiction. The two added tests cover the accepted path and the forgot-the-route case, and the manual-test-plan section flags the bond migration as worth applying on real hardware, which is the right call given the render cannot prove the node keeps its route across the switch.

Local go test ./... is clean, including the golden renders — stock output is still byte-identical to before for both presets across controlplane/worker and legacy/multi-doc.

Non-blocking follow-ups

  1. The default-route check can be satisfied without a default route (_helpers.tpl:1171). The condition tests only that $extra.routes is non-empty, so a destination-scoped route passes it. With routes: [{gateway: 192.168.201.1, destination: 172.16.0.0/12}] on a bond over the default-route link, the render is accepted and the emitted BondConfig carries only that scoped route — no default route anywhere. The guard's purpose is "so the node keeps its way out", and this satisfies the check without achieving it. Requiring at least one route with no destination would close it. Non-blocking because it takes an operator actively writing a destination while omitting the default; the render does emit exactly what it was given.

  2. A slave's non-default static route is dropped without a word. A link carrying a scoped route (e.g. 10.0.0.0/24 via 10.0.0.1) has it on its own document; enslaving that link drops the document and the route, and the guard does not look at anything beyond addresses and the default route. Lower severity than losing connectivity, and arguably outside the guard's stated scope, but it is the same class of silent loss the guard exists to prevent — worth either extending the check or naming the limitation in the values documentation.

Earlier findings, all confirmed fixed

registryTLS host quoting on both schemas; IPv6 VIP canonicalisation via the new ipCanonical helper, covering the strip, the duplicate check and the extraLinks guard; full-CIDR prefix validation on addresses and route destinations; the integer check on vlanId ahead of the range check; and LinkAliasConfig %d patterns registering as a prefix so net0 resolves while the literal net%d does not — with the accepted pattern shape matching machinery's own Validate. The bare apiServer: nit was declined with a rationale and a pinning test, which is a reasonable answer for a pre-existing cosmetic artifact.

The hardening added along the way — mtu digits plus the kernel 68-65535 bounds, the timeServers shape guard, and folding six duplicated extraArgs loops into talm.render.component_args with map/slice rejection — all behaves as documented in local checks.

Comment thread charts/talm/templates/_helpers.tpl Outdated
{{- if and $slaveAddresses (not $extra.addresses) }}
{{- fail (printf "talm: network.extraLinks bond %q enslaves %q, which discovery reports carrying its own addresses (%s). A bond slave holds no addressing of its own, so this apply would take the node's connectivity away without saying where it goes. Put those addresses on the bond entry, or enslave a link that carries none." ($extra.interface | toString) $slaveName (join ", " $slaveAddresses)) }}
{{- end }}
{{- if and (eq $slaveName $defaultLinkName) (not $extra.routes) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up, non-blocking. This tests only that $extra.routes is non-empty, so a route that is not a default route satisfies it.

With routes: [{gateway: 192.168.201.1, destination: 172.16.0.0/12}] on a bond over the default-route link, the render is accepted and the emitted BondConfig is:

routes:
  - gateway: 192.168.201.1
    destination: 172.16.0.0/12

No default route anywhere — the node comes back up without a way off its subnet, which is the outcome the message below is written to prevent.

Requiring at least one entry with no destination would close it. Not blocking: it takes an operator actively writing a destination while omitting the default, and the render faithfully emits what it was given.

{{- range $linkName := $configurableLinks }}
{{- $link := lookup "links" "" $linkName }}
{{- if $link }}
{{- if and $link (not (has $linkName $bondSlaveNames)) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up, non-blocking. Filtering the slave here also drops any non-default static route it carried, and the guard above only inspects addresses and the default route.

Concretely: a link carrying 10.0.0.0/24 via 10.0.0.1 on its own document loses that route when enslaved, with no refusal and no warning. Less severe than losing connectivity, and arguably outside this guard's stated scope — but it is the same class of silent loss, so either extending the check to scoped routes or naming the limitation in the network.extraLinks values documentation would round it out.

Expose operator-visible values on the cozystack, generic, and talm charts
so a node's Talos config can be described from values.yaml instead of
forking the template:

- timeServers: machine.time.servers
- extra{ApiServer,ControllerManager,Scheduler}Args: passthrough
  control-plane component args, coerced to strings (Talos wants
  map[string]string) and guarded against colliding with preset built-ins
- registryMirrors: declarative machine registry mirrors (the default
  docker.io -> mirror.gcr.io is preserved)
- registryTLS: TLS posture per registry endpoint host, keyed by endpoint
  hostname rather than mirror name so a self-signed pull-through cache can
  be trusted without touching the mirror list
- vips: multiple Layer2 VIPs, each pinned to a link
- network.preserveExisting: emit the running interfaces verbatim
- network.extraLinks: declare bonds, VLANs, and extra addresses, with the
  same bond tuning the discovery path reconstructs (xmitHashPolicy,
  lacpRate, miimon, updelay, downdelay) plus mtu and routes on both a link
  and its VLAN children, so an externally-routed tagged uplink is
  expressible without a template fork

Every knob defaults empty so a stock render stays byte-identical, pinned
by TestGoldenRender. Each input Talos would reject fails fast at render
with a hinted message: extraArgs colliding with a built-in, a duplicate or
dangling VIP, a VIP on a link already in the running config, a VLAN on a
missing parent, an empty bond, a route with no gateway, a malformed
registry entry, or a registryTLS entry that leaves its security posture
unstated. Contract tests cover the happy and rejected paths across the
legacy and multi-doc schemas for both presets.

An extraLinks bond slave gets no document of its own, matching the filter
discovery applies once the bond exists. Moving an already-addressed NIC
into a bond works once the entry restates everything that link was
carrying — its addresses, a destination-less route where it held the
default one, and a matching route per static destination. Anything left
behind fails the render, naming what to restate, rather than costing the
node its connectivity or a reachable subnet. VIP addresses are compared
canonically, so a VIP spelled differently from what the node reports is
still stripped from the static addresses rather than shipping twice.

pkg/applycheck learns which links a config creates, so a VLANConfig.parent,
BondConfig.links[] or Layer2VIPConfig.link pointing at one of them resolves
instead of blocking the first apply — without it a declared bond is
unusable. A LinkAliasConfig name ending in %d is registered as a pattern,
because Talos expands it into one alias per matched link.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/engine/contract_network_multidoc_test.go (1)

2383-2561: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Error-path cases here drop advertisedSubnets, unlike the accept-path tests.

Most of these rely solely on a message substring to prove the intended guard fired. For cases whose assertion is just an interface name (Line 2620 "eth1", Line 2763 "eth9"), an unrelated render failure caused by the missing base values would satisfy the assertion. Consider including advertisedSubnets consistently in the failure cases, or asserting on a guard-specific phrase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/engine/contract_network_multidoc_test.go` around lines 2383 - 2561, Add
the required advertisedSubnets fixture to the affected error-path inputs in the
network multidoc contract tests, matching the accept-path setup so failures
reach the intended validation guard. In particular, update cases such as
TestContract_NetworkMultidoc_ExtraLinksAddressIsVipAcrossSpellings_Fails and
other eth1/eth9 assertions, or strengthen their checks to require a
guard-specific error phrase rather than only an interface name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/engine/contract_network_multidoc_test.go`:
- Around line 2383-2561: Add the required advertisedSubnets fixture to the
affected error-path inputs in the network multidoc contract tests, matching the
accept-path setup so failures reach the intended validation guard. In
particular, update cases such as
TestContract_NetworkMultidoc_ExtraLinksAddressIsVipAcrossSpellings_Fails and
other eth1/eth9 assertions, or strengthen their checks to require a
guard-specific error phrase rather than only an interface name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a79d8c7c-0fea-4d31-8bb8-5c484ac060f2

📥 Commits

Reviewing files that changed from the base of the PR and between 3f158c9 and 5e8be43.

📒 Files selected for processing (17)
  • README.md
  • charts/cozystack/templates/_helpers.tpl
  • charts/cozystack/values.yaml
  • charts/generic/templates/_helpers.tpl
  • charts/generic/values.yaml
  • charts/talm/templates/_helpers.tpl
  • docs/manual-test-plan.md
  • pkg/applycheck/refs.go
  • pkg/applycheck/validate.go
  • pkg/applycheck/validate_test.go
  • pkg/engine/contract_cluster_test.go
  • pkg/engine/contract_errors_test.go
  • pkg/engine/contract_machine_test.go
  • pkg/engine/contract_network_legacy_test.go
  • pkg/engine/contract_network_multidoc_test.go
  • pkg/engine/helm/engine.go
  • pkg/engine/render_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • pkg/engine/helm/engine.go
  • charts/generic/values.yaml
  • pkg/engine/contract_errors_test.go
  • pkg/applycheck/validate.go
  • pkg/applycheck/refs.go
  • pkg/engine/render_test.go
  • charts/generic/templates/_helpers.tpl
  • pkg/applycheck/validate_test.go
  • charts/cozystack/values.yaml
  • docs/manual-test-plan.md
  • charts/cozystack/templates/_helpers.tpl
  • pkg/engine/contract_cluster_test.go
  • pkg/engine/contract_network_legacy_test.go
  • pkg/engine/contract_machine_test.go
  • charts/talm/templates/_helpers.tpl

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 7d48e60 into main Jul 29, 2026
8 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the feat/preset-values-knobs branch July 29, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants