Skip to content

values.schema.json closes global with additionalProperties: false, so the chart cannot be used as a subchart #11

Description

@tada5hi

values.schema.json sets additionalProperties: false on global, which makes the chart unusable as a subchart

charts/authup/values.schema.json:1361-1394 declares:

"global": {
"additionalProperties": false,
"properties": {
"defaultStorageClass": { ... },
"imagePullSecrets": { ... },
"imageRegistry": { ... }
},
"required": ["imageRegistry", "imagePullSecrets", "defaultStorageClass"],
"title": "global",
"type": "object"
}

Helm copies the parent chart's entireglobal map into every subchart's values (chartutil.CoalesceValuescoalesceGlobals) and then validates those coalesced values against the subchart's own schema (chartutil.ValidateAgainstSchema, which recurses over chrt.Dependencies()). Both happen inside ToRenderValues, i.e. before a single template renders.

So additionalProperties: false on global means: any umbrella chart that puts any key under global other than the three above cannot install authup as a dependency. Not "cannot use the feature" — cannot render at all.

Minimal reproduction

A parent chart with nothing in it but one global key, and authup 0.2.1 in charts/:

# parent/Chart.yamlapiVersion: v2name: parentversion: 0.1.0
# parent/values.yamlglobal:
anythingAtAll: x
$ helm template r parent # helm v3.15.4Error: values don't meet the specifications of the schema(s) in the following chart(s):authup:- global: Additional property anythingAtAll is not allowed

This is not consumer-specific. It fires for global.imagePullPolicy, for Bitnami's global.compatibility.openshift / global.security.allowInsecureImages, for global.clusterDomain, for any org-wide global convention. global is the one node in a values tree that a chart does not own, and closing it with additionalProperties: false asserts ownership the chart does not have.

The concrete consumer: PrivateAIM/flame-hub

flame-hub is replacing its vendored charts/third-party/authup (v0.1.0) with authup/authup 0.2.1. It cannot, today.

flame-hub keeps cross-chart config under global.flameHub.* — and its values.yaml:56-57 states why, verbatim:

# PostgreSQL service and secret, kept under global so subcharts (authup) can read# them: Helm shares only `global` into a subchart's values.postgresql:
host: "postgresql"secretName: "flame-hub-pg"existingSecret: ""

Alongside that: global.flameHub.publicHttps, global.flameHub.ingress.*, global.flameHub.gatewayApi.* (hostname, TLS, external Gateway parentRef, NGF snippets), plus global.imagePullPolicy. 33 keys under global.flameHub in values.yaml, 80 .Values.global.* references across charts/flame-hub/templates, 15 files touching global.flameHub specifically.

The intended subchart wiring — already written against this chart's advertised tpl passthrough:

authup:
database:
host: '{{ include "flameHub.postgresql.host" . }}'existingSecret: '{{ include "flameHub.postgresql.secretName" . }}'trustedOrigins: '{{- if or .Values.global.flameHub.ingress.enabled .Values.global.flameHub.gatewayApi.enabled }}{{ include "flameHub.uiDomain" . }}{{- end }}'

And the manifest flame-hub renders itself, next to the subchart, which reads the same globals (charts/flame-hub/templates/authup/httproute.yaml):

{{- if or .Values.global.flameHub.gatewayApi.enabled .Values.authup.gatewayApi.enabled -}}apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:
name: {{ .Release.Name }}-authupspec:
parentRefs:
{{- include "flame-hub.gateway.parentRefs" . | nindent 8 }}hostnames:
- {{ .Values.authup.gatewayApi.hostname | default .Values.global.flameHub.gatewayApi.hostname | quote }}rules:
- matches:
- path:
type: PathPrefixvalue: {{ .Values.authup.gatewayApi.path }}...backendRefs:
- name: {{ .Release.Name }}-authup-server # upstream authup.server.fullnameport: 3000

Adding the authup dependency to that chart produces:

Error: values don't meet the specifications of the schema(s) in the following chart(s):authup:- global: Additional property flameHub is not allowed- global: Additional property imagePullPolicy is not allowed

This contradicts the chart's own design

  • templates/_helpers.tpl:64-73authup.tplvalues.render tpl-renders values with "context" $. In a subchart, $ is the subchart's scoped context, so .Values.global.* is the only path from a subchart value to parent config.
  • templates/_database.tpl:16-24 (externalDatabase.host), _database.tpl:58-68 (externalDatabase.existingSecret), _secrets.tpl:22-28 (auth.existingSecret), _database.tpl:88-96 (externalRedis.existingSecret) all route through that helper. That machinery exists specifically so an umbrella can wire authup to shared infrastructure — and the schema forbids the only mechanism that can feed it.
  • .agents/references/privateaim-helm.md:32-34 lists "Cross-chart config under global.* + helpers that evaluate in subchart context" as a consumer pattern this chart is built to support.
  • DESIGN.md:377 advertises global.{imageRegistry,imagePullSecrets,defaultStorageClass} as passthrough — correct for what the chart reads, but the schema turns "what I read" into "all that may exist".

Why CI didn't catch it

Makefile's template target only renders standalone:

template:
@for f in charts/authup/ci/*-values.yaml;do\echo"=== $$f";\
helm template test charts/authup -f "$$f">/dev/null ||exit 1;\done
@echo "all ci values render"

No ci values file mentions global (grep -rn global charts/authup/ci/*.yaml → no hits), and nothing installs the chart as a dependency of a parent chart.

Proposed fix — one annotation

values.schema.json is generated by ghcr.io/dadav/helm-schema (make schema), which emits additionalProperties: false by default. The chart already uses the escape hatch elsewhere (e.g. values.yaml:21-24 for commonLabels). So the source change is three comment lines in charts/authup/values.yaml:3-5:

## @section Global parameters# @schema# additionalProperties: true# @schemaglobal:
# -- Global container image registry override (takes precedence over image.registry)imageRegistry: ""# -- Global image pull secrets (list of names or objects)imagePullSecrets: []# -- Global default storage class for dynamic provisioningdefaultStorageClass: ""

then make schema, which flips values.schema.json:1362 to "additionalProperties": true. No new values, no new keys, no template changes. The chart keeps validating the three globals it actually consumes and stops asserting ownership of the ones it doesn't.

Verified: patching only that single property in a local copy of 0.2.1 makes the exact flame-hub wiring render correctly —

global:
imagePullPolicy: IfNotPresentflameHub:
postgresql: {host: my-pg, secretName: my-pg-secret}authup:
postgresql: {enabled: false}externalDatabase:
host: '{{ .Values.global.flameHub.postgresql.host }}'existingSecret: '{{ .Values.global.flameHub.postgresql.secretName }}'
# rel-authup-server-env ConfigMapDB_HOST: "my-pg"# rel-authup-server Deployment
- name: DB_PASSWORDvalueFrom:
secretKeyRef:
name: my-pg-secretkey: password

Optionally also drop "global" from the root required array (values.schema.json:3892-3893) so authup: {global: null} stays legal for consumers who want to block global propagation outright — but with additionalProperties: true that is no longer needed.

Worth adding a regression test: a tiny parent chart under charts/authup/ci/ (or a make template step) that depends on authup and sets an unrelated global.someKey, so this class of breakage fails CI rather than a downstream user's install.

Why there is no acceptable workaround

Everything below was tested against 0.2.1 with helm v3.15.4.

AttemptResult
authup.global.flameHub: nullcoalesce.go:161: Conflict: cannot merge map onto non-map for "flameHub". Skipping. — key survives as null, still an additional property. Same error.
authup.global: nullStops the copy (coalesce.go:138: warning: skipping globals because destination global is not a table) but then fails - (root): global is required. Would also disable imageRegistry/imagePullSecrets/defaultStorageClass.
extraDeploy / extraVolumes / extraEnvVars / server.config / existingConfigMap / commonAnnotations / parent rendering its own manifestsIrrelevant. Schema validation runs before any template renders; the failure is that the parent has globals, not what the subchart does with them.
Per-subchart global scoping in HelmDoes not exist. coalesceGlobals copies the map unconditionally; there is no allowlist and no inverse of import-values.
--skip-schema-validationHelm ≥3.16 only (absent in 3.15.4). Disables validation for the whole release tree — flame-hub's own values plus bitnami redis/rabbitmq/grafana/prometheus, harbor, seaweedfs. Cannot be declared by the chart; every operator, CI job, Argo CD and Flux config must opt in by hand.
Vendor the chart and rm values.schema.jsonWorks, but forks the published artifact, breaks helm dependency update against https://helm.authup.org, and re-creates the vendoring burden the migration exists to remove.
Delete global.flameHub, duplicate literals per subchartNot expensive — impossible. flame-hub/values.yaml:194-195,203 pass tpl strings that are evaluated in the subchart's context, where global is the only reachable parent state. Without global.flameHub those values cannot be expressed at all, templated or static. flame-hub already carries flameHub.validateNames guards (charts/flame-hub/templates/_helpers.tpl:154-181) as the cost of exactly this duplication for goharbor's un-templatable literals; this would multiply that across every shared value.

One deleted line in the generated schema (three comment lines in the source) unblocks the chart for every umbrella consumer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions