Skip to content

[helm] Support a name-keyed map for dagster-user-deployments.deployments#34022

Open
liadsh-twine wants to merge 1 commit into
dagster-io:masterfrom
twinesec:helm/user-deployments-map-support
Open

[helm] Support a name-keyed map for dagster-user-deployments.deployments#34022
liadsh-twine wants to merge 1 commit into
dagster-io:masterfrom
twinesec:helm/user-deployments-map-support

Conversation

@liadsh-twine

Copy link
Copy Markdown

Summary & Motivation

The Dagster Helm chart only accepts dagster-user-deployments.deployments as a YAML array. Helm merges arrays by replacement, not by key, so a base values.yaml plus per-environment overlays force you to duplicate every deployment entry in full for each environment.

This PR makes deployments accept either the existing array or a map keyed by deployment name. The map form lets Helm deep-merge overlays by key — so an override file (e.g. a per-environment layer, as used with an ArgoCD ApplicationSet) can change a single field of a single deployment instead of restating the whole list. Existing array users are unaffected.

How it works

  • A new template helper dagsterUserDeployments.deploymentsList normalizes the value (list or map) into a canonical list. When a map is used, the map key becomes the deployment name (the per-entry name is then optional; the key wins if both are present).
  • Every existing range over deployments is routed through the helper — the three subchart templates (deployment-user, service-user, configmap-env-user) and the parent chart's configmap-workspace, deployment-daemon init containers, and webserver init containers.
  • The helper is defined identically in both the umbrella and subchart _helpers.tpl (mirroring how dagster.fullname/dagster.labels are already shared), so it's available whether the subchart is rendered via the umbrella or standalone — the subchart is conditional (condition: dagster-user-deployments.enableSubchart), so a helper defined only there would be unavailable to the parent when the subchart is disabled, and the parent's helpers are unavailable when the subchart is rendered on its own.
  • Because the array path passes straight through the helper, array-form output is byte-for-byte identical to before this change.

Example

dagster-user-deployments:
  deployments:
    k8s-example-user-code-1:
      image:
        repository: "docker.io/dagster/user-code-example"
        pullPolicy: Always
      dagsterApiGrpcArgs: ["--python-file", "/example_project/example_repo/repo.py"]
      port: 3030

Schema

UserDeployments.deployments and DagsterUserDeploymentsHelmValues.deployments become list[UserDeployment] | dict[str, UserDeployment], and UserDeployment.name becomes optional (the map key supplies it; the templates fail if a list entry omits name). Both values.schema.json files were regenerated with dagster-helm schema apply — the deployments property now renders as an anyOf of array/object.

Test Plan

  • helm lint helm/dagster --with-subcharts --strict — 0 failures.
  • Rendered the map form end-to-end and confirmed names derive from keys: Deployments dagster-<key>, Services <key>, workspace load_from with location_name=<key>, and daemon/webserver init containers init-user-deployment-<key>.
  • Confirmed the array form renders byte-for-byte identically to master.
  • Confirmed dagster-helm schema apply is idempotent (CI's git diff --exit-code gate passes).
  • New pytest coverage in test_user_deployments.py and test_workspace.py: map rendering, deployment/service/configmap/workspace/daemon naming from keys, array↔map equivalence (including the config checksum annotation), key-wins-over-name, and a guard that a list entry without name fails.
  • Full schema suite: 279 passed. ruff check + format clean.

Changelog

The Helm chart's dagster-user-deployments.deployments value now also accepts a map keyed by deployment name (in addition to a list), making it easier to override individual deployments across layered values files.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds support for supplying dagster-user-deployments.deployments as a name-keyed map in addition to the existing list form, enabling Helm's deep-merge behavior when layering values files. The backward-compatibility guarantee is strong: the dagsterUserDeployments.deploymentsList helper passes the list form through unchanged, so all existing array-form values produce byte-for-byte identical output.

  • A new dagsterUserDeployments.deploymentsList Helm helper normalizes both forms into a canonical list; it is intentionally duplicated in the umbrella and subchart _helpers.tpl files (mirroring the existing dagster.fullname pattern) so it is available in both standalone-subchart and full-chart rendering contexts.
  • The UserDeployment.name Python model field is made optional with None default, and the JSON schema's list branch restores the required: [\"name\"] constraint via an allOf overlay, keeping helm lint enforcement intact for existing list users while allowing the map form to omit the per-entry name.
  • New pytest coverage validates map rendering, key-wins-over-entry-name semantics, list/map checksum equivalence, service and configmap naming, and the missing-name failure path.

Confidence Score: 5/5

Safe to merge; the array-form code path is structurally unchanged and the new map-form helper is well-tested with equivalence checks against the list form.

All six template files that iterate over deployments receive the same mechanical substitution. The helper's merge semantics (dst-wins, deepCopy prevents mutation) are correct. The schema correctly enforces name for list entries via allOf while relaxing it for the map form. The equivalence test, which includes the config checksum annotation, confirms no regression for existing array users.

No files require special attention; both helper copies are identical as documented by the sync comments.

Important Files Changed

Filename Overview
helm/dagster/charts/dagster-user-deployments/templates/helpers/_helpers.tpl Adds dagsterUserDeployments.deploymentsList helper — correctly uses merge with dst-wins semantics and deepCopy to normalize maps into a list; identical copy in parent chart helpers is intentional per sync comment
helm/dagster/templates/helpers/_helpers.tpl Parent-chart copy of the same helper; verbatim duplicate required so the template is available both when the subchart runs standalone and when rendered under the umbrella
helm/dagster/schema/schema/charts/dagster_user_deployments/subschema/user_deployments.py Makes UserDeployment.name optional, introduces _RequireName annotation to re-add required: ["name"] only for the list branch via allOf — keeps schema tight for existing list users while supporting the new map form
helm/dagster/schema/schema_tests/test_user_deployments.py Adds six map-form tests covering render, service, configmap-env, key-wins-over-name, list/map equivalence (including checksum), and the missing-name failure case; backstop template-fail path is not directly tested (see comment)
helm/dagster/charts/dagster-user-deployments/values.schema.json deployments property correctly becomes anyOf[array, object]; array branch restores required: ["name"] via allOf so helm lint catches missing names; map branch allows name to be absent
helm/dagster/values.schema.json Umbrella chart schema mirrors the subchart changes; anyOf with allOf+required for list form and additionalProperties for map form is correctly regenerated
helm/dagster/templates/configmap-workspace.yaml Single-line change to normalize deployments through the helper before iterating; ternary evaluates both branches eagerly but that is pre-existing behavior
helm/dagster/schema/schema_tests/test_workspace.py New test verifies workspace configmap renders correctly from map-form deployments with sorted-key ordering for grpc_server entries

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[dagsterUserDeployments.deploymentsList] --> B{kindIs map?}
    B -- Yes / map form --> C[Initialize empty list]
    C --> D[range name, deployment over map in sorted key order]
    D --> E["merge dict(name=key) with deepCopy(deployment) - key wins over entry name"]
    E --> F[append entry to list]
    F --> D
    D -- done --> G[emit list as YAML]
    B -- No / list form --> H[range each deployment in list]
    H --> I{deployment.name set?}
    I -- No --> J[fail: each user deployment must set name]
    I -- Yes --> H
    H -- done --> K[emit list as YAML unchanged]
    G --> L[fromYamlArray at call site produces canonical list]
    K --> L
    L --> M[range in deployment-user, service-user, configmap-env-user, configmap-workspace, daemon and webserver init containers]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[dagsterUserDeployments.deploymentsList] --> B{kindIs map?}
    B -- Yes / map form --> C[Initialize empty list]
    C --> D[range name, deployment over map in sorted key order]
    D --> E["merge dict(name=key) with deepCopy(deployment) - key wins over entry name"]
    E --> F[append entry to list]
    F --> D
    D -- done --> G[emit list as YAML]
    B -- No / list form --> H[range each deployment in list]
    H --> I{deployment.name set?}
    I -- No --> J[fail: each user deployment must set name]
    I -- Yes --> H
    H -- done --> K[emit list as YAML unchanged]
    G --> L[fromYamlArray at call site produces canonical list]
    K --> L
    L --> M[range in deployment-user, service-user, configmap-env-user, configmap-workspace, daemon and webserver init containers]
Loading

Reviews (2): Last reviewed commit: "[helm] Support a name-keyed map for dags..." | Re-trigger Greptile

Comment on lines 654 to 657
}
},
"required": [
"name",
"image",

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.

P2 Schema no longer enforces name for list-form entries

Removing name from the required array means helm lint and JSON-Schema validators will silently accept a list-form UserDeployment without a name. The only enforcement is the fail inside the Helm template helper, which runs only at helm template / helm install time. Before this PR, a schema validator would catch the missing field immediately; now it passes validation and only fails on render. Consider keeping the schema constraint tight by validating name conditionally — or at minimum noting in the error message that schema validation will not catch it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in the latest push. name now stays required for the list form: a _RequireName annotation overlays required: ["name"] onto just the array branch of the generated schema via allOf, reusing the single UserDeployment $def (no duplicate def). The map branch keeps name optional since the key supplies it. So a nameless list entry now fails at schema-validation / helm lint time again, not only at render. The template fail guard is kept as a backstop for when schema validation is skipped.

port: 3030
```

The map form is useful when you layer values files — for example a base file plus a per-environment override. Helm merges maps by key but replaces lists wholesale, so with the map form an override file can change a single field of a single deployment instead of restating the entire list. When the map form is used, the map key is the deployment name and the per-entry `name` field is optional.

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.

P2 Map-form ordering is alphabetical but not documented

Go templates iterate maps in sorted key order, so deployments in the map form are always rendered alphabetically by key — regardless of the order they appear in the YAML. This differs from the list form, where users control ordering explicitly. The init-container readiness probes, the workspace load_from list, and any other ordered downstream artifacts will all follow alphabetical order. Mentioning this helps users who care about ordering or who are migrating from a list and notice the order changed.

Suggested change
The map form is useful when you layer values files — for example a base file plus a per-environment override. Helm merges maps by key but replaces lists wholesale, so with the map form an override file can change a single field of a single deployment instead of restating the entire list. When the map form is used, the map key is the deployment name and the per-entry `name` field is optional.
The map form is useful when you layer values files — for example a base file plus a per-environment override. Helm merges maps by key but replaces lists wholesale, so with the map form an override file can change a single field of a single deployment instead of restating the entire list. When the map form is used, the map key is the deployment name and the per-entry `name` field is optional. Note that Helm renders map keys in alphabetical order, so deployments will always be emitted in sorted key order rather than declaration order.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call — added a note that Helm renders map keys in alphabetical order, so deployments are emitted in sorted key order rather than declaration order.

Comment on lines +9 to +15
{{/*
Normalize the user-code deployments value into a list.
Accepts either a list of deployment entries or a map keyed by deployment name.
For the map form, the key becomes each entry's `name` (the key wins over any `name` field).
Emits YAML that call sites parse with `fromYamlArray`.
*/}}
{{- define "dagsterUserDeployments.deploymentsList" -}}

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.

P2 Duplicate helper definition needs an explicit sync comment

This helper is defined identically in helm/dagster/templates/helpers/_helpers.tpl. Adding a cross-reference comment in both copies makes the intentional duplication visible and reminds future editors to keep both files in sync.

Suggested change
{{/*
Normalize the user-code deployments value into a list.
Accepts either a list of deployment entries or a map keyed by deployment name.
For the map form, the key becomes each entry's `name` (the key wins over any `name` field).
Emits YAML that call sites parse with `fromYamlArray`.
*/}}
{{- define "dagsterUserDeployments.deploymentsList" -}}
{{/*
Normalize the user-code deployments value into a list.
Accepts either a list of deployment entries or a map keyed by deployment name.
For the map form, the key becomes each entry's `name` (the key wins over any `name` field).
Emits YAML that call sites parse with `fromYamlArray`.
NOTE: this helper is intentionally duplicated verbatim in
helm/dagster/templates/helpers/_helpers.tpl
Keep both copies in sync when making changes.
*/}}
{{- define "dagsterUserDeployments.deploymentsList" -}}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — added a cross-reference note in both _helpers.tpl copies explaining the intentional duplication and to keep them in sync.

Allow `dagster-user-deployments.deployments` to be specified as a map keyed
by deployment name, in addition to the existing array form. Helm merges maps
by key but replaces arrays wholesale, so the map form lets layered values
files (e.g. a base file plus per-environment overrides, as used with an
ArgoCD ApplicationSet) override a single field of a single deployment instead
of restating the entire list.

A new `dagsterUserDeployments.deploymentsList` template helper normalizes the
value (list or map) into a canonical list; when a map is used the key becomes
the deployment name. Every existing `range` over deployments (three subchart
templates and the parent chart's workspace configmap, daemon init containers,
and webserver init containers) is routed through the helper, so array-form
output is unchanged.

The pydantic models are updated so `deployments` becomes
`list[UserDeployment] | dict[str, UserDeployment]`. `name` is optional on the
base model (the map key supplies it), but it stays required for the list form:
a `_RequireName` annotation overlays `required: ["name"]` onto the array branch
of the generated JSON schema via `allOf`, reusing the single `UserDeployment`
$def, so a nameless list entry still fails schema validation / `helm lint`. Both
values.schema.json files are regenerated via `dagster-helm schema apply`.

Adds a commented map example to both values.yaml files and a docs section
(noting map keys render in sorted order), plus pytest coverage for the map
form (rendering, naming, array/map equivalence, key-wins-over-name, and the
missing-name guard).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@liadsh-twine
liadsh-twine force-pushed the helm/user-deployments-map-support branch from 29a3d8e to 47f4e05 Compare July 19, 2026 10:43
@liadsh-twine

Copy link
Copy Markdown
Author

Opened this PR to support #8172

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