Replaces the dependency on upstream's valkey chart with this repo's own, resolved from the published index: version: "1.0.0+up9.1.2.alpine" repository: "https://git.jk-dev.de/api/packages/jlab-cluster/helm" The condition is "valkeySubchart.enabled", NOT "valkey.enabled", and that is forced rather than stylistic. Everything under "valkey:" is handed to the subchart as its values and validated against ITS schema, and our valkey chart ships additionalProperties: false with no "enabled" property - so "valkey.enabled" is rejected by the subchart: valkey: - at '': additional properties 'enabled' not allowed Measured. And because Helm skips a DISABLED subchart's schema validation, that error would have surfaced only for whoever first set it to true: every render, lint and install-test in CI would have stayed green. Adding "enabled" to valkey/values.schema.json was rejected as the repair - that chart does not read the key, and it would have needed its own PR and a publish first. The host helper was also wrong for our chart and is fixed: our Service is "<release>-valkey-service", not upstream's bare "<release>-valkey". The values block is rewritten for our chart's schema, with the pair chosen rather than inherited: CACHE (persistence off + allkeys-lru), because everything Stalwart keeps in the in-memory store - rate limiters, auth tokens, sessions, auto-ban entries, coordination pub/sub - is reconstructible, and the node-id lease lives in the data store. The cost is named where it is set: with allkeys-lru the rate limiters and the auto-ban FAIL OPEN under memory pressure, so the answer is to size memory, not to switch to noeviction (which would make writes error and take out authentication instead). secrets.authSecretRef wants an item whose single field "auth-conf" holds the complete "requirepass <pw>" line, not a bare password. Chart.yaml's comments are corrected where they became false: nothing is "consumed as it comes" any more and there is no shortfall of someone else's to document. The Renovate reasoning stays and is now backed by measurement. New trap, in Chart.yaml and in three places in the wiki: NEVER change valkey/ and a chart depending on it in the same PR. The dependency resolves from the published index, so a combined PR lints, packages, renders AND install-tests against the OLD subchart - green on an artifact that is not the one in the diff. Two consecutive PRs. Two things measured, not assumed, because either could have broken the update path with no symptom but "no updates ever": - helm dependency update AND build resolve the exact build-metadata version from the URL alone with an EMPTY HELM_REPOSITORY_CONFIG - a fresh runner's state - so the anonymous index needs no credential and ci/helm-dependencies.sh is untouched; - Renovate 44.132.2 dry run with the dependency pinned low: manager helmv3 matched the file, took registryUrls from Chart.yaml and offered newVersion "1.0.0+up9.1.2.alpine", versioning "helm". The self-hosted index and the "+up..." suffix are both handled. README: the claim "no registry address appears in this repository" is narrowed to what still holds - the publish target and its credentials come from 1Password and are not in the repo. The read path is now in Chart.yaml by necessity, carries no credential, and the host was always in CLAUDE.md. Done here rather than later so the paperless PR finds it already corrected. Re-verified after the change: helm lint --strict (0 failed), helm package (the subchart is embedded in the .tgz), helm template with the subchart OFF and ON (ON was previously impossible), against CI test-values, the rendered config.json, ci/run-quantity-render-test.sh (0 failed), NOTES.txt through the scratch-ConfigMap trick, and the fail paths including the reworded abort. The rendered subchart Service name matches what the helper emits, the cache pairing really renders (maxmemory-policy allkeys-lru, appendonly no, save "", emptyDir) and no PVC appears. Part of #65
434 lines
20 KiB
Smarty
434 lines
20 KiB
Smarty
{{/*
|
|
Resource / replica / null-safety helpers.
|
|
|
|
These are copied from template-chart with "stalwart." as the define prefix,
|
|
as in every other chart here: sharing them would mean a library chart, and a
|
|
dependency is admissible but expensive (README, guideline 14) - this chart
|
|
already carries one and does not need a second. template-chart holds the
|
|
reference implementation; the resource-quantity render test in CI exists
|
|
because a copy can carry an old bug into a diff nobody reads.
|
|
*/}}
|
|
{{- define "stalwart.resources" -}}
|
|
{{- $resources := . | default dict -}}
|
|
{{- $factor := 3.0 -}}
|
|
{{- if not (kindIs "invalid" $resources.limitFactor) -}}
|
|
{{- $factor = float64 $resources.limitFactor -}}
|
|
{{- end -}}
|
|
{{- $requests := dict -}}
|
|
{{- range $key, $value := ($resources.requests | default dict) -}}
|
|
{{- if not (kindIs "invalid" $value) -}}
|
|
{{- $_ := set $requests (include "stalwart.resourceKey" $key) $value -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- $limits := dict -}}
|
|
{{- range $key, $value := ($resources.limits | default dict) -}}
|
|
{{- if not (kindIs "invalid" $value) -}}
|
|
{{- $_ := set $limits (include "stalwart.resourceKey" $key) $value -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- range $key, $request := $requests -}}
|
|
{{- if and (ne $key "cpu") (not (hasKey $limits $key)) -}}
|
|
{{- $_ := set $limits $key (include "stalwart.multiplyQuantity" (dict "quantity" $request "factor" $factor)) -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- $out := dict -}}
|
|
{{- with $requests }}{{- $_ := set $out "requests" . }}{{- end -}}
|
|
{{- with $limits }}{{- $_ := set $out "limits" . }}{{- end -}}
|
|
{{- toYaml $out -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
Normalize a resource name from values to its Kubernetes key.
|
|
*/}}
|
|
{{- define "stalwart.resourceKey" -}}
|
|
{{- if eq . "ephemeralStorage" -}}ephemeral-storage{{- else -}}{{ . }}{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
Multiply a Kubernetes quantity by a factor, keeping its suffix.
|
|
Input: dict "quantity" <string|number> "factor" <float64>.
|
|
*/}}
|
|
{{- define "stalwart.multiplyQuantity" -}}
|
|
{{- $quantity := include "stalwart.formatNumber" .quantity -}}
|
|
{{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}}
|
|
{{- if not $number -}}
|
|
{{- fail (printf "cannot parse resource quantity %q" $quantity) -}}
|
|
{{- end -}}
|
|
{{- $suffix := trimPrefix $number $quantity -}}
|
|
{{- if not (regexMatch "^(m|k|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$" $suffix) -}}
|
|
{{- fail (printf "unsupported resource quantity suffix %q in %q" $suffix $quantity) -}}
|
|
{{- end -}}
|
|
{{- $product := mulf (float64 $number) .factor -}}
|
|
{{- if eq $product (floor $product) -}}
|
|
{{- printf "%d%s" (int64 $product) $suffix -}}
|
|
{{- else -}}
|
|
{{- printf "%s%s" (include "stalwart.formatNumber" $product) $suffix -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
Render a number as a plain decimal string, never in exponential notation
|
|
(issue #157): the null-safe helpers pass values through fromYaml, which parses
|
|
every number as float64, and Go's "%v" switches to exponential notation from
|
|
about 1e6 upwards - not a valid Kubernetes quantity.
|
|
*/}}
|
|
{{- define "stalwart.formatNumber" -}}
|
|
{{- if kindIs "float64" . -}}
|
|
{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}}
|
|
{{- else -}}
|
|
{{- printf "%v" . -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The replica count of one node group.
|
|
Input: dict "scaleDown" <bool|nil> "replicas" <int|nil>.
|
|
The explicit kindIs "invalid" check is what keeps an explicit 0 - which this
|
|
chart relies on, since the "mx" group ships with replicas: 0.
|
|
*/}}
|
|
{{- define "stalwart.replicas" -}}
|
|
{{- if .scaleDown -}}
|
|
0
|
|
{{- else if not (kindIs "invalid" .replicas) -}}
|
|
{{- .replicas -}}
|
|
{{- else -}}
|
|
1
|
|
{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
Merge a user-supplied values sub-tree onto a dict of chart defaults so that a
|
|
null / empty override falls back to the defaults instead of erasing them
|
|
(issues #61 and #99). Input: dict "defaults" <dict> "given" <dict|nil> "path"
|
|
<string>. Output: YAML, consume it with fromYaml.
|
|
*/}}
|
|
{{- define "stalwart.defaultedDict" -}}
|
|
{{- $defaults := .defaults | default dict -}}
|
|
{{- $given := .given -}}
|
|
{{- $path := .path | default "value" -}}
|
|
{{- if kindIs "invalid" $given -}}
|
|
{{- $given = dict -}}
|
|
{{- end -}}
|
|
{{- if not (kindIs "map" $given) -}}
|
|
{{- fail (printf "%s must be a mapping or null, got %s (%v)" $path (kindOf $given) $given) -}}
|
|
{{- end -}}
|
|
{{- $out := deepCopy $defaults -}}
|
|
{{- range $key, $value := $given -}}
|
|
{{- if not (kindIs "invalid" $value) -}}
|
|
{{- $default := index $defaults $key -}}
|
|
{{- if and (kindIs "map" $value) (kindIs "map" $default) -}}
|
|
{{- $_ := set $out $key (fromYaml (include "stalwart.defaultedDict" (dict "defaults" $default "given" $value "path" (printf "%s.%s" $path $key)))) -}}
|
|
{{- else if kindIs "map" $default -}}
|
|
{{- fail (printf "%s.%s must be a mapping or null, got %s (%v)" $path $key (kindOf $value) $value) -}}
|
|
{{- else -}}
|
|
{{- $_ := set $out $key $value -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- toYaml $out -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
Pick one sub-block out of a values block that may itself be null.
|
|
Input: dict "block" <dict|nil> "key" <string> "path" <string>.
|
|
*/}}
|
|
{{- define "stalwart.subBlock" -}}
|
|
{{- $block := .block -}}
|
|
{{- if kindIs "invalid" $block -}}
|
|
{{- $block = dict -}}
|
|
{{- end -}}
|
|
{{- if not (kindIs "map" $block) -}}
|
|
{{- fail (printf "%s must be a mapping or null, got %s (%v)" .path (kindOf $block) $block) -}}
|
|
{{- end -}}
|
|
{{- toYaml (dict "value" (index $block .key)) -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The effective security contexts, probes and resources (issue #99).
|
|
|
|
The defaults below MUST stay in sync with values.yaml, which stays the
|
|
documented, user-facing place for them; this copy is only the fallback for the
|
|
case where an override has erased the block.
|
|
|
|
Two rules these fallbacks exist to honour (README, guideline 12):
|
|
- reproduce THIS chart's defaults, not the repo standard. uid/gid 2000 is the
|
|
image's own user, and there is deliberately NO fsGroup here because this
|
|
chart mounts no PVC.
|
|
- carry the probe's CONTEXT, not just its timings. The paths and the port
|
|
name are part of these defaults; without them the probes would hit "/" on
|
|
a port that may not exist, and liveness would stop being independent of
|
|
the data store.
|
|
*/}}
|
|
{{- define "stalwart.podSecurityContext" -}}
|
|
{{- $given := (fromYaml (include "stalwart.subBlock" (dict "block" . "key" "pod" "path" "securityContext"))).value -}}
|
|
{{- $defaults := dict
|
|
"runAsNonRoot" true
|
|
"runAsUser" 2000
|
|
"runAsGroup" 2000
|
|
"seccompProfile" (dict "type" "RuntimeDefault") -}}
|
|
{{- include "stalwart.defaultedDict" (dict "defaults" $defaults "given" $given "path" "securityContext.pod") -}}
|
|
{{- end -}}
|
|
|
|
{{- define "stalwart.containerSecurityContext" -}}
|
|
{{- $given := (fromYaml (include "stalwart.subBlock" (dict "block" . "key" "container" "path" "securityContext"))).value -}}
|
|
{{/*
|
|
NET_BIND_SERVICE is part of the FALLBACK, not only of values.yaml, and that is
|
|
the whole point of rule "reproduce THIS chart's defaults, not the repo
|
|
standard". The image carries setcap cap_net_bind_service=+ep on its binary, so
|
|
with an empty bounding set the kernel fails the execve itself - an erased
|
|
securityContext block that fell back to the repo-standard "add: []" would stop
|
|
the chart from starting at all, with "operation not permitted" and nothing
|
|
pointing at this file.
|
|
*/}}
|
|
{{- $defaults := dict
|
|
"allowPrivilegeEscalation" false
|
|
"readOnlyRootFilesystem" true
|
|
"runAsNonRoot" true
|
|
"capabilities" (dict "drop" (list "ALL") "add" (list "NET_BIND_SERVICE")) -}}
|
|
{{- include "stalwart.defaultedDict" (dict "defaults" $defaults "given" $given "path" "securityContext.container") -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The probe fallbacks carry the calculated intervals, not just the paths. The
|
|
arithmetic is in values.yaml: Stalwart rate-limits anonymous /healthz requests
|
|
to 100 per 60s PER SOURCE IP, probes come from the node IP, so every pod on a
|
|
node shares one bucket. 20/20/10 costs 6 requests/min/pod in both phases
|
|
(the startup probe gates the other two, so they never run together) and leaves
|
|
room for ~16 pods per node. The obvious 10/10/5 would halve that to ~8.
|
|
Tightening these here without redoing that sum produces 429s, and a 429 on
|
|
liveness is a restart loop.
|
|
*/}}
|
|
{{- define "stalwart.livenessProbe" -}}
|
|
{{- $defaults := dict
|
|
"httpGet" (dict "path" "/healthz/live" "port" "http")
|
|
"periodSeconds" 20
|
|
"timeoutSeconds" 5
|
|
"failureThreshold" 3 -}}
|
|
{{- include "stalwart.defaultedDict" (dict "defaults" $defaults "given" . "path" "livenessProbe") -}}
|
|
{{- end -}}
|
|
|
|
{{- define "stalwart.readinessProbe" -}}
|
|
{{- $defaults := dict
|
|
"httpGet" (dict "path" "/healthz/ready" "port" "http")
|
|
"periodSeconds" 20
|
|
"timeoutSeconds" 5
|
|
"failureThreshold" 3 -}}
|
|
{{- include "stalwart.defaultedDict" (dict "defaults" $defaults "given" . "path" "readinessProbe") -}}
|
|
{{- end -}}
|
|
|
|
{{- define "stalwart.startupProbe" -}}
|
|
{{- $defaults := dict
|
|
"httpGet" (dict "path" "/healthz/ready" "port" "http")
|
|
"periodSeconds" 10
|
|
"timeoutSeconds" 5
|
|
"failureThreshold" 30 -}}
|
|
{{- include "stalwart.defaultedDict" (dict "defaults" $defaults "given" . "path" "startupProbe") -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
=============================================================================
|
|
Chart-specific helpers
|
|
=============================================================================
|
|
*/}}
|
|
|
|
{{/*
|
|
The common name prefix. One place, because it appears in every object name and
|
|
in the Service selectors.
|
|
*/}}
|
|
{{- define "stalwart.fullname" -}}
|
|
{{- printf "%s-%s" .Release.Name .Chart.Name -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The validated "ports" map. Rejects a non-mapping and an empty map, and checks
|
|
every entry for the one field the chart cannot do without.
|
|
|
|
Why the number is checked here rather than where it is used: an erased
|
|
ports.smtp.number used to render "port:" and "targetPort:" empty into the
|
|
Service and an empty "containerPort" into the StatefulSet - rejected by nothing
|
|
until the API server saw it (the satisfactory lesson, Gitea #16).
|
|
*/}}
|
|
{{- define "stalwart.ports" -}}
|
|
{{- $ports := .Values.ports -}}
|
|
{{- if kindIs "invalid" $ports -}}
|
|
{{- fail "ports must be a mapping of port name to { containerPort, nodePort, protocol, public }, and it cannot be erased: every Service, containerPort and NetworkPolicy rule in this chart is rendered from it" -}}
|
|
{{- end -}}
|
|
{{- if not (kindIs "map" $ports) -}}
|
|
{{- fail (printf "ports must be a mapping, got %s" (kindOf $ports)) -}}
|
|
{{- end -}}
|
|
{{- if eq (len $ports) 0 -}}
|
|
{{- fail "ports is empty - the chart would render workloads with no containerPort and no Service at all" -}}
|
|
{{- end -}}
|
|
{{- range $name, $spec := $ports -}}
|
|
{{- if not (kindIs "map" $spec) -}}
|
|
{{- fail (printf "ports.%s must be a mapping, got %s (%v)" $name (kindOf $spec) $spec) -}}
|
|
{{- end -}}
|
|
{{- if kindIs "invalid" $spec.containerPort -}}
|
|
{{- fail (printf "ports.%s.containerPort is required - a port without one renders an empty containerPort and an empty Service targetPort. Note containerPort and nodePort are two different numbers here and are not interchangeable: containerPort is what the application binds (and what the NetworkListener object in the database must say), nodePort is what the cluster publishes" $name) -}}
|
|
{{- end -}}
|
|
{{- if and $spec.public (kindIs "invalid" $spec.nodePort) -}}
|
|
{{- fail (printf "ports.%s is marked public but has no nodePort - a NodePort Service without a nodePort would be assigned a random one, which the HA proxy in front of the cluster does not know about" $name) -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- toYaml $ports -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The validated "nodes" map, with each group's exposedPorts checked against the
|
|
ports map. Input: the root context.
|
|
|
|
The exposedPorts check is the one that earns its keep: a typo there would
|
|
silently drop a port from a group's containerPorts and from its NetworkPolicy,
|
|
leaving a Service selecting pods that never bound the port.
|
|
*/}}
|
|
{{- define "stalwart.nodes" -}}
|
|
{{- $ports := fromYaml (include "stalwart.ports" .) -}}
|
|
{{- $nodes := .Values.nodes -}}
|
|
{{- if kindIs "invalid" $nodes -}}
|
|
{{- fail "nodes must be a mapping of cluster-role name to node group, and it cannot be erased: it is what every StatefulSet in this chart is rendered from" -}}
|
|
{{- end -}}
|
|
{{- if not (kindIs "map" $nodes) -}}
|
|
{{- fail (printf "nodes must be a mapping of cluster-role name to node group, got %s - note it is a MAP and not a list, so that an override never depends on a position" (kindOf $nodes)) -}}
|
|
{{- end -}}
|
|
{{- if eq (len $nodes) 0 -}}
|
|
{{- fail "nodes is empty - the release would contain Services and an Ingress but no workload" -}}
|
|
{{- end -}}
|
|
{{- range $group, $spec := $nodes -}}
|
|
{{- if not (kindIs "map" $spec) -}}
|
|
{{- fail (printf "nodes.%s must be a mapping, got %s (%v)" $group (kindOf $spec) $spec) -}}
|
|
{{- end -}}
|
|
{{- if not (regexMatch "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" $group) -}}
|
|
{{- fail (printf "nodes.%s is not usable as a Kubernetes name segment: a group key becomes part of every object name and of a label value, so it must be lowercase alphanumeric with dashes. It is ALSO the cluster-role name passed in STALWART_ROLE, so renaming it here without renaming the ClusterRole object in the database turns the group into a full node" $group) -}}
|
|
{{- end -}}
|
|
{{- $exposed := $spec.exposedPorts -}}
|
|
{{- if kindIs "invalid" $exposed -}}
|
|
{{- fail (printf "nodes.%s.exposedPorts is required - a group with no ports is unreachable, and because this list is also the NetworkPolicy's ingress rule, an erased one is indistinguishable from a deliberate lockout" $group) -}}
|
|
{{- end -}}
|
|
{{- if not (kindIs "slice" $exposed) -}}
|
|
{{- fail (printf "nodes.%s.exposedPorts must be a list of port names, got %s (%v)" $group (kindOf $exposed) $exposed) -}}
|
|
{{- end -}}
|
|
{{- range $exposed -}}
|
|
{{- if not (hasKey $ports .) -}}
|
|
{{- fail (printf "nodes.%s.exposedPorts names %q, which is not a key of ports: - known ports are %s. Left unchecked this would drop the port from the group's containerPorts and from its NetworkPolicy while the Service still selected its pods" $group . (keys $ports | sortAlpha | join ", ")) -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- if not (has "http" $exposed) -}}
|
|
{{- fail (printf "nodes.%s.exposedPorts does not include \"http\", but all three probes target that port - the group would never become Ready. Every group needs it, including an SMTP-only one; it is simply not published there" $group) -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- toYaml $nodes -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The effective resources of one node group.
|
|
Input: dict "group" <name> "given" <the group's resources block|nil>
|
|
"limitFactor" <the chart-global .Values.limitFactor|nil>.
|
|
|
|
The per-group defaults are reproduced here, not a single generic set: "mx" is
|
|
deliberately lighter than "full", and a fallback that handed an erased
|
|
mx.resources the heavier figures would quietly change what the chart had been
|
|
rendering. A group the chart does not ship gets the "full" figures, which is
|
|
the safe direction - too much accounting rather than too little.
|
|
*/}}
|
|
{{- define "stalwart.effectiveResources" -}}
|
|
{{- $factor := 3 -}}
|
|
{{- if not (kindIs "invalid" .limitFactor) -}}
|
|
{{- $factor = .limitFactor -}}
|
|
{{- end -}}
|
|
{{- $requests := dict "cpu" "200m" "memory" "512Mi" "ephemeral-storage" "64Mi" -}}
|
|
{{- if eq .group "mx" -}}
|
|
{{- $requests = dict "cpu" "100m" "memory" "256Mi" "ephemeral-storage" "64Mi" -}}
|
|
{{- end -}}
|
|
{{- $defaults := dict "limitFactor" $factor "requests" $requests -}}
|
|
{{- $merged := fromYaml (include "stalwart.defaultedDict" (dict "defaults" $defaults "given" .given "path" (printf "nodes.%s.resources" .group))) -}}
|
|
{{- include "stalwart.resources" $merged -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The label a pod carries for one port it is reachable on. The per-port Services
|
|
select on these, which is what lets one NodePort Service serve several node
|
|
groups - NodePorts are cluster-globally unique, so "full" and "mx" can only
|
|
accept SMTP behind the SAME Service object.
|
|
*/}}
|
|
{{- define "stalwart.portLabel" -}}
|
|
{{- printf "stalwart.jk-dev.de/port-%s" . -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The address of the in-memory store, and the render abort that goes with it.
|
|
Input: the root context. Returns the host, or "" when none is needed.
|
|
|
|
The abort is the point. From v0.16 the cluster coordinator's Default variant
|
|
"reuses the Redis connection already configured on the in-memory store", so
|
|
more than one node without an in-memory store is a cluster with no
|
|
coordination path - and the symptom is not a failed install but a cluster that
|
|
comes up and disagrees with itself. Note what this value does NOT do: it does
|
|
not configure Stalwart. The in-memory store is an object in the database; this
|
|
is the address to type into the WebUI, and the port for the egress rule.
|
|
*/}}
|
|
{{- define "stalwart.valkeyHost" -}}
|
|
{{- $given := "" -}}
|
|
{{- if not (kindIs "invalid" .Values.externalServices) -}}
|
|
{{- $valkey := .Values.externalServices.valkey -}}
|
|
{{- if and (not (kindIs "invalid" $valkey)) (not (kindIs "invalid" $valkey.host)) -}}
|
|
{{- $given = $valkey.host -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- $subchartOn := false -}}
|
|
{{- if and (not (kindIs "invalid" .Values.valkeySubchart)) .Values.valkeySubchart.enabled -}}
|
|
{{- $subchartOn = true -}}
|
|
{{- end -}}
|
|
{{- if $given -}}
|
|
{{- $given -}}
|
|
{{- else if $subchartOn -}}
|
|
{{/* The Service our own valkey chart renders:
|
|
{{ .Release.Name }}-{{ .Chart.Name }}-service
|
|
and inside a subchart .Chart.Name is the SUBCHART's name, so this is
|
|
"<release>-valkey-service" with no special case. Reproduced rather than
|
|
included, because a parent cannot call a subchart's define with the
|
|
subchart's context.
|
|
|
|
Note the "-service" suffix: this repo's naming scheme ends every Service
|
|
that way, which is NOT what upstream's valkey chart does (it would be a
|
|
bare "<release>-valkey"). An earlier draft of this chart depended on
|
|
upstream's and carried their name; getting this wrong points the operator
|
|
at an address that does not resolve, and nothing in the chart would say
|
|
so. */}}
|
|
{{- printf "%s-valkey-service" .Release.Name | trunc 63 | trimSuffix "-" -}}
|
|
{{- else -}}
|
|
{{- $total := 0 -}}
|
|
{{- range $group, $spec := fromYaml (include "stalwart.nodes" .) -}}
|
|
{{- $total = add $total (int (include "stalwart.replicas" (dict "scaleDown" $.Values.scaleDown "replicas" $spec.replicas))) -}}
|
|
{{- end -}}
|
|
{{- if gt $total 1 -}}
|
|
{{- fail (printf "this release runs %d Stalwart pods, which is a cluster, but no in-memory store has an address: externalServices.valkey.host is empty and valkeySubchart.enabled is false. Stalwart's cluster coordinator rides on the in-memory store connection, so a multi-node install has no coordination path at all - and that failure does not show up as a failed install, it shows up as nodes that do not agree with each other. Set externalServices.valkey.host to an existing instance, or set valkeySubchart.enabled: true to deploy the subchart" $total) -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/*
|
|
The environment-variable names that config.json references and the StatefulSet
|
|
sets. They are helpers rather than literals in two files because a mismatch
|
|
between the two is not a render error: the server starts, resolves authSecret,
|
|
finds no such variable, and fails to open the data store with
|
|
"Environment variable '<name>' not found". One definition, used by both.
|
|
|
|
Note the upstream resolver treats an EMPTY value as not found, so a 1Password
|
|
item whose field exists but is blank fails exactly like a missing field.
|
|
*/}}
|
|
{{- define "stalwart.env.dbPassword" -}}STALWART_DB_PASSWORD{{- end -}}
|
|
{{- define "stalwart.env.s3AccessKey" -}}STALWART_S3_ACCESS_KEY{{- end -}}
|
|
{{- define "stalwart.env.s3SecretKey" -}}STALWART_S3_SECRET_KEY{{- end -}}
|
|
|
|
{{/*
|
|
The node group the Ingress routes to, validated. Input: the root context.
|
|
*/}}
|
|
{{- define "stalwart.ingressNodeGroup" -}}
|
|
{{- $nodes := fromYaml (include "stalwart.nodes" .) -}}
|
|
{{- $group := required "ingress.nodeGroup must name the node group the Ingress routes to" .Values.ingress.nodeGroup -}}
|
|
{{- if not (hasKey $nodes $group) -}}
|
|
{{- fail (printf "ingress.nodeGroup is %q, which is not a key of nodes: - known groups are %s" $group (keys $nodes | sortAlpha | join ", ")) -}}
|
|
{{- end -}}
|
|
{{- $group -}}
|
|
{{- end -}}
|