Files
aptly-containerized/charts/aptly/templates/_helpers.tpl
T
f.weber 1675ea9a4e
CI / lint (push) Failing after 2s
CI / smoke-test (push) Failing after 9s
Release chart / release (release) Successful in 12s
Enhance Helm chart with ServiceAccount support and configurable ports
- Updated Chart.yaml to version 0.2.0 and added annotations for changes.
- Modified release-chart.yaml to trigger releases via Gitea and handle pre-releases.
- Introduced ServiceAccount configuration in values.yaml and related templates.
- Adjusted internal container ports to prevent conflicts between nginx and aptly.
- Updated README.md and NOTES.txt to reflect new configurations and usage instructions.
2026-08-13 12:12:30 +02:00

277 lines
11 KiB
Smarty

{{/*
Standard name/label helpers, bookstack-chart style.
*/}}
{{- define "aptly.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "aptly.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- define "aptly.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "aptly.labels" -}}
helm.sh/chart: {{ include "aptly.chart" . }}
{{ include "aptly.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{- define "aptly.selectorLabels" -}}
app.kubernetes.io/name: {{ include "aptly.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{/*
Image helpers global.imageRegistry prefixes the repository when set,
matching the bookstack-chart convention.
*/}}
{{- define "aptly.image" -}}
{{- $registry := .Values.global.imageRegistry -}}
{{- $repo := .Values.image.repository -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion -}}
{{- if $registry -}}
{{- printf "%s/%s:%s" $registry $repo $tag -}}
{{- else -}}
{{- printf "%s:%s" $repo $tag -}}
{{- end -}}
{{- end -}}
{{- define "aptly.nginxImage" -}}
{{- $registry := .Values.global.imageRegistry -}}
{{- $repo := .Values.nginx.image.repository -}}
{{- $tag := .Values.nginx.image.tag -}}
{{- if $registry -}}
{{- printf "%s/%s:%s" $registry $repo $tag -}}
{{- else -}}
{{- printf "%s:%s" $repo $tag -}}
{{- end -}}
{{- end -}}
{{/*
Shared by templates/ingress.yaml and templates/httproute.yaml: proxy.enabled=false
means aptly's unauthenticated write API sits directly behind whatever routes to
it, on a single listener with no path-based auth split possible — refuse to wire
that up to an Ingress OR a Gateway API HTTPRoute unless security.preset=open
confirms it's intended.
*/}}
{{- define "aptly.exposureGuard" -}}
{{- if and (not .Values.proxy.enabled) (or .Values.ingress.enabled .Values.gateway.enabled) (ne .Values.security.preset "open") -}}
{{- fail "proxy.enabled=false publishes aptly's unauthenticated write API through the Ingress/HTTPRoute (no path-based auth split is possible on a single listener). Set security.preset=open to confirm this is intended, or keep proxy.enabled=true." -}}
{{- end -}}
{{- end -}}
{{/*
Resolves to the ServiceAccount name the pod should bind to: a generated or
overridden name when serviceAccount.create is true, the explicit override
when false, or "" (falls back to the namespace's "default" SA) otherwise.
*/}}
{{- define "aptly.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "aptly.fullname" .) .Values.serviceAccount.name -}}
{{- else -}}
{{- .Values.serviceAccount.name -}}
{{- end -}}
{{- end -}}
{{- define "aptly.imagePullSecrets" -}}
{{- $secrets := concat (.Values.global.imagePullSecrets | default list) (.Values.image.pullSecrets | default list) -}}
{{- if $secrets }}
imagePullSecrets:
{{- range $secrets }}
- name: {{ if kindIs "map" . }}{{ .name }}{{ else }}{{ . }}{{ end }}
{{- end }}
{{- end }}
{{- end -}}
{{/*
Whether aptly serves the published tree itself (no nginx proxy in front).
Only sane in combination with security.preset=open — enforced in
templates/statefulset.yaml (the aptly config) and templates/ingress.yaml
(the hard `fail` guard).
*/}}
{{- define "aptly.serveInApiMode" -}}
{{- if .Values.proxy.enabled -}}false{{- else -}}true{{- end -}}
{{- end -}}
{{- define "aptly.apiListen" -}}
{{- if .Values.proxy.enabled -}}127.0.0.1:{{ .Values.ports.aptly }}{{- else -}}0.0.0.0:{{ .Values.ports.aptly }}{{- end -}}
{{- end -}}
{{/*
Resolve security.preset + explicit read/write overrides into a plain dict
{r, ra, w, wa} (read-enabled, read-auth, write-enabled, write-auth). Both
nginx.conf.tpl and templates/ingress.yaml key off this so preset expansion
lives in exactly one place.
*/}}
{{- define "aptly.security" -}}
{{- $p := .Values.security.preset -}}
{{- $presets := dict
"open" (dict "r" true "ra" false "w" true "wa" false)
"publicRead" (dict "r" true "ra" false "w" true "wa" true)
"authenticated" (dict "r" true "ra" true "w" true "wa" true)
"readOnly" (dict "r" true "ra" false "w" false "wa" false)
-}}
{{- $base := index $presets $p -}}
{{- if not $base -}}
{{- fail (printf "security.preset: unknown value %q (must be one of open, publicRead, authenticated, readOnly)" $p) -}}
{{- end -}}
{{- $d := deepCopy $base -}}
{{- if kindIs "bool" .Values.security.read.enabled }}{{- $_ := set $d "r" .Values.security.read.enabled -}}{{- end -}}
{{- if kindIs "bool" .Values.security.read.requireAuth }}{{- $_ := set $d "ra" .Values.security.read.requireAuth -}}{{- end -}}
{{- if kindIs "bool" .Values.security.write.enabled }}{{- $_ := set $d "w" .Values.security.write.enabled -}}{{- end -}}
{{- if kindIs "bool" .Values.security.write.requireAuth }}{{- $_ := set $d "wa" .Values.security.write.requireAuth -}}{{- end -}}
{{- $d | toJson -}}
{{- end -}}
{{/*
Render the aptly config (YAML) from the curated values.aptly.* keys, then
deep-merge aptly.configOverrides on top so every current/future aptly config
key stays reachable without a chart change. Curated keys are OMITTED when
unset, so a default install matches aptly's own upstream defaults exactly
(see utils/config.go in aptly-dev/aptly for the canonical defaults).
*/}}
{{- define "aptly.config" -}}
{{- $v := .Values.aptly -}}
{{- $c := dict "root_dir" "/var/lib/aptly" -}}
{{- with $v.logLevel }}{{- $_ := set $c "log_level" . -}}{{- end -}}
{{- with $v.logFormat }}{{- $_ := set $c "log_format" . -}}{{- end -}}
{{- if $v.architectures }}{{- $_ := set $c "architectures" $v.architectures -}}{{- end -}}
{{- with $v.download.concurrency }}{{- $_ := set $c "download_concurrency" . -}}{{- end -}}
{{- with $v.download.limit }}{{- $_ := set $c "download_limit" . -}}{{- end -}}
{{- with $v.download.retries }}{{- $_ := set $c "download_retries" . -}}{{- end -}}
{{- if kindIs "bool" $v.download.sourcePackages }}{{- $_ := set $c "download_sourcepackages" $v.download.sourcePackages -}}{{- end -}}
{{- $_ := set $c "gpg_provider" ($v.gpg.provider | default "gpg") -}}
{{- $_ := set $c "gpg_disable_sign" (not $v.gpg.enabled) -}}
{{- $_ := set $c "gpg_disable_verify" (not $v.gpg.verify) -}}
{{- if kindIs "bool" $v.publishing.skipContents }}{{- $_ := set $c "skip_contents_publishing" $v.publishing.skipContents -}}{{- end -}}
{{- if kindIs "bool" $v.publishing.skipBz2 }}{{- $_ := set $c "skip_bz2_publishing" $v.publishing.skipBz2 -}}{{- end -}}
{{- $_ := set $c "enable_metrics_endpoint" ($v.metrics.enabled | default false) -}}
{{- $_ := set $c "enable_swagger_endpoint" ($v.swagger.enabled | default false) -}}
{{- $_ := set $c "serve_in_api_mode" (eq (include "aptly.serveInApiMode" .) "true") -}}
{{- $_ := set $c "filesystem_publish_endpoints" (dict $.Values.proxy.publishEndpointName (dict "root_dir" "/var/lib/aptly/public" "link_method" "hardlink")) -}}
{{- $merged := mergeOverwrite $c (deepCopy ($v.configOverrides | default dict)) -}}
{{- toYaml $merged -}}
{{- end -}}
{{/*
Render nginx's server{} block (mounted at /etc/nginx/conf.d/default.conf,
which the base image's own nginx.conf already includes from inside its own
http{} block this template must therefore emit ONLY a server{} block, see
compose/config/nginx.*.conf for the same constraint hit empirically).
*/}}
{{- define "aptly.nginxConf" -}}
{{- $sec := include "aptly.security" . | fromJson -}}
{{- $p := .Values.proxy -}}
server {
listen {{ .Values.ports.nginx }};
server_name _;
client_max_body_size {{ $p.maxUploadSize }};
absolute_redirect off;
{{- range .Values.security.trustedProxies }}
set_real_ip_from {{ . }};
{{- end }}
{{- if .Values.security.trustedProxies }}
real_ip_header X-Forwarded-For;
real_ip_recursive on;
{{- end }}
location = /healthz { access_log off; return 200 "ok\n"; }
location = /api/ready { access_log off; proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }}; }
location = /api/healthy { access_log off; proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }}; }
{{- if $sec.w }}
location /api/ {
{{- if .Values.security.write.allowCIDRs }}
{{- if and .Values.security.write.allowCIDRs (not .Values.security.trustedProxies) }}
# WARNING: write.allowCIDRs is set without security.trustedProxies. Behind
# an Ingress controller, $remote_addr is the CONTROLLER's pod IP, not the
# real client — this will match every client on earth. Set
# trustedProxies to the controller's CIDR, or use networkPolicy instead.
{{- end }}
{{- range .Values.security.write.allowCIDRs }}
allow {{ . }};
{{- end }}
deny all;
satisfy {{ if $sec.wa }}any{{ else }}all{{ end }};
{{- end }}
{{- if $sec.wa }}
auth_basic "aptly";
auth_basic_user_file /run/aptly/htpasswd;
{{- else }}
auth_basic off;
{{- end }}
proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_request_buffering off;
proxy_read_timeout {{ .Values.proxy.readTimeout }};
proxy_send_timeout {{ .Values.proxy.readTimeout }};
}
{{- else }}
location /api/ { return 404; }
{{- end }}
location = /signing-key.asc {
alias /run/aptly/pub/signing-key.asc;
default_type text/plain;
}
{{- if $sec.r }}
location / {
{{- if $sec.ra }}
auth_basic "aptly";
auth_basic_user_file /run/aptly/htpasswd;
{{- end }}
root /var/lib/aptly/public;
autoindex on;
autoindex_exact_size off;
location ~* /(InRelease|Release|Release\.gpg|Packages(\.[a-z0-9]+)?|Sources(\.[a-z0-9]+)?)$ {
{{- if $sec.ra }}
auth_basic "aptly";
auth_basic_user_file /run/aptly/htpasswd;
{{- end }}
root /var/lib/aptly/public;
add_header Cache-Control "no-cache" always;
}
}
{{- if .Values.proxy.compatPaths }}
location /repos/{{ .Values.proxy.publishEndpointName }}/ {
{{- if $sec.ra }}
auth_basic "aptly";
auth_basic_user_file /run/aptly/htpasswd;
{{- end }}
alias /var/lib/aptly/public/;
autoindex on;
}
{{- end }}
{{- else }}
location / { return 404; }
{{- end }}
}
{{- if .Values.metrics.service.enabled }}
# Separate, unauthenticated listener so scraping never needs the write-path
# credentials and a ServiceMonitor never needs a basicAuth secret.
server {
listen {{ .Values.ports.metrics }};
server_name _;
location = /api/metrics { proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }}; }
location / { return 404; }
}
{{- end }}
{{- end -}}