Files
helm-charts/freesailarr/templates/_helpers.tpl
T
jklein 39e9613ee3 freesailarr: one volume entry per media claim (2.0.2+up1.0.0)
media.downloads, media.movies and media.tv rendered three fixed volume
entries, one per category. Two categories on the same existingClaim
therefore produced two pod volumes with an identical claimName, and such a
pod never starts: it stays in Init:0/1 without a single kubelet event
(measured on RKE2/Longhorn, RWO and RWX alike).

The volume list is now deduplicated by claim name. A new mediaVolumeName
helper names the entry after the first ENABLED category in the fixed order
downloads, movies, tv that carries the claim; the other categories mount
that same entry and differ only in their subPath. The restriction to
enabled categories matters because the three entries render under
different conditions - a canonical owner whose entry is not rendered would
be a dangling reference.

With three distinct claims the chart renders byte-identically to 2.0.1,
hence a patch bump.

Also corrected in the values documentation, after measuring instead of
assuming: the kubelet creates missing subPath directories itself, but they
inherit the claim root's mode, and fsGroup cannot be relied on to fix that
- Longhorn's fsGroupPolicy (ReadWriteOnceWithFSType) skips fsGroup on RWX
claims entirely. What has to hold is that the claim root is writable for
PUID/PGID. No chown or init container was added.
2026-08-24 21:01:53 +02:00

805 lines
24 KiB
Smarty

{{/*
Render the body of a container "resources:" block from a values dict of the form
resources:
limitFactor: 3 # optional, default 3
requests: { cpu, memory, ephemeral-storage / ephemeralStorage, ... }
limits: { ... } # optional, every entry optional
Rules (variant B, see issue #19):
- requests are rendered as given (camelCase "ephemeralStorage" is normalized
to the Kubernetes key "ephemeral-storage").
- an explicitly set limit always wins.
- a missing memory / ephemeral-storage limit is computed as
limitFactor x the corresponding request.
- a missing cpu limit is NOT set at all (the kubelet then treats CPU as
unlimited; CPU is compressible, so this cannot kill the workload).
Usage:
resources:
{{- include "freesailarr.resources" .Values.<app>.resources | nindent 12 }}
NOTE: charts in this repo deliberately have no dependencies, so this helper is
duplicated into every chart's templates/_helpers.tpl with the chart's name as
define prefix. The reference implementation lives in template-chart.
*/}}
{{- define "freesailarr.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 "freesailarr.resourceKey" $key) $value -}}
{{- end -}}
{{- end -}}
{{- $limits := dict -}}
{{- range $key, $value := ($resources.limits | default dict) -}}
{{- if not (kindIs "invalid" $value) -}}
{{- $_ := set $limits (include "freesailarr.resourceKey" $key) $value -}}
{{- end -}}
{{- end -}}
{{- range $key, $request := $requests -}}
{{- if and (ne $key "cpu") (not (hasKey $limits $key)) -}}
{{- $_ := set $limits $key (include "freesailarr.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 "freesailarr.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>.
Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary
("512Mi", "1Gi", "10Ki") and milli/SI suffixes ("100m", "500M"). Integer
results are printed without a decimal point.
*/}}
{{- define "freesailarr.multiplyQuantity" -}}
{{- $quantity := printf "%v" .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 "%v%s" $product $suffix -}}
{{- end -}}
{{- end -}}
{{/*
Render the replica count for a Deployment/StatefulSet.
Input: dict
"scaleDown" the chart-global .Values.scaleDown flag (may be nil)
"replicas" the workload's replicas value (may be nil)
Rules (issue #25):
- scaleDown: true takes precedence and always yields 0.
- otherwise an explicitly set replicas value is used as given, including an
explicit 0 — Helm's "default" would swallow 0, hence the explicit
kindIs "invalid" nil check.
- a missing replicas value defaults to 1.
Usage:
replicas: {{ include "freesailarr.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.replicas) }}
NOTE: like the resources helper above, this is duplicated into every chart's
templates/_helpers.tpl with the chart's name as define prefix. The reference
implementation lives in template-chart.
*/}}
{{- define "freesailarr.replicas" -}}
{{- if .scaleDown -}}
0
{{- else if not (kindIs "invalid" .replicas) -}}
{{- .replicas -}}
{{- else -}}
1
{{- end -}}
{{- end -}}
{{/*
Render additive env entries (the repo-wide <app>.env convention: tpl-rendered,
supporting the value, secretKeyRef and configMapKeyRef forms). Shared as a
helper here because this chart has nine containers, each with its own env block.
Usage:
{{- include "freesailarr.env" (dict "root" $ "env" .Values.<app>.env) | nindent 12 }}
*/}}
{{- define "freesailarr.env" -}}
{{- $root := .root -}}
{{- range $value := .env }}
{{- if $value.value }}
- name: {{ $value.name }}
value: {{ tpl $value.value $root | quote }}
{{- else if $value.valueFrom }}
{{- if $value.valueFrom.secretKeyRef }}
- name: {{ $value.name }}
valueFrom:
secretKeyRef:
name: {{ tpl $value.valueFrom.secretKeyRef.name $root | quote }}
key: {{ tpl $value.valueFrom.secretKeyRef.key $root | quote }}
{{- else if $value.valueFrom.configMapKeyRef }}
- name: {{ $value.name }}
valueFrom:
configMapKeyRef:
name: {{ tpl $value.valueFrom.configMapKeyRef.name $root | quote }}
key: {{ tpl $value.valueFrom.configMapKeyRef.key $root | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end -}}
{{/*
The enabled app containers that listen on a port, as a JSON array of
{name, port} dicts (container port name == app name == service name suffix).
Consumed by the per-app services, the per-app ingresses and the firewall-ports
helper below:
{{- $apps := include "freesailarr.portApps" . | fromJsonArray }}
recyclarr is deliberately absent: in daemon mode it serves no port, so it gets
no container port, no service and no ingress. flaresolverr is present - it
listens on 8191 and therefore gets a service - but it has no ingress block in
values and is never exposed from outside the cluster.
*/}}
{{- define "freesailarr.portApps" -}}
{{- $apps := list -}}
{{- range $app := list
(dict "name" "qbittorrent" "port" 8080)
(dict "name" "prowlarr" "port" 9696)
(dict "name" "flaresolverr" "port" 8191)
(dict "name" "radarr" "port" 7878)
(dict "name" "sonarr" "port" 8989)
(dict "name" "seerr" "port" 5055)
(dict "name" "bazarr" "port" 6767) -}}
{{- if (index $.Values $app.name).enabled -}}
{{- $apps = append $apps $app -}}
{{- end -}}
{{- end -}}
{{- toJson $apps -}}
{{- end -}}
{{/*
The apps that render an Ingress: the enabled port apps whose values carry an
ingress block with enabled: true. Used by the ingress template.
{{- $apps := include "freesailarr.ingressApps" . | fromJsonArray }}
*/}}
{{- define "freesailarr.ingressApps" -}}
{{- $apps := list -}}
{{- range $app := include "freesailarr.portApps" . | fromJsonArray -}}
{{- $config := index $.Values $app.name -}}
{{- if and $config.ingress $config.ingress.enabled -}}
{{- $apps = append $apps $app -}}
{{- end -}}
{{- end -}}
{{- toJson $apps -}}
{{- end -}}
{{/*
Comma-separated FIREWALL_INPUT_PORTS for gluetun: the ports of the enabled app
containers (ingress traffic and the kubelet probes both arrive on the pod IP)
plus 9999 for gluetun's own health server.
Measured caveat: inside a pod network these rules never actually match. gluetun
detects the pod's own subnet and inserts
"-A INPUT -d <pod-subnet> -i eth0 -j ACCEPT" ahead of the per-port rules, and
every packet arriving at the pod IP matches it by destination - the packet
counters on the --dport rules stayed at 0 while the subnet rule absorbed the
traffic. The declaration is kept as an explicit statement of which ports are
meant to be reachable, and because that subnet detection is gluetun's, not a
guarantee of the chart's: a CNI that hands out a /32 or a future gluetun that
drops the rule would make these the only thing keeping probes and ingress
alive. It is not what opens the ports today.
*/}}
{{- define "freesailarr.firewallInputPorts" -}}
{{- $ports := list -}}
{{- range $app := include "freesailarr.portApps" . | fromJsonArray -}}
{{- $ports = append $ports (int $app.port) -}}
{{- end -}}
{{- $ports = append $ports 9999 -}}
{{- join "," $ports -}}
{{- end -}}
{{/*
Whether a media category is mounted at all, i.e. whether at least one app that
mounts it is enabled. "true" when it is, the empty string when it is not (an
empty string is falsy in a template "if", so the result can be used directly).
downloads: qbittorrent, radarr, sonarr
movies: radarr, bazarr
tv: sonarr, bazarr
Input: dict "root" $ "category" <"downloads"|"movies"|"tv">.
*/}}
{{- define "freesailarr.mediaCategoryEnabled" -}}
{{- $v := .root.Values -}}
{{- if eq .category "downloads" -}}
{{- if or $v.qbittorrent.enabled $v.radarr.enabled $v.sonarr.enabled -}}true{{- end -}}
{{- else if eq .category "movies" -}}
{{- if or $v.radarr.enabled $v.bazarr.enabled -}}true{{- end -}}
{{- else if eq .category "tv" -}}
{{- if or $v.sonarr.enabled $v.bazarr.enabled -}}true{{- end -}}
{{- else -}}
{{- fail (printf "unknown media category %q" .category) -}}
{{- end -}}
{{- end -}}
{{/*
The pod volume name a media category mounts (issue #164).
Several categories may point at the same existingClaim - one library volume with
the categories as sub-directories is the intended layout. A pod may reference
such a claim only ONCE: two volume entries with the same claimName leave the pod
in Init:0/1 forever, without a single kubelet event (measured on RKE2/Longhorn,
with RWO as well as RWX). The claim therefore gets exactly one volume entry and
the categories differ only in the subPath of their mounts.
This helper names the owner of that entry: the FIRST ENABLED category, in the
fixed order
downloads, movies, tv
that carries the same claim. Two properties of that rule are load-bearing:
- The order is fixed and defined here, not derived from the values. A rule
like "the alphabetically smallest" or "whichever comes first in the values"
would move the volume name when an unrelated flag changes, and a moved
volume name is a pod restart.
- Only ENABLED categories can own the entry, because the three entries are
rendered under different conditions (see mediaCategoryEnabled above). A
disabled owner renders no entry, so its name would be a dangling reference
and the pod would be rejected by the API server. Example: with qbittorrent,
radarr and sonarr off and bazarr on, "downloads" is not mounted at all -
"movies" owns a claim shared by all three categories.
A category with no claim owns itself, so the required() message for the missing
claim still fires at the category the user actually configured.
Input: dict "root" $ "category" <"downloads"|"movies"|"tv">.
Usage (mount) : name: {{ include "freesailarr.mediaVolumeName" (dict "root" $ "category" "movies") }}
Usage (volume): render the entry only for a category that owns itself.
*/}}
{{- define "freesailarr.mediaVolumeName" -}}
{{- $root := .root -}}
{{- $claim := (index $root.Values.media .category).existingClaim | default "" -}}
{{- $owner := "" -}}
{{- if $claim -}}
{{- range $candidate := list "downloads" "movies" "tv" -}}
{{- if not $owner -}}
{{- if include "freesailarr.mediaCategoryEnabled" (dict "root" $root "category" $candidate) -}}
{{- if eq ((index $root.Values.media $candidate).existingClaim | default "") $claim -}}
{{- $owner = $candidate -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $owner -}}
{{- $owner = .category -}}
{{- end -}}
media-{{ $owner }}
{{- end -}}
{{/*
Whether a media category owns its volume entry, i.e. whether the volumes block
renders an entry for it. "true" or the empty string, see mediaVolumeName above.
Input: dict "root" $ "category" <"downloads"|"movies"|"tv">.
*/}}
{{- define "freesailarr.mediaVolumeOwned" -}}
{{- if eq (include "freesailarr.mediaVolumeName" .) (printf "media-%s" .category) -}}true{{- end -}}
{{- end -}}
{{/*
Recursive merge of an override onto defaults where null never deletes and set
values always win, including false and 0 (issue #99).
Input: dict "defaults" <dict> "given" <dict|nil> "path" <values path, for errors>.
NOTE: like the resources helper above, this is duplicated into every chart's
templates/_helpers.tpl with the chart's name as define prefix. The reference
implementation lives in template-chart.
*/}}
{{- define "freesailarr.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 "freesailarr.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>.
Needed because ".Values.<app>.securityContext" can itself be erased, and
indexing into nil inside a template is not an error but silently yields
nothing.
*/}}
{{- define "freesailarr.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 chart's own defaults for every block of the "silent class" from issue #99 -
security contexts, probes and resources. An override that erases one of these
blocks (`key:` with no children, i.e. null) would otherwise render a container
without hardening, without a probe or without resource accounting: it comes up,
reports green and is quietly weaker. The accessors below merge the user's block
onto these defaults with freesailarr.defaultedDict, where null never deletes.
These MUST stay in sync with values.yaml, which remains the documented,
user-facing place for them; this copy is only the fallback. The sync is not left
to discipline: rendering the chart with every one of these blocks explicitly set
to null must produce byte-identical output to rendering it with the defaults,
which is how the table was verified and how a drift shows up.
Two rules this table exists to honour (README, "Chart style guidelines"):
- It reproduces THIS chart's defaults, deviations included, not the repo
standard. The five linuxserver.io containers deliberately run without
runAsNonRoot and with a writable root filesystem, because their s6-overlay
init starts as root and drops to PUID/PGID; "restoring" the clean standard
here would leave them on the image's built-in uid 911 and make the existing
media volumes inaccessible - the exact failure calibre-web-automated hit.
flaresolverr's numerically pinned uid/gid 1000 and gluetun's NET_ADMIN are
reproduced for the same reason.
- The probe defaults carry their CONTEXT, not just timings: every httpGet
keeps its path and port, and recyclarr - which serves no HTTP port in
daemon mode - keeps its exec command. A probe default without them is worse
than no default.
*/}}
{{- define "freesailarr.fallbacks" -}}
gluetun:
livenessProbe:
httpGet:
path: /
port: 9999
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
httpGet:
path: /
port: 9999
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /
port: 9999
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.1
memory: 128Mi
ephemeralStorage: 50Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- NET_ADMIN
qbittorrent:
livenessProbe:
httpGet:
path: /
port: 8080
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 8080
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /
port: 8080
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.2
memory: 512Mi
ephemeralStorage: 100Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
prowlarr:
livenessProbe:
httpGet:
path: /ping
port: 9696
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ping
port: 9696
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /ping
port: 9696
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.1
memory: 256Mi
ephemeralStorage: 100Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
flaresolverr:
livenessProbe:
httpGet:
path: /health
port: 8191
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8191
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /health
port: 8191
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.2
memory: 512Mi
ephemeralStorage: 200Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop:
- ALL
radarr:
livenessProbe:
httpGet:
path: /ping
port: 7878
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ping
port: 7878
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /ping
port: 7878
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.1
memory: 256Mi
ephemeralStorage: 100Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
recyclarr:
livenessProbe:
exec:
command:
- recyclarr
- --version
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
exec:
command:
- recyclarr
- --version
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
startupProbe:
exec:
command:
- recyclarr
- --version
periodSeconds: 5
timeoutSeconds: 10
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.05
memory: 128Mi
ephemeralStorage: 50Mi
securityContext:
runAsUser: {{ .Values.puid }}
runAsGroup: {{ .Values.pgid }}
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
sonarr:
livenessProbe:
httpGet:
path: /ping
port: 8989
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ping
port: 8989
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /ping
port: 8989
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.1
memory: 256Mi
ephemeralStorage: 100Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
seerr:
livenessProbe:
httpGet:
path: /api/v1/settings/public
port: 5055
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /api/v1/settings/public
port: 5055
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /api/v1/settings/public
port: 5055
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.1
memory: 256Mi
ephemeralStorage: 100Mi
securityContext:
runAsUser: {{ .Values.puid }}
runAsGroup: {{ .Values.pgid }}
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
bazarr:
livenessProbe:
httpGet:
path: /
port: 6767
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 6767
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /
port: 6767
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
limitFactor: 3
requests:
cpu: 0.1
memory: 256Mi
ephemeralStorage: 100Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
configInit:
resources:
limitFactor: 3
requests:
cpu: 10m
memory: 16Mi
ephemeralStorage: 1Mi
securityContext:
runAsUser: {{ .Values.puid }}
runAsGroup: {{ .Values.pgid }}
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
{{- end -}}
{{/*
The effective pod security context. Separate from the per-container table
because it is the one block shared by all nine containers.
*/}}
{{- define "freesailarr.podSecurityContext" -}}
{{- $given := (fromYaml (include "freesailarr.subBlock" (dict "block" .Values.securityContext "key" "pod" "path" "securityContext"))).value -}}
{{- $defaults := dict
"fsGroup" 1000
"fsGroupChangePolicy" "OnRootMismatch"
"seccompProfile" (dict "type" "RuntimeDefault") -}}
{{- include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" $given "path" "securityContext.pod") -}}
{{- end -}}
{{/*
The effective probe / security context / resources for one container.
Input: dict "root" $ "app" <key in the fallback table> "given" <the values block>
and, for probes, "kind" <livenessProbe|readinessProbe|startupProbe>.
"given" is passed in rather than derived from "app" because the qbittorrent init
container's blocks live under qbittorrent.configInit, not under a top-level key
of the same name.
*/}}
{{- define "freesailarr.probeFor" -}}
{{- $fb := fromYaml (include "freesailarr.fallbacks" .root) -}}
{{- $defaults := index $fb .app .kind -}}
{{- include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" .given "path" (printf "%s.%s" .app .kind)) -}}
{{- end -}}
{{- define "freesailarr.securityContextFor" -}}
{{- $fb := fromYaml (include "freesailarr.fallbacks" .root) -}}
{{- $defaults := index $fb .app "securityContext" -}}
{{- include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" .given "path" (printf "%s.securityContext" .app)) -}}
{{- end -}}
{{- define "freesailarr.resourcesFor" -}}
{{- $fb := fromYaml (include "freesailarr.fallbacks" .root) -}}
{{- $defaults := index $fb .app "resources" -}}
{{- $merged := fromYaml (include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" .given "path" (printf "%s.resources" .app))) -}}
{{- include "freesailarr.resources" $merged -}}
{{- end -}}