Skip to content

Commit faa2571

Browse files
fix(ontoserver): reject scaledReadOnly overrides from the config passthroughs
The chart sets ontoserver.internal.deployment.scaledReadOnly on scaled deployments, but ontoserver.config and ontoserver.secretConfig render after it in the container's env list. Kubernetes does not reject duplicate env names and the later entry is the one that applies, so a passthrough copy silently overrode the chart and re-enabled scaled read-write while still satisfying the isReadOnly guard. Removing allowScaledReadWrite in 0.5.1 closed one route to that topology and left this one open. Validation now rejects the key in either passthrough. Matching is normalised — lowercased with _ and - folded to . — so the dotted name, the older all-lowercase spelling, and the uppercase underscored environment form are all caught, since Spring resolves the property from any of them. existingSecretConfig and externalSecret are deliberately not checked: their contents are opaque at render time, and they arrive via envFrom, which Kubernetes applies before the container's own env, so the chart's entry still wins.
1 parent 888f51c commit faa2571

3 files changed

Lines changed: 80 additions & 1 deletion

File tree

charts/ontoserver/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ The chart supports four deployment combinations controlled by `ontoserver.deploy
8383
> on any scaled deployment**: setting `ontoserver.deployment.scaled` makes the server reject writes
8484
> regardless of what else is configured, so a scaled read-write server is not a topology that exists.
8585
> The chart therefore refuses to render `type: scaled` with `isReadOnly: false` rather than deploy
86-
> something that cannot work.
86+
> something that cannot work. It also rejects an `ontoserver.config` or `ontoserver.secretConfig`
87+
> entry for `ontoserver.internal.deployment.scaledReadOnly`: the passthroughs render after the
88+
> chart's own env entry and a later duplicate wins at runtime, so such a copy would quietly
89+
> contradict the chart. All spellings Spring accepts are matched, including the uppercase
90+
> underscored environment form.
8791
>
8892
> Load content with a **single-instance read-write** deployment, then publish it to a syndication
8993
> server and serve it from a **scaled read-only** cluster. See

charts/ontoserver/templates/validate-values.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,28 @@
124124
{{- fail "Scaled deployments must set ontoserver.deployment.isReadOnly: true. Ontoserver forces read-only on a scaled deployment, and each replica has its own Lucene index, so content written through the round-robin Service is only indexed on the replica that served the write and $expand/$validate-code fail on the others. Load content with a single-instance read-write deployment, then serve it scaled and read-only." }}
125125
{{- end }}
126126

127+
{{- /* The chart sets ontoserver.internal.deployment.scaledReadOnly itself on scaled deployments.
128+
The config passthroughs render after that entry in the container's env list, and a later
129+
duplicate wins, so a passthrough copy would silently override the chart and re-enable the
130+
unsupported scaled read-write topology while still passing the isReadOnly guard above.
131+
Reject it instead of rendering a manifest that contradicts itself.
132+
133+
Matching is normalised because Spring resolves a property from the environment in several
134+
forms: the dotted name, an older all-lowercase spelling of it, and the uppercase underscored
135+
form. Lowercasing and folding _ and - to . collapses all of them to one comparison.
136+
137+
Not checked, because it cannot be: existingSecretConfig and externalSecret contents are
138+
opaque at render time. They arrive via envFrom, which Kubernetes applies *before* the
139+
container's own env, so the chart's entry still wins there. */ -}}
140+
{{- $reserved := "ontoserver.internal.deployment.scaledreadonly" }}
141+
{{- range $section, $entries := dict "ontoserver.config" (.Values.ontoserver.config | default dict) "ontoserver.secretConfig" (.Values.ontoserver.secretConfig | default dict) }}
142+
{{- range $key, $value := $entries }}
143+
{{- if eq ($key | lower | replace "_" "." | replace "-" ".") $reserved }}
144+
{{- fail (printf "%s must not set %s. Scaled read-write is not supported: Ontoserver forces read-only on a scaled deployment and the chart sets this itself, so a passthrough copy would only produce a manifest whose env list contradicts itself. Remove the entry. To serve content read-write, use a single-instance deployment." $section $key) }}
145+
{{- end }}
146+
{{- end }}
147+
{{- end }}
148+
127149
{{- if and (eq .Values.ontoserver.deployment.kind "StatefulSet") .Values.ontoserver.deployment.persistence.files.existingVolume.enabled }}
128150
{{- fail "StatefulSet deployments do not support ontoserver.deployment.persistence.files.existingVolume. Each replica needs its own volume; use dynamic provisioning via storageClass or a Deployment for single-instance prebound volumes." }}
129151
{{- end }}

charts/ontoserver/tests/validate_values_test.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,59 @@ tests:
204204
- failedTemplate:
205205
errorMessage: "Scaled deployments must set ontoserver.deployment.isReadOnly: true. Ontoserver forces read-only on a scaled deployment, and each replica has its own Lucene index, so content written through the round-robin Service is only indexed on the replica that served the write and $expand/$validate-code fail on the others. Load content with a single-instance read-write deployment, then serve it scaled and read-only."
206206

207+
# The chart sets scaledReadOnly itself, and the config passthroughs render after it, so a
208+
# duplicate would win at runtime and re-enable scaled read-write while still passing the
209+
# isReadOnly guard. Each accepted spelling must be rejected.
210+
- it: rejects a scaledReadOnly override in ontoserver.config
211+
values:
212+
- fixtures/scaled-values.yaml
213+
set:
214+
ontoserver.config:
215+
ontoserver.internal.deployment.scaledReadOnly: "false"
216+
asserts:
217+
- failedTemplate:
218+
errorPattern: "ontoserver\\.config must not set .*Scaled read-write is not supported"
219+
220+
- it: rejects the older lowercase scaledReadonly spelling
221+
values:
222+
- fixtures/scaled-values.yaml
223+
set:
224+
ontoserver.config:
225+
ontoserver.internal.deployment.scaledReadonly: "false"
226+
asserts:
227+
- failedTemplate:
228+
errorPattern: "must not set .*Scaled read-write is not supported"
229+
230+
- it: rejects the uppercase underscored environment form
231+
values:
232+
- fixtures/scaled-values.yaml
233+
set:
234+
ontoserver.config:
235+
ONTOSERVER_INTERNAL_DEPLOYMENT_SCALEDREADONLY: "false"
236+
asserts:
237+
- failedTemplate:
238+
errorPattern: "must not set ONTOSERVER_INTERNAL_DEPLOYMENT_SCALEDREADONLY"
239+
240+
- it: rejects a scaledReadOnly override in ontoserver.secretConfig
241+
values:
242+
- fixtures/scaled-values.yaml
243+
set:
244+
ontoserver.secretConfig:
245+
ontoserver.internal.deployment.scaledReadOnly: "false"
246+
asserts:
247+
- failedTemplate:
248+
errorPattern: "ontoserver\\.secretConfig must not set"
249+
250+
# The guard must not catch unrelated passthrough entries.
251+
- it: allows other ontoserver.config entries on a scaled deployment
252+
values:
253+
- fixtures/scaled-values.yaml
254+
set:
255+
ontoserver.config:
256+
ontoserver.fhir.closureTable.max: "500"
257+
asserts:
258+
- notFailedTemplate: {}
259+
207260
# A single-instance deployment is the supported way to load content, so read-write there
208261
# must stay legal — the guard has to key on `type`, not on isReadOnly alone.
209262
- it: allows a single read-write deployment

0 commit comments

Comments
 (0)