Skip to content

feat(deploy): render Helm images, mark default-valued images, link crds and werf charts - #2677

Open
DeusData wants to merge 4 commits into
mainfrom
feat/deploy-graph-gaps
Open

DeusData wants to merge 4 commits into
mainfrom
feat/deploy-graph-gaps

Conversation

@DeusData

@DeusData DeusData commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Summary

This closes the four gaps recorded in #2663's "Found, not changed here" and #2654's "Default values still name images".

  1. Images named from a default value are marked; overrides found in the repository get their own edges.
  2. Helm template images render from values, include / define helpers and release values, by a bounded Go template evaluator (src/pipeline/helm_template.c).
  3. A chart deploys its crds/.
  4. A werf .helm/ dir is a chart.

No new node label or edge type is added. The image identity rules of #2654 stay: no registry host is ever invented.

1. Default-valued images (pass_docker.c, pass_ci.c, pass_shell.c)

Where Edge Marks
Dockerfile FROM / COPY --from filled by an ARG default Module -DEPENDS_ON-> Image default_of: ["GO","BASE"], every ARG of a chain
compose image: ${X:-d}, .env not setting X Service -USAGE-> Image default_of: ["X"]
docker build -t ${IMAGE:-x} (also through IMAGE=${IMAGE:-x}) Image -DEPENDS_ON {via: build}-> Dockerfile default_of: ["IMAGE"]
compose build.args, docker build --build-arg built Image -DEPENDS_ON {via: from}-> the base they choose override_of: ["GO"], dockerfile, tool, file, build_line
compose .env replacing an inline default Service -USAGE-> effective Image override_of, override_source: ".env"; the default kept as {ref: image_default} (not run, so the env join and hubs ignore it)
  • A literal reference to the same Image wins over a default one, so the mark never depends on the order in which edges merge.
  • A built Image's override edges move with its build edge when an image hub splits it.
  • ${X:-d} with no ARG X is not marked, because no build argument can set it.

2. Helm template images (helm_template.c, pass_k8s.c, pass_terraform.c)

Values are read the way Helm reads them:

  • go-yaml v2 scalars: YAML 1.1 booleans, every number a float64 printed like Go's %v (1.10 → 1.1, 20231010 → 2.023101e+07);
  • Helm's coalesce, where null deletes a key;
  • --set typing for set.

Templates render with Go text/template semantics:

  • {{- / -}} trimming;
  • if / else if / with / range (map keys sorted) / break;
  • variables, define / template / block / include / tpl;
  • pipelines (the piped value is the last argument), lazy and / or;
  • the Sprig functions image expressions use.

Anything not modelled is UNKNOWN, and an image that reads an UNKNOWN names no Image: .Release.*, .Capabilities, lookup, a missing define, a field of a missing map. A rendered value is checked against the image reference grammar before it names an Image.

What the graph gets:

  • Each templated container image renders in its chart family's context. That is the root chart's values, flowing to subcharts under their alias with global; a dependency condition that is false disables the subchart.
    • The object USES the Image {default_of: <values keys read>}.
    • The Chart DEPLOYS it with the same mark, or none when an object names it literally.
  • A release renders its chart with its values over the defaults. Each image that differs is the release's own: <release> -DEPLOYS {via, ref: image, image, override_of}-> Image. Release sources:
    • Flux HelmRelease spec.values;
    • kustomize helmCharts valuesFile + valuesInline. A chart without repo: now links to the chart in charts/ instead of an external node;
    • Terraform helm_release values = [...] (heredoc, file(), yamlencode) then set (block or provider-v3 list). Literals only: an interpolation or set_list leaves the release unread. set_sensitive is never read.
  • A release of a chart outside the repository has no templates here. Its values name images by convention: a mapping with repository (+ tag / digest / registry), and only when the reference names a registry host. That gives {inferred: values, values_key}; a host-less repository names none.

3. and 4. crds/ and werf

  • crds/: Chart -DEPLOYS {via: chart, phase: crds}-> each CRD of <chart>/crds/.
  • werf: a werf.yaml beside a .helm/ without Chart.yaml makes a Chart named after the werf project, and the dir links like any chart: objects, values, includes, images. deploy.helmChartDir is not followed.

Incremental runs (pipeline_incremental.c)

These edits now decline to the full rebuild, like their Chart.yaml-chart equivalents:

  • a werf.yaml edit (helm_chart_changed);
  • a werf chart's values or templates;
  • a release's values file, i.e. a kustomize valuesFile or a Terraform file() (helm_release_values_changed). Terraform's values file now gets IMPORTS {local_name: values}, like kustomize's valuesFile.

No index format bump: the change only adds edges and properties.

Proof on real input

All 20 pinned infra corpora, base 9f85f104 against this branch.

Oracle: helm template with Helm 4.3.1, local only, --kube-version 1.30.0, on every root chart whose dependencies are in the repository (84 charts).

  • Helm writes 110 container images. The graph matches 105, with 0 different and 0 extra (no wrong image).
  • The 5 not matched are templates whose whole pod spec comes from an include ({{ include "x.podSpec" . | nindent }}). There is no image: key in the template to render; recorded below.
  • 39 charts don't render in Helm itself: deckhouse's helm_lib is a symlink not in the corpus copy, and two charts are required-value or YAML errors.

What the graph gains:

count
objects USING a rendered template image {default_of} 90
Chart → Image {default_of} 86
release → Image by values convention {inferred: values} (home-ops 28) 30
Dockerfile FROM {default_of} (ARG defaults, chains) 9
built Image → base {override_of} (--build-arg, deckhouse) 2
Chart → CRD {phase: crds} 158
werf Charts (deckhouse) 2
  • New nodes: 90 Images, 2 Charts. The one edge removed is the deckhouse similarity edge below.
  • The only change outside these edges is in deckhouse: the werf charts' 14 include CALLS edges are now drawn, and the similarity pass moves accordingly (+14 / −1 SEMANTICALLY_RELATED).
  • The 4 local-chart Terraform releases in the corpora get no image edge: one sets no values, three interpolate module outputs. HelmRelease and kustomize local-chart releases have no real instances in the corpora; they are proven on fixtures, against helm template -f / --set output.
  • Deterministic: two full runs of all 20 corpora are identical, properties included.
  • Indexing time: within 1 s per corpus (whole-second timer).

Tests

All of these are RED on 9f85f104 by their assertions, then green:

  • probe_docker_default_images_marked_and_overrides
  • probe_helm_template_images_render_with_defaults: a bitnami-style common.images.image helper, with, the if/else digest branch, a subchart alias with overlay and global; .Release.Name, a missing define and a field of a missing map render nothing.
  • probe_helm_release_images: HelmRelease local and external, kustomize local, Terraform set.
  • probe_helm_chart_crds_and_werf_chart
  • incremental_werf_values_edit_keeps_chart_edge and incremental_release_values_edit_rebuilds. With only their decline rule reverted they are RED at the edit.
  • probe_helm_chart_deploys_its_objects now expects its templated nginx image (host-less, kept as written).

helm_values_follow_helm_semantics unit-tests the values rules. It tests the new module directly, so it has no RED-on-base run.

Verification

  • macOS, ASan + UBSan test runner, whole suite on the final tree: 9716 passed, 0 failed. The 11 skipped tests are platform-only (Linux user namespaces, Windows ACL, Job Object and PowerShell probes). No sanitizer reports.
  • clang-format (Homebrew LLVM), cppcheck 2.20 (the CI version), lint-no-suppress and lint-memory-core are clean.
  • Skipped locally: the Linux and Windows legs. PR CI runs both.

Found, not changed here

  • A pod spec written by an include (5 of 110 oracle images): the container's image: key is in the define, not the template.
  • Argo CD helm.values / valuesObject, Flux valuesFrom / chart.spec.valuesFiles, kustomize valuesMerge / additionalValuesFiles / helmGlobals.chartHome, werf deploy.helmChartDir, subchart tags.
  • docker run / docker pull in scripts, Makefile ?=, skaffold buildArgs, compose multi-file merges, docker-bake.hcl, --target stage pruning.
  • A compose build without image: gets no override edge: its Service source would go stale on a Dockerfile-only edit.
  • Helm does not template crds/, but files there containing {{ are still skeletonized before parsing.

Review round 1: requested changes

Every case below ends in an explicit unknown (no image), never in a hang, an OOM, a partial or a wrong image.

Bounds (helm_template.c)

  • A per-render budget of 10,000,000 steps and 64 MiB of output, counted and never timed. A range over a huge int, a huge indent or defines that include each other in a fan stop the render. Every probe of that template is UNKNOWN, including one captured before the trip, and the trip is logged as helm.render.budget. No env knob was added.
  • go-yaml v2's alias limit: values whose decodes come mostly from alias expansion (a billion laughs) do not parse, as in Helm.
  • --set: an index past strvals' MaxIndex (65536), or a key longer than the parser holds, is no value, so the release is unknown. It never pads a list or splits a key.

Reads by a built path (pass_k8s.c, pass_terraform.c)

  • A values file, Chart.yaml, kustomize valuesFile or Terraform file() is read only when it is a discovered file of the repository. So a symlink, or a path through one, is never followed out of it.
  • These reads have the same size cap and tree-sitter padding as every other read.
  • A chart file that exists but is not read makes the chart's values unknown, not empty.

Wrong images

  • F1: a Terraform number set is Terraform's exact decimal (1.10 → 1.1), not %.17g.
  • F2: a release with a value source that is not read has no image of its own:
    • Flux valuesFrom, chart.spec.valuesFiles, postRenderers;
    • kustomize additionalValuesFiles, a valuesMerge other than override, a valuesFile that cannot be read;
    • Terraform dynamic "set".
  • F3: an aliased subchart renders with .Chart.Name set to its alias.
  • F11:
    • An UNKNOWN if/with/range makes what its bodies assign UNKNOWN, and leaves the loop when a break in them would.
    • set/unset/merge poison the map they change; a merge also poisons the maps within it.
    • A change that may reach values another part holds marks every later capture unknown.
    • A change of a new value does not: fromYaml, deepCopy, an empty dict, or a part dug out of one. Without that exception, 42 correct image edges on the infra corpora were lost; with it, none are.

Marks and secrets

  • F5: the chart edge reads default_of as JSON, so list paths like images[0].repository are kept. It is not written if its mark cannot be carried whole.
  • F7: build arguments with a secret, by name or by value (cbm_is_secret_binding), are never kept.
  • F8: build-edge properties, chart-edge properties, values-path marks and --set keys are sized to their content, never cut.

Proof on the 20 infra corpora

  • Compared with this PR's previous head d21949e: identical nodes, edges and Image edges, properties included.
  • Zero budget trips. A hostile fixture trips it, and that trip is logged.
  • Two runs of the Helm-heavy corpora are identical.
  • Since the images did not change, the helm-oracle result (105 of 110, 0 wrong) stands.

Tests

  • One test per bound and per wrong-image case, each RED on d21949e by its assertion: 9 unit tests in pipeline_semantic_manifest_repro, 10 probes in grammar_probe_f.
  • One control test is green on both: a change of a new value spoils nothing.
  • A Helm chart joins the Kubernetes complexity fixture.

Verification

  • macOS ASan + UBSan full suite on the final tree.
  • clang-format (Homebrew LLVM), cppcheck 2.20 (CI's lint image), lint-no-suppress, lint-memory-core, scripts/security-audit.sh.
  • Skipped locally: the Linux and Windows legs; PR CI runs both.

Found in review round 1, not changed here

  • F4: a nested chart dir that is no charts/<x> subchart is still a family member.
  • F6: a variable carrying a values read loses its key in default_of.
  • F9, F10 (evaluator divergences), D1 (USAGE {ref: image_default}) and D2 (the convention's host check): left for a decision.
  • Without a depth bound on deep YAML: hy_node and the other parsers that walk the values recursively.
  • Quadratic hst_record/hy_mapping.
  • One template's set on .Values changes what later templates read in Helm; no render models that, and an include of a template file by path is the same case. The text a tpl renders is not scanned.
  • set_sensitive names are not compared with the keys an image reads. (Done in review round 2, N9.)

Review round 2: requested changes (N1-N12)

Every case below ends in an explicit unknown (no image), never in an OOM, a quadratic stall or a wrong image.

Bounds

  • N1 (helm_template.c): capture arrays grow by doubling, so n captures of a probe cost memory linear in n. Arena growth since the render began counts against a third budget, 256 MiB, beside steps and output. A trip logs helm.render.budget with its reason (steps, output or memory). Release images are deduplicated by hash (pass_k8s.c), not by a list scan.
  • N10: poisoned maps are a pointer hash set; merged values keep a path list. A loop that poisons a new dict per iteration keeps every later read O(1).

Wrong images (helm_template.c)

  • N2: a set/merge in a body an UNKNOWN condition skips is judged by its target, whatever the target is. A call's result such as index .Values "image" is evaluated in the frame: own, or a known map (now poisoned), is fine; anything else taints.
  • N3: putting a value another part holds into an own map revokes own. A part of a poisoned map is never own.
  • N4: a run-time set whose target is an UNKNOWN call result (index .Values.apps .Release.Name) taints.
  • N5: an UNKNOWN break/continue spoils the rest of the loop body around it. A continue is now a continue, not a break.
  • N6: the scan notes redefinitions and assignments and carries what it saw across the actions of one branch. A variable the skipped part rebinds is never judged by its value in the running frame.
  • N11: a define's "may not change its argument" that leaned on a define still being scanned (mutual includes) is not kept; it is scanned again when next asked.
  • N12: an include that is not run because its argument is poisoned or UNKNOWN spoils that argument: a known one is poisoned deep, an unknown one that is not own taints.

Releases and charts (pass_k8s.c, pass_terraform.c)

  • N8: an empty values.yaml is values {}, as in Helm, not a file that was not read.
  • N9: each of these leaves the release without an image of its own:
    • a Terraform set_sensitive whose name overlaps a key an image reads (a db.password one does not);
    • a Terraform postrender block;
    • a kustomization with its own transformers: images, patches, patchesJson6902, patchesStrategicMerge, replacements, transformers, components.

Tests

  • N7: the two symlink probes are POSIX-only (SKIP_PLATFORM on Windows), like every other symlink test. The Windows CI leg is the check.
  • One test per item, each RED on 4cea54f by its assertion: 9 unit tests in pipeline_semantic_manifest_repro, 3 probes in grammar_probe_f (N9 a and b share one probe; both of its wrong edges show on 4cea54f).
  • N10's test is a correctness control, green on both by design: no test decides by time. Its cost was measured outside the suite on the ASan test runner (Apple M5 Pro). Absolute times are inflated; the ratios are the point.
Case 4cea54f this head
N1: arena for range 5000 around an image line 200.8 MB 1.0 MB
N1: arena for range 20000 3,203 MB (x16 for x4) 4.1 MB (x4)
N10: range 20000 of $c := dict + set $c 1,450 ms 147 ms
N10: range 40000 5,250 ms (x3.6 for x2) 296 ms (x2.0)

Proof on the 20 infra corpora

  • Compared with d21949e: identical nodes, edges and Image edges, properties included. All 20 index with rc=0.
  • Zero budget trips on all three budgets.

Verification

  • macOS ASan + UBSan full suite on the final tree.
  • clang-format (Homebrew LLVM), cppcheck 2.20 (CI's lint image), lint-no-suppress, lint-memory-core, scripts/security-audit.sh.
  • Skipped locally: the Linux and Windows legs; PR CI runs both.

Found in review round 2, not changed here

  • Quadratic prefix checks in hst_record and in capture key matching.
  • No depth bound in hy_node recursion on deep YAML.
  • A set across templates and the text a tpl renders are still not modelled (as in round 1).

Review round 3: the coarse rule (user decision)

The user chose a rule that is sound by construction over round 3's precise model: a change of maps the render does not run exactly leaves every later image of that render UNKNOWN. There are no exemptions. Its recall cost was measured before any push, and the user accepted it (table below).

The rule (helm_template.c)

  • Under UNKNOWN control, any of the following leaves the images after it UNKNOWN:

    • a set/unset/merge, the must* forms included;
    • any tpl;
    • any include/template of a name that is not known, that is no define or template file of the chart, or whose define or file may change maps.

    UNKNOWN control means a skipped if/with/range body, the rest of a loop an UNKNOWN break/continue may leave, an include or template not run, or a tpl not run.

  • An executed set/merge does the same unless its target is a known map that can be poisoned exactly. A merge also does it when the target is already poisoned (R3-7), is the .Values root (R3-3), or holds a map that is not known.

  • Round 3's own provenance and freshness scan are removed. R3-4, R3-5 and R3-6 came from them.

.Template.BasePath (approved by the user after the first measurement)

  • It is modelled as Helm's engine sets it: <ChartFullPath>/templates. ChartFullPath is the top chart's Chart.yaml name, then /charts/<alias or name> for each subchart down. pass_k8s.c computes it where the alias is resolved. .Template.Name is not modelled (UNKNOWN).
  • An include whose name falls under the render's BasePath names <chart dir>/templates/<rest> among the family's template files. That file is scanned with the same rule, memoized per render, and its output stays UNKNOWN. This is the checksum idiom include (print $.Template.BasePath "/secret.yaml") . | sha256sum.
  • A file reached again while it is being scanned (a cycle) counts as "may change". So does a name that maps to no file.

Bounds

  • R3-1: a capture's keys are a hash set. Its mark is built in one pass, with one sort for the prefix check and steps charged; there is no copy of the mark so far per key.
  • R3-2: each render works in an arena of its own, and the caller's arena keeps only its results. Renders run one after another no longer keep each other's work.
  • The trip reason is recorded where the budget trips, so an output trip is now logged as output, not memory.

Releases (pass_terraform.c, pass_k8s.c)

  • R3-8: any set_sensitive leaves the release without an image of its own. Round 3's name-overlap check is removed.
  • R3-9: so does a postrender written as a dynamic block or as a provider-v3 attribute.

Cost on the 20 infra corpora (this head against 5c5964e, whose graph equals d21949e's there; Apple M5 Pro, 2026-10-19)

Corpus Image edges lost Added Cause
prometheus-community/helm-charts 11 0 3: admission-webhook, a second mustMerge into a values map the first merge poisoned. 8: jiralert, cloudwatch-exporter, postgres-exporter and yet-another-cloudwatch-exporter, each checksum-including a configmap/secret file that holds tpl
kubermatic 4 0 grafana: the included configmaps.yaml holds tpl
gardener 3 0 test redis chart: the included configmap.yaml holds tpl
opentelemetry-helm-charts 2 0 ebpf-instrumentation: a skipped part that may change maps
the other 16 0 0
  • All 20 index with rc=0. No budget trips. No Image edge is added.
  • 5 of the 20 losses are changes the rule rightly cannot follow. The other 15 are rule-correct too: a template file holding a tpl may change maps, because a tpl's text is not looked into.
  • Before BasePath the coarse rule cost 42 edges, 37 of them from the checksum idiom. BasePath won back 22.
  • R3-8 and R3-9 cost no edge on these corpora.
  • Running an included file, so that a known-text tpl in it runs exactly, is a follow-up PR with its own measurement (user decision).

Controls that flipped to UNKNOWN (part of the measured cost; their tests now say so)

  • helm_change_of_a_new_value_spoils_after (was ..._spoils_nothing): changes of fromYaml/deepCopy/empty-dict values. These kept their image in rounds 1-3, and it is the opentelemetry-ebpf-instrumentation pattern above.
  • helm_skipped_set_after_redefinition, its control: a set of an outer new dict in a skipped body.
  • probe_helm_release_terraform_sensitive_and_postrender: the release with a db.password set_sensitive (R3-8).

Tests that fail on 5c5964e by their assertion

  • pipeline_semantic_manifest_repro:
    • R3-1 helm_capture_keys_cost_linear_memory (arena peak);
    • R3-2 helm_render_keeps_only_its_results;
    • trip log helm_render_budget_logs_its_reason;
    • R3-3 helm_merge_into_values_root;
    • R3-4 helm_skipped_set_beside_a_nested_scope;
    • R3-6 helm_unknown_break_spoils_later_elements and helm_skipped_range_sets_its_element;
    • R3-7 helm_deep_merge_reaches_a_held_values_map;
    • BasePath helm_include_of_a_changing_template_file, helm_subchart_base_path_names_its_own_file, helm_include_of_a_missing_template_file and helm_template_file_including_itself;
    • the two flipped controls above.
  • grammar_probe_f:
    • R3-8 probe_helm_release_terraform_sensitive_read_earlier and the flipped ..._sensitive_and_postrender;
    • R3-9 probe_helm_release_terraform_postrender_forms;
    • BasePath end to end, an aliased subchart, probe_helm_checksum_include_by_base_path.
  • On the coarse rule without BasePath, helm_checksum_include_keeps_the_image fails as well, together with the BasePath tests (a)-(d) and the probe. 5c5964e did not taint a computed include, so it keeps that image.
  • The wrong-image scenarios of rounds 2 and 3 (N2-N6, N11, N12, R3-3, R3-4, R3-6, R3-7) stay as tests, and each reads UNKNOWN under the rule.

Corrections to the round-2 text (statements that were not true at 5c5964e)

  • "never in an OOM, a quadratic stall or a wrong image":
    • R3-1 and R3-2 (memory) and R3-3, R3-4 and R3-6 to R3-9 (wrong images) were open; this round fixes them.
    • R3-10, the quadratic per-image loops in pass_k8s.c, is still open (below).
  • The trip reason: an output trip was logged as memory; it is output now.
  • N5 "spoils the rest of the loop body": it spoiled it for the current element only. Now any change in the loop body taints.
  • N6 "never judged by its value in the running frame": range variables still were. Now the frame is not consulted at all.
  • N10 "merged values keep a path list": the .Values root path matched nothing. Now a merge into .Values taints.

Verification

  • macOS ASan + UBSan full suite on the final tree.
  • clang-format (Homebrew LLVM), cppcheck 2.20 (CI's lint image), lint-no-suppress, lint-memory-core, scripts/security-audit.sh.
  • Skipped locally: the Linux and Windows legs; PR CI runs both.

Not changed here (a follow-up issue after the merge)

  • R3-10: quadratic per-image loops in pass_k8s.c (k8s_helm_literal_use, the defaults same loop).
  • R3-11: tpl trees outside the arena.
  • R3-12: a break in a skipped range's else.
  • R3-13: first/last/join copy the whole list.
  • The round-3 nits.

…ds and werf charts

Four gaps of the deployment graph, closed together.

Images named from a default value keep their Image; the edge says so.
A Dockerfile FROM / COPY --from filled by an ARG default (an ARG chain
names each ARG) carries default_of, as do compose `${X:-d}` images and
`docker build -t ${IMAGE:-x}` tags. An override found in the repository
adds its own edge: compose build.args and --build-arg make the Image the
build names DEPEND_ON the base they choose {override_of}; compose's .env
replacing an inline default marks the effective edge override_of and keeps
the default as {ref: image_default}, an image the service does not run.

Helm template images render (helm_template.c): a bounded Go
text/template + Sprig evaluator over the vendored gotmpl grammar, with
values read the way Helm reads them (go-yaml v2 scalars, float64 numbers,
Helm's coalesce). Each templated container image renders from its chart
family's defaults -- include / define helpers, with / range / if, subchart
values under their alias, global -- and the object USES it {default_of:
the values keys read}; the chart DEPLOYS it. A value that does not render
(a .Release field, lookup, a missing define, a field of a missing map, a
function not modelled) names no Image; a rendered value is checked against
the image reference grammar first. Releases render their charts with their
values: a Flux HelmRelease's spec.values, a kustomize helmCharts entry's
valuesFile + valuesInline (a chart in chartHome now links locally), a
Terraform helm_release's values + set (literals only; set_sensitive is
never read) -- each image that differs is the release's own DEPLOYS
{override_of}. A release of a chart outside the repository names images by
convention only where its values' `repository` names a registry host
{inferred: values}; a host-less one names none.

A chart DEPLOYS the CRDs of its crds/ {phase: crds}. A werf `.helm` dir
beside werf.yaml is a chart named after the werf project.

Incremental runs: a werf.yaml edit and a werf chart's values / templates
decline like a Chart.yaml chart's; a release's values file (a kustomize
valuesFile, a Terraform file()) declines too -- Terraform's now IMPORTS
{local_name: values} it, like kustomize's valuesFile.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…ng release images

Review round 1 of #2677. Every case ends in an explicit unknown, never in a
hang, an OOM, a partial or a wrong image.

Bounds (helm_template.c):
- A per-render step and output budget (10,000,000 steps, 64 MiB): a range
  over a huge int, a huge indent, defines that include each other in a fan
  stop the render; every probe of the template is UNKNOWN, also one captured
  before, and the trip is logged (helm.render.budget). Counted, never timed.
  On the 20 infra corpora no render reaches it.
- go-yaml v2's alias limit: values whose decodes are mostly alias expansion
  (a billion laughs) do not parse, as in Helm.
- --set: an index past strvals' MaxIndex (65536) or a key longer than the
  parser holds is no value (the release is unknown), never a padded list or
  a split key.

Reads (pass_k8s.c, pass_terraform.c): a values file, Chart.yaml, kustomize
valuesFile or Terraform file() named by path is read only when it is a
discovered file of the repository -- never through a symlink out of it --
with the size cap and tree-sitter padding of every other read. A chart file
that is there but not read makes the chart's values unknown, not empty.

Wrong images:
- Terraform number sets reach the chart as Terraform's exact decimal
  (1.10 -> "1.1"), not %.17g.
- A release with a value source not read -- Flux valuesFrom, valuesFiles,
  postRenderers; kustomize additionalValuesFiles, a valuesMerge other than
  override, a valuesFile not read; Terraform dynamic "set" -- has no image of
  its own.
- An aliased subchart renders with .Chart.Name its alias.
- An UNKNOWN if / with / range makes what its bodies assign UNKNOWN and
  leaves the loop a break in them would; set / unset / merge poison the map
  they change (merge: the maps within too). A change that may reach values
  another part holds taints every later capture; one of a new value
  (fromYaml, deepCopy, an empty dict, a part of one) does not.

Marks and secrets:
- The chart edge reads default_of as JSON (list paths kept), builds its
  properties to size, and writes no edge whose mark it could not carry whole.
- Build arguments with a secret (by name or value) are never kept; build
  edge properties and values-path marks are never cut.

Tests: one per bound and per wrong-image case, each RED on d21949e by its
assertion; a Helm chart joins the Kubernetes complexity fixture.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…change

Review round 2 of #2677 (N1-N12). Every case ends in an explicit unknown,
never in an OOM, a quadratic stall or a wrong image.

Bounds (helm_template.c, pass_k8s.c):
- N1: capture arrays grow by doubling, so n captures cost memory linear in
  n. Arena growth since the render began counts against a third budget
  (256 MiB) beside steps and output; a trip is logged with its reason.
- N10: poisoned maps are a pointer hash set (merged values a path list), so
  a loop poisoning a new dict per iteration keeps reads O(1). Release images
  are deduplicated by hash.

Wrong images (helm_template.c):
- N2: a set / merge in a body an UNKNOWN condition skips is judged by its
  target whatever it is -- a call's result like `index .Values "image"` is
  evaluated in the frame: own, or a known map (poisoned), else it taints.
- N3: putting a value another part holds into an own map revokes own; a
  part of a poisoned map is never own.
- N4: a run-time set whose target is an UNKNOWN call result taints.
- N5: an UNKNOWN break / continue spoils the rest of the loop body around
  it; a continue is a continue, not a break.
- N6: the scan notes redefinitions and assignments, carries what it saw
  across the actions of one branch, and never judges a variable the part
  rebinds by its value in the frame.
- N11: a define's "may not change" that leaned on a define still being
  scanned (mutual includes) is not kept.
- N12: an include not run because its argument is poisoned or UNKNOWN
  spoils that argument (poisoned deep, or a taint).

Releases and charts (pass_k8s.c, pass_terraform.c):
- N8: an empty values.yaml is values {} (Helm's), not a file not read.
- N9: a Terraform set_sensitive whose name overlaps a key an image reads, a
  Terraform postrender, or a kustomization with its own transformers
  (images, patches, replacements, transformers, components) leaves the
  release without an image of its own.

Tests (N7): the symlink probes are POSIX-only (SKIP_PLATFORM on Windows).
One test per item, RED on 4cea54f by its assertion; N10's is a correctness
control, its cost measured (linear vs quadratic) outside the suite.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
… render memory

Review round 3 of #2677. The user decided on a rule sound by construction:
a change of maps the render does not run exactly leaves every later image
of that render UNKNOWN, with no exemption.

Changes of maps (helm_template.c):
- Under UNKNOWN control -- a skipped if / with / range body, the rest of a
  loop an UNKNOWN break / continue may leave, an include or template not
  run, a tpl not run -- any set / unset / merge (the must* forms too), any
  tpl, and any include / template of a name not known, of no define or
  template file of the chart, or of one that may change maps, leaves the
  images after it UNKNOWN.
- An executed set / merge does too unless its target is a known map that
  is poisoned exactly; a merge also for a target already poisoned, the
  .Values root, or a map not known within it. The own provenance and the
  freshness scan are gone.
- .Template.BasePath is modelled as Helm sets it: <chart path>/templates,
  the top chart's name, then /charts/<alias or name> per subchart
  (pass_k8s.c). An include naming one of the chart's template files by it
  (the checksum idiom) is scanned with the same rule; a file reached again
  while scanned, or no such file, may change maps.

Bounds (helm_template.c):
- A capture's keys are a hash set and its mark is built in one pass (one
  sort for the prefix check), its steps charged.
- Each render works in an arena of its own; the caller keeps its results
  only.
- The trip reason is recorded where a budget trips: an output trip is
  logged as output.

Releases (pass_terraform.c, pass_k8s.c):
- Any set_sensitive leaves the release without an image of its own (the
  name-overlap check is gone), and so does a postrender written as a
  dynamic block or an attribute.

Cost on the 20 infra corpora against 5c5964e: 20 Image edges lost, 0
added, no budget trip. 5 are changes the rule rightly cannot follow (a
second mustMerge into a poisoned values map; a skipped part that may
change maps), 15 come from checksum-included files that hold a tpl. Two
controls of rounds 1-3 now read UNKNOWN; their tests say so.

Tests: one per item, each failing on 5c5964e by its assertion (the
BasePath ones also on the coarse rule without it); the wrong-image
scenarios of rounds 2 and 3 stay as tests.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

This branch has not been deployed

No deployments
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.

1 participant