initial commit
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
This commit is contained in:
commit
951dd8d26b
175 changed files with 50041 additions and 0 deletions
19
deploy/helm/go-bananas/.helmignore
Normal file
19
deploy/helm/go-bananas/.helmignore
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Files excluded from the packaged chart. Anything matched here is neither sent to
|
||||
# the cluster nor included in `helm package`, which keeps the artifact small and
|
||||
# stops a local editor's swap file from ever reaching a release.
|
||||
.DS_Store
|
||||
.git/
|
||||
.gitignore
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*.tgz
|
||||
*.md
|
||||
|
||||
# Documentation is excluded from the packaged chart: it is several hundred
|
||||
# kilobytes of prose that the cluster does not need, and a chart artifact
|
||||
# should contain only what is required to deploy.
|
||||
*.md
|
||||
36
deploy/helm/go-bananas/Chart.yaml
Normal file
36
deploy/helm/go-bananas/Chart.yaml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
apiVersion: v2
|
||||
name: go-bananas
|
||||
description: >-
|
||||
Runtime design-token service. Author design-token themes in a web interface and
|
||||
serve them to any application as CSS custom properties at run time, with no npm
|
||||
dependency and no build step on the consumer side.
|
||||
type: application
|
||||
|
||||
# The chart version. Bumped for every change to the chart itself.
|
||||
version: 1.0.0
|
||||
|
||||
# The version of the application this chart deploys, which follows the image tag.
|
||||
# Keeping the two in step is what `helm upgrade --reuse-values` needs to roll
|
||||
# forward correctly.
|
||||
appVersion: "1.0.0"
|
||||
|
||||
kubeVersion: ">=1.28.0-0"
|
||||
|
||||
keywords:
|
||||
- design-system
|
||||
- design-tokens
|
||||
- theming
|
||||
- css-variables
|
||||
- frontend
|
||||
|
||||
icon: https://raw.githubusercontent.com/urpi/go-bananas/main/docs/assets/logo.svg
|
||||
home: https://github.com/urpi/go-bananas
|
||||
sources:
|
||||
- https://github.com/urpi/go-bananas
|
||||
maintainers:
|
||||
- name: urpi platform team
|
||||
url: https://github.com/urpi
|
||||
|
||||
annotations:
|
||||
category: DeveloperTools
|
||||
licenses: MIT
|
||||
89
deploy/helm/go-bananas/templates/NOTES.txt
Normal file
89
deploy/helm/go-bananas/templates/NOTES.txt
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
{{- $name := include "go-bananas.fullname" . }}
|
||||
|
||||
go-bananas {{ .Chart.AppVersion }} is deployed as {{ $name }} in namespace {{ .Release.Namespace }}.
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
{{- if .Values.ingress.enabled }}
|
||||
|
||||
Your instance is reachable at:
|
||||
|
||||
{{- range .Values.ingress.hosts }}
|
||||
{{- $scheme := "http" }}
|
||||
{{- if $.Values.ingress.tls }}{{ $scheme = "https" }}{{ end }}
|
||||
{{- range $.Values.ingress.hosts }}
|
||||
{{ $scheme }}://{{ .host }}
|
||||
{{- end }}
|
||||
{{- break }}
|
||||
{{- end }}
|
||||
|
||||
Follow the admin interface from there. Sign in with your identity provider.
|
||||
{{- else if eq .Values.service.type "NodePort" }}
|
||||
|
||||
Your instance is on node port {{ .Values.service.nodePort }}. Find the address with:
|
||||
|
||||
kubectl get nodes -o wide
|
||||
kubectl port-forward --address 0.0.0.0 svc/{{ $name }} 8080:{{ .Values.service.port }}
|
||||
{{- else }}
|
||||
|
||||
Port-forward to reach the admin interface:
|
||||
|
||||
kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ $name }} 8080:{{ .Values.service.port }}
|
||||
|
||||
Then open http://localhost:8080
|
||||
{{- end }}
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Runtime integration
|
||||
|
||||
{{- if eq .Values.identity.mode "oidc" }}
|
||||
|
||||
Consumers fetch themes with no dependency and no build step. Add this to any
|
||||
application's <head>:
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="{{ include "go-bananas.publicUrl" . }}/rt/v1/theme.css?slug=midnight">
|
||||
|
||||
Full instructions are at {{ include "go-bananas.publicUrl" . }}/rt/v1/embed?slug=midnight
|
||||
|
||||
Available themes:
|
||||
|
||||
{{- $base := printf "%s/rt/v1" (include "go-bananas.publicUrl" .) }}
|
||||
{{- $base }}{{ "/theme.css?slug=<slug>" }}
|
||||
{{- $base }}{{ "/theme.json?slug=<slug>" }}
|
||||
{{- $base }}{{ "/manifest.json" }}
|
||||
{{- $base }}{{ "/runtime.js" }} (framework-free ES module)
|
||||
{{- $base }}{{ "/react.js" }} (React provider and hooks)
|
||||
{{- $base }}{{ "/embed.js" }} (one-line script tag)
|
||||
{{- else }}
|
||||
|
||||
Authentication is set to "{{ .Values.identity.mode }}". Every request is currently
|
||||
treated as an administrator. Before exposing this to a network, set:
|
||||
|
||||
identity.mode=oidc
|
||||
identity.oidc.issuer=https://auth-dev.urpi.be/realms/<realm>
|
||||
identity.oidc.clientId=go-bananas
|
||||
identity.oidc.existingSecret=<secret-name>
|
||||
{{- end }}
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Verify it is working
|
||||
|
||||
kubectl --namespace {{ .Release.Namespace }} get pods -l app.kubernetes.io/instance={{ .Release.Name }}
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ $name }} 8080:{{ .Values.service.port }}
|
||||
curl -s localhost:{{ .Values.service.port }}/readyz
|
||||
{{- if .Values.config.enableMetrics }}
|
||||
|
||||
curl -s localhost:{{ .Values.service.port }}/metrics | head
|
||||
{{- end }}
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Notes
|
||||
|
||||
* The database is a single SQLite file on a {{ .Values.persistence.type | default "disabled" }} volume
|
||||
{{- if and .Values.persistence.enabled (eq .Values.persistence.type "persistentVolumeClaim") }}
|
||||
at {{ .Values.persistence.mountPath }}. It is retained on uninstall.
|
||||
{{- end }}
|
||||
* {{ .Values.deployment.replicaCount }} replica(s). With a ReadWriteOnce volume, more than one
|
||||
replica needs a filesystem that permits concurrent mounts.
|
||||
* Built-in themes are reconciled at start-up. A built-in theme an operator has
|
||||
edited is never overwritten.
|
||||
256
deploy/helm/go-bananas/templates/_helpers.tpl
Normal file
256
deploy/helm/go-bananas/templates/_helpers.tpl
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "go-bananas.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
A fully qualified app name, truncated to the 63-character DNS label limit.
|
||||
|
||||
The release name is included because a chart may legitimately be installed more
|
||||
than once in a namespace: one for staging themes and one for production. Without
|
||||
it, the two would collide on every object name.
|
||||
*/}}
|
||||
{{- define "go-bananas.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 }}
|
||||
|
||||
{{/*
|
||||
Chart name and version, as used by the standard chart label.
|
||||
*/}}
|
||||
{{- define "go-bananas.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels.
|
||||
|
||||
These are deliberately stable: a Deployment's selector is immutable, so changing
|
||||
these labels on an existing release makes the upgrade fail. That failure is the
|
||||
point — it prevents the controller from orphaning pods.
|
||||
*/}}
|
||||
{{- define "go-bananas.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "go-bananas.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels, including the recommended Kubernetes labels and the operator's own.
|
||||
*/}}
|
||||
{{- define "go-bananas.labels" -}}
|
||||
helm.sh/chart: {{ include "go-bananas.chart" . }}
|
||||
{{ include "go-bananas.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
app.kubernetes.io/part-of: go-bananas
|
||||
{{- with .Values.global.extraLabels }}
|
||||
{{ toYaml . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The image reference, honouring the digest override.
|
||||
|
||||
A digest is preferred over a tag when both are available: it is the only way to
|
||||
guarantee that a rollout is running exactly the image that was reviewed, rather
|
||||
than whatever a mutable tag points at today.
|
||||
|
||||
Both branches build the reference the same way — registry, then repository path,
|
||||
then the tag or digest — so the digest branch cannot drift into producing a
|
||||
doubled registry prefix such as ghcr.io/ghcr.io/urpi/go-bananas.
|
||||
*/}}
|
||||
{{- define "go-bananas.image" -}}
|
||||
{{- $registry := .Values.image.registry -}}
|
||||
{{- if .Values.global.imageRegistry -}}
|
||||
{{- $registry = .Values.global.imageRegistry -}}
|
||||
{{- end -}}
|
||||
{{- if not $registry -}}
|
||||
{{- fail "image.registry must not be empty. It is the registry host, for example ghcr.io or registry.example.com." -}}
|
||||
{{- end -}}
|
||||
{{- $repository := .Values.image.repository -}}
|
||||
{{- /* Convention: image.registry is the host and image.repository is the path.
|
||||
But an operator who writes the host into repository should get the reference
|
||||
they asked for, not ghcr.io/quay.io/other/app. A first path segment
|
||||
containing a dot or a colon, or equal to localhost, is a registry host
|
||||
rather than a namespace. */ -}}
|
||||
{{- $first := first (splitList "/" $repository) -}}
|
||||
{{- $looksQualified := or (eq $repository $registry) (hasPrefix (printf "%s/" $registry) $repository) (contains "." $first) (contains ":" $first) (eq $first "localhost") -}}
|
||||
{{- $name := $repository -}}
|
||||
{{- if not $looksQualified -}}
|
||||
{{- $name = printf "%s/%s" $registry $repository -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.image.digest -}}
|
||||
{{- printf "%s@%s" $name .Values.image.digest -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s:%s" $name (default .Chart.AppVersion .Values.image.tag) -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The ServiceAccount name to use.
|
||||
*/}}
|
||||
{{- define "go-bananas.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "go-bananas.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Whether a generated Secret should be produced.
|
||||
|
||||
Either mode needs a Secret, but the two differ in ownership. An existing Secret is
|
||||
the operator's, so the chart only reads it. A generated one is created when
|
||||
generateSecret is true, and the value is preserved across upgrades so that a
|
||||
rolling update does not silently invalidate every session.
|
||||
*/}}
|
||||
{{- define "go-bananas.usesExistingSecret" -}}
|
||||
{{- if and .Values.identity.oidc.existingSecret .Values.identity.oidc.generateSecret -}}
|
||||
{{- fail "identity.oidc.existingSecret and identity.oidc.generateSecret are mutually exclusive. Set generateSecret to false when supplying an existing Secret." -}}
|
||||
{{- end -}}
|
||||
{{- if not (or .Values.identity.oidc.existingSecret .Values.identity.oidc.generateSecret) -}}
|
||||
{{- fail "identity.oidc needs a Secret. Set identity.oidc.existingSecret to a Secret you manage, or identity.oidc.generateSecret to true for a chart-managed one." -}}
|
||||
{{- end -}}
|
||||
{{- .Values.identity.oidc.existingSecret -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The name of the Secret holding the session and client secrets.
|
||||
*/}}
|
||||
{{- define "go-bananas.secretName" -}}
|
||||
{{- if .Values.identity.oidc.existingSecret -}}
|
||||
{{- .Values.identity.oidc.existingSecret -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-auth" (include "go-bananas.fullname" .) -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The key inside the Secret holding the session signing secret.
|
||||
*/}}
|
||||
{{- define "go-bananas.sessionSecretKey" -}}
|
||||
{{- .Values.identity.sessionSecretKey -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The key inside the Secret holding the OIDC client secret.
|
||||
*/}}
|
||||
{{- define "go-bananas.clientSecretKey" -}}
|
||||
{{- default .Values.identity.oidc.clientSecretKey .Values.identity.oidc.existingSecretKeys.clientSecret -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Validate the identity configuration before anything is rendered.
|
||||
|
||||
Failing at template time is much better than failing at run time: a misconfigured
|
||||
release never reaches the API server, so there is no half-created Deployment and no
|
||||
CrashLoopBackOff to diagnose. Each check names the value to change.
|
||||
*/}}
|
||||
{{- define "go-bananas.validate" -}}
|
||||
{{- $mode := .Values.identity.mode -}}
|
||||
{{- if not (has $mode (list "oidc" "dev" "disabled")) -}}
|
||||
{{- fail (printf "identity.mode %q is not valid. Use oidc, dev or disabled." $mode) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $mode "oidc" -}}
|
||||
{{- if not .Values.identity.oidc.issuer -}}
|
||||
{{- fail "identity.oidc.issuer is required when identity.mode is oidc. For the development realm this is https://auth-dev.urpi.be/realms/<realm>." -}}
|
||||
{{- end -}}
|
||||
{{- if not (regexMatch "^https?://" .Values.identity.oidc.issuer) -}}
|
||||
{{- fail (printf "identity.oidc.issuer %q is not a URL. It must include the scheme." .Values.identity.oidc.issuer) -}}
|
||||
{{- end -}}
|
||||
{{- if not .Values.identity.oidc.clientId -}}
|
||||
{{- fail "identity.oidc.clientId is required when identity.mode is oidc." -}}
|
||||
{{- end -}}
|
||||
{{- /* The result is discarded on purpose. This call exists for its fail-fast side
|
||||
effect, and returning the Secret *name* from the helper would write it into
|
||||
the rendered manifest as a bare line above `apiVersion`, which is invalid
|
||||
YAML. It only went unnoticed because with the default generateSecret the
|
||||
helper returns the empty string. */ -}}
|
||||
{{- $_ := include "go-bananas.usesExistingSecret" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if eq $mode "oidc" -}}
|
||||
{{- /* Refused in production because it disables certificate verification on the
|
||||
connection to the identity provider, which is the one connection where a
|
||||
man-in-the-middle would be most damaging. */}}
|
||||
{{- if and .Values.identity.oidc.skipTLSVerify (eq .Values.config.environment "production") -}}
|
||||
{{- fail "identity.oidc.skipTLSVerify cannot be combined with config.environment: production. It disables certificate verification against the identity provider. Set config.environment to development, or supply a trusted CA instead." -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if not .Values.config.publicBaseUrl -}}
|
||||
{{- fail "config.publicBaseUrl is required. It is used to build the OIDC redirect URI, the absolute URLs in the runtime manifest, and the HSTS decision." -}}
|
||||
{{- end -}}
|
||||
{{- if not (regexMatch "^https?://[^/]+" .Values.config.publicBaseUrl) -}}
|
||||
{{- fail (printf "config.publicBaseUrl %q must be an absolute URL with a scheme and host, for example https://themes.example.com" .Values.config.publicBaseUrl) -}}
|
||||
{{- end -}}
|
||||
{{- if hasSuffix "/" .Values.config.publicBaseUrl -}}
|
||||
{{- fail (printf "config.publicBaseUrl %q must not end with a slash. Use https://themes.example.com, not https://themes.example.com/." .Values.config.publicBaseUrl) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if and (gt (int .Values.config.shutdownTimeoutMs) 0) (gt (int .Values.deployment.terminationGracePeriodSeconds) 0) -}}
|
||||
{{- /* The grace period is in seconds and the timeout in milliseconds, so the
|
||||
comparison converts one to the other rather than comparing 25000 to 45. */}}
|
||||
{{- $graceMs := mul (int .Values.deployment.terminationGracePeriodSeconds) 1000 -}}
|
||||
{{- if ge (int .Values.config.shutdownTimeoutMs) $graceMs -}}
|
||||
{{- fail (printf "config.shutdownTimeoutMs (%d) must be less than deployment.terminationGracePeriodSeconds (%ds), otherwise Kubernetes sends SIGKILL before graceful shutdown finishes and in-flight requests are cut off." (int .Values.config.shutdownTimeoutMs) (int .Values.deployment.terminationGracePeriodSeconds)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The public origin, with the base path removed so that it can be concatenated with
|
||||
a route.
|
||||
*/}}
|
||||
{{- define "go-bananas.publicUrl" -}}
|
||||
{{- .Values.config.publicBaseUrl -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The base path, normalised to either an empty string or a leading-slash path with no
|
||||
trailing slash.
|
||||
*/}}
|
||||
{{- define "go-bananas.basePath" -}}
|
||||
{{- .Values.config.basePath | trimSuffix "/" -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Whether the deployment is served over TLS, which decides the Secure cookie flag and
|
||||
whether HSTS is advertised.
|
||||
*/}}
|
||||
{{- define "go-bananas.isSecure" -}}
|
||||
{{- if .Values.service.sessionSecure -}}true{{- end -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The database file path inside the container.
|
||||
*/}}
|
||||
{{- define "go-bananas.databasePath" -}}
|
||||
{{- printf "%s/go-bananas.db" (trimSuffix "/" .Values.persistence.mountPath) -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
The data volume name, whether it is a PVC or an emptyDir.
|
||||
*/}}
|
||||
{{- define "go-bananas.dataVolumeName" -}}
|
||||
{{- if .Values.persistence.existingClaim -}}
|
||||
{{- .Values.persistence.existingClaim -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-data" (include "go-bananas.fullname" .) -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
93
deploy/helm/go-bananas/templates/configmap.yaml
Normal file
93
deploy/helm/go-bananas/templates/configmap.yaml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{{/*
|
||||
The ConfigMap holding every non-secret setting.
|
||||
|
||||
Splitting configuration from secrets is the point of this file: a ConfigMap is
|
||||
readable by anyone with namespace access and appears in `kubectl describe`, so
|
||||
nothing sensitive can end up in one by accident.
|
||||
*/}}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
# ---------------------------------------------------------------- runtime ---
|
||||
BANANAS_ENVIRONMENT: {{ .Values.config.environment | quote }}
|
||||
BANANAS_HTTP_ADDR: ":8080"
|
||||
BANANAS_PUBLIC_BASE_URL: {{ include "go-bananas.publicUrl" . | quote }}
|
||||
BANANAS_BASE_PATH: {{ include "go-bananas.basePath" . | quote }}
|
||||
BANANAS_STATIC_DIR: "/srv/web"
|
||||
BANANAS_SDK_DIR: "/srv/sdk"
|
||||
|
||||
BANANAS_LOG_LEVEL: {{ .Values.config.logLevel | quote }}
|
||||
BANANAS_LOG_FORMAT: {{ .Values.config.logFormat | quote }}
|
||||
|
||||
# ---------------------------------------------------------------- storage ---
|
||||
BANANAS_DATABASE_PATH: {{ include "go-bananas.databasePath" . | quote }}
|
||||
BANANAS_DB_MAX_CONNS: {{ .Values.config.database.maxConns | quote }}
|
||||
BANANAS_DB_BUSY_TIMEOUT: {{ printf "%dms" (int .Values.config.database.busyTimeoutMs) | quote }}
|
||||
BANANAS_SEED_ON_START: "true"
|
||||
|
||||
# ------------------------------------------------------------------- http ---
|
||||
BANANAS_HTTP_READ_TIMEOUT: "15s"
|
||||
BANANAS_HTTP_WRITE_TIMEOUT: "30s"
|
||||
BANANAS_HTTP_IDLE_TIMEOUT: "120s"
|
||||
BANANAS_SHUTDOWN_TIMEOUT: {{ printf "%dms" (int .Values.config.shutdownTimeoutMs) | quote }}
|
||||
BANANAS_RATE_LIMIT_PER_MINUTE: {{ .Values.config.rateLimitPerMinute | quote }}
|
||||
BANANAS_ENABLE_METRICS: {{ .Values.config.enableMetrics | quote }}
|
||||
BANANAS_ENABLE_PROFILER: {{ .Values.config.enableProfiler | quote }}
|
||||
|
||||
# Rendered as a comma-separated list because that is what the config loader
|
||||
# parses. An empty value is deliberate: it means forwarding headers are ignored,
|
||||
# so the audit log records the real peer rather than a spoofable header.
|
||||
BANANAS_TRUSTED_PROXIES: {{ join "," .Values.config.trustedProxies | quote }}
|
||||
BANANAS_CORS_ALLOWED_ORIGINS: {{ join "," .Values.config.corsAllowedOrigins | quote }}
|
||||
|
||||
# ----------------------------------------------------------------- runtime ---
|
||||
BANANAS_RUNTIME_PUBLIC: "true"
|
||||
BANANAS_RUNTIME_CACHE_CONTROL: "public, max-age=60, s-maxage=300, stale-while-revalidate=86400"
|
||||
# Set this when assets are served from a CDN, so the manifest hands consumers the
|
||||
# CDN URL rather than this service.
|
||||
BANANAS_RUNTIME_CDN_BASE_URL: {{ .Values.config.runtimeCdnBaseUrl | default "" | quote }}
|
||||
|
||||
# ------------------------------------------------------------- identity ---
|
||||
BANANAS_AUTH_MODE: {{ .Values.identity.mode | quote }}
|
||||
|
||||
{{- if eq .Values.identity.mode "oidc" }}
|
||||
BANANAS_OIDC_ISSUER: {{ .Values.identity.oidc.issuer | quote }}
|
||||
BANANAS_OIDC_CLIENT_ID: {{ .Values.identity.oidc.clientId | quote }}
|
||||
BANANAS_OIDC_SCOPES: {{ join "," .Values.identity.oidc.scopes | quote }}
|
||||
BANANAS_OIDC_REDIRECT_URL: {{ .Values.identity.oidc.redirectURL | default "" | quote }}
|
||||
BANANAS_OIDC_POST_LOGOUT_REDIRECT_URL: {{ .Values.identity.oidc.postLogoutRedirectURL | default "" | quote }}
|
||||
{{- with .Values.identity.oidc.adminGroups }}
|
||||
BANANAS_OIDC_ADMIN_GROUPS: {{ join "," . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.identity.oidc.editorGroups }}
|
||||
BANANAS_OIDC_EDITOR_GROUPS: {{ join "," . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.identity.oidc.viewerGroups }}
|
||||
BANANAS_OIDC_VIEWER_GROUPS: {{ join "," . | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.identity.oidc.skipTLSVerify }}
|
||||
BANANAS_OIDC_SKIP_TLS_VERIFY: "true"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if eq .Values.identity.mode "dev" }}
|
||||
# Development bypass: every request is a local administrator. The application
|
||||
# refuses to start in this mode outside a development environment, so this block
|
||||
# is refused by config.environment: production above.
|
||||
BANANAS_DEV_FALLBACK_ROLE: "admin"
|
||||
{{- end }}
|
||||
|
||||
# ----------------------------------------------------------------- session ---
|
||||
BANANAS_SESSION_COOKIE_NAME: "go-bananas-session"
|
||||
BANANAS_SESSION_TTL: {{ printf "%dh" (int .Values.config.sessionTTLHours) | quote }}
|
||||
BANANAS_SESSION_SECURE: {{ .Values.service.sessionSecure | quote }}
|
||||
BANANAS_SESSION_SAMESITE: {{ .Values.service.sessionSameSite | quote }}
|
||||
218
deploy/helm/go-bananas/templates/deployment.yaml
Normal file
218
deploy/helm/go-bananas/templates/deployment.yaml
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
{{- include "go-bananas.validate" . }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: server
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.deployment.replicaCount }}
|
||||
{{- end }}
|
||||
revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }}
|
||||
minReadySeconds: {{ .Values.deployment.minReadySeconds }}
|
||||
progressDeadlineSeconds: {{ .Values.deployment.progressDeadlineSeconds }}
|
||||
strategy:
|
||||
{{- toYaml .Values.deployment.strategy | nindent 4 }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "go-bananas.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
# Rolling the pods when the ConfigMap changes is what makes `helm upgrade`
|
||||
# actually apply a configuration change: without this annotation the
|
||||
# Deployment spec is unchanged and Kubernetes would not restart anything.
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
{{- if and .Values.persistence.enabled (eq .Values.persistence.type "persistentVolumeClaim") (not .Values.persistence.existingClaim) (gt (int .Values.deployment.replicaCount) 1) }}
|
||||
# A ReadWriteOnce volume can be mounted by one pod at a time. With more than
|
||||
# one replica the others will fail to start unless the storage class
|
||||
# supports concurrent mounts. Set deployment.replicaCount to 1, or use a
|
||||
# ReadWriteMany filesystem.
|
||||
go-bananas.urpi.be/storage-note: >-
|
||||
ReadWriteOnce volume with {{ .Values.deployment.replicaCount }} replicas.
|
||||
Only one replica can mount it unless the storage class permits concurrent
|
||||
mounts.
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.identity.mode "oidc") .Values.identity.oidc.generateSecret }}
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "go-bananas.selectorLabels" . | nindent 8 }}
|
||||
{{- with .Values.deployment.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "go-bananas.serviceAccountName" . }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.global.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.priorityClassName }}
|
||||
priorityClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.hostAliases }}
|
||||
hostAliases:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds }}
|
||||
{{- if .Values.deployment.lifecycle.preStop.enabled }}
|
||||
# A preStop sleep gives the Service endpoints controller time to remove this
|
||||
# pod from the endpoint list before the process starts refusing connections.
|
||||
# Without it, a rolling update can drop requests that were already routed.
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command: ["/bin/sh", "-c", "sleep {{ .Values.deployment.lifecycle.preStop.sleepSeconds }}"]
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: go-bananas
|
||||
image: {{ include "go-bananas.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- with .Values.deployment.extraArgs }}
|
||||
args:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
{{- if ne .Values.identity.mode "disabled" }}
|
||||
env:
|
||||
{{- /*
|
||||
Secrets are injected through env rather than a mounted volume. Both
|
||||
are equally protected by the kubelet; env avoids the temptation to
|
||||
read the value from a file path into a log line, and the application
|
||||
never echoes it back.
|
||||
*/}}
|
||||
- name: BANANAS_SESSION_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "go-bananas.secretName" . }}
|
||||
key: {{ include "go-bananas.sessionSecretKey" . }}
|
||||
optional: false
|
||||
{{- if and (eq .Values.identity.mode "oidc") (not (empty (include "go-bananas.usesExistingSecret" .))) }}
|
||||
- name: BANANAS_OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "go-bananas.secretName" . }}
|
||||
key: {{ include "go-bananas.clientSecretKey" . }}
|
||||
optional: false
|
||||
{{- end }}
|
||||
{{- with .Values.extraEnv }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
{{- if .Values.probes.startup.enabled }}
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.startup.path }}
|
||||
port: http
|
||||
periodSeconds: {{ .Values.probes.startup.periodSeconds }}
|
||||
failureThreshold: {{ .Values.probes.startup.failureThreshold }}
|
||||
{{- end }}
|
||||
{{- if .Values.probes.liveness.enabled }}
|
||||
# Liveness deliberately uses /healthz, which does not touch the database.
|
||||
# A probe that fails on a slow database would have the kubelet restart a
|
||||
# process that is otherwise healthy, turning a dependency blip into an
|
||||
# outage.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.liveness.path }}
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
|
||||
{{- end }}
|
||||
{{- if .Values.probes.readiness.enabled }}
|
||||
# Readiness does check the database: a pod that cannot reach it cannot
|
||||
# serve anything, and removing it from the endpoints is the correct
|
||||
# response.
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.readiness.path }}
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: {{ .Values.persistence.mountPath }}
|
||||
{{- /*
|
||||
The root filesystem is read-only, so /tmp has to be an explicit
|
||||
emptyDir. Go writes temporary files there during compression and
|
||||
the SQLite driver uses it for spill, so without this the process
|
||||
fails in ways that look unrelated.
|
||||
*/}}
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{{- with .Values.extraVolumeMounts }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: data
|
||||
{{- if not .Values.persistence.enabled }}
|
||||
emptyDir: {}
|
||||
{{- else if .Values.persistence.existingClaim }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.persistence.existingClaim }}
|
||||
readOnly: false
|
||||
{{- else if eq .Values.persistence.type "persistentVolumeClaim" }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "go-bananas.dataVolumeName" . }}
|
||||
readOnly: false
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 32Mi
|
||||
{{- with .Values.extraVolumes }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.topologySpreadConstraints }}
|
||||
topologySpreadConstraints:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
145
deploy/helm/go-bananas/templates/hpa.yaml
Normal file
145
deploy/helm/go-bananas/templates/hpa.yaml
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- with .Values.autoscaling.behavior }}
|
||||
behavior:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.podDisruptionBudget.enabled }}
|
||||
---
|
||||
{{/*
|
||||
A PodDisruptionBudget only earns its keep if it does not also block a node drain.
|
||||
minAvailable of 1 with two replicas permits exactly one disruption, which is the
|
||||
useful setting: a drain can proceed, but not all pods at once.
|
||||
*/}}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
|
||||
{{- else }}
|
||||
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
|
||||
{{- end }}
|
||||
{{- with .Values.podDisruptionBudget.unhealthyPodEvictionPolicy }}
|
||||
unhealthyPodEvictionPolicy: {{ . }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "go-bananas.selectorLabels" . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- if .Values.networkPolicy.enabled }}
|
||||
---
|
||||
{{/*
|
||||
Default-deny ingress with explicit allowances.
|
||||
|
||||
The default posture is deny: the administration API performs writes, so a cluster
|
||||
where every pod can reach it means every pod can change every theme. The two
|
||||
allowances are the ingress controller, which is how real users arrive, and the
|
||||
metrics paths, so Prometheus can still scrape.
|
||||
*/}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "go-bananas.selectorLabels" . | nindent 6 }}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
{{- if .Values.networkPolicy.allowedNamespaces }}
|
||||
- from:
|
||||
{{- range .Values.networkPolicy.allowedNamespaces }}
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ . }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
{{- else }}
|
||||
# An empty allowedNamespaces list means the cluster's ingress controllers are
|
||||
# allowed, identified by their well-known labels rather than by a namespace
|
||||
# name, which differs between distributions.
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
{{- end }}
|
||||
{{- if .Values.config.enableMetrics }}
|
||||
- from:
|
||||
{{- if .Values.networkPolicy.allowedNamespaces }}
|
||||
{{- range .Values.networkPolicy.allowedNamespaces }}
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ . }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
- namespaceSelector: {}
|
||||
{{- end }}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.extraManifests }}
|
||||
---
|
||||
{{ toYaml . }}
|
||||
{{- end }}
|
||||
43
deploy/helm/go-bananas/templates/ingress.yaml
Normal file
43
deploy/helm/go-bananas/templates/ingress.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{{- if .Values.ingress.enabled }}
|
||||
{{- $fullName := include "go-bananas.fullname" . }}
|
||||
{{- $servicePort := .Values.service.port }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- if or .Values.ingress.annotations .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.ingress.className }}
|
||||
ingressClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType | default "Prefix" }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $servicePort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
161
deploy/helm/go-bananas/templates/pvc.yaml
Normal file
161
deploy/helm/go-bananas/templates/pvc.yaml
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
{{- if .Values.persistence.enabled }}
|
||||
{{- if eq .Values.persistence.type "persistentVolumeClaim" }}
|
||||
{{- if not .Values.persistence.existingClaim }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "go-bananas.dataVolumeName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- /*
|
||||
Retain the volume when the release is uninstalled. The themes in it are the
|
||||
only copy of an operator's work: deleting a volume on uninstall would make
|
||||
`helm uninstall` destructive in a way that is very easy to do by accident.
|
||||
*/}}
|
||||
helm.sh/resource-policy: keep
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
{{- with .Values.persistence.storageClassName }}
|
||||
storageClassName: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and .Values.persistence.snapshot.enabled (eq .Values.persistence.type "persistentVolumeClaim") }}
|
||||
---
|
||||
{{/*
|
||||
A scheduled SQLite snapshot.
|
||||
|
||||
Three things this gets right that the obvious implementation does not:
|
||||
|
||||
1. *It is a CronJob, not a Helm hook.* Combining `schedule` with
|
||||
`helm.sh/hook: pre-upgrade` is a contradiction: a hook runs on every upgrade
|
||||
regardless of the schedule, and with `hook-succeeded` the resource is then
|
||||
deleted. As a hook it would both ignore its own schedule and never produce
|
||||
anything to restore.
|
||||
|
||||
2. *The snapshots land on the same volume.* An `emptyDir` destination would be
|
||||
discarded the moment the pod exits, which makes the whole exercise pointless.
|
||||
|
||||
3. *It runs the service's own `backup` command.* Copying a database file while a
|
||||
write is in flight produces a torn copy. `go-bananas backup` uses SQLite's
|
||||
VACUUM INTO, which reads through the database's own backup machinery and is
|
||||
consistent without blocking writers. Using the application binary rather than a
|
||||
`sqlite3` from some other image also guarantees the SQLite library is the same
|
||||
version the schema was written for, and means no third-party image and no
|
||||
package installation into a read-only root filesystem.
|
||||
|
||||
The known limitation is stated rather than hidden: the job mounts the same
|
||||
ReadWriteOnce volume as the Deployment, so on a cluster whose CSI driver enforces
|
||||
single-node attachment strictly the job can fail to mount while the application is
|
||||
running. If your cluster reports MultiAttach errors, give the job a node affinity
|
||||
that pins it alongside the running pod, or take snapshots from outside the cluster.
|
||||
A release using `emptyDir` data has nothing to snapshot and is unaffected.
|
||||
|
||||
Restoring is manual, because a restore should be a deliberate act:
|
||||
|
||||
kubectl -n <namespace> exec <pod> -- /usr/local/bin/go-bananas backup --to /tmp/check.db
|
||||
# then stop the service, replace the database file, and start it again.
|
||||
*/}}
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}-snapshot
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
schedule: {{ .Values.persistence.snapshot.schedule | quote }}
|
||||
concurrencyPolicy: Forbid
|
||||
# Snapshots are named with a timestamp, so two runs cannot collide, and an
|
||||
# overlap is a sign the volume is too slow rather than a problem to solve by
|
||||
# running them at once.
|
||||
startingDeadlineSeconds: 600
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 8 }}
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "go-bananas.selectorLabels" . | nindent 12 }}
|
||||
app.kubernetes.io/component: snapshot
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
containers:
|
||||
- name: snapshot
|
||||
# The service image itself: the snapshot command is part of the
|
||||
# binary, so there is no second image to keep in step with this one.
|
||||
image: {{ include "go-bananas.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
env:
|
||||
- name: DATABASE_PATH
|
||||
value: {{ include "go-bananas.databasePath" . | quote }}
|
||||
- name: SNAPSHOT_RETENTION_DAYS
|
||||
value: {{ .Values.persistence.snapshot.retention | quote }}
|
||||
# A shell, because the snapshot filename needs a timestamp and an argv
|
||||
# list cannot expand one.
|
||||
command: ["/bin/sh", "-euc"]
|
||||
args:
|
||||
- |
|
||||
dest="/snapshot/go-bananas-$(date -u +%Y%m%dT%H%M%SZ).db"
|
||||
/usr/local/bin/go-bananas backup --to "$dest"
|
||||
|
||||
# Retention by age, run here rather than as a lifecycle hook so it
|
||||
# happens on the same schedule as the snapshot and cannot be skipped
|
||||
# by a container that failed to start. An old snapshot is removed even
|
||||
# though the Job that produced it has long since been garbage
|
||||
# collected.
|
||||
find /snapshot -name 'go-bananas-*.db' \
|
||||
-mtime "+$SNAPSHOT_RETENTION_DAYS" -delete
|
||||
ls -la /snapshot
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: {{ .Values.persistence.mountPath }}
|
||||
readOnly: true
|
||||
- name: snapshot
|
||||
# The writable half of the same volume. Only the snapshot
|
||||
# directory is writable; the database file itself is mounted
|
||||
# read-only above.
|
||||
mountPath: /snapshot
|
||||
subPath: snapshots
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.persistence.existingClaim | default (include "go-bananas.dataVolumeName" .) }}
|
||||
readOnly: true
|
||||
- name: snapshot
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.persistence.existingClaim | default (include "go-bananas.dataVolumeName" .) }}
|
||||
# Writable, and only for the subPath above. The pod as a whole can
|
||||
# still reach the database, which is why the read-only mount and the
|
||||
# `runAsNonRoot` security context are both kept.
|
||||
readOnly: false
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
63
deploy/helm/go-bananas/templates/secret.yaml
Normal file
63
deploy/helm/go-bananas/templates/secret.yaml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{{/*
|
||||
The Secret holding the session signing key and, unless an existing Secret is
|
||||
referenced, the OIDC client secret.
|
||||
|
||||
Two things are deliberate here:
|
||||
|
||||
* `lookup` is used to read an already-generated value back on upgrade. Without it,
|
||||
every `helm upgrade` would rotate the session secret and silently sign every
|
||||
user out. The client secret is not looked up: rotating a client secret is
|
||||
occasionally the intent, and an operator who wants to keep it uses
|
||||
`existingSecret`.
|
||||
* Values are base64-encoded, not stringData, because stringData is transformed by
|
||||
the API server and would make the stored manifest differ from what Helm holds,
|
||||
which in turn makes the upgrade detection above unreliable.
|
||||
*/}}
|
||||
{{- if and (eq .Values.identity.mode "oidc") .Values.identity.oidc.generateSecret }}
|
||||
{{- $secretName := include "go-bananas.secretName" . }}
|
||||
{{- $sessionKey := include "go-bananas.sessionSecretKey" . }}
|
||||
{{- $clientKey := include "go-bananas.clientSecretKey" . }}
|
||||
{{- $existing := lookup "v1" "Secret" .Release.Namespace $secretName }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $secretName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
# A hook annotation would be tempting, but a Secret must exist before the
|
||||
# Deployment starts and a pre-install hook cannot guarantee ordering with a
|
||||
# plain Deployment.
|
||||
helm.sh/resource-policy: keep
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
type: Opaque
|
||||
data:
|
||||
{{- /* Reuse the previous session secret so an upgrade does not sign everyone out. */}}
|
||||
{{- $session := "" }}
|
||||
{{- if $existing }}
|
||||
{{- $session = (index $existing.data $sessionKey | default "") }}
|
||||
{{- end }}
|
||||
{{- if $session }}
|
||||
{{ $sessionKey }}: {{ $session }}
|
||||
{{- else }}
|
||||
{{ $sessionKey }}: {{ randAlphaNum 64 | b64enc }}
|
||||
{{- end }}
|
||||
|
||||
{{- /*
|
||||
The OIDC client secret is only generated when one was not supplied out of
|
||||
band. Leaving it empty produces a deployment that fails to authenticate, which
|
||||
the application reports clearly at start-up rather than as a login loop.
|
||||
*/}}
|
||||
{{- $client := "" }}
|
||||
{{- if $existing }}
|
||||
{{- $client = (index $existing.data $clientKey | default "") }}
|
||||
{{- end }}
|
||||
{{- if $client }}
|
||||
{{ $clientKey }}: {{ $client }}
|
||||
{{- else }}
|
||||
{{ $clientKey }}: {{ "" | b64enc }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
64
deploy/helm/go-bananas/templates/service.yaml
Normal file
64
deploy/helm/go-bananas/templates/service.yaml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: server
|
||||
{{- if or .Values.service.annotations .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.service.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
{{- with .Values.service.clusterIP }}
|
||||
clusterIP: {{ . }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
|
||||
nodePort: {{ .Values.service.nodePort }}
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "go-bananas.selectorLabels" . | nindent 4 }}
|
||||
{{- if and (eq .Values.persistence.type "persistentVolumeClaim") (gt (int .Values.deployment.replicaCount) 1) }}
|
||||
# More than one replica against a ReadWriteOnce volume needs a rollout strategy
|
||||
# that does not overlap the two mounts. The values validator warns about this
|
||||
# combination; this annotation records the intent in the live object.
|
||||
sessionAffinity: None
|
||||
{{- end }}
|
||||
{{- with .Values.service.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if and .Values.metrics.serviceMonitor.enabled .Values.config.enableMetrics }}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- with .Values.metrics.serviceMonitor.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "go-bananas.selectorLabels" . | nindent 6 }}
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: {{ .Values.metrics.serviceMonitor.interval }}
|
||||
scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }}
|
||||
honorLabels: {{ .Values.metrics.serviceMonitor.honorLabels }}
|
||||
{{- end }}
|
||||
56
deploy/helm/go-bananas/templates/serviceaccount.yaml
Normal file
56
deploy/helm/go-bananas/templates/serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{{- if .Values.serviceAccount.create }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "go-bananas.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- if or .Values.serviceAccount.annotations .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- /*
|
||||
The API token is not mounted. The service talks to nothing inside the cluster:
|
||||
its database is a local file and its identity provider is external. Mounting a
|
||||
token it does not need would be a credential available to any container escape.
|
||||
*/}}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- end }}
|
||||
{{- if .Values.rbac.create }}
|
||||
---
|
||||
apiVersion: rbac.authoriz.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
{{- with .Values.global.extraAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- toYaml .Values.rbac.rules | nindent 2 }}
|
||||
---
|
||||
apiVersion: rbac.authoriz.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "go-bananas.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ include "go-bananas.fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "go-bananas.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
44
deploy/helm/go-bananas/values-dev.yaml
Normal file
44
deploy/helm/go-bananas/values-dev.yaml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Dev values for go-bananas
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: nexus.urpi.be/docker-hosted/go-bananas
|
||||
tag: "0.0.1"
|
||||
pullPolicy: Always
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
hosts:
|
||||
- host: bananas-dev.intern-urpi.be
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: go-bananas-tls
|
||||
hosts:
|
||||
- bananas-dev.intern-urpi.be
|
||||
|
||||
certManager:
|
||||
enabled: true
|
||||
issuerRef:
|
||||
name: letsencrypt-staging
|
||||
kind: ClusterIssuer
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
51
deploy/helm/go-bananas/values-lcl.yaml
Normal file
51
deploy/helm/go-bananas/values-lcl.yaml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Local (LCL) values for go-bananas.
|
||||
# Served at https://bananas-lcl.intern-urpi.be by the docker TLS edge.
|
||||
#
|
||||
# NOTE: this chart validates values against values.schema.json, which allows
|
||||
# only the keys defined there -- no certManager and no replicaCount here
|
||||
# (values-dev.yaml still carries both and fails `helm template`).
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
image:
|
||||
registry: localhost:5000
|
||||
repository: go-bananas
|
||||
tag: "0.0.1"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
# Local environment: development unlocks the local authentication bypass, so
|
||||
# the deployment works without the shared Keycloak realm at auth-dev.urpi.be
|
||||
# (go-loose does not serve /realms/... endpoints, so the oidc default cannot
|
||||
# be pointed at it either).
|
||||
config:
|
||||
environment: development
|
||||
publicBaseUrl: "https://bananas-lcl.intern-urpi.be"
|
||||
|
||||
identity:
|
||||
mode: dev
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: traefik
|
||||
annotations: {}
|
||||
hosts:
|
||||
- host: bananas-lcl.intern-urpi.be
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: local-dev-cert
|
||||
hosts:
|
||||
- bananas-lcl.intern-urpi.be
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
59
deploy/helm/go-bananas/values-prd-fixed.yaml
Normal file
59
deploy/helm/go-bananas/values-prd-fixed.yaml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# PRD values for go-bananas
|
||||
global:
|
||||
imagePullSecrets:
|
||||
- name: nexus-registry
|
||||
|
||||
image:
|
||||
registry: nexus.urpi.be
|
||||
repository: docker-hosted/go-bananas
|
||||
tag: "dev-1791360078"
|
||||
pullPolicy: Always
|
||||
|
||||
deployment:
|
||||
replicaCount: 1
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
hosts:
|
||||
- host: bananas.urpi.be
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: go-bananas-tls
|
||||
hosts:
|
||||
- bananas.urpi.be
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
|
||||
config:
|
||||
environment: production
|
||||
publicBaseUrl: "https://bananas.urpi.be"
|
||||
|
||||
identity:
|
||||
mode: oidc
|
||||
oidc:
|
||||
issuer: "https://auth.urpi.be/realms/urpi"
|
||||
clientId: "go-bananas"
|
||||
existingSecret: "go-bananas-auth"
|
||||
generateSecret: false
|
||||
scopes:
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
adminGroups: ["go-bananas-admin"]
|
||||
editorGroups: ["go-bananas-editor"]
|
||||
47
deploy/helm/go-bananas/values-prd.yaml
Normal file
47
deploy/helm/go-bananas/values-prd.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# PRD values for go-bananas
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
imagePullSecrets:
|
||||
- name: nexus-registry
|
||||
|
||||
image:
|
||||
repository: nexus.urpi.be/docker-hosted/go-bananas
|
||||
tag: "0.0.1"
|
||||
pullPolicy: Always
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
hosts:
|
||||
- host: bananas.urpi.be
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: go-bananas-tls
|
||||
hosts:
|
||||
- bananas.urpi.be
|
||||
|
||||
certManager:
|
||||
enabled: true
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
44
deploy/helm/go-bananas/values-tst.yaml
Normal file
44
deploy/helm/go-bananas/values-tst.yaml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# TST values for go-bananas
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: nexus.urpi.be/docker-hosted/go-bananas
|
||||
tag: "0.0.1"
|
||||
pullPolicy: Always
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
hosts:
|
||||
- host: bananas-tst.intern-urpi.be
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: go-bananas-tls
|
||||
hosts:
|
||||
- bananas-tst.intern-urpi.be
|
||||
|
||||
certManager:
|
||||
enabled: true
|
||||
issuerRef:
|
||||
name: letsencrypt-staging
|
||||
kind: ClusterIssuer
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
338
deploy/helm/go-bananas/values.schema.json
Normal file
338
deploy/helm/go-bananas/values.schema.json
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||
"title": "Go Bananas!! Helm values",
|
||||
"description": "Validation schema for the Go Bananas!! chart. Editors use this to offer completion and to catch a mistyped key before the chart is rendered.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["image", "identity", "config", "service"],
|
||||
"properties": {
|
||||
"global": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"imageRegistry": { "type": "string" },
|
||||
"imagePullSecrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": { "name": { "type": "string" } },
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"extraLabels": { "type": "object" },
|
||||
"extraAnnotations": { "type": "object" }
|
||||
}
|
||||
},
|
||||
|
||||
"nameOverride": { "type": "string", "maxLength": 63 },
|
||||
"fullnameOverride": { "type": "string", "maxLength": 63 },
|
||||
|
||||
"image": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["repository"],
|
||||
"properties": {
|
||||
"registry": { "type": "string" },
|
||||
"repository": { "type": "string", "minLength": 1 },
|
||||
"tag": { "type": "string" },
|
||||
"digest": { "type": "string" },
|
||||
"pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"] }
|
||||
}
|
||||
},
|
||||
|
||||
"deployment": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"replicaCount": { "type": "integer", "minimum": 0, "maximum": 100 },
|
||||
"extraArgs": { "type": "array", "items": { "type": "string" } },
|
||||
"revisionHistoryLimit": { "type": "integer", "minimum": 0 },
|
||||
"minReadySeconds": { "type": "integer", "minimum": 0 },
|
||||
"progressDeadlineSeconds": { "type": "integer", "minimum": 1 },
|
||||
"terminationGracePeriodSeconds": { "type": "integer", "minimum": 0 },
|
||||
"strategy": { "type": "object" },
|
||||
"podAnnotations": { "type": "object" },
|
||||
"podLabels": { "type": "object" },
|
||||
"hostAliases": { "type": "array" },
|
||||
"nodeSelector": { "type": "object" },
|
||||
"tolerations": { "type": "array" },
|
||||
"affinity": { "type": "object" },
|
||||
"topologySpreadConstraints": { "type": "array" },
|
||||
"priorityClassName": { "type": "string" },
|
||||
"lifecycle": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"preStop": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"sleepSeconds": { "type": "integer", "minimum": 0, "maximum": 60 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"resources": { "type": "object" },
|
||||
|
||||
"config": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["environment", "publicBaseUrl"],
|
||||
"properties": {
|
||||
"environment": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "The deployment name. Anything other than a development or test value disables the dev authentication bypass."
|
||||
},
|
||||
"logLevel": { "enum": ["debug", "info", "warn", "error"] },
|
||||
"logFormat": { "enum": ["json", "text"] },
|
||||
"publicBaseUrl": {
|
||||
"type": "string",
|
||||
"pattern": "^https?://[^/]+",
|
||||
"description": "The externally reachable origin, with no trailing slash."
|
||||
},
|
||||
"basePath": {
|
||||
"type": "string",
|
||||
"pattern": "^$|^/",
|
||||
"description": "A route prefix for a path-based ingress. Empty or a leading-slash path."
|
||||
},
|
||||
"rateLimitPerMinute": { "type": "integer", "minimum": 0 },
|
||||
"trustedProxies": { "type": "array", "items": { "type": "string" } },
|
||||
"corsAllowedOrigins": { "type": "array", "items": { "type": "string" } },
|
||||
"enableMetrics": { "type": "boolean" },
|
||||
"enableProfiler": { "type": "boolean" },
|
||||
"shutdownTimeoutMs": { "type": "integer", "minimum": 0, "maximum": 300000 },
|
||||
"runtimeCdnBaseUrl": { "type": "string" },
|
||||
"database": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"maxConns": { "type": "integer", "minimum": 1, "maximum": 128 },
|
||||
"busyTimeoutMs": { "type": "integer", "minimum": 0, "maximum": 60000 }
|
||||
}
|
||||
},
|
||||
"sessionTTLHours": { "type": "integer", "minimum": 1, "maximum": 720 }
|
||||
}
|
||||
},
|
||||
|
||||
"persistence": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"type": { "enum": ["persistentVolumeClaim", "emptyDir"] },
|
||||
"accessMode": {
|
||||
"enum": ["ReadWriteOnce", "ReadWriteMany", "ReadWriteOncePod", "ReadOnlyMany"]
|
||||
},
|
||||
"size": { "type": "string", "pattern": "^[0-9]+(Ei|Pi|Ti|Gi|Mi|Ki|E|P|T|G|M|K)?$" },
|
||||
"storageClassName": { "type": "string" },
|
||||
"existingClaim": { "type": "string" },
|
||||
"mountPath": { "type": "string", "pattern": "^/" },
|
||||
"snapshot": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"schedule": { "type": "string", "minLength": 1 },
|
||||
"retention": { "type": "integer", "minimum": 1, "maximum": 365 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": { "enum": ["ClusterIP", "NodePort", "LoadBalancer"] },
|
||||
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
||||
"nodePort": { "type": ["integer", "string"] },
|
||||
"clusterIP": { "type": "string" },
|
||||
"sessionSecure": { "type": "boolean" },
|
||||
"sessionSameSite": { "enum": ["Lax", "Strict", "None"] },
|
||||
"annotations": { "type": "object" },
|
||||
"labels": { "type": "object" }
|
||||
}
|
||||
},
|
||||
|
||||
"ingress": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"className": { "type": "string" },
|
||||
"annotations": { "type": "object" },
|
||||
"hosts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["host"],
|
||||
"properties": {
|
||||
"host": { "type": "string", "minLength": 1 },
|
||||
"paths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": { "type": "string", "pattern": "^/" },
|
||||
"pathType": { "enum": ["Prefix", "Exact", "ImplementationSpecific"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["secretName"],
|
||||
"properties": {
|
||||
"secretName": { "type": "string" },
|
||||
"hosts": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"identity": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["mode"],
|
||||
"properties": {
|
||||
"mode": {
|
||||
"enum": ["oidc", "dev", "disabled"],
|
||||
"description": "oidc is the production choice. dev and disabled are refused by the application outside a development environment."
|
||||
},
|
||||
"sessionSecretKey": { "type": "string", "minLength": 1 },
|
||||
"oidc": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"issuer": { "type": "string", "pattern": "^https?://" },
|
||||
"clientId": { "type": "string" },
|
||||
"clientSecretKey": { "type": "string", "minLength": 1 },
|
||||
"existingSecret": { "type": "string" },
|
||||
"existingSecretKeys": { "type": "object" },
|
||||
"generateSecret": { "type": "boolean" },
|
||||
"scopes": { "type": "array", "items": { "type": "string" } },
|
||||
"adminGroups": { "type": "array", "items": { "type": "string" } },
|
||||
"editorGroups": { "type": "array", "items": { "type": "string" } },
|
||||
"viewerGroups": { "type": "array", "items": { "type": "string" } },
|
||||
"redirectURL": { "type": "string" },
|
||||
"postLogoutRedirectURL": { "type": "string" },
|
||||
"logoutURL": { "type": "string" },
|
||||
"skipTLSVerify": { "type": "boolean" },
|
||||
"timeoutSeconds": { "type": "integer", "minimum": 1, "maximum": 300 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"probes": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"liveness": { "$ref": "#/definitions/probe" },
|
||||
"readiness": { "$ref": "#/definitions/probe" },
|
||||
"startup": { "$ref": "#/definitions/probe" }
|
||||
}
|
||||
},
|
||||
|
||||
"autoscaling": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"minReplicas": { "type": "integer", "minimum": 1 },
|
||||
"maxReplicas": { "type": "integer", "minimum": 1 },
|
||||
"targetCPUUtilizationPercentage": { "type": "integer", "minimum": 1, "maximum": 100 },
|
||||
"targetMemoryUtilizationPercentage": { "type": "integer", "minimum": 0, "maximum": 100 },
|
||||
"behavior": { "type": "object" }
|
||||
}
|
||||
},
|
||||
|
||||
"podDisruptionBudget": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"minAvailable": { "type": ["integer", "string"] },
|
||||
"maxUnavailable": { "type": ["integer", "string"] },
|
||||
"unhealthyPodEvictionPolicy": { "enum": ["IfHealthyBudget", "AlwaysAllow"] }
|
||||
}
|
||||
},
|
||||
|
||||
"serviceAccount": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"create": { "type": "boolean" },
|
||||
"name": { "type": "string" },
|
||||
"annotations": { "type": "object" },
|
||||
"automountServiceAccountToken": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
|
||||
"rbac": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"create": { "type": "boolean" },
|
||||
"rules": { "type": "array" }
|
||||
}
|
||||
},
|
||||
|
||||
"networkPolicy": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"allowedNamespaces": { "type": "array", "items": { "type": "string" } },
|
||||
"metricsPaths": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
|
||||
"podSecurityContext": { "type": "object" },
|
||||
"securityContext": { "type": "object" },
|
||||
"extraEnv": { "type": "array" },
|
||||
"extraVolumes": { "type": "array" },
|
||||
"extraVolumeMounts": { "type": "array" },
|
||||
|
||||
"metrics": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"serviceMonitor": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"interval": { "type": "string" },
|
||||
"scrapeTimeout": { "type": "string" },
|
||||
"labels": { "type": "object" },
|
||||
"honorLabels": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"extraManifests": { "type": "array" }
|
||||
},
|
||||
|
||||
"definitions": {
|
||||
"probe": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"path": { "type": "string", "pattern": "^/" },
|
||||
"initialDelaySeconds": { "type": "integer", "minimum": 0 },
|
||||
"periodSeconds": { "type": "integer", "minimum": 1 },
|
||||
"timeoutSeconds": { "type": "integer", "minimum": 1 },
|
||||
"failureThreshold": { "type": "integer", "minimum": 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
481
deploy/helm/go-bananas/values.yaml
Normal file
481
deploy/helm/go-bananas/values.yaml
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
# Go Bananas!! Helm values
|
||||
#
|
||||
# Every value has a safe default so that `helm install` works with no arguments at
|
||||
# all. The exceptions are the authentication credentials, which are required when
|
||||
# auth.mode is oidc and are supplied through an existing Secret.
|
||||
#
|
||||
# The defaults are tuned for a small internal deployment. Before running this in
|
||||
# production, read the annotated sections: replica count, the ingress, the resource
|
||||
# requests and the persistence choice all deserve a deliberate decision.
|
||||
|
||||
# ------------------------------------------------------------------ global ---
|
||||
|
||||
global:
|
||||
# imageRegistry overrides the registry for every image the chart pulls. Used by a
|
||||
# cluster with a mirrored registry or an air-gapped pull-through cache.
|
||||
imageRegistry: ""
|
||||
# imagePullSecrets are added to every ServiceAccount.
|
||||
imagePullSecrets:
|
||||
- name: nexus-registry
|
||||
# extraLabels are added to every object the chart creates, for cost allocation
|
||||
# and for a Prometheus `release` label if the cluster does not add one.
|
||||
extraLabels: {}
|
||||
# extraAnnotations are added to every object.
|
||||
extraAnnotations: {}
|
||||
|
||||
# ------------------------------------------------------------------- image ---
|
||||
|
||||
image:
|
||||
registry: nexus.urpi.be
|
||||
repository: docker-hosted/go-bananas
|
||||
# tag defaults to the chart's appVersion, so upgrading the chart upgrades the
|
||||
# image. Set it to an immutable digest for a reproducible deployment.
|
||||
tag: "0.0.1"
|
||||
# digest pins the image by content. Takes precedence over tag when set, which is
|
||||
# the strongest guarantee that a rollout is running exactly what was reviewed.
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# --------------------------------------------------------------- deployment ---
|
||||
|
||||
deployment:
|
||||
# Two replicas by default: the service is stateless apart from the SQLite file,
|
||||
# so horizontal scaling is only safe when persistence is a shared volume or a
|
||||
# network filesystem. See the persistence section.
|
||||
replicaCount: 2
|
||||
|
||||
# extraArgs are appended to the container command. The binary takes no positional
|
||||
# arguments; this exists for future flags without a chart change.
|
||||
extraArgs: []
|
||||
|
||||
# revisionHistoryLimit keeps the last N ReplicaSets so a rollback is possible
|
||||
# without the Deployment controller's default of ten consuming etcd history.
|
||||
revisionHistoryLimit: 5
|
||||
|
||||
# minReadySeconds makes a new pod count as ready only after it has been healthy
|
||||
# for this long, which stops a rolling update from proceeding on the strength of
|
||||
# a single lucky probe.
|
||||
minReadySeconds: 10
|
||||
|
||||
# progressDeadlineSeconds aborts a rollout that cannot complete, rather than
|
||||
# leaving a Deployment stuck in progress indefinitely.
|
||||
progressDeadlineSeconds: 600
|
||||
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
|
||||
# hostAliases is mostly useful for a development cluster where the identity
|
||||
# provider is not in public DNS.
|
||||
hostAliases: []
|
||||
|
||||
# terminationGracePeriodSeconds must exceed the application's shutdown timeout
|
||||
# (config.shutdownTimeoutMs) or in-flight requests are cut off mid-response.
|
||||
# The chart refuses to render when it does not.
|
||||
terminationGracePeriodSeconds: 45
|
||||
|
||||
# A preStop sleep gives the Service endpoints controller time to remove this pod
|
||||
# from the endpoint list before the process starts refusing connections.
|
||||
# Without it, a rolling update drops requests that were already routed.
|
||||
lifecycle:
|
||||
preStop:
|
||||
enabled: true
|
||||
sleepSeconds: 3
|
||||
|
||||
# nodeSelector, tolerations, affinity and topologySpreadConstraints are passed
|
||||
# through to the pod spec unchanged.
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
|
||||
# priorityClassName for a cluster where the API tier matters.
|
||||
priorityClassName: ""
|
||||
|
||||
# ---------------------------------------------------------------- resources ---
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 96Mi
|
||||
limits:
|
||||
# No CPU limit is set deliberately. A CPU limit throttles the process when the
|
||||
# node is busy, which for a latency-sensitive asset server produces exactly the
|
||||
# tail latency it is meant to prevent. The memory limit is the one that matters:
|
||||
# it is what keeps a runaway cache from taking the node down.
|
||||
memory: 384Mi
|
||||
|
||||
# ------------------------------------------------------------------- config ---
|
||||
|
||||
config:
|
||||
# environment is the deployment name. Anything other than development or test
|
||||
# turns off the dev authentication bypass and the TLS-skip switch.
|
||||
environment: production
|
||||
|
||||
logLevel: info
|
||||
# json in production so the log is structured for a collector.
|
||||
logFormat: json
|
||||
|
||||
# publicBaseUrl is the externally reachable origin, with no trailing slash.
|
||||
# It is used to build the OIDC redirect URI, the absolute URLs in the runtime
|
||||
# manifest and the HSTS decision, so getting it wrong means a login loop.
|
||||
#
|
||||
# The default points at a local hostname so that `helm install` works with no
|
||||
# arguments. Override it for any real deployment.
|
||||
publicBaseUrl: "http://themes.go-bananas.local"
|
||||
|
||||
# basePath prefixes every route, for deployments behind a path-based ingress
|
||||
# such as https://example.com/themes/. Leave empty for the default of /.
|
||||
basePath: ""
|
||||
|
||||
# rateLimitPerMinute caps requests per client per minute on the runtime API.
|
||||
# The default is high because the legitimate pattern is one request per page
|
||||
# view; the limit exists to stop abuse, not to ration traffic.
|
||||
rateLimitPerMinute: 3000
|
||||
|
||||
# trustedProxies are the CIDRs whose X-Forwarded-For header may be believed.
|
||||
# Empty means forwarding headers are ignored entirely and the direct peer is
|
||||
# recorded, which is the safe default. Set this to the ingress controller's
|
||||
# address or the audit log will record the ingress pod instead of the client.
|
||||
trustedProxies: []
|
||||
|
||||
# corsAllowedOrigins are browser origins allowed to call the admin API
|
||||
# cross-origin. The runtime API is public by design and is not affected.
|
||||
corsAllowedOrigins: []
|
||||
|
||||
# enableMetrics exposes /metrics in Prometheus format.
|
||||
enableMetrics: true
|
||||
# enableProfiler exposes net/http/pprof. Off by default: a profiler on a
|
||||
# network-reachable endpoint is a denial-of-service surface, so it is opt-in and
|
||||
# should be paired with a NetworkPolicy.
|
||||
enableProfiler: false
|
||||
|
||||
# shutdownTimeoutMs bounds graceful shutdown. Must be lower than
|
||||
# deployment.terminationGracePeriodSeconds.
|
||||
shutdownTimeoutMs: 25000
|
||||
|
||||
# runtimeCdnBaseUrl is the absolute base the runtime manifest hands to
|
||||
# consumers. Set it when the stylesheets are served from a CDN or a separate
|
||||
# asset host, so that consumers fetch from there instead of from this service.
|
||||
runtimeCdnBaseUrl: ""
|
||||
|
||||
# database tuning. The defaults suit an embedded SQLite file; see docs on
|
||||
# persistence before changing them.
|
||||
database:
|
||||
# maxConns bounds the connection pool. SQLite serialises writers, so a small
|
||||
# pool is faster than a large one, not slower.
|
||||
maxConns: 8
|
||||
# busyTimeoutMs is how long a blocked writer waits before failing.
|
||||
busyTimeoutMs: 5000
|
||||
|
||||
# sessionTTLHours is how long an administrative session lasts. The session is a
|
||||
# signed cookie with no server-side record, so an expiry cannot be revoked
|
||||
# early — see the identity section for the trade-off this represents.
|
||||
sessionTTLHours: 8
|
||||
|
||||
# -------------------------------------------------------------- persistence ---
|
||||
#
|
||||
# The database is a single SQLite file. Two deployment shapes are supported:
|
||||
#
|
||||
# type: emptyDir A per-pod temporary file. Every theme change is lost when a
|
||||
# pod restarts. Correct only for a demo or for evaluating the
|
||||
# service; the built-in themes are re-seeded automatically so
|
||||
# the instance still comes up, but operator work is not kept.
|
||||
#
|
||||
# type: persistentVolumeClaim A ReadWriteOnce volume. Correct for a
|
||||
# single-replica deployment, which is the shape this chart
|
||||
# recommends in production.
|
||||
#
|
||||
# Note on replicas: RWO volumes cannot be attached to two pods at once. With
|
||||
# replicaCount > 1 and a PVC, only one pod mounts the volume successfully and the
|
||||
# others crash-loop. Running more than one replica therefore requires either
|
||||
# replicaCount: 1, or a filesystem that permits multiple mounts.
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
type: persistentVolumeClaim
|
||||
accessMode: ReadWriteOnce
|
||||
size: 2Gi
|
||||
# storageClassName: "" uses the cluster default.
|
||||
storageClassName: ""
|
||||
# existingClaim mounts a volume the operator manages. Its lifecycle is then the
|
||||
# operator's responsibility: this chart will not delete or resize it.
|
||||
existingClaim: ""
|
||||
mountPath: /srv/data
|
||||
|
||||
# A snapshot taken before an upgrade, when the upgrade involves a destructive
|
||||
# change to the schema. Restoring is a manual kubectl apply.
|
||||
snapshot:
|
||||
enabled: false
|
||||
# CronJob schedule in the cluster's timezone.
|
||||
schedule: "0 3 * * *"
|
||||
# How long to keep snapshots.
|
||||
retention: 14
|
||||
|
||||
# ------------------------------------------------------------------ service ---
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
# nodePort, only used when type is NodePort.
|
||||
nodePort: ""
|
||||
# ClusterIP: set to None for a headless service, which a StatefulSet needs.
|
||||
clusterIP: ""
|
||||
# The session cookie's Secure attribute and SameSite=None both require the
|
||||
# browser to see the service over HTTPS. Set to true when TLS terminates at the
|
||||
# ingress.
|
||||
sessionSecure: true
|
||||
sessionSameSite: Lax
|
||||
annotations: {}
|
||||
labels: {}
|
||||
|
||||
# ----------------------------------------------------------------- ingress ---
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: nginx
|
||||
annotations: {}
|
||||
hosts:
|
||||
- host: themes.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
# tls is empty for a plain-HTTP installation. For the usual production shape,
|
||||
# point it at a cert-manager cluster issuer so the certificate is renewed
|
||||
# without anyone remembering to.
|
||||
tls: []
|
||||
# - secretName: go-bananas-tls
|
||||
# hosts:
|
||||
# - themes.example.com
|
||||
|
||||
# ---------------------------------------------------------------- identity ---
|
||||
#
|
||||
# This is the part that most often goes wrong in a deployment, so it is worth being
|
||||
# explicit about the three modes.
|
||||
|
||||
identity:
|
||||
# disabled No authentication. Every request is granted administrator access.
|
||||
# Refused by the application outside a development environment, so
|
||||
# this cannot be deployed to production by accident.
|
||||
# dev Every request is authenticated as a local administrator.
|
||||
# Also refused outside development.
|
||||
# oidc OpenID Connect against a real provider. The production choice.
|
||||
mode: oidc
|
||||
|
||||
# sessionSecretKey is the key inside the Secret that holds the session signing
|
||||
# secret. The secret must be at least 32 bytes.
|
||||
#
|
||||
# kubectl create secret generic go-bananas-auth \
|
||||
# --from-literal=session-secret="$(openssl rand -base64 48)" \
|
||||
# --from-literal=oidc-client-secret="..."
|
||||
#
|
||||
# The secret is never templated into a ConfigMap or written to a log. Rotating
|
||||
# it invalidates every open session at once.
|
||||
sessionSecretKey: session-secret
|
||||
|
||||
oidc:
|
||||
# issuer is the OIDC issuer URL. Everything else — the authorization,
|
||||
# token, userinfo and JWKS endpoints — is discovered from
|
||||
# /.well-known/openid-configuration, so this is the only provider-specific
|
||||
# value needed.
|
||||
#
|
||||
# The default is the shared development realm. The realm path is part of the
|
||||
# issuer: this string must match the `iss` claim in the ID token exactly,
|
||||
# including a trailing slash if the provider emits one. A mismatch here is the
|
||||
# single most common cause of "invalid issuer" at the callback.
|
||||
issuer: "https://auth-dev.urpi.be/realms/urpi"
|
||||
|
||||
# clientId is the client identifier registered with the provider.
|
||||
clientId: "go-bananas"
|
||||
|
||||
# clientSecretKey names the key inside existingSecret (or generatedSecret)
|
||||
# holding the client secret. Referencing it by key name rather than by value
|
||||
# is what keeps the secret out of values.yaml.
|
||||
clientSecretKey: oidc-client-secret
|
||||
|
||||
# existingSecret is a Secret you manage. Preferred for production: the chart
|
||||
# will not create, rotate or delete it, and its contents never appear in a
|
||||
# rendered manifest.
|
||||
#
|
||||
# kubectl create secret generic go-bananas-auth \
|
||||
# --from-literal=session-secret="$(openssl rand -base64 48)" \
|
||||
# --from-literal=oidc-client-secret="…"
|
||||
existingSecret: ""
|
||||
# existingSecretKeys overrides the key names inside existingSecret, for a
|
||||
# Secret managed by an external secrets operator with its own naming.
|
||||
existingSecretKeys: {}
|
||||
|
||||
# generatedSecret creates a Secret owned by this release. The session secret is
|
||||
# preserved across upgrades by reading the live Secret back, so an upgrade does
|
||||
# not sign every user out.
|
||||
#
|
||||
# The OIDC client secret cannot be generated — it has to come from the provider.
|
||||
# A generated Secret therefore contains an empty client secret, which means the
|
||||
# deployment will fail to authenticate until one is supplied. For anything real,
|
||||
# use existingSecret.
|
||||
generateSecret: true
|
||||
|
||||
# scopes requested in addition to openid. The email scope is what makes
|
||||
# `email` appear in the session; groups is what the role mapping reads.
|
||||
scopes:
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
|
||||
# Group-to-role mapping, read from the `groups` claim.
|
||||
#
|
||||
# The most privileged matching group wins; a user in no configured group gets
|
||||
# viewer. The default is deliberately least privilege, so a mapping mistake
|
||||
# fails closed rather than granting access nobody intended.
|
||||
adminGroups: []
|
||||
editorGroups: []
|
||||
viewerGroups: []
|
||||
# With all three empty, every authenticated user is a viewer. Populate these
|
||||
# from the provider's actual group names before rolling this out to real users;
|
||||
# the group names are realm-specific and are not guessed here.
|
||||
|
||||
# redirectURL overrides the computed callback. Leave empty unless the
|
||||
# provider has a different redirect URI registered, which happens behind a
|
||||
# path-based ingress or when several environments share a provider client.
|
||||
redirectURL: ""
|
||||
|
||||
postLogoutRedirectURL: ""
|
||||
# End-session endpoint, for providers that do not follow the RP-initiated
|
||||
# logout convention.
|
||||
logoutURL: ""
|
||||
|
||||
# skipTLSVerify disables certificate verification against the provider. It
|
||||
# exists so that a development provider presenting a self-signed certificate
|
||||
# can be used, and the application refuses to start with it enabled outside
|
||||
# development.
|
||||
skipTLSVerify: false
|
||||
|
||||
# timeout and insecureSkipVerify exist for the same development reason.
|
||||
timeoutSeconds: 15
|
||||
|
||||
# ------------------------------------------------------------------- probes ---
|
||||
|
||||
probes:
|
||||
liveness:
|
||||
enabled: true
|
||||
path: /healthz
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
readiness:
|
||||
enabled: true
|
||||
path: /readyz
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startup:
|
||||
enabled: true
|
||||
path: /healthz
|
||||
periodSeconds: 3
|
||||
failureThreshold: 30
|
||||
|
||||
# --------------------------------------------------------------- autoscaling ---
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 70
|
||||
# targetMemoryUtilizationPercentage is left unset by default: memory here is
|
||||
# dominated by the render cache, which grows with the number of themes rather
|
||||
# than with load, so scaling on it would respond to a content change rather than
|
||||
# to traffic.
|
||||
targetMemoryUtilizationPercentage: 0
|
||||
behavior: {}
|
||||
|
||||
# ------------------------------------------------------- disruption budgets ---
|
||||
#
|
||||
# A PDB only helps if it does not also block a node drain. minAvailable: 1 with two
|
||||
# replicas permits exactly one disruption, which is the useful setting.
|
||||
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
# maxUnavailable: 1
|
||||
# unhealthyPodEvictionPolicy: AlwaysAllow
|
||||
|
||||
# --------------------------------------------------------- service account ---
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: ""
|
||||
annotations: {}
|
||||
automountServiceAccountToken: false
|
||||
|
||||
rbac:
|
||||
# create is off by default: the service talks to nothing in the cluster, so the
|
||||
# default ServiceAccount needs no rules at all.
|
||||
create: false
|
||||
rules: []
|
||||
|
||||
# ------------------------------------------------------------ network policy ---
|
||||
#
|
||||
# A default-deny ingress with an explicit allowance for the ingress controller and
|
||||
# for Prometheus. Without this, any pod in the cluster can reach the admin API.
|
||||
|
||||
networkPolicy:
|
||||
enabled: false
|
||||
# ingressNamespaces restricts which namespaces may reach the service. Empty means
|
||||
# the whole cluster.
|
||||
allowedNamespaces: []
|
||||
# metricsPaths is the additional path allowed from the metrics sources.
|
||||
metricsPaths: ["/metrics"]
|
||||
|
||||
# ------------------------------------------------------------------- extras ---
|
||||
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
|
||||
# extraEnv is for one-off variables that do not warrant a chart value. Anything
|
||||
# added here is the operator's responsibility to keep consistent across replicas.
|
||||
extraEnv: []
|
||||
|
||||
# extraVolumes and extraVolumeMounts are for a sidecar or a config file the chart
|
||||
# does not model.
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
# metricsServiceMonitor creates a Prometheus Operator ServiceMonitor. Requires the
|
||||
# Prometheus Operator CRDs to be installed.
|
||||
metrics:
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
labels: {}
|
||||
honorLabels: true
|
||||
|
||||
# ------------------------------------------------------------------- extras ---
|
||||
#
|
||||
# `extraManifests` is an escape hatch for a resource this chart does not model. It
|
||||
# is templated, so a value can reference another template's output.
|
||||
extraManifests: []
|
||||
# - apiVersion: v1
|
||||
# kind: ConfigMap
|
||||
# metadata:
|
||||
# name: {{ include "go-bananas.fullname" . }}-extra
|
||||
# data:
|
||||
# example: "value"
|
||||
Loading…
Add table
Add a link
Reference in a new issue