{ "$schema": "https://json-schema.org/draft-07/schema#", "title": "outline values", "description": "Strict values schema (issue #68). additionalProperties is false on every chart-owned object level, so a key that the chart does not read is rejected at render time instead of being silently ignored. The only objects that stay open are the ones passed verbatim into the Kubernetes pod spec (probes, security contexts, global) - the chart does not interpret their contents, so it cannot judge which keys are meaningful. The hardening, probe and resources blocks additionally accept null, because an erased block falls back to the chart defaults (issue #99).", "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" }, "scaleDown": { "type": "boolean" }, "secrets": { "type": "object", "additionalProperties": false, "properties": { "configSecretRef": { "type": ["string", "null"] }, "redisSecretRef": { "type": ["string", "null"] }, "dbSecretRef": { "type": ["string", "null"] } } }, "outline": { "type": "object", "additionalProperties": false, "properties": { "replicas": { "type": "integer", "minimum": 0 }, "maxUploadSize": { "description": "Passed to the app as a byte count; the chart's values keep it a string on purpose.", "type": ["string", "number", "null"] }, "forceHttps": { "description": "Passed through as a string ('true' / 'false'), matching the app's env parsing.", "type": ["string", "boolean", "null"] }, "oidc": { "type": "object", "additionalProperties": false, "properties": { "displayName": { "type": ["string", "null"] } } }, "storage": { "description": "The uploads PVC. Not null-safe on purpose: an erased block yields an invalid PVC rather than a silently degraded workload.", "type": "object", "additionalProperties": false, "properties": { "size": { "type": ["string", "null"] }, "storageClass": { "type": ["string", "null"] } } }, "securityContext": { "$ref": "#/definitions/securityContext" }, "livenessProbe": { "$ref": "#/definitions/probe" }, "readinessProbe": { "$ref": "#/definitions/probe" }, "startupProbe": { "$ref": "#/definitions/probe" }, "env": { "$ref": "#/definitions/env" }, "resources": { "$ref": "#/definitions/resources" } } }, "ingress": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "description": "Gates the whole Ingress template. Mutually exclusive with httpRoute.enabled - the render aborts when both are true. false omits the Ingress and makes clusterIssuer unnecessary - but NOT domain, which is also Outline's public URL and carries its own required() in the StatefulSet.", "type": "boolean" }, "maxUploadFileSize": { "description": "Rendered into the ingress-nginx proxy-body-size and nginx.org/client-max-body-size annotations, so it takes effect only while the Ingress is enabled. NOT ENFORCED in Gateway mode (Gitea #114): the Gateway API has no body-size field, the effective ceiling is the Gateway's, and this chart deliberately renders no implementation-specific policy object to set it. Together with outline.maxUploadSize this is the pair of Gitea #12 - the smaller of the two wins, and in Gateway mode the smaller one may be neither.", "type": ["string", "null"] }, "clusterIssuer": { "type": ["string", "null"] }, "domain": { "type": ["string", "null"] }, "className": { "type": ["string", "null"] }, "annotations": { "description": "Extra Ingress annotations, merged with the cert-manager and proxy-body-size annotations the chart always sets. Free-form keys by nature. No counterpart in Gateway mode: the Gateway API has no annotation channel, so these are gone when httpRoute.enabled is used instead - including the upload cap the chart itself sets from ingress.maxUploadFileSize.", "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. ingress.maxUploadFileSize is not enforced in this mode.", "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "parentRefs": { "description": "The Gateway(s) the route attaches to. Required while httpRoute.enabled is true, 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. A hostname set here does not move Outline's URL variable, which keeps following ingress.domain - set both together or the app hands out links under the old name.", "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 the 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 this chart aborts the render 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": { "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.", "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.", "type": ["object", "null"] } } }