Merge pull request #177 from jk-cluster/feat/template-chart-notes-and-157
template-chart: NOTES.txt and the naked-integer render fix (4.1.0+upxxx)
This commit is contained in:
4 files changed
+168
-5
No files matched your search
@@ -115,7 +115,7 @@ If the dependency dashboard reports `Failed to look up github-releases package
|
||||
|
||||
## Creating a new chart
|
||||
|
||||
1. Copy `template-chart/` to `<chart-name>/` and replace every `xxx` placeholder: template file names (`xxx.deployment.yaml` → `<app>.deployment.yaml`), `Chart.yaml`, the `values.yaml` keys, the `.Values.xxx.*` references inside the templates, **and both the `xxx.` helper prefixes and the values paths in `values.schema.json`**. Then walk the five points in "Values that must not lie" below — the schema and the null-safe fallbacks are copied with the template, but their content is chart-specific and wrong until you adapt it.
|
||||
1. Copy `template-chart/` to `<chart-name>/` and replace every `xxx` placeholder: template file names (`xxx.deployment.yaml` → `<app>.deployment.yaml`), `Chart.yaml`, the `values.yaml` keys, the `.Values.xxx.*` references inside the templates, **and both the `xxx.` helper prefixes and the values paths in `values.schema.json`**. Then walk the five points in "Values that must not lie" below — the schema and the null-safe fallbacks are copied with the template, but their content is chart-specific and wrong until you adapt it. The same goes for `templates/NOTES.txt`: it comes with the template as a worked example and is placeholder text until you replace it with what an operator of *your* app needs after the install (see "Everything else" below); its leading `{{/* … */}}` block explains the bar and is meant to be deleted.
|
||||
2. Add `.github/workflows/publish-<chart-name>.yml` (copy an existing one, e.g. `publish-outline.yml`).
|
||||
3. Add the chart name to the choice list in `.github/workflows/change-app-version.yml`.
|
||||
4. Add a `customManagers` block for the chart's main image in `renovate.json5` (copy a neighbouring one, swap `managerFilePatterns` and `depNameTemplate`) — without it the chart's app version is never updated. Check the image's tag list first: if it carries a flavour, date or prerelease suffix, add a `versioningTemplate` with a comment saying why.
|
||||
@@ -256,6 +256,6 @@ Reference implementation: `template-chart/values.schema.json` and `template-char
|
||||
|
||||
- `icon:` is mandatory in every `Chart.yaml`. **Documented exception — freesailarr, the only chart without one**: the rule everywhere else is "the icon is this chart's one upstream's logo", and a chart bundling nine independently versioned projects has no such upstream — picking one of the nine would imply a "main app" the stack deliberately does not have. That is the whole criterion, and it applies to no other chart in the repo: a single-upstream chart without an icon is a missing icon, not an exception. The omission costs an INFO line in `helm lint --strict` (`icon is recommended`), which still exits 0.
|
||||
- `helm lint --strict` must pass without findings.
|
||||
- `NOTES.txt` for every chart is planned (#43); eight charts ship one so far (`apache-tika`, `cyberchef`, `freesailarr`, `gotenberg`, `homeassistant`, `patchmon`, `rss-bridge`, `searxng`).
|
||||
- `NOTES.txt` for every chart is planned (#43); nine charts ship one so far (`apache-tika`, `bentopdf`, `cyberchef`, `freesailarr`, `gotenberg`, `homeassistant`, `patchmon`, `rss-bridge`, `searxng`). Reference implementation: `template-chart/templates/NOTES.txt`, which is a placeholder-carrying template rather than finished prose — a new chart adapts it instead of starting from an empty file. The bar it sets: print what an operator would otherwise have to look up in the templates (the rendered access URL, **which fields the 1Password item must carry**, the options actually active in this release, and the chart's known traps), conditional on the values really set. A `NOTES.txt` that only repeats the values is skimmed once and never read again. **No real domain anywhere** — the URL renders from `ingress.domain`, and where an example is unavoidable it is `example.com`. Adding one renders no Kubernetes object, so it is purely additive: a minor bump.
|
||||
- CI: every installable chart has fixtures under `ci/<chart>/` (`test-values.yaml`, optionally `fixtures.yaml`/`fixtures.env.yaml`/`settings.env`) or a justified entry in `ci/excluded-charts.txt` — the install-test fails if both are missing.
|
||||
- Charts have no dependencies (`charts/` directories are empty).
|
||||
@@ -5,7 +5,7 @@ icon: https://helm.sh/img/helm.svg
|
||||
|
||||
type: application
|
||||
|
||||
version: 4.0.0+upxxx
|
||||
version: 4.1.0+upxxx
|
||||
appVersion: "xxx"
|
||||
|
||||
maintainers:
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
{{/*
|
||||
NOTES.txt — the reference implementation for issue #43.
|
||||
|
||||
Copy this file with the rest of template-chart and replace every xxx: the
|
||||
values paths (.Values.xxx.*), the helper prefix ("xxx.replicas") and the app
|
||||
name in the headline, exactly as in the other template files. This comment
|
||||
block renders nothing and is meant to be deleted along the way.
|
||||
|
||||
THE STANDARD THIS FILE IS MEASURED AGAINST. A NOTES.txt that repeats the
|
||||
values is worthless - it is skimmed once after the first install and never
|
||||
read again. Print what an operator would otherwise have to look up in the
|
||||
templates: the rendered access URL, WHICH FIELDS the 1Password item has to
|
||||
carry, the options that are actually active in THIS release, and the traps of
|
||||
this chart. Keep it conditional on the values that are really set, so the
|
||||
output describes the installation at hand instead of every possible one.
|
||||
|
||||
NEVER PUT A REAL DOMAIN IN HERE. The URL is rendered from
|
||||
.Values.ingress.domain, so it comes from the operator's values and not from
|
||||
the chart; where an example is unavoidable, use example.com.
|
||||
|
||||
Note for the copier: a NOTES.txt renders no Kubernetes object, so adding one
|
||||
is purely additive - a MINOR bump, and safe to roll out at any time.
|
||||
*/}}
|
||||
{{- $replicas := include "xxx.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.xxx.replicas) -}}
|
||||
xxx {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||
|
||||
{{- if .Values.ingress.enabled }}
|
||||
|
||||
ACCESS
|
||||
https://{{ .Values.ingress.domain }}
|
||||
|
||||
The certificate is requested by cert-manager through the "{{ .Values.ingress.clusterIssuer }}"
|
||||
cluster issuer. Until it has been issued the browser shows a warning; follow it with
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get certificate tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
{{- else }}
|
||||
|
||||
ACCESS — NO INGRESS
|
||||
ingress.enabled is false, so this release has no Ingress and no certificate,
|
||||
and ingress.clusterIssuer is not needed. ingress.domain STAYS REQUIRED even
|
||||
so: the env block in values.yaml reads it for NORMAL_ENV, which is why that
|
||||
read carries a required() of its own. Without it the workload would start
|
||||
with an empty address and nothing would point at the mistake.
|
||||
|
||||
Reach the release inside the cluster with
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-{{ .Chart.Name }}-service 8080:80
|
||||
|
||||
and open http://localhost:8080.
|
||||
{{- end }}
|
||||
{{ if eq $replicas "0" }}
|
||||
SCALED TO ZERO
|
||||
{{ if .Values.scaleDown }}scaleDown: true{{ else }}xxx.replicas: 0{{ end }} — the workload runs with replicas: 0, so the endpoint
|
||||
above does not answer until you set it back and upgrade again. PVC, Secret{{ if .Values.ingress.enabled }},
|
||||
Service and Ingress stay in place, so the certificate is not re-issued.{{ else }} and
|
||||
Service stay in place.{{ end }}
|
||||
{{ end }}
|
||||
THE 1PASSWORD ITEM MUST CARRY THE FIELDS THE CHART READS
|
||||
secrets.xxxSecretRef points at
|
||||
{{ .Values.secrets.xxxSecretRef }}
|
||||
The 1Password operator materializes that item as the Kubernetes Secret
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-secret, with one Secret key per field of the item. So the
|
||||
fields are not free-form: this chart reads the field
|
||||
|
||||
secretKey (values.yaml, env entry ENV_FROM_SECRET)
|
||||
|
||||
and an item without it leaves the container stuck in
|
||||
CreateContainerConfigError. Watch the materialization with
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get onepassworditem {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
||||
kubectl -n {{ .Release.Namespace }} get secret {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
||||
|
||||
When you copy this chart, list YOUR chart's fields here. This is the one
|
||||
piece of information that is otherwise only findable by reading the
|
||||
templates, and it is the reason this section exists.
|
||||
|
||||
CHANGING THE SECRET DOES NOT RESTART ANYTHING HERE
|
||||
Neither editing the values inside the 1Password item nor repointing
|
||||
secrets.xxxSecretRef at a different item rolls the pods: this chart stamps
|
||||
checksum/config over the ConfigMap, but no checksum/secret-ref over the
|
||||
rendered OnePasswordItem. Helm never sees a secret's content, so the content
|
||||
case cannot be closed at all — but the ref case can, and a chart that needs
|
||||
it adds the second annotation (README, "Config changes must restart the
|
||||
workload"). Until then, after any secret change:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} rollout restart deployment/{{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
|
||||
CONFIGURATION
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-configmap is mounted into the workload, and the pod template
|
||||
carries checksum/config over it — so a changed ConfigMap really does roll the
|
||||
pods. That annotation is not decoration: a subPath mount is never refreshed
|
||||
by the kubelet, and most apps read their config only at start, so without it
|
||||
a config change would apply to nothing while everything stayed green.
|
||||
|
||||
The hash is taken over the rendered ConfigMap, not over a timestamp: an
|
||||
unchanged config yields an identical hash and the upgrade stays a no-op.
|
||||
|
||||
STORAGE
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-pvc, {{ .Values.storage.amount }}, {{ if .Values.storage.storageClass }}storage class "{{ .Values.storage.storageClass }}"{{ else }}on the cluster's default storage class
|
||||
(storage.storageClass is unset){{ end }}.
|
||||
|
||||
helm uninstall does NOT delete this PVC, which is deliberate — the data
|
||||
survives a reinstall of the same release name. It also means a release
|
||||
removed and recreated under a DIFFERENT name starts on an empty volume while
|
||||
the old one keeps occupying space.
|
||||
|
||||
WHAT A GREEN POD PROVES
|
||||
All three probes are HTTP GETs against "/" on the port named http. They
|
||||
prove that something answers on that port — not that the application is
|
||||
functional, and not that anything behind the Ingress works, because probes
|
||||
go straight to the pod and never pass through it.
|
||||
|
||||
When you copy this chart, point the probes at a real health endpoint and
|
||||
carry the probe's CONTEXT into the fallback defaults in _helpers.tpl, not
|
||||
just its timings: a default that loses a port name, a Host header or an exec
|
||||
command leaves the workload with a probe that cannot succeed.
|
||||
|
||||
THIS CHART IS A SCAFFOLD — IT IS NOT MEANT TO RUN
|
||||
If you are reading this from an actual install of template-chart, three
|
||||
things will keep it from becoming Ready, all of them on purpose:
|
||||
|
||||
- The image is the placeholder "ghcr.io/ORG/IMAGE:{{ .Chart.AppVersion }}" and does not
|
||||
exist, so the pods stay in ImagePullBackOff. This is also why the chart
|
||||
sits in ci/excluded-charts.txt and is never published.
|
||||
- The chart renders BOTH a Deployment and a StatefulSet, as the two shapes
|
||||
to choose between. Delete the one your app does not need; the Service
|
||||
selects the Deployment's pods, so a stateful app has to move that selector
|
||||
along with it.
|
||||
- The env block in values.yaml demonstrates the three supported forms
|
||||
(value, secretKeyRef, configMapKeyRef) — it is an example, not a working
|
||||
configuration. Note in particular that ENV_FROM_CONFIGMAP names
|
||||
"{{ .Release.Name }}-{{ .Chart.Name }}-config-map" while the ConfigMap this chart creates is
|
||||
"{{ .Release.Name }}-{{ .Chart.Name }}-configmap": replace the whole block with your app's real
|
||||
variables rather than repairing the example.
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
|
||||
EXTRA INGRESS ANNOTATIONS ARE ACTIVE
|
||||
{{- range $key, $value := . }}
|
||||
{{ $key }}: {{ $value }}
|
||||
{{- end }}
|
||||
These are merged with the cert-manager cluster-issuer annotation the chart
|
||||
always sets. Annotations are interpreted by the ingress controller, so a
|
||||
misspelled key is silently ignored — it does not fail the render.
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -67,7 +67,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary
|
||||
results are printed without a decimal point.
|
||||
*/}}
|
||||
{{- define "xxx.multiplyQuantity" -}}
|
||||
{{- $quantity := printf "%v" .quantity -}}
|
||||
{{- $quantity := include "xxx.formatNumber" .quantity -}}
|
||||
{{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}}
|
||||
{{- if not $number -}}
|
||||
{{- fail (printf "cannot parse resource quantity %q" $quantity) -}}
|
||||
@@ -80,7 +80,24 @@ results are printed without a decimal point.
|
||||
{{- if eq $product (floor $product) -}}
|
||||
{{- printf "%d%s" (int64 $product) $suffix -}}
|
||||
{{- else -}}
|
||||
{{- printf "%v%s" $product $suffix -}}
|
||||
{{- printf "%s%s" (include "xxx.formatNumber" $product) $suffix -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Render a number as a plain decimal string, never in exponential notation.
|
||||
|
||||
Why this exists (issue #157): the null-safe fallback helpers pass values
|
||||
through fromYaml, which parses every number as float64. Go's "%v" switches
|
||||
float64 to exponential notation from about 1e6 upwards, so a bare byte count
|
||||
like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the
|
||||
render aborted. Strings ("512Mi") are passed through untouched.
|
||||
*/}}
|
||||
{{- define "xxx.formatNumber" -}}
|
||||
{{- if kindIs "float64" . -}}
|
||||
{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%v" . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
Reference in new issue
Block a user