Skip to content

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

Open
DeusData wants to merge 1 commit into
mainfrom
feat/deploy-graph-gaps
Open

DeusData wants to merge 1 commit into
mainfrom
feat/deploy-graph-gaps

Conversation

@DeusData

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.

…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>

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