Skip to content

[dmt] feat: matrix rendering and Kubernetes schema validation - #426

Merged
ldmonster merged 4 commits into
mainfrom
feat/check-resources-by-crds
Sep 5, 2026
Merged

[dmt] feat: matrix rendering and Kubernetes schema validation#426
ldmonster merged 4 commits into
mainfrom
feat/check-resources-by-crds

Conversation

@ldmonster

@ldmonster ldmonster commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

Two user-facing additions to dmt lint.

--matrix — lint every value combination, not just the default one

A single default render only exercises one branch of a chart's templates, so
resources behind a feature flag, a mode enum or an x-examples alternative are
never produced and never linted. --matrix discovers the axes of variation from
the module's own openapi schema and renders each combination:

  • a node carrying x-examples contributes one variant per example;
  • an enum contributes one variant per allowed value;
  • a boolean contributes true and false.
dmt lint --matrix ./modules
dmt lint --matrix --matrix-limit 500 ./modules

The first variant is always the default render, so matrix output is a superset
of an ordinary lint. Combinations are the cartesian product capped by
--matrix-limit (default 100); past the cap it falls back to an all-pairs
(pairwise) set, so every pair of axis values still co-occurs somewhere — enough to
reach a resource gated by two conditions at once.

The CI plumbing for this already exists on main: the DMT-verify jobs carry
matrix / matrix_limit workflow_dispatch inputs that pass --matrix through
to the binary. They now do something.

schema-validation — check rendered resources against the Kubernetes API

A new rule in the templates linter. Each rendered standard Kubernetes
resource is decoded into the Go type that serves its apiVersion/kind,
strictly. Two things are reported:

  • a field of the wrong typereplicas: "3" where an integer belongs;
  • a field the API does not declare — a typo, a renamed key, or a value put one
    level off, such as resources.memory instead of resources.limits.memory.
    This is the error server-side apply reports as "field not declared in schema".
    Each offending field is reported separately, by its full path.
resource does not match the DaemonSet API: unknown field "spec.template.spec.containers[0].resources.memory"
resource does not match the Deployment API: unrecognized type: int32

Configurable like every other rule:

linters-settings:
  templates:
    rules:
      schema-validation:
        impact: error
    exclude-rules:
      schema-validation:
        - kind: MyResource
          name: my-resource

Why custom resources are not checked

The branch name says CRDs, and an earlier iteration did resolve CRD schemas — from
the module's own crds/, from a repo-wide scan of the deckhouse tree, and from a
bundled third-party catalog. All of it was dropped.

The definition a cluster actually serves is not knowable from a source tree. The
only answers available come either from a snapshot that lags upstream or from a
CRD that may not be the one installed, and a false finding against a stale schema
costs more than the check is worth.

Two consequences worth knowing:

  • Nothing is embedded. No generated schema blob in the tree, no catalog to
    regenerate, no sync job to keep green.
  • The Kubernetes version checked against is whatever k8s.io/api is in
    go.mod.
    Bumping that dependency is the whole of updating the rule, and the
    check can never drift from the API types the rest of dmt already uses.

Custom resources — anything served by a CRD — have no registered Go type and are
skipped silently. The rule reports violations, never the absence of a type.

Design notes

Targets are streamed, not collected. manager.Source.Targets now pushes each
target into a yield callback instead of returning []Target. A matrix run
expands one module into many renders, and materializing them would hold every
rendered chart in memory at once. yield blocks while the worker pool is full, so
the source renders the next variant only once an earlier one has been linted and
freed — a module expanding to thousands of combinations never has more than the
worker count resident. Object stores are pooled and handed back by
Module.Release() as soon as a target is linted.

Rendering and linting of one module never overlap. Rendering writes into the
module's own templates/: an image-resolution helper goes in for the duration of
each render and comes out again, and a template that aborts the render is
neutralized in place and restored. Because matrix re-renders the same directory,
a variant target is linted to completion before the source is let go to render the
next one. Without this, dmt lint --matrix crashed 3 runs out of 12 on a path
that vanished mid-walk. Ordinary runs render each module once, never wait, and stay
fully parallel.

Findings are de-duplicated in a variant run. The same resource is linted under
many combinations, so a finding that does not depend on the combination would be
reported once per combination. Findings identical in every user-visible field are
collapsed, and the summary counts the same set that was printed.

Bugs found and fixed along the way

  • --values-file silently did nothing. OverrideValues wrapped the caller's
    values under an extra Values key, landing them at .Values.Values.…, where no
    template reads them.
  • One unreadable path crashed a whole run. fsutils.GetFiles discarded the
    filepath.Walk error and dereferenced the nil FileInfo that comes with it.
    Every other walker in the repo handles the error; this one did not.
  • Generated placeholder values tripped downstream schemas. The default string
    placeholder was mixed-case, which fails the DNS-1123 pattern that name, namespace
    and label fields are constrained by. An int-or-string oneOf — the quantity
    shape used for resources.requests.cpu and VPA bounds — generated {} instead
    of a scalar.
  • Matrix overrides leaked between variants. mergo shares nested maps when the
    destination has no key yet, so with --values-file each variant's overrides were
    written into the shared base tree and accumulated across the run.

@ldmonster ldmonster self-assigned this Jul 14, 2026
@ldmonster ldmonster added the enhancement New feature or request label Jul 14, 2026
@ldmonster
ldmonster force-pushed the feat/check-resources-by-crds branch 2 times, most recently from ea5ee49 to 3d9fe5a Compare August 26, 2026 14:10
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
@ldmonster
ldmonster force-pushed the feat/check-resources-by-crds branch from 3d9fe5a to 05efdab Compare September 4, 2026 17:22
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
@ldmonster ldmonster changed the title [dmt] add schema validation for known fields [dmt] feat: matrix rendering and Kubernetes schema validation Sep 5, 2026
@ldmonster
ldmonster merged commit d14d179 into main Sep 5, 2026
8 checks passed
@ldmonster
ldmonster deleted the feat/check-resources-by-crds branch September 5, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant