The reference implementation for Gitea #5: one NetworkPolicy per workload, networkPolicy.enabled defaulting to true, plus the values block, the schema entry and the NOTES.txt section that go with it. The shape is taken from the two charts that already had one - valkey and stalwart - rather than invented, because they carry the expensive lessons: policyTypes only where there is something to restrict, namespaces always spelled out because an empty "from" allows everything, kubernetes.io/metadata.name so no foreign namespace needs labelling, POD ports and never NodePorts, egress by port because the object cannot express a hostname, and the probe path left open by default because the kubelet probes from the node's own IP. Three things are written down rather than assumed. The ingress source is measured on the cluster and is NOT what the issue body assumes: ingress-nginx is gone, the only IngressClass is traefik, and the controller is the DaemonSet rke2-traefik in kube-system - which also serves the Gateway API here, so one source covers both the Ingress and the HTTPRoute path. Egress stays coarse: with Canal (Flannel plus Calico) there is no FQDN rule, and Calico's GlobalNetworkSet would bind every chart to Calico, the same argument that kept implementation-specific objects out of #114. And a policy is only a boundary where the CNI enforces one - unenforced, it is accepted and filters nothing. All reasoning sits in Go template comments, not in "#" comments: those are part of the rendered object and travel into the cluster. Writing this header also found the second-order trap, which is now in the header itself - prose inside a template comment must not spell out the comment's own closing delimiter, or the parser ends the comment early and the remaining prose is emitted as manifest content. Part of #5
273 lines
13 KiB
JSON
273 lines
13 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
|
"title": "xxx values",
|
|
"description": "Reference values schema (issue #68). Copy this file alongside the rest of template-chart and replace every xxx. Two rules carry the whole idea: additionalProperties is false on every chart-owned object level, so a key the chart does not read is rejected at render time instead of being silently ignored; and the objects that go verbatim into the Kubernetes pod spec (probes, security contexts, global, free-form annotations) stay open, because the chart does not interpret their contents and cannot judge which keys are meaningful. The hardening, probe and resources blocks additionally accept null - see the note on that in the definitions below.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"global": {
|
|
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
|
"type": "object"
|
|
},
|
|
"networkPolicy": {
|
|
"description": "The chart's NetworkPolicy (Gitea #5), on by default. A ceiling on reachability and not an authentication layer: only the declared container ports are reachable at all, egress is capped by PORT (a NetworkPolicy matches CIDRs and selectors, never hostnames, so a destination host cannot be expressed), and ingress is capped by source for every port no network probe touches. Only a boundary where the cluster's CNI enforces policies - an unenforced policy is accepted by the API server and filters nothing. The template's header carries the full list of what it does not do.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": {
|
|
"type": "boolean"
|
|
},
|
|
"allowedNamespaces": {
|
|
"description": "Namespaces whose pods may reach this workload, in addition to the release's own namespace, which is always allowed. Matched on the kubernetes.io/metadata.name label Kubernetes sets on every namespace itself, so no labelling work is needed - and no typo protection either: a name that does not exist matches nothing and is silently ineffective. An entry opens the port to EVERY pod in that namespace; for the ingress controller use ingressController instead.",
|
|
"type": ["array", "null"],
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"probeSources": {
|
|
"description": "Source CIDRs allowed to reach the port the network probes address. EMPTY MEANS OPEN, not blocked: with no entry that rule is rendered without a \"from\" selector. The kubelet's probes come from the IP of the worker node running the pod, an address the chart cannot know and must not guess - a guessed CIDR that excludes the real node blocks every probe and the pod never becomes Ready, which reads as a broken application rather than as a policy. Setting this restricts the rule, which is why the chart adds the own namespace, ingressController and allowedNamespaces to the same rule.",
|
|
"type": ["array", "null"],
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"ingressController": {
|
|
"description": "The ingress controller allowed to reach the routed port once probeSources restricts it; rendered only while ingress.enabled or httpRoute.enabled is true. The two keys become ONE \"from\" entry and are therefore an AND - pods carrying podLabels IN namespace - which is the whole point: the controller's namespace in allowedNamespaces would open the port to every pod in kube-system. The default is measured on this cluster (DaemonSet rke2-traefik in kube-system, which also serves the Gateway API here), not assumed; a policy written against the wrong controller is accepted, looks strict and silently blocks all web traffic.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"namespace": {
|
|
"type": ["string", "null"]
|
|
},
|
|
"podLabels": {
|
|
"description": "Label set the controller's pods carry, rendered verbatim as a podSelector's matchLabels. Kept open because the chart does not interpret the keys.",
|
|
"type": ["object", "null"]
|
|
}
|
|
}
|
|
},
|
|
"egress": {
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"extraPorts": {
|
|
"description": "Additional outbound TCP ports, to ANY destination - ports and not destinations, because a NetworkPolicy cannot express a hostname. DNS is always allowed and has no switch. Without an entry here an outbound port the chart does not already derive is blocked, which is the cap's whole purpose and also its upgrade risk.",
|
|
"type": ["array", "null"],
|
|
"items": {
|
|
"type": "integer"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"scaleDown": {
|
|
"type": "boolean"
|
|
},
|
|
"secrets": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"xxxSecretRef": {
|
|
"description": "op:// item path. Name the key exactly as the template reads it - a plausible abbreviation is a dead key, and this schema is what turns it into a named error instead of a missing secret.",
|
|
"type": ["string", "null"]
|
|
}
|
|
}
|
|
},
|
|
"xxx": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"replicas": {
|
|
"type": "integer",
|
|
"minimum": 0
|
|
},
|
|
"resources": {
|
|
"$ref": "#/definitions/resources"
|
|
},
|
|
"livenessProbe": {
|
|
"$ref": "#/definitions/probe"
|
|
},
|
|
"readinessProbe": {
|
|
"$ref": "#/definitions/probe"
|
|
},
|
|
"startupProbe": {
|
|
"$ref": "#/definitions/probe"
|
|
},
|
|
"securityContext": {
|
|
"$ref": "#/definitions/securityContext"
|
|
},
|
|
"ports": {
|
|
"description": "Rendered verbatim as the container's ports list; kept open because the chart does not interpret the entries.",
|
|
"type": ["array", "null"]
|
|
},
|
|
"env": {
|
|
"$ref": "#/definitions/env"
|
|
}
|
|
}
|
|
},
|
|
"storage": {
|
|
"description": "The data PVC. Deliberately NOT null-safe: an erased block yields an invalid PVC, which fails loudly. Issue #99 covers only the silent class - an error that shows itself needs no fallback, and every extra bit of template logic has to be read by someone.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"amount": {
|
|
"type": ["string", "null"]
|
|
},
|
|
"storageClass": {
|
|
"type": ["string", "null"]
|
|
}
|
|
}
|
|
},
|
|
"ingress": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": {
|
|
"description": "Gates the whole Ingress template (issue #93). Whether switching it off also frees domain depends on the chart: if anything outside the Ingress template reads ingress.domain, that read needs its own required() and the value stays mandatory. Check, do not assume (guideline 1).",
|
|
"type": "boolean"
|
|
},
|
|
"clusterIssuer": {
|
|
"type": ["string", "null"]
|
|
},
|
|
"domain": {
|
|
"type": ["string", "null"]
|
|
},
|
|
"className": {
|
|
"type": ["string", "null"]
|
|
},
|
|
"annotations": {
|
|
"description": "Extra Ingress annotations, merged with the cert-manager annotation the chart always sets. Free-form keys by nature, so this object stays open. No counterpart in Gateway mode: the Gateway API has no annotation channel, so these are gone when httpRoute.enabled is used instead.",
|
|
"type": "object"
|
|
}
|
|
}
|
|
},
|
|
"httpRoute": {
|
|
"description": "Gateway-API alternative to the Ingress (Gitea #114). Mutually exclusive with ingress.enabled - the render aborts when both are true. The chart renders only an HTTPRoute with parentRefs; class and certificate sit on the Gateway and belong to the platform. The Gateway-API CRDs must be present in the cluster, because the chart cannot detect them.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": {
|
|
"type": "boolean"
|
|
},
|
|
"parentRefs": {
|
|
"description": "The Gateway(s) the route attaches to. Required while httpRoute.enabled is true, and enforced in the template (an empty list aborts the render) - a JSON Schema \"required\" cannot do it, because the key ships as an empty list and is therefore present. Entries are the Gateway API's ParentReference and are passed through unchanged.",
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/definitions/parentRef"
|
|
}
|
|
},
|
|
"hostnames": {
|
|
"description": "Empty means [ingress.domain], so the hostname has a single source of truth.",
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"definitions": {
|
|
"parentRef": {
|
|
"description": "A Gateway API ParentReference, passed verbatim into HTTPRoute.spec.parentRefs. Deliberately left OPEN (no additionalProperties: false), for the same reason probes and security contexts are: Kubernetes owns this schema, the Gateway API adds fields to it across releases, and a closed list here would reject a field only upstream knows about. What a chart can assert is that \"name\" is there - it is required by the API, and a reference without it attaches to nothing, which is the silent failure the render aborts over.",
|
|
"type": "object",
|
|
"required": ["name"]
|
|
},
|
|
"resources": {
|
|
"description": "May be null / empty: the chart then falls back to its default requests and computed limits (issue #99) instead of rendering a container without resource accounting.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"limitFactor": {
|
|
"type": "number",
|
|
"exclusiveMinimum": 0
|
|
},
|
|
"requests": {
|
|
"$ref": "#/definitions/resourceList"
|
|
},
|
|
"limits": {
|
|
"$ref": "#/definitions/resourceList"
|
|
}
|
|
}
|
|
},
|
|
"resourceList": {
|
|
"description": "Both spellings of the ephemeral-storage key are accepted because the resources helper normalizes camelCase to the Kubernetes key.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"cpu": {
|
|
"type": ["string", "number", "null"]
|
|
},
|
|
"memory": {
|
|
"type": ["string", "number", "null"]
|
|
},
|
|
"ephemeral-storage": {
|
|
"type": ["string", "number", "null"]
|
|
},
|
|
"ephemeralStorage": {
|
|
"type": ["string", "number", "null"]
|
|
}
|
|
}
|
|
},
|
|
"env": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["name"],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"value": {
|
|
"type": "string"
|
|
},
|
|
"valueFrom": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"secretKeyRef": {
|
|
"$ref": "#/definitions/keyRef"
|
|
},
|
|
"configMapKeyRef": {
|
|
"$ref": "#/definitions/keyRef"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"keyRef": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["name", "key"],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"key": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"securityContext": {
|
|
"description": "May be null / empty at any level: the chart then falls back to its hardening defaults (issue #99) instead of rendering an unhardened workload. Where a chart deviates from the repo hardening standard on purpose, its fallback reproduces the DEVIATION, not the standard (guideline 4).",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"pod": {
|
|
"description": "Merged onto the chart's pod hardening defaults and rendered as the pod's securityContext; kept open because the chart does not interpret the individual fields.",
|
|
"type": ["object", "null"]
|
|
},
|
|
"container": {
|
|
"description": "Merged onto the chart's container hardening defaults and rendered as the container's securityContext; kept open because the chart does not interpret the individual fields.",
|
|
"type": ["object", "null"]
|
|
}
|
|
}
|
|
},
|
|
"probe": {
|
|
"description": "Merged onto the chart's probe defaults and rendered as a container probe; kept open because the chart does not interpret the individual fields. May be null / empty, which means the defaults apply (issue #99) rather than the probe disappearing. The chart's default MUST carry the probe's context - port name, Host header, exec command - not just its timings (guideline 5).",
|
|
"type": ["object", "null"]
|
|
}
|
|
}
|
|
}
|