Files
helm-charts/ytdlp-web-player/values.schema.json
T
jklein e4b41577ae feat(ytdlp-web-player): Gateway-API as an alternative to the Ingress
Group 3 of the #114 sweep, last chart - and the one that carried the group-2
pattern this sweep was told to look for. It reads ingress.enabled OUTSIDE its
Ingress template, in ytdlp-web-player.validate, and in Gateway mode
ingress.enabled is false by construction, so that guard would have gone
silent. Fixed with ytdlp-web-player.published = ingress.enabled OR
httpRoute.enabled - the freesailarr.published / sure.publishedOverTls shape,
identical to ingress.enabled while httpRoute.enabled is false, which is why it
changes no existing render.

AND THE GUARD IS NEEDED MORE IN GATEWAY MODE, NOT LESS. It refuses to publish
the application and the oauth2-proxy on the same host:

  - In INGRESS mode the consequence of ignoring it is loud: ingress-nginx's
    admission webhook rejects the second Ingress and the Fleet bundle goes to
    ErrApplied.
  - In GATEWAY mode THE WEBHOOK NEVER FIRES. It compares Ingress objects with
    Ingress objects, and an HTTPRoute is a different kind - so nothing rejects
    anything, both objects stay valid, and one hostname is served over two
    data paths that are NOT equivalent: the proxy's Ingress goes through
    AUTHENTICATION and this chart's HTTPRoute goes straight to the
    application. Which one a client reaches depends on what DNS resolves to.
    That is an authentication bypass that comes and goes with a DNS record and
    that no Kubernetes object reports.

So the Gateway-mode abort has its own message saying exactly that. Both
messages exercised, plus the collision reached through httpRoute.hostnames
rather than through the ingress.domain fallback, and the guard still firing
when only the Deployment is rendered.

THE SUBCHART HAS NO GATEWAY SUPPORT, and this chart cannot give it one -
upstream's chart is consumed as it comes (README, "Chart dependencies"). It
renders an Ingress or nothing. So in the RECOMMENDED configuration, where the
proxy sits in the data path and owns the host, the publishing object is the
SUBCHART'S INGRESS in both of this chart's modes, and turning httpRoute on
buys nothing. The route is for the proxy-disabled case, or for the application
on a host of its own - which is the documented unauthenticated bypass. Said in
the template header, in values.yaml, in the schema and in the proxy branch of
NOTES.txt, because someone migrating hosts will otherwise set the value and
wonder why nothing moved.

The 16k proxy-buffer-size this chart recommends is NOT lost by this switch,
and that needed stating rather than assuming: it belongs on the PROXY's
Ingress (oauth2-proxy.ingress.annotations) as the second half of the 4kB
session-cookie trap, and the subchart keeps rendering an Ingress. This chart's
own ingress.annotations default is {} and it sets nothing but the cert-manager
annotation, so there is no chart-side annotation loss to name.

CORRECTED CLAIM: values.yaml and the schema both said that everything the
Ingress needs is read only inside its template, so switching it off freed BOTH
ingress.domain and ingress.clusterIssuer. clusterIssuer is still freed; domain
is now read by the HTTPRoute, so it is freed only with both modes off. The
reason the claim was made still holds and is kept - the application has no
base-URL setting and builds no absolute links.

NOTHING WAS MEASURED ON A LIVE INGRESS OF THIS CHART, because there is none.
Measured 2026-10-12: the release runs in namespace jlab-player with both
Deployments Ready, and NOT ONE of the cluster's 23 Ingress objects is in that
namespace - neither this chart's nor the subchart's. What publishes it instead
could not be determined: this workspace's identity may not list httproutes or
secrets, so neither a hand-written HTTPRoute nor the release's Helm values are
readable. Recorded as the open question it is, in values.yaml and in the
overlay, rather than guessed at.

Minor: additive, defaults unchanged. Empty helm template diff against
origin/main with ci/ytdlp-web-player/test-values.yaml.

Part of #114
2026-10-13 08:59:09 +00:00

221 lines
12 KiB
JSON

{
"$schema": "https://json-schema.org/draft-07/schema#",
"title": "ytdlp-web-player values",
"description": "additionalProperties is false on every level this chart owns, so a key the chart does not read is rejected at render time instead of being silently ignored (#68). Two kinds of object stay open: the ones passed verbatim into the pod spec (probes, security contexts, ports, free-form annotations), because Kubernetes owns their schema - and the whole oauth2-proxy block, because that schema belongs to the subchart. Closing the latter would mean re-declaring somebody else's values and rejecting every key they add next release.",
"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 - and note that with a subchart present this is no longer hypothetical: whatever is set here reaches oauth2-proxy too.",
"type": "object"
},
"scaleDown": {
"type": "boolean"
},
"secrets": {
"type": "object",
"additionalProperties": false,
"properties": {
"oauth2ProxySecretRef": {
"description": "op:// item path for the proxy credentials; the item must carry the fields client-id, client-secret and cookie-secret. Read only while the proxy is enabled and still points at the Secret this chart renders.",
"type": ["string", "null"]
}
}
},
"ytdlpWebPlayer": {
"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. It does read one thing out of them: the entry named \"http\" carries the port that becomes PORT, the Service target and every probe target. A list without it fails the render with that message.",
"type": ["array", "null"]
},
"env": {
"$ref": "#/definitions/env"
}
}
},
"storage": {
"description": "The two emptyDirs. Deliberately not null-safe: an erased block means both size caps are unset, which is also the default, so nothing silent can happen here.",
"type": ["object", "null"],
"additionalProperties": false,
"properties": {
"dataSizeLimit": {
"type": ["string", "null"]
},
"cacheSizeLimit": {
"type": ["string", "null"]
}
}
},
"ingress": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"description": "Gates this chart's whole Ingress template (#93). Mutually exclusive with httpRoute.enabled - the render aborts when both are true. CORRECTED with Gitea #114: it used to say this frees BOTH domain and clusterIssuer because nothing outside the Ingress template reads either. clusterIssuer is still freed (no Certificate is rendered in either mode by anything but the Ingress), but ingress.domain is read by the HTTPRoute as well, so it is freed only when BOTH modes are off. What still holds is the reason the claim was made: the application needs no hostname of its own, so the searxng/matrix-synapse case of a domain read from an env block does not arise.",
"type": "boolean"
},
"clusterIssuer": {
"type": ["string", "null"]
},
"domain": {
"description": "The public hostname. Read by the Ingress and by the HTTPRoute (when httpRoute.hostnames is empty), and by nothing else - the application takes no base URL.",
"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. Inert in Gateway mode. Note this is NOT where the 16k proxy-buffer-size belongs: that one goes on the PROXY's Ingress (oauth2-proxy.ingress.annotations), and the subchart renders an Ingress in either mode, so it is unaffected by the switch.",
"type": "object"
}
}
},
"httpRoute": {
"description": "Gateway-API alternative to the Ingress (Gitea #114). Mutually exclusive with ingress.enabled - the render aborts when both are true, naming both value paths. The chart renders only an HTTPRoute with parentRefs; class and certificate sit on the Gateway and belong to the platform. WHAT IT DOES NOT DO: the oauth2-proxy subchart has NO Gateway support and this chart cannot give it one (upstream's chart is consumed as it comes), so in the recommended configuration - proxy in the data path, proxy owning the host - the publishing object is the SUBCHART'S INGRESS whatever this value says. This route is for the proxy-disabled case or for the application on a host of its own, which is an unauthenticated way past the proxy. THE HOST GUARD IS SHARPER HERE, NOT WEAKER: if this chart and the proxy's Ingress claim one host, the ingress-nginx admission webhook never fires (it compares Ingress with Ingress, and an HTTPRoute is another kind), so two valid objects serve one hostname over two data paths - one authenticated, one not - and which a client reaches depends on DNS. The chart refuses that combination in both modes via the ytdlp-web-player.published helper. The Gateway-API CRDs must be present in the cluster, because the chart cannot detect them.",
"type": ["object", "null"],
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"parentRefs": {
"description": "The Gateway(s) the route attaches to. Required while httpRoute.enabled is true, enforced by required()-equivalent 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. The host guard reads this list too, so a hostname set here that the proxy's Ingress also claims is caught rather than silently published twice.",
"type": "array",
"items": { "type": "string" }
}
}
},
"oauth2-proxy": {
"description": "Values of the oauth2-proxy subchart, passed straight through. Open on purpose and at every level: this chart owns neither the keys nor their meaning, and a closed copy would reject whatever the subchart adds in its next release - the exact failure mode a schema is supposed to prevent, only pointed at the wrong chart. This chart reads exactly three of them: enabled (the dependency condition), ingress.enabled and ingress.hosts (the host guard, which since Gitea #114 compares them against this chart's hostnames in EITHER mode). The subchart has no Gateway-API support and renders an Ingress or nothing, which is why httpRoute.enabled on this chart does not move the proxy off its Ingress.",
"type": "object"
}
},
"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 (#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 (#99) instead of rendering an unhardened workload. The fallback reproduces THIS chart's defaults, including its pinned non-root uid for a root-by-default image (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 (#99) rather than the probe disappearing. The defaults carry the probe's context - the port NAME \"http\", so a changed container port moves the probes with it.",
"type": ["object", "null"]
}
}
}