fix(charts): make superset, wordpress and outline configurable from the UI — and installable at all - #301
Conversation
…p upgrades crashing every pod
values.schema.json was fully enum-locked: 0 of 25 leaves editable, and no `mutable`
anywhere, so the published form was EMPTY. Now 39 leaves are mutable with
descriptions and defaults asserted against values.yaml.
The values existed but no template read them, so the previous OOM fix was dead
code: every deployment still hardcoded the image and 250m/500Mi. image, resources,
secretKey and the DB env are now wired into all five workloads (node, worker, beat,
flower, init Job).
Real bugs, each reproduced on a live cluster:
* Any boolean feature flag bricked the release. featureFlags/config rendered with
bare {{ $value }}, but superset_config.py is PYTHON, so a YAML true became Go's
`true`:
File "/app/pythonpath/superset_config.py", line 44, in <module>
"ALERT_REPORTS": true,
NameError: name 'true' is not defined
Every pod CrashLooped on upgrade. New superset.pyValue helper renders Python
literals (True/False/None, quoted strings, bare numbers, raw dict/list).
* SECRET_KEY was minted by randAlphaNum on every render, so EVERY upgrade rotated
it. It signs sessions AND encrypts the database passwords Superset stores, so an
upgrade logged everyone out and left saved connections undecryptable. Now
generated once and read back from the env Secret via lookup.
* Enabling flower could only ever fail. Its command was a 4-element array passed to
`sh -c`, which runs only its first operand. Fixed to one -c string. That exposed a
second, independent defect: all three probes used path /api/workers, which this
flower refuses without auth —
401 GET /api/workers: FLOWER_UNAUTHENTICATED_API environment variable is
required to enable API without authentication
so the pod ran fine but was NEVER Ready and the rollout never completed. Verified
in-pod that /healthcheck returns 200 and /api/workers 401; all three probes now
use /healthcheck.
* The web tier could not be scaled: node/deployment.yaml hardcoded `replicas: 1`, so
only the Celery worker was adjustable. Now supersetNode.replicaCount (the name the
upstream apache/superset chart uses). celerybeat is deliberately left at 1 — two
schedulers double every scheduled report and alert.
* OOM on boot: limits.memory was 500Mi while run-server.sh pip-installs drivers
before gunicorn starts. Now 2Gi.
Also drops postgres.postgresRootPassword and services[].password from values.yaml:
postgres v0.0.12 generates both at random and never reads them, so they only misled
whoever set them.
redis is pinned to 0.0.1 on purpose: from 0.0.5 it ships an alerts.yaml whose
PrometheusRule is named {{ .Release.Name }} — the same name postgres already uses —
and helm refuses two objects of one kind+name ("prometheusrules ... already exists").
Verified on minikube: install + 5 upgrades, 41/41 field readbacks pass, booleans
land as Python True/False with no lowercase leak, SECRET_KEY and DB_USER stable
across two upgrades, admin user present in Superset's own DB before and after, and
flower/beat both reaching Ready.
…_HTTPS survivable
values.schema.json was enum-locked. Now 16 leaves are mutable with descriptions and
defaults asserted against values.yaml.
Three real bugs, each reproduced on a live cluster:
* The chart could not boot at all. It shipped URL: "", and Outline validates URL as
a real URL on startup and exits. Default is now http://localhost:3000. (Under
service 0.0.17 an empty env value was dropped; 0.0.32 removed that filter so it
arrives as an empty string — broken either way.)
* /data was an emptyDir, and FILE_STORAGE=local writes every uploaded image and
attachment there, so a restart silently discarded all of it. Now a 10Gi PVC.
* Turning on FORCE_HTTPS bricked the release. It is a mutable field and the normal
setting once Outline sits behind TLS, but Outline answers "/" with a 301 to
https://, and the kubelet FOLLOWS a probe redirect — to :443, where nothing
listens (TLS terminates at the Ingress):
Readiness probe failed: Get "https://10.244.1.39/": dial tcp ...:443:
connect: connection refused
Container outline failed liveness probe, will be restarted
so the pod CrashLooped forever. Verified in-pod that "/" returns 301 and /_health
returns 200, so heartbeatURL is now /_health — a purpose-built health endpoint
that is correct with FORCE_HTTPS either on or off.
SECRET_KEY and UTILS_SECRET are exposed with descriptions stating plainly that the
shipped values are PUBLIC placeholders every install shares until replaced.
redis is pinned to 0.0.1 and service.alerts.enabled is false to avoid two
PrometheusRules of the same name; outline was already un-installable at HEAD for
that reason, so this is not a regression from the version bumps.
Verified on minikube: install + 3 upgrades, 38/38 field readbacks pass, pods stay
Ready with FORCE_HTTPS=true, postgres DB_USER stable across two successive upgrades,
and an app-version image bump rolling out in the pod.
…ult install work
values.schema.json was enum-locked. Now 14 leaves are mutable with descriptions and
defaults asserted against values.yaml.
Real bugs, each reproduced on a live cluster:
* WordPress had NO volume, so every restart discarded all uploads, themes and
plugins. Now a 10Gi PVC at /var/www/html.
* The service dependency is pinned back to 0.0.17. From 0.0.18 the chart stopped
rendering .Values.env as container env entries and moved them into a
<name>-env-configmap consumed via envFrom, but the kubelet expands $(VAR) ONLY
inside env[].value — never inside a ConfigMap. Under 0.0.32 WordPress therefore
received the literal string "$(DB_HOST):$(DB_PORT)" as its database host and could
not connect. Verified after the pin: the pod's WORDPRESS_DB_HOST is
wordpress-mysql:3306 and a real mysqli_connect from inside the pod succeeds.
* The DEFAULT install was impossible. service.name defaulted to "wordpress" and the
datastore ConfigMap naming in charts/service forces the release to be called
"wordpress" too, so the service chart's PrometheusRule (named after service.name)
and mysql's (named after the release) collided:
Error: prometheusrules.monitoring.coreos.com "wordpress" already exists
service 0.0.17 has no alerts on/off switch (that arrives in 0.0.30, which no
longer expands $(VAR)) and mysql's rule cannot be gated either, so the only lever
left inside this chart is the name: service.name now defaults to wordpress-app.
This renames the Deployment/Service/Ingress/ServiceMonitor, and nothing working
breaks — the default install was already failing before this change, so there was
no working release to rename. service.name is not in the schema, so no form field
changes.
WORDPRESS_DEBUG's description now says the value is the string '1' rather than "set
to 1": the schema declares string, so an integer is rejected with
"Invalid type. Expected: string, given: integer".
service.maxReplicas is deliberately NOT exposed: 0.0.17's hpa.yaml is gated on
hpa_enable, which this chart never sets, so it is a dead value — and RWO storage
caps the release at one pod anyway.
Verified on minikube: install + 3 upgrades, 36/36 field readbacks pass, the default
install renders zero duplicate kind+name objects, MySQL credentials stable across
two successive upgrades with WordPress still connecting, and an app-version image
bump rolling out in the pod.
Packaged per CONTRIBUTING.md steps 4-6 so the new versions resolve from https://helm.zop.dev (docs/ is what GitHub Pages serves, per the root CNAME): helm package ./charts/<chart> --version "v<version>" -d docs/ cd docs && helm repo index . --url https://helm.zop.dev Versions carry the `v` prefix because that is what the published packages use (docs/superset-v0.0.7.tgz has version: v0.0.7 inside), while the source Chart.yaml keeps the bare number — same split as the existing entries. index.yaml is regenerated, not hand-edited. Verified nothing was lost: 174 -> 177 (chart, version) pairs, the three additions are exactly superset v0.0.8, outline v0.0.9 and wordpress v0.0.8, no existing entry was dropped and no existing digest changed. The remaining diff is regeneration reordering entries.
d43a22b to
fb0a01e
Compare
arunesh-j
left a comment
There was a problem hiding this comment.
Reviewed statically — helm lint, helm template across many value permutations, subchart extraction, a programmatic schema-vs-values.yaml diff, and a structural diff of docs/index.yaml. No cluster was reachable from my side, so I could not re-run the live readbacks; everything below comes from the templates and packages themselves.
Overall: this is genuinely good work and I'd approve it with changes. The diagnosis is accurate, the in-line reasoning is unusually well documented, and I independently reproduced the headline claims. There are a handful of real issues, one of which makes an advertised feature a no-op.
Claims I independently confirmed ✅
| Claim | Result |
|---|---|
helm lint clean on all 3 |
pass; Chart.lock digests stable (no drift on helm dependency build) |
Every schema default equals values.yaml, nothing orphaned |
exact match — 0 mismatched defaults, 0 schema keys absent from values.yaml, 0 leaves without a default, across all three charts |
| Mutable counts 39 / 16 / 14; zero single-value enums left | confirmed |
| Zero duplicate kind+name at default values | confirmed |
outline and wordpress were un-installable on main |
confirmed. Both render two PrometheusRules of the same name. And the release name really is forced: service 0.0.17 references {datastore}-{database}-{Release.Name}-mysql-configmap while mysql creates {Release.Name}-{name}-{database}-..., so release ≠ wordpress → envFrom points at a ConfigMap that does not exist. There was no working release to rename, so the service.name: wordpress-app argument holds. |
wordpress pinned to service 0.0.17 |
confirmed — 0.0.32 deletes the env: block entirely, and $(VAR) is never expanded inside a ConfigMap |
--wait / --atomic limitation |
confirmed — postgres/mysql render a bare kind: Pod, restartPolicy: OnFailure, no hook annotations → Succeeded / Ready=False forever |
docs/index.yaml 174 → 177 |
confirmed against origin/main: exactly 3 additions, 0 removals, 0 digest or URL changes. All new tgz sha256 match their index digests, packaged versions correctly v-prefixed, subcharts vendored. |
pyValue renders Python literals |
confirmed for bool / "None" / dict-string / list-string / int / float / plain string |
One thing the PR undersells: postgres 0.0.11 → 0.0.12 is almost entirely docker.io/bitnami/postgresql → bitnamilegacy. Those Bitnami tags were pulled from Docker Hub, so 0.0.11 is heading for ImagePullBackOff. That bump is a fix in its own right and worth stating.
Findings
Details are in the inline comments. Summary, with suggested gating:
Fix before merge
- 🔴
supersetNode.replicaCountis a no-op — an unconditional HPA ownsspec.replicas. - 🔴
superset.pyValuestill emits invalid Python for native maps, lists andnull— the same crash class this PR fixes, via a different input shape. - 🟠 Two schema descriptions are factually wrong (
db_host,redis_hostare read unconditionally). - 🟡 Empty
image.tagsilently yieldsapache/superset:1.0; addminLength: 1.
Decide before merge
5. 🟡 outline's 15-version service bump buys one flag, when the wordpress-style rename would have worked with no bump — and it has three unmentioned side effects.
6. Scope call: the disclosed --wait/--atomic limitation means all three charts still fail to install through the platform installer after this PR. The title's "installable at all" holds only for plain helm install. The PR is honest about it and the fix does belong in charts/postgres + charts/mysql, but if the platform is the only consumer, this lands as staged progress rather than a working install — your call.
Follow-up issues
7. 🟠 New RWO PVCs with no strategy: Recreate — rolling upgrades can deadlock on multi-node clusters.
8. 🟡 The PrometheusRule naming root cause is untouched and now worked around in three places. Seven datasource charts (postgres, redis, mysql, clickhouse, kafka, solr, solrcloud) name their rule {{ .Release.Name }} while the rule group inside is already suffixed. A one-line change per chart retires all three workarounds here and unpins redis from 0.0.1 (stuck on redis:6.2.13, which is EOL). outline and superset are the only app charts bundling two of these, so the blast radius is small.
9. 🟡 Flower pip installs itself from PyPI at container start — breaks in egress-restricted clusters.
10. 🟡 wordpress has no readiness probe; with the new PVC, first boot copies the whole WordPress tree into /var/www/html while the Service already routes to the pod.
CONTRIBUTING.md and the release flow
The steps here were followed correctly; the doc itself has gaps that this PR surfaces:
- The
v-prefix split is undocumented. Step 4 sayshelm package ... --version "<version>"and never mentions that published packages usev0.0.8while the sourceChart.yamlstays0.0.8. This PR had to infer it from existing tgz files. - Step 6 churns the entire index.
helm repo index . --url https://helm.zop.devre-stampscreatedon all 174 pre-existing entries — that is the whole ~350-line diff here (I verified nothing else changed).--merge index.yamlpreserves them. The same churn is in #299 and #294, so it is systemic, not this PR's fault. Fixing the doc would make future index diffs ~20 lines and make "did anything get dropped?" reviewable at a glance. helm dependency updateis never mentioned, yet a packaged app chart must vendor its subcharts (these do — verified).- The schema conventions the UI actually relies on are not documented. CONTRIBUTING presents
mutable: trueandeditDisabled: trueas opposites, but says nothing about (a) themutable: true+editDisabled: truecombination — "settable at install, locked afterwards" — which ~20 charts use and which this PR uses correctly fordiskSizeandvolumeMounts.pvc, or (b) thecategoryvalues from #292/#293 (compute/runtime/environment/advanced). I nearly filed the double-flag as a bug before finding the precedent; other reviewers will too. .github/workflows/release.yamlis a second, parallel publishing path. It runshelm/chart-releaser-actionon anycharts/**push tomain, which normally publishes to GitHub Releases plus agh-pagesindex.yaml. There is nogh-pagesbranch and no release since 2026-05-06 — the runs "succeed" in 8–19s doing nothing. Inert today, but if it ever engages it will publishsuperset-0.0.8(unprefixed) alongsidedocs/'sv0.0.8. Worth deleting or reconciling.
Minor: re-categorising service from compute → runtime leaves outline and wordpress with no compute group at all, even though minCPU/minMemory/maxCPU/maxMemory live inside service. charts/clickhouse shows per-leaf category works, so those four leaves could carry category: compute. All four category values are in repo-wide use, so nothing breaks either way.
One process note
The verification table in the description is unusually strong, but findings 1 and 7 are both cases where a single-node minikube pass hides the failure. Two additions would catch that class: re-read spec.replicas after the HPA's 5-minute downscale stabilisation window, and run at least one upgrade on a multi-node cluster.
| release: {{ .Release.Name }} | ||
| spec: | ||
| replicas: 1 | ||
| replicas: {{ .Values.supersetNode.replicaCount }} |
There was a problem hiding this comment.
🔴 This is a no-op — an unconditional HPA owns spec.replicas.
charts/superset/templates/node/hpa.yaml has no if guard and hardcodes minReplicas: 1, maxReplicas: 15 targeting exactly this Deployment. Setting replicaCount: 3 writes spec.replicas: 3, then the HPA reconciles it straight back to whatever CPU/memory utilisation dictates. Worse, helm and the HPA then fight on every upgrade: helm writes 3, the HPA writes 1, the next helm upgrade writes 3 again — pod churn for no reason.
Why the live test passed: a status.readyReplicas readback right after rollout sees 3. The HPA's downscale stabilisation window is 5 minutes by default, so the value only reverts after the check has already gone green.
The diagnosis in the commit message ("the web tier could not be scaled") is correct — the fix just doesn't land. Suggest wiring supersetNode.replicaCount into hpa.minReplicas and exposing maxReplicas too, or gating the HPA behind a value.
Related, same file: the HPA also targets memory at 80% of requests. Raising requests.memory 250Mi → 512Mi is an improvement, but Superset's web RSS routinely exceeds 410Mi at idle, so this HPA plausibly pegs the tier at maxReplicas: 15. Pre-existing, but now user-reachable since resources became mutable.
| A string that already looks like a dict/list, or that spells True/False, is passed | ||
| through unquoted so callers can still hand in raw Python. | ||
| */ -}} | ||
| {{- define "superset.pyValue" -}} |
There was a problem hiding this comment.
🔴 Still emits invalid Python for native maps, lists and null — the same crash class this PR set out to fix, reached through a different input shape.
featureFlags and config are declared type: object with no additionalProperties, so the schema accepts all of these and each one CrashLoops every pod:
supersetNode:
config:
HTTP_HEADERS: {X-Frame-Options: SAMEORIGIN} # → HTTP_HEADERS = map[X-Frame-Options:SAMEORIGIN]
ALLOWED_LIST: [a, b] # → ALLOWED_LIST = [a b]
NOTHING: # → NOTHING =
featureFlags:
NESTED: {x: 1} # → "NESTED": map[x:1],Verified with helm template; the schema raises no objection. A nested object is a plausible thing for someone to type into a free-form form field, and HTTP_HEADERS in particular is a normal Superset setting that is naturally a dict.
Two complementary fixes:
- add
kindIs "map"/kindIs "slice"branches (toJsoncovers the common case correctly — JSON object/array syntax is valid Python, and the leaf handling here already normalises bools) plus a nil branch; - constrain the schema:
"additionalProperties": {"type": ["string","number","integer","boolean"]}onconfig, and{"type": "boolean"}onfeatureFlags.
| "redis_celery_db": { "type": "string", "enum": ["0"] }, | ||
| "db_host": { | ||
| "type": "string", | ||
| "description": "PostgreSQL host for Superset metadata. Only read when the bundled postgres is turned off", |
There was a problem hiding this comment.
🟠 This description is wrong, on a mutable field. db_host is read unconditionally, not only when the bundled postgres is off — env-secrets.yaml has:
DB_HOST: {{ tpl .Values.supersetNode.connections.db_host . | quote }}
with no if. It has to be, since it is how Superset reaches the bundled postgres in the first place. Only db_user / db_pass are gated by superset.dbEnv — those two descriptions are accurate.
The risk is concrete: a user reads "only read when the bundled postgres is turned off", concludes the field is inert on their default install, edits it, and breaks a working release. Same wording problem on redis_host (line 256) — REDIS_HOST is also set unconditionally.
Two-line fix. Suggest something like: "PostgreSQL host for Superset metadata. Always used — with the bundled postgres on, leave it at the default; point it at your own server when postgres.enabled is false."
| "default": "apachesuperset.docker.scarf.sh/apache/superset", | ||
| "mutable": true | ||
| }, | ||
| "tag": { |
There was a problem hiding this comment.
🟡 Empty tag silently produces an image that does not exist.
superset.image is .Values.image.repository:{{ .Values.image.tag | default .Chart.AppVersion }} and this chart's appVersion is "1.0", so clearing the field in the UI renders apachesuperset.docker.scarf.sh/apache/superset:1.0 → ImagePullBackOff on all five workloads. Verified with --set-string image.tag="".
Add "minLength": 1 here and on image.repository; same applies to service.image in the outline and wordpress schemas.
| # It also never sourced superset_bootstrap.sh, and the Superset image does not | ||
| # ship Flower - `celery flower` fails with "Error: No such command 'flower'" | ||
| # (verified on minikube), so the package has to be installed first. | ||
| - ". /app/pythonpath/superset_bootstrap.sh; pip install --no-cache-dir flower==2.0.1; celery --app=superset.tasks.celery_app:app flower --port=5555" |
There was a problem hiding this comment.
🟡 pip install --no-cache-dir flower==2.0.1 at container start means Flower cannot start in an egress-restricted or air-gapped cluster, and pod startup is coupled to PyPI availability and latency. The 300s startup budget (failureThreshold: 60 × periodSeconds: 5) is adequate, so this is not a correctness bug — just a deployability constraint worth a comment.
Given the diagnosis was that /api/workers needs FLOWER_UNAUTHENTICATED_API, setting that env var and keeping the richer probe may be cheaper than a runtime install. Or use an image that ships Flower. Either way the sh -c fix and the /healthcheck probe change are both clearly right.
| repository: https://helm.zop.dev | ||
| - name: service | ||
| version: 0.0.17 | ||
| version: 0.0.32 |
There was a problem hiding this comment.
🟡 A 15-version jump to obtain one flag. The stated reason for 0.0.17 → 0.0.32 is alerts.enabled. But service.name: outline-app — the exact fix applied to wordpress in this same PR — resolves the same collision with no version bump at all. Two charts, same problem, opposite mitigations, and outline ends up with no app-level PrometheusRule while wordpress keeps one.
The bump also has three side effects the description does not mention:
SECRET_KEYandUTILS_SECRETmove out of the Deployment spec and into a plaintext ConfigMap (outline-env-configmap), because0.0.32removed theenv:block in favour ofenvFrom. Comparable exposure, but it is a change in where secrets live.- The app
ServiceMonitorand itsmetrics-portdisappear —0.0.32dropped themetricsPort: 2121default from itsvalues.yaml. Since nothing ever listened on 2121 inside the outline container, this removes a permanently-failing scrape target, so it is net positive; it is just undocumented. 0.0.32also rewrote the pod/containersecurityContexthandling and addedreplace "_" "-"to the datastore ConfigMap names. Neither changes the render for outline today, but it is surface area the PR is not claiming to have tested.
Either rename the service (consistent with wordpress, keeps alerts, zero bump) or keep the bump and state in the description why the two charts diverge.
| version: 0.0.11 | ||
| version: 0.0.12 | ||
| repository: https://helm.zop.dev | ||
| # redis is PINNED to 0.0.1 ON PURPOSE - do not bump alongside the postgres subchart. |
There was a problem hiding this comment.
🟡 The analysis here is exactly right, and the comment is excellent — but the root cause is being worked around in three places in this one PR (this pin, the same pin in outline, the wordpress service.name rename, and outline's alerts.enabled: false).
The actual bug is in the datastore charts. Seven of them name their PrometheusRule after the bare release name while the rule group inside is already suffixed:
charts/{postgres,redis,mysql,clickhouse,kafka,solr,solrcloud}/templates/alerts.yaml
metadata.name: {{ .Release.Name }}
spec.groups[0].name: {{ .Release.Namespace }}.{{ .Release.Name }}-redis.rules # ← already unique
A one-line change per chart (name: {{ .Release.Name }}-redis) retires every workaround in this PR and unpins redis from 0.0.1, which is stuck on redis:6.2.13 — Redis 6.2 is EOL, so this pin is accruing security debt.
Blast radius is small: outline and superset are the only app charts in the repo bundling two of these seven. Worth a follow-up issue — these comments already contain everything needed to make it a 20-minute change.
| # volume every restart loses them. The service subchart creates this PVC. | ||
| volumeMounts: | ||
| pvc: | ||
| - name: wordpress-data |
There was a problem hiding this comment.
🟠 New RWO PVC with no strategy: Recreate. Neither service chart version (0.0.17 or 0.0.32) sets spec.strategy, so the default RollingUpdate applies: maxSurge 25% of 1 replica = 1, meaning the replacement pod is created before the old one terminates. On single-node minikube both pods land on the same node and the RWO volume attaches fine. On a real multi-node cluster this is a Multi-Attach error and the upgrade stalls until the timeout.
Same applies to outline's /data PVC. This is precisely the failure class local single-node testing hides, so it is worth flagging even though the fix needs strategy support in charts/service rather than a change here.
Also on this chart: there is no heartbeatURL, so 0.0.17 renders no probes at all. With this PVC, first boot copies the entire WordPress tree into /var/www/html (tens of seconds) while the Service is already routing traffic to the pod. heartbeatURL: / would gate that — WordPress answers / with a 302 to the install wizard on the same host:80, so probe redirect-following is harmless here.
| # longer expands $(VAR) - see Chart.yaml), and mysql's rule cannot be gated | ||
| # either, so distinguishing the two names here is the only fix available inside | ||
| # this chart. charts/service and charts/mysql are shared and out of scope to edit. | ||
| name: wordpress-app |
There was a problem hiding this comment.
✅ Verified this reasoning end to end, and it holds. On main wordpress is un-installable in every configuration, not just at defaults:
- release named
wordpress→ twoPrometheusRules calledwordpress(one fromcharts/serviceafterservice.name, one fromcharts/mysqlafter the release) →already exists; - release named anything else →
charts/service0.0.17builds the datastore ref as{datastore}-{database}-{Release.Name}-mysql-configmapwhilecharts/mysqlcreates{Release.Name}-{name}-{database}-mysql-configmap. For releaseblogthat iswordpress-wordpress-blog-mysql-configmapreferenced vsblog-wordpress-wordpress-mysql-configmapcreated →CreateContainerConfigError, forever.
They only match when the release name equals services[].name/database, i.e. wordpress — which is the colliding case. So there was no working release to rename and this is not a regression. Confirmed also that service.name is absent from the schema, so no form field moves.
Worth adding to the description explicitly: the k8s Deployment/Service/Ingress/ServiceMonitor all rename to wordpress-app. If anything in the zop.dev console resolves workloads by application name rather than by release, that is the one thing to sanity-check before merge.
| # release. Outline exempts /_health from the redirect (verified in-pod: "/" -> 301, | ||
| # "/_health" -> 200) and it is a purpose-built health endpoint, so it is the correct | ||
| # probe target with FORCE_HTTPS either on or off. | ||
| heartbeatURL: /_health |
There was a problem hiding this comment.
✅ Nice catch, and the reasoning is right — the kubelet does follow probe redirects, and a 301 to https:// on a pod with no TLS listener is an unconditional CrashLoop. /_health being exempt from the redirect makes it correct with FORCE_HTTPS either way.
One note on the neighbouring alerts.enabled: false: that switch drops outline's app-level PrometheusRule entirely, so outline loses replica/availability alerting while wordpress (which fixes the identical collision by renaming) keeps it. See my comment on Chart.yaml — renaming here instead would keep the alerts and avoid the subchart bump.
…rs too
Review found two defects in the previous commit. Both are fixed here.
1. supersetNode.replicaCount was a NO-OP. node/hpa.yaml was unconditional and hardcoded
minReplicas: 1 / maxReplicas: 15 targeting this Deployment, so writing spec.replicas
just got reconciled away - and helm and the HPA then overwrite each other on every
upgrade. The live readback passed only because the HPA's downscale stabilisation window
is 5 minutes, longer than the check.
The HPA is now gated on supersetNode.autoscaling.enabled and its floor comes from
replicaCount; maxReplicas and both utilisation targets are values too. The Deployment
OMITS spec.replicas whenever the HPA exists, so only one controller owns the count.
Defaults reproduce the previous behaviour exactly: 1..15 at 80%/80%.
The memory target is a percentage of requests.memory, and Superset's web RSS is in the
hundreds of MiB at idle, so that trade-off is now documented next to the value.
2. superset.pyValue still emitted invalid Python for maps, lists and null - the same crash
class this chart was meant to be rid of, reached through a different input:
HTTP_HEADERS: {X-Frame-Options: SAMEORIGIN} -> map[X-Frame-Options:SAMEORIGIN]
ALLOWED_LIST: [a, b] -> [a b]
NOTHING: -> (empty)
It now recurses. `toJson` was NOT used: JSON spells booleans and null true/false/null,
so {"a": true} would be exactly as broken as the bare boolean. Walking the structure
normalises a bool or nil at any depth, which matters because config.HTTP_HEADERS is a
dict in normal Superset use and featureFlags values are booleans.
featureFlags is additionally constrained to additionalProperties: boolean. config is
deliberately left permissive - a dict there is legitimate and now renders correctly.
Also from review:
* db_host / redis_host descriptions claimed the fields are "only read when the bundled
postgres/redis is turned off". They are read UNCONDITIONALLY - env-secrets.yaml sets
DB_HOST and REDIS_HOST with no `if`. A user could have read that, assumed the field was
inert, edited it and broken a working release.
* image.repository / image.tag get minLength: 1. Clearing the tag silently rendered
apache/superset:1.0 from appVersion, which is not a published tag, so all five workloads
went ImagePullBackOff. Now rejected up front:
image.tag: String length must be greater than or equal to 1
Verified on minikube (install + 6 upgrades, 58/59 checks): the rendered manifest omits
replicas while the live object honours the new HPA floor of 2; defaults still render 1..15;
autoscaling off gives a plain Deployment of 2 and no HPA; celerybeat stays a singleton; and
the live superset_config.py contains HTTP_HEADERS = {"X-Frame-Options": "SAMEORIGIN"},
ALLOWED = ["a", "b"], NOTHING = None and MIXED = {"n": None, "on": True}, with explicit
assertions that no JSON `: true` and no Go `map[` leak. The whole file also parses under
ast.parse. The one non-pass was a single init-db hook Job attempt that errored and then
succeeded on retry (helm rc=0); it did not reproduce on an identical prior run.
Review pointed out that outline and wordpress fixed the same PrometheusRule collision in opposite ways, and that outline came off worse: it switched its own alerts off, so the release lost app-level availability alerting entirely, while wordpress kept them by renaming. outline now uses the same mitigation as wordpress: service.name defaults to outline-app, and service.alerts.enabled goes back to true. The release renders two distinctly named rules - "outline" from postgres and "outline-app" from service - with zero duplicate kind+name objects, so nothing has to be given up. The service 0.0.32 pin is kept, and Chart.yaml now says why the two charts diverge rather than leaving it implicit: wordpress MUST stay on 0.0.17 because from 0.0.18 .Values.env moved into a ConfigMap and the kubelet only expands $(VAR) inside container env[].value; outline uses no $(VAR), and 0.0.17 would add a permanently-failing scrape target because it defaults metricsPort to 2121 and nothing in the outline container listens there. The three differences 0.0.32 brings are now documented too: secrets arrive via envFrom rather than inline env, no app ServiceMonitor is emitted, and datastore ConfigMap names gain a replace "_" "-" that changes nothing here. service.image gets minLength: 1, same reasoning as superset's image fields. Verified on minikube: install + 3 upgrades, 38/38 readbacks pass, the app objects are named outline-app, and the FORCE_HTTPS fix still holds (probes on /_health, pods stay Ready).
…y image
Review noted that this chart renders NO probes at all - service 0.0.17 needs both
readinessProbe.enable and a non-empty heartbeatURL - and that the PVC added in this PR makes
that worse: on first boot the entrypoint copies the whole WordPress tree into the empty
volume ("WordPress not found in /var/www/html - copying now..."), which takes tens of
seconds, and the Service routes to the pod for that entire window.
A readiness probe on "/" is added, with a ~2.5 minute grace window for that first copy.
"/" is safe here: WordPress answers it with a 302 to the install wizard on the same host:80,
so a probe that follows the redirect still lands on a listening port - unlike outline, where
a 301 to https:// pointed the kubelet at a closed :443.
Liveness is deliberately NOT enabled: a slow first-boot copy would trip it and restart the
pod mid-copy, turning a slow start into a loop.
service.image gets minLength: 1, same reasoning as superset's image fields.
Verified on minikube: install + 3 upgrades, 36/36 readbacks pass, pods reach Ready in ~31s
on a first install and ~16s on upgrades, and WordPress still opens a real mysqli connection
with the expanded $(DB_HOST):$(DB_PORT) credentials.
…ew fixes The chart contents changed (HPA gating, recursive Python rendering, outline's service.name rename, wordpress's readiness probe, minLength guards), so the packages and their digests are regenerated. Versions are unchanged because none of these three are published yet - main still has superset v0.0.7, outline v0.0.8 and wordpress v0.0.7. Re-verified: 174 -> 177 (chart, version) pairs, nothing dropped, no pre-existing digest changed, and each new index digest matches the sha256 of its packaged file.
|
Thanks — this was a genuinely useful review. Both 🔴s were real; one of them made an advertised feature a no-op exactly as you described. All four "fix before merge" items are done, plus the outline decision and one of the follow-ups. Pushed in 1. 🔴
|
| chart | install + upgrades | readbacks |
|---|---|---|
| superset | install + 6 upgrades | 58/59 |
| outline | install + 3 upgrades | 38/38 |
| wordpress | install + 3 upgrades | 36/36 |
The one superset non-pass was a single init-db hook Job attempt that errored and then succeeded on retry (helm rc=0, all field readbacks passed). It did not reproduce on an identical prior run, so I'm reporting it as observed-once rather than claiming it's benign.
Packages and index.yaml regenerated for the new contents: still 174 → 177 pairs, nothing dropped, no pre-existing digest changed, and each new index digest matches the sha256 of its file. Versions unchanged, since none of the three are published on main yet.
arunesh-j
left a comment
There was a problem hiding this comment.
Re-reviewed at 9654ed4. I re-verified every fix independently and re-ran the full integrity sweep, since the values files changed. Everything I raised as fix-before-merge is genuinely fixed — approving, with two small notes inline.
Same caveat as last time: no cluster reachable from my side, so this is static verification only.
Fixes verified
1. 🔴 replicaCount no-op — fixed
Checked all four combinations:
| Config | Deployment replicas |
HPA objects | floor / ceiling |
|---|---|---|---|
| default | omitted | 1 | 1 / 15 |
replicaCount=3 |
omitted | 1 | 3 / 15 |
autoscaling.enabled=false, replicaCount=4 |
4 | 0 | — |
maxReplicas=8, targetCPU=60 |
omitted | 1 | 1 / 8, 60% / 80% |
Exactly one controller owns the count in each mode, and the defaults reproduce the old 1..15 / 80% / 80% behaviour. replicaCount also carries minimum: 1, which forecloses the minReplicas: 0 invalid-HPA case — good catch, I hadn't flagged that one.
2. 🔴 pyValue — fixed, and your correction is right
toJson would not have been safe: JSON spells nested booleans and null true/false/null, so {"a": true} would have reproduced the exact NameError this helper exists to prevent. Recursion is the correct fix — my suggestion was wrong on that point.
Verified beyond the cases I originally reported. I extracted the rendered superset_config.py and ran it through ast.parse:
HTTP_HEADERS = {"X-Deep": {"k": [True, False, None, 2.5]}, "X-Frame-Options": "SAMEORIGIN"}
ALLOWED = ["a", "b"]; EMPTY_MAP = {}; EMPTY_LIST = []; NIL = None; RATIO = 1.5
→ PARSES AS VALID PYTHON
→ no ": true" / ": false" / ": null" / "map[" leaks anywhere in the file
Bools and nils normalise at arbitrary depth, empty containers are fine, and sorting the map keys keeps the config checksum stable across renders.
I also agree with leaving config unconstrained. HTTP_HEADERS as a dict is legitimate Superset config and now renders correctly, so restricting it to scalars would remove a valid use rather than protect one.
3. 🟠 Descriptions — fixed
Both now read "Always used, including with the bundled …", and db_user/db_pass correctly retain the gated wording.
4. 🟡 minLength: 1 — fixed and enforced
On superset's image.repository / image.tag and on outline's and wordpress's service.image. All three reject an empty value at lint time.
5. 🟡 outline divergence — took the rename, and it is the better call
Renders two distinctly named rules (outline from postgres, outline-app from service), alerts back on, zero duplicates. I specifically checked the thing most likely to break in a rename: every envFrom reference still resolves to an object the release actually creates —
outline-app-env-configmap
outline-outline-outline-postgres-configmap
outline-outline-outline-postgres-database-secret
outline-redis-service-configmap
The datastore ref genuinely does not involve service.name, so the rename is safe.
And I agree with keeping 0.0.32 — don't revert. Going back to 0.0.17 would reintroduce the metricsPort: 2121 default and with it a permanently-failing scrape target. That is a better reason for the pin than the one originally given, and the Chart.yaml comment now documents the divergence and all three side effects accurately.
10. 🟡 wordpress readiness — fixed
/ on :80, initialDelaySeconds: 10 + 30 × 5s ≈ 160s of grace, no liveness probe. The reasoning holds: WordPress answers / with a 302 to the install wizard on the same host:80, and httpGet treats 3xx as success — genuinely unlike the outline case, where the 301 pointed the kubelet at a closed :443.
Integrity re-swept after the changes
helm lintclean ×3; zero duplicate kind+name ×3- Schema vs
values.yaml: still exact — 43 / 16 / 14 mutable leaves, 0 mismatched defaults, 0 schema keys absent from values, 0 leaves without a default - All three repackaged tgz are byte-identical to the new source (modulo Chart.yaml normalisation), and each index digest matches its file's sha256
docs/index.yaml: still 174 → 177, 0 removals, 0 digest or URL changes- Regression sweep green:
postgres.enabled=false, flower + beat, outlineFORCE_HTTPS+ host, wordpress host, and old 0.0.7 values still validating against the new schema
Two new small things
Both inline. Neither blocks: (A) maxReplicas < replicaCount renders an HPA the API server rejects, and (B) existing superset releases take a one-time transient replica dip on the first upgrade after spec.replicas is omitted.
Standing items, unchanged as agreed
#6 scope call — after this PR all three still fail the platform installer; that is the maintainer's decision, and keeping the limitation prominent rather than buried is the right handling. #7 strategy: Recreate — agreed, not fixable from an app chart. #8 PrometheusRule root cause and #9 Flower pip install — both follow-ups.
The CONTRIBUTING.md items are repo-level and out of scope here, though note helm repo index --merge would have shrunk this second index diff from 362 lines as well.
Finally: reporting the single init-db hook Job attempt that errored and then succeeded on retry as observed-once, rather than rationalising it, is the right instinct. It is a post-install,post-upgrade hook with a before-hook-creation delete policy and a dockerize -wait initContainer, so a transient first-attempt failure is plausible without being benign by construction — worth watching across the next few installs rather than chasing now.
| # count set here was reconciled straight back. It is now gated and driven by these | ||
| # values; defaults reproduce the previous behaviour exactly (1..15, 80%/80%). | ||
| enabled: true | ||
| maxReplicas: 15 |
There was a problem hiding this comment.
🟡 Nit, new with this change: nothing stops maxReplicas being set below replicaCount, and both are mutable, so it is trivially reachable from the form:
--set supersetNode.replicaCount=5 --set supersetNode.autoscaling.maxReplicas=3
→ minReplicas: 5
maxReplicas: 3
helm template renders it happily; the API server then rejects the object with spec.maxReplicas: Invalid value: 3: must be greater than or equal to minReplicas and the upgrade fails.
JSON Schema cannot express the cross-field constraint, and it fails loudly at apply rather than corrupting anything — so this is genuinely a nit. A clause in the maxReplicas description ("must be at least replicaCount") would be enough.
| spec: | ||
| replicas: 1 | ||
| {{- if not .Values.supersetNode.autoscaling.enabled }} | ||
| {{- /* omitted when an HPA targets this Deployment: it owns spec.replicas, and setting |
There was a problem hiding this comment.
🟡 Worth one line in the PR description: this is the right pattern, but existing superset releases take a one-time transient replica dip on the first upgrade after it lands.
The live Deployment currently has spec.replicas set. Helm's three-way merge sees the field in the previous manifest and absent in the new one, so it patches it out — and the API server then applies the Deployment default of 1 until the HPA's next reconcile pulls it back up. Anyone the HPA had scaled above 1 sees a brief drop mid-upgrade.
Self-correcting, and a well-known caveat of the omit-replicas pattern rather than a defect in this implementation. It just shouldn't be a surprise to whoever runs the first upgrade.
jatintalgotra-zd
left a comment
There was a problem hiding this comment.
Good PR. The core claim holds up: single-value enums did lock these three forms shut, the fix is the right one, and the bugs it uncovered along the way are real. I checked the mechanical parts rather than taking the description's word for them, and they all pass.
Verified
| Check | Result |
|---|---|
helm lint × 3 charts |
pass |
| Duplicate kind+name across 6 value permutations (defaults, beat+flower on, hpa off, pg/redis off) | none |
Every schema default equals values.yaml |
43 / 16 / 14, all match |
mutable: true leaf count |
superset 43, outline 16, wordpress 14 |
docs/index.yaml vs main |
174 → 177, 3 added, 0 removed, 0 existing entries altered |
Packaged .tgz sha256 vs index digests |
all 3 match |
.tgz contents vs charts/ sources |
identical (templates, values, schema) |
superset.pyValue |
True/False, nested {"X-Frame-Options": "SAMEORIGIN"}, nil → None — correct |
superset_config.py stability |
no longer contains a random key; checksum/superset_config.py stable across renders |
| init Job immutability on image bump | safe — post-install,post-upgrade hook with before-hook-creation |
diskSize reaches the subchart PVCs |
yes, all three |
service.nginx.host is actually wired |
yes, node/ingress.yaml |
Findings
Four inline, ordered by how likely they are to bite:
- RWO PVC + default RollingUpdate deadlocks on a multi-node cluster (wordpress + outline) — the one I would most want resolved before merge. Minikube cannot surface it.
supersetNode.secretKeyis mutable but changing it rolls nothing — the UI reports success and the key silently applies at some later, unrelated rollout.postgres.enabled/redis.enabledare mutable in superset's schema — one-line fix,editDisabled: true.- outline's
SECRET_KEY/UTILS_SECRETnow render into a plaintext ConfigMap — a consequence of the deliberate 0.0.17 → 0.0.32 bump.
Smaller notes
- wordpress keeps the failing scrape target. On 0.0.17 it renders
ServiceMonitor wordpress-app→metrics-port2121, path/metrics, and nothing listens there. That is the exact defect cited as a reason to move outline off 0.0.17. Pre-existing and not a regression, but since the Chart.yaml notes reason about it explicitly, the asymmetry deserves a line. - Flower pip-installs at container start.
pip install --no-cache-dir flower==2.0.1runs on every restart, so an air-gapped cluster CrashLoops — the shell continues tocelery ... flowerregardless of whether pip succeeded. Off by default and the 300s startupProbe budget is right, so low priority. - The
service.namerename is a workload replacement.wordpress→wordpress-appandoutline→outline-appreplace the Deployment, Service, ServiceMonitor and PrometheusRule on upgrade. In-cluster DNS changes, so anything pointing at the old Service name breaks. Should be in the release notes. - PVC names are not release-scoped (
wordpress-data,outline-storage) and carry nohelm.sh/resource-policy: keep. Two releases in one namespace collide, andhelm uninstalltakes the site content with it. Subchart behaviour, but this PR is what introduces the PVCs. superset.service.nginxusestlshost/tlsSecretnamewhile outline and wordpress usetlsHost/tlsSecretName. Pre-existing, and the schema correctly matches values — just inconsistent across the repo.
Findings 1 and 2 are the ones worth addressing before merge. 3 is a one-line schema change. The rest can be follow-ups or comments.
| # WordPress writes uploads, themes and plugins under /var/www/html, so without a | ||
| # volume every restart loses them. The service subchart creates this PVC. | ||
| volumeMounts: | ||
| pvc: |
There was a problem hiding this comment.
New RWO PVC will deadlock rolling updates on a multi-node cluster.
charts/service renders this as accessModes: [ReadWriteOnce] and sets no strategy on the Deployment (confirmed absent in both 0.0.17 and 0.0.32). Default is RollingUpdate; at minReplicas: 1 that resolves to maxSurge=1 / maxUnavailable=0, so the new pod is created before the old one terminates. If the scheduler places it on a different node it blocks on Multi-Attach error for volume, the rollout stalls to progressDeadlineSeconds (600s), and the installer's --atomic --timeout 10m rolls the release back.
This cannot reproduce on minikube — it is single-node, and two pods on one node may share an RWO volume. It matters here specifically because every field this PR makes mutable (image, env.*, minCPU/maxMemory) triggers exactly this rollout.
The real fix is strategy: Recreate in charts/service, which is out of scope. At minimum it belongs in the "Known limitation" section next to the --wait note — same shape of problem: shared subchart, only visible outside minikube.
Same applies to charts/outline/values.yaml:109.
| # is a PersistentVolumeClaim (created by the service subchart) instead. | ||
| volumeMounts: | ||
| emptyDir: | ||
| pvc: |
There was a problem hiding this comment.
Same RWO-PVC / RollingUpdate deadlock as charts/wordpress/values.yaml:89 — see that comment. charts/service 0.0.32 has no strategy field either.
Separate, smaller note on this block: adding volumeMounts.pvc also silently applies container runAsUser: 1000 / runAsGroup: 1000 and pod fsGroup: 1000, because charts/service couples them:
{{- if and (hasKey .Values.volumeMounts "pvc") (not (empty .Values.volumeMounts.pvc)) }}
runAsUser: 1000
runAsGroup: 1000
{{- end }}
Both containers previously ran as the image default (root for wordpress:php8.4). It is verified working, but it now constrains service.image — which this same PR makes a mutable UI field. Any future image that needs root or a different fixed UID breaks on bump. Worth a comment next to the block so the coupling is not rediscovered later.
| # Flask SECRET_KEY. Generated once and read back out of THIS Secret on every | ||
| # later render, so an upgrade cannot rotate it: it signs session cookies and | ||
| # encrypts the DB-connection passwords Superset stores in its metadata DB. | ||
| SUPERSET_SECRET_KEY: {{ include "superset.secretKey" . | quote }} |
There was a problem hiding this comment.
supersetNode.secretKey is mutable, but changing it rolls nothing.
The key lands only here, and every pod consumes it via envFrom: secretRef. Env-from-Secret is not hot-reloaded, and nothing triggers a restart — the four checksum annotations on the node/worker/beat deployments cover superset-config, initscript, connections and configOverrides, and secretKey is in none of them (superset_config.py now reads it at runtime via env('SUPERSET_SECRET_KEY'), which is what makes the config stable — correct fix, but it also removes the only thing that used to churn the checksum). There is no stakater reloader annotation on these deployments either.
Net effect: setting a new key from the UI reports success and does nothing, then takes effect at the next unrelated rollout — logging every user out at a moment nobody connects to the change.
Adding checksum/env-secret: {{ include "superset.secretKey" . | sha256sum }} to the node / worker / beat / flower pod templates closes it.
| "type": "boolean", | ||
| "description": "Deploy the bundled PostgreSQL for Superset metadata. Turn off to use your own database, then set the db_host / db_port / db_user / db_pass / db_name fields below", | ||
| "default": true, | ||
| "mutable": true |
There was a problem hiding this comment.
postgres.enabled and redis.enabled (line 385) are mutable: true with no editDisabled.
Flipping either off on a live release deletes the StatefulSet and its Service; Superset is then pointed at a host that no longer exists, and the metadata DB is orphaned. These are install-time decisions, same as diskSize right below — which correctly carries editDisabled: true.
Outline and wordpress do not expose enabled at all, so superset is the odd one out here.
| # datastore name contains an underscore. | ||
| - name: service | ||
| version: 0.0.17 | ||
| version: 0.0.32 |
There was a problem hiding this comment.
Consequence of this bump worth calling out: SECRET_KEY and UTILS_SECRET now land in a plaintext ConfigMap.
Rendered at defaults on 0.0.32:
# Source: outline/charts/service/templates/env-configmap.yaml
kind: ConfigMap
metadata:
name: outline-app-env-configmap
data:
SECRET_KEY: "106ff5ec40e340972540fefd770dad24db868deb68bec9d2556365f2ff66ed99"
UTILS_SECRET: "45aec6e6b7340f3c8e5b34d533f9d63fd72a2830ee35d1345cb144d484205ba2"The schema describes both as values the operator must generate with openssl rand -hex 32. ConfigMaps are routinely readable by roles that cannot read Secrets, and they appear plainly in kubectl get cm -o yaml and GitOps diffs.
Worth checking whether 0.0.32's appSecrets path can carry these two instead. If it cannot, this deserves an explicit line in the Chart.yaml note, since the bump is a deliberate choice made in this PR.
|
Thanks for the approval and for re-deriving everything independently — including catching that the Both new notes are addressed in (A) 🟡
|
| approach | docs/index.yaml diff |
|---|---|
| full regen (what CONTRIBUTING.md documents) | +249 / −175 |
--merge over only the new packages |
+75 / −1 |
One wrinkle worth recording for the next person: --merge alone isn't enough. Running it with docs/ as the indexed directory still re-derives all 178 packages and reproduces the full-regen churn. The small diff needs the indexed directory to contain only the new packages, merged over the existing index:
helm package ./charts/<c> --version "v<X>" -d <tmp>
cd <tmp> && helm repo index . --url https://helm.zop.dev --merge <existing index.yaml>
Existing entries are now preserved byte-for-byte instead of re-derived. Re-verified: 174 → 177 pairs, nothing dropped, no digest or URL change on any pre-existing entry, all 29 charts intact, and each new digest matches its file's sha256. The single removed line is the generated: timestamp.
Updating CONTRIBUTING.md to document this is repo-level and out of scope for this PR, but it seems worth doing — happy to raise it separately.
Full re-verification
Everything re-run on a clean cluster after these changes — 132 checks, 0 failures:
| chart | install + upgrades | readbacks |
|---|---|---|
| superset | install + 6 upgrades | 58/58 |
| outline | install + 3 upgrades | 38/38 |
| wordpress | install + 3 upgrades | 36/36 |
On the init-db hook Job: it did not recur in this run, which is consistent with transient but still isn't proof. Thanks for the detail about the dockerize -wait initContainer and the before-hook-creation policy — that's a better explanation of how a first-attempt failure happens than I had, and it stays on the watch list rather than being closed out.
Standing items unchanged
#6 scope call is yours. #7 strategy: Recreate, #8 PrometheusRule root cause, #9 Flower pip install — all still follow-ups, none fixable from an app chart in this PR's scope.
…a dip
Both from the approval review, both non-blocking.
* maxReplicas can be set below replicaCount, and both are mutable, so it is reachable from
the form. JSON Schema cannot express a cross-field constraint, so this is documented
rather than enforced - it fails loudly at apply rather than corrupting anything.
Confirmed against a live API server (server-side dry-run), and the description now quotes
the real error:
The HorizontalPodAutoscaler "sup-superset-hpa" is invalid: spec.maxReplicas:
Invalid value: 3: must be greater than or equal to `minReplicas`
* node/deployment.yaml records the one-time transient replica dip that existing releases
see on the first upgrade after spec.replicas is omitted: helm's three-way merge patches
the field out, the API server applies the Deployment default of 1, and the HPA restores
it on its next reconcile. Self-correcting, first upgrade only - it just should not
surprise whoever runs it.
docs/index.yaml is now built with `helm repo index --merge` over ONLY the new packages,
also suggested in review. Same result, far less churn: +75/-1 instead of +249/-175, and the
one removed line is the `generated:` timestamp. Existing entries are preserved byte-for-byte
rather than re-derived. Note --merge alone is not enough: run over docs/ it still re-derives
all 178 packages: the indexed directory has to contain only the new ones.
Re-verified: 174 -> 177 pairs, nothing dropped, no digest or URL change on any pre-existing
entry, all 29 charts intact, and each new digest matches its file's sha256.
16bbac1 to
7c16edd
Compare
Description
These three app charts shipped a
values.schema.jsonin which every scalar was locked asenum: [<current value>]. A single-value enum makes that value the only legal one, soany edit is rejected at install/upgrade time:
Superset was the extreme case — 0 of 25 leaves editable and no
mutableanywhere, so itspublished form was empty. Same class of problem, and same fix, as jupyterhub in #299.
Every exposed leaf now carries
mutable: true, a plain-Englishdescription, and adefaultread programmatically out ofvalues.yamland asserted equal — never hand-copied.Types are still enforced; no genuine multi-choice enum existed, so nothing loosens that
shouldn't.
Unlocking the forms then exposed a set of fields that were actively broken — several
would have bricked a release the first time someone touched them from the UI. Details are in
the individual commits; the ones worth calling out:
service.nameand the release name bothproduced a PrometheusRule called
wordpress, and helm refuses two objects of one kind+name.It also had no volume, so every restart discarded uploads, themes and plugins.
superset_config.pyis Python, but booleansrendered as Go's
true→NameError: name 'true' is not defined. ItsSECRET_KEYwas alsoregenerated on every render, logging out every user and making saved database connections
undecryptable. Enabling flower could only ever fail, and after fixing its
sh -ccommand asecond defect surfaced: all three probes hit
/api/workers, which returns 401 without auth,so the pod ran fine but was never Ready.
URL, which it validates on startup), lost every upload onrestart (
emptyDirunderFILE_STORAGE=local), and CrashLooped permanently if youenabled
FORCE_HTTPS, because the kubelet follows a probe redirect to :443 where nothinglistens.
postgres 0.0.11 → 0.0.12is a fix in its own right, not just a version bump:0.0.11pulls
docker.io/bitnami/postgresqland0.0.12pullsdocker.io/bitnamilegacy/postgresql.Those Bitnami tags were withdrawn from Docker Hub, so
0.0.11is heading forImagePullBackOff. (Raised in review.)Superset's web tier is now scalable (
supersetNode.replicaCount); it was hardcoded toreplicas: 1, so only the Celery worker could be adjusted. Celery beat is deliberately leftat 1 — two schedulers would double every scheduled report and alert.
Type of Change
Testing
Every chart was installed and upgraded on a local minikube cluster (helm v3.16.4, the
version CI's
ubuntu-latestships).rc=0was not accepted as a pass: for each exposedfield the value was changed and then read back out of the live cluster — pod env via
printenv, Deployment spec, ConfigMaps/Secrets, PVC requests,status.readyReplicas.Full re-run after every review fix: 132 checks, 0 failures. The
init-dbhook Job attemptthat errored once in an earlier run did not recur here.
Each ran ≥2 successive upgrades on purpose: a
lookupthat regenerates a Secret onlymisbehaves on the second apply.
SECRET_KEYand datastoreDB_USER/passwords were verifiedstable across two upgrades, and each chart did a real app-version image bump that rolled out
in the pod. WordPress additionally opens a live
mysqliconnection from inside its pod, andsuperset's admin user is confirmed with
superset fab list-usersagainst its own metadata DB.Upgrade note for existing superset releases
The web Deployment now omits
spec.replicaswhen the HPA is enabled, so only onecontroller owns the count. Existing releases take a one-time transient replica dip on the
first upgrade after this lands: their live Deployment has
spec.replicasset, helm'sthree-way merge patches the field out, and the API server applies the Deployment default of 1
until the HPA's next reconcile pulls it back up. Self-correcting, first upgrade only — noting
it so it isn't a surprise. (Raised in review.)
Related:
supersetNode.autoscaling.maxReplicasmust be at leastsupersetNode.replicaCount.JSON Schema can't express a cross-field constraint, so it's documented on the field rather
than enforced; a lower value is rejected by the API server at apply time with
spec.maxReplicas: Invalid value: must be greater than or equal to minReplicas.Known limitation, not introduced here
None of these charts pass the installer's exact command
(
helm upgrade --install --atomic --timeout 10m --wait): it times out at ~601s with everypod healthy and
--atomicrolls back a working release.charts/postgres(
init-script-config-map.yaml) andcharts/mysql(database-pod.yaml) render a barekind: Pod, not aJob; helm v3's--waitrequiresPodReady=True, and a finished Pod sitsat
Succeeded / Ready=False / PodCompletedforever.jupyterhubis the only app chart thatpasses, and it is the only one with no datastore subchart. Those charts are shared by ~29
charts so they are out of scope here, but the fix belongs there (a
Job, ideally a hook Job —note a Job's spec is immutable, so it only survives upgrades because these templates
lookupand reuse the existing credentials). Verification here therefore installs without
--waitandgates on its own readiness check.
Checklist
helm lintpasses without errors on all three chartsvalues.yamlprogrammaticallydocs/andindex.yamlregenerated per CONTRIBUTING.md steps 4-6(verified: 174 → 177 entries, nothing dropped, no existing digest changed)