Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build keycloak (pull_request) Successful in 8s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 24s
Install-test / Install-test keycloak (pull_request) Successful in 3m29s
Validate Helm Charts / Install-test (pull_request) Successful in 3m29s
keycloak.realmHosts gives a realm a hostname of its own: this release's Service published under a further name, with that name's bare / sent to /realms/<realm>/account/. It renders in BOTH modes - one Ingress per entry carrying nginx.ingress.kubernetes.io/app-root, or one HTTPRoute per entry whose Exact match on / carries a RequestRedirect filter - because a routing value in this repository is mode-independent by design - ingress.domain is read in Gateway mode too - so a feature rendering in one mode only would silently disappear when an installation switched. Default []: nothing renders, and the render is byte-identical to 1.2.0 for the fixture's values in both modes. Minor, additive, no value becomes required. ONE OBJECT PER ENTRY, not one with several hostnames. spec.hostnames applies to the whole object and a rule's matches are path-based, so inside a shared route every entry would match Exact: / and the realm could not be told apart. Names therefore carry the LIST INDEX, as homeassistant's per-host routes do and for the same reason - a realm name is not a dependable object-name fragment. The consequence is written down rather than left to be discovered: reordering the list renames and recreates the objects. THE WORK IS IN THE INGRESS HALF, NOT THE ROUTE. Two convention rules do not survive these objects, and both deviations are required rather than tolerated: tlsSecretName is a per-entry value with NO default, because these hostnames already have certificates and several entries typically share one, so a derived name would ask cert-manager for a second certificate for a name a wildcard already covers; and the cert-manager annotation is OPTIONAL per entry, because cert-manager keys a Certificate by the secret it writes and only one object per secret may request it - the same deliberate omission the qBittorrent object in freesailarr makes. Annotations are per entry too: of the four hostnames this was written for, three carry the proxy-buffer pair and one does not, which a chart-global block could not express. statusCode: 302 is written out at the field's own default. 301 and 302 are both Support: Core, so the portability trap of #119 - an implementation declining an Extended code sets Accepted: False on the WHOLE route - does not apply here; ingress-nginx emits 302 for app-root, and a value that is only accidentally right documents nothing. Five render aborts, each against a failure that reports nothing: an entry without a host (ingress-nginx reads an empty host as a catch-all for the whole controller), without a realm (/realms//account/, a 404 behind a healthy hostname and certificate), without a tlsSecretName in Ingress mode (the controller's default certificate, a browser-only failure), a repeated host or one the release already publishes (webhook rejection in Ingress mode, two merged routes decided by object age in Gateway mode), and a filled list with both modes off (no object at all, and nothing in the release that reveals the list was ignored). The entry-shape checks sit in a shared helper so they exist in both modes, and the Ingress template skips them while httpRoute.enabled is also true so that the specific message cannot hide the mutual-exclusion one. CI: the two entries live in ci/keycloak/test-values.yaml, which ci/httproute/keycloak.yaml is applied on top of, so one definition covers both modes - two Ingress objects in the install-test, two HTTPRoutes in the render test. They cover both Ingress forms, with and without a clusterIssuer, and the render test holds their inertness in Gateway mode. The httproute match test goes from 56 assertions over 21 charts to 60, keycloak's share from 4 to 8 plus two "not checked" lines for the backend-less redirect rules. The #118 prefix/redirect pairing correctly does not fire: it is scoped to rules that strip a prefix, and a realm route strips nothing. NOT IN THIS CHANGE: the migration. Nothing in jk-cluster-config is touched. With the default [] no running system changes, so the order a takeover has to run in is documented instead - remove the objects in the owning repository first, then install with the list filled, because the reverse or both at once is rejected by ingress-nginx's admission webhook and leaves a Fleet bundle in ErrApplied until forceSyncGeneration is raised. Whether anything there depends on those objects BY NAME is unchecked and named as unchecked. Fixes #122
361 lines
26 KiB
JSON
361 lines
26 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
|
"title": "keycloak values",
|
|
"description": "Strict values schema (issue #68). additionalProperties is false on every chart-owned object level, so a key this chart does not read is rejected at render time instead of being silently ignored; the objects that go verbatim into the Kubernetes pod spec (probes, security contexts, global, free-form annotation maps) stay open, because the chart does not interpret their contents. The blocks the chart dereferences additionally accept null, because an erased block means 'chart defaults apply' (issue #99) rather than a vanished setting. Note what this schema CANNOT enforce: secrets.databaseSecretRef, keycloak.db.host, ingress.domain and ingress.admin.domain all ship as null and JSON Schema's 'required' tests presence, not null-ness, so a 'required' array would never fire - the enforcement is required() in the templates (issue #18).",
|
|
"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": {
|
|
"description": "true renders the Deployment with replicas: 0 and takes precedence over keycloak.replicas. Also the first half of the minor-upgrade procedure, because Keycloak supports a rolling update only inside a minor line.",
|
|
"type": "boolean"
|
|
},
|
|
"secrets": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"databaseSecretRef": {
|
|
"description": "op:// item path. The item must carry the fields 'username' and 'password' of the PostgreSQL role, which has to be able to create its own schema. Always required - the chart has no default database.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"bootstrapAdminSecretRef": {
|
|
"description": "op:// item path. The item must carry the fields 'username' and 'password' of the TEMPORARY bootstrap administrator. Required while keycloak.bootstrapAdmin.enabled is true.",
|
|
"type": ["string", "null"]
|
|
}
|
|
}
|
|
},
|
|
"keycloak": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"replicas": {
|
|
"description": "Default 2, because an SSO outage takes every dependent login with it. An explicit 0 is allowed and is the per-workload way to stop the release. Above 1 the rolling-update restriction applies: a minor version jump needs the whole cluster down.",
|
|
"type": "integer",
|
|
"minimum": 0
|
|
},
|
|
"db": {
|
|
"description": "May be null / empty: the chart then falls back to its defaults. The host has no default and is enforced by required() in the Deployment.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"vendor": {
|
|
"description": "A Keycloak BUILD option - it is passed to `kc.sh build` in the init container, never to the serving container. dev-file and dev-mem are rejected on purpose: they are Keycloak's development defaults, and an identity provider on an embedded database works until the pod moves. Only 'postgres' is exercised by this chart's CI.",
|
|
"type": "string",
|
|
"enum": ["postgres", "mariadb", "mysql", "mssql", "oracle"]
|
|
},
|
|
"host": {
|
|
"description": "Host of the external PostgreSQL. Required - enforced by required() in the Deployment, because a null here would render an empty KC_DB_URL_HOST and a pod that waits on a connection it can never make.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"port": { "$ref": "#/definitions/port" },
|
|
"database": {
|
|
"type": ["string", "null"],
|
|
"minLength": 1
|
|
},
|
|
"pool": {
|
|
"description": "Connection pool sizes, PER REPLICA. Upstream's db-pool-max-size default is 100; this chart ships 20, because two replicas at 100 ask a default PostgreSQL (max_connections = 100) for twice what it has.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"initialSize": { "type": ["integer", "null"], "minimum": 0 },
|
|
"minSize": { "type": ["integer", "null"], "minimum": 0 },
|
|
"maxSize": { "type": ["integer", "null"], "minimum": 1 }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"bootstrapAdmin": {
|
|
"description": "The temporary bootstrap administrator. Switch this off once a permanent administrator exists - it also stops the per-start log line about the initial administrator not being created.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": { "type": "boolean" }
|
|
}
|
|
},
|
|
"metrics": {
|
|
"description": "A Keycloak BUILD option (metrics-enabled). ON, and not for the metrics: upstream's table of health checks has a 'Requires Metrics' column, and the DATABASE check is the row that says yes. With this false, /health/ready still answers 200 while the database is gone - the urlaubsverwaltung shape (#218), with the dependency hidden behind an option whose name says observability. The price is that /metrics then answers on the management port, unauthenticated, carrying realm and client names - that port is ClusterIP-only and in no Ingress. Switching this off is a decision about whether readiness can fail.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": { "type": "boolean" }
|
|
}
|
|
},
|
|
"features": {
|
|
"description": "Additional Keycloak feature flags (--features), a BUILD option. A list rather than a comma-separated string, so two entries cannot arrive as one unknown feature name - which Keycloak rejects at build time, inside the init container. The pattern keeps a value out that could not be a feature name.",
|
|
"type": ["array", "null"],
|
|
"items": {
|
|
"type": "string",
|
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9:._-]*$"
|
|
}
|
|
},
|
|
"cache": {
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"clusterName": {
|
|
"description": "--cache-embedded-cluster-name. null means Keycloak's default, 'ISPN'. Read the comment in values.yaml before setting it: two releases sharing one database MERGE into one cluster unless the names differ, but upstream also warns that cache invalidation does not work between differently-named clusters.",
|
|
"type": ["string", "null"],
|
|
"minLength": 1
|
|
}
|
|
}
|
|
},
|
|
"ports": {
|
|
"description": "May be null / empty: the chart then falls back to its defaults. The Deployment renders these as containerPorts and the Services address them by name, so both follow one source.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"http": { "$ref": "#/definitions/port" },
|
|
"management": {
|
|
"description": "The management port. It carries the unauthenticated /health/* endpoints and optionally /metrics, and all three probes address it by name. It is deliberately in no Ingress.",
|
|
"$ref": "#/definitions/port"
|
|
},
|
|
"jgroups": {
|
|
"description": "JGroups transport port. Declared as a containerPort for the record; jdbc-ping opens direct pod-to-pod connections on it.",
|
|
"$ref": "#/definitions/port"
|
|
}
|
|
}
|
|
},
|
|
"resources": {
|
|
"description": "Resources of the SERVING container. The memory request is not only about scheduling here: the image sizes the JVM heap relative to the container limit (-XX:MaxRAMPercentage=70), so request x limitFactor decides how much memory the application believes it has.",
|
|
"$ref": "#/definitions/resources"
|
|
},
|
|
"build": {
|
|
"description": "The build init container, which runs the one-shot Quarkus augmentation. Its resources are transient - it is gone before the serving container starts - but they count for scheduling, because a pod's effective request is the maximum of its init containers' and the sum of its containers'.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"resources": { "$ref": "#/definitions/resources" }
|
|
}
|
|
},
|
|
"livenessProbe": { "$ref": "#/definitions/probe" },
|
|
"readinessProbe": { "$ref": "#/definitions/probe" },
|
|
"startupProbe": { "$ref": "#/definitions/probe" },
|
|
"securityContext": { "$ref": "#/definitions/securityContext" },
|
|
"podDisruptionBudget": {
|
|
"description": "The first PodDisruptionBudget in this repository. It is rendered only while the effective replica count is >= 2: minAvailable 1 with a single replica forbids every voluntary eviction, so a node drain would block forever on a pod nothing may move.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": { "type": "boolean" },
|
|
"minAvailable": { "type": "integer", "minimum": 1 }
|
|
}
|
|
},
|
|
"env": { "$ref": "#/definitions/env" },
|
|
"extraVolumes": {
|
|
"description": "Additional pod volumes, rendered verbatim into the pod spec. The ITEMS stay loosely typed on purpose - a volume source can be any of the Kubernetes forms (configMap, secret, persistentVolumeClaim, emptyDir, projected, csi, ...), and a stricter schema here would reject valid configurations rather than catch mistakes; the API server validates the shape anyway. Declaring the key itself is not optional, because the levels above carry additionalProperties: false. Places files, configures nothing: an extra root CA additionally needs KC_TRUSTSTORE_PATHS (a runtime option, so keycloak.env) and a theme additionally needs to be selected per realm in Keycloak's database.",
|
|
"type": ["array", "null"]
|
|
},
|
|
"extraVolumeMounts": {
|
|
"description": "Additional volume mounts, rendered verbatim and TWICE - on the build init container as well as on the serving container (Gitea #88). That is deliberate: a login theme is read at runtime, but an SPI provider JAR must be present while `kc.sh build` writes the provider registry, or it is never registered and nothing reports it. Items loosely typed for the same reason as extraVolumes. Never mount on /opt/keycloak/themes itself - it would hide the image's base, keycloak and keycloak.v2 themes; use a subpath /opt/keycloak/themes/<name>.",
|
|
"type": ["array", "null"]
|
|
},
|
|
"realmHosts": {
|
|
"description": "One hostname per realm (Gitea #122), rendered in BOTH routing modes: one Ingress per entry with nginx.ingress.kubernetes.io/app-root, or one HTTPRoute per entry whose Exact match on / carries a RequestRedirect filter to /realms/<realm>/account/. Empty by default and then renders nothing at all. One object per entry, named by list INDEX - so reordering the list renames and recreates them. host and realm are required and the realm is NOT derived from the hostname; the render also aborts on a repeated host and on a host this release already publishes, because one hostname cannot serve two different things at /. tlsSecretName is required in Ingress mode and never derived, and clusterIssuer is optional there - the documented deviation from the one-secret/always-annotate convention, so that a certificate already covering these zones is consumed rather than requested a second time. See values.yaml for the full reasoning and for the order a takeover of existing hostnames has to run in.",
|
|
"type": ["array", "null"],
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["host", "realm"],
|
|
"properties": {
|
|
"host": {
|
|
"description": "The hostname serving this realm. Must differ from every other entry's and from the hostnames this release already publishes.",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"realm": {
|
|
"description": "The realm as Keycloak names it, interpolated into the redirect target /realms/<realm>/account/. Not derivable from the hostname - the two need not correspond and in the installation this was written for they do not. A slash in it aborts the render, because it would retarget the redirect instead of being escaped.",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"tlsSecretName": {
|
|
"description": "The TLS secret this hostname's certificate lives in. REQUIRED IN INGRESS MODE and deliberately not derived from release and chart name: these zones already have certificates, often one wildcard for several entries, and a derived name would mean one further ACME order per hostname for a name that is already covered. Not read in Gateway mode - there the certificate sits on the Gateway's listener.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"clusterIssuer": {
|
|
"description": "Optional cert-manager issuer for THIS object. Empty or absent renders no cert-manager annotation, which is the normal case: cert-manager keys a Certificate by the secret it writes, so of several objects naming one secret exactly one may carry the annotation. Same deliberate omission as the qBittorrent object in freesailarr. Not read in Gateway mode.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"className": {
|
|
"description": "Optional ingress class for this object, falling back to ingress.className - the same shape as ingress.admin.className, because a realm hostname may belong to a zone served by another controller. Not read in Gateway mode, where the class is a property of the Gateway.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"annotations": {
|
|
"description": "Extra annotations for THIS entry's Ingress, merged after the app-root and cert-manager annotations. Per entry rather than chart-global on purpose: the set is not uniform across hostnames - of the four this was written for, three carry the proxy-buffer pair and one does not. Free-form keys by nature, so this object stays open. INERT in Gateway mode, and for an account console that loses the proxy buffers in the case where large headers are the norm rather than the edge.",
|
|
"type": "object"
|
|
},
|
|
"parentRefs": {
|
|
"description": "Optional Gateway(s) for this entry's HTTPRoute, falling back to httpRoute.parentRefs - the counterpart of className above, because \"which data path\" is a property of the Gateway in this API. Not decoration here: realm hostnames sit in several DNS zones, so a chart-global parentRef pinning one sectionName would attach every realm route to one certificate's listener, and a route whose listener does not match its hostname is not accepted and simply does not answer. Not read in Ingress mode.",
|
|
"type": "array",
|
|
"items": { "$ref": "#/definitions/parentRef" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"ingress": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": {
|
|
"description": "Gates the public Ingress object only (issue #93). Checked in both directions for this chart: ingress.domain is ALSO read as KC_HOSTNAME on the Deployment, so switching the Ingress off frees clusterIssuer but NOT domain - Keycloak refuses to start in production mode without a hostname.",
|
|
"type": "boolean"
|
|
},
|
|
"clusterIssuer": { "type": ["string", "null"] },
|
|
"domain": {
|
|
"description": "The public hostname, a plain host - the chart renders it as the full URL https://<domain> for KC_HOSTNAME, which upstream requires as soon as hostname-admin is in play. Required even with enabled: false.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"className": { "type": ["string", "null"] },
|
|
"annotations": {
|
|
"description": "Extra annotations for the public Ingress, merged with the cert-manager annotation the chart always sets. Free-form keys by nature, so this object stays open.",
|
|
"type": "object"
|
|
},
|
|
"admin": {
|
|
"description": "The administration console on a hostname of its own. NOT AN ACCESS CONTROL: hostname-admin decides which URLs the server generates; upstream states that it does not prevent reaching the Administration REST API through the public hostname, and that restricting that API is reverse-proxy work. The chart renders no deny rule for /admin.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": { "type": "boolean" },
|
|
"domain": {
|
|
"description": "The admin hostname, a plain host - rendered as https://<domain> for KC_HOSTNAME_ADMIN. Required while enabled.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"className": {
|
|
"description": "null falls back to ingress.className. An internal ingress class here keeps the console off the public controller, which is the useful half of the admin hostname.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"annotations": {
|
|
"description": "Extra annotations for the admin Ingress, merged with the cert-manager annotation. Free-form keys by nature, so this object stays open. Inert in Gateway mode.",
|
|
"type": "object"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"httpRoute": {
|
|
"description": "Gateway-API alternative to the TWO Ingresses (Gitea #114). Mutually exclusive with ingress.enabled - the render aborts when both are true, naming both value paths. The chart renders only HTTPRoutes with parentRefs; class and certificate sit on the Gateway and belong to the platform. TWO routes, the public hostname and the admin one; the second is gated on ingress.admin.enabled, because that value also sets KC_HOSTNAME_ADMIN on the Deployment and is therefore not an Ingress switch. The Gateway-API CRDs must be present in the cluster, because the chart cannot detect them. NOT COVERED IN THIS MODE: the proxy-buffer-size and proxy-buffers-number annotations this installation relies on - the Gateway API has no counterpart for proxy buffer sizing, and Keycloak's large headers make a too-small buffer answer 502 at the proxy while the application logs success. See values.yaml, httpRoute block.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": { "type": "boolean" },
|
|
"parentRefs": {
|
|
"description": "The Gateway(s) the public 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 public hostname has a single source of truth.",
|
|
"type": "array",
|
|
"items": { "type": "string" }
|
|
},
|
|
"admin": {
|
|
"description": "The admin route. Rendered while ingress.admin.enabled is true - the same switch the admin Ingress uses, so the admin hostname is available in both modes.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"parentRefs": {
|
|
"description": "Empty falls back to httpRoute.parentRefs - the counterpart of ingress.admin.className falling back to ingress.className, because \"which data path\" is a property of the Gateway in this API and not of the route. An internal Gateway here is what an internal ingress class is in the other mode, and still not an access control.",
|
|
"type": "array",
|
|
"items": { "$ref": "#/definitions/parentRef" }
|
|
},
|
|
"hostnames": {
|
|
"description": "Empty means [ingress.admin.domain].",
|
|
"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"]
|
|
},
|
|
"port": {
|
|
"type": ["integer", "null"],
|
|
"minimum": 1,
|
|
"maximum": 65535
|
|
},
|
|
"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": {
|
|
"description": "Additive container env entries. An entry repeating a name the chart already sets does not replace it - on an existing release the pair collapses to the chart's own value while helm upgrade reports success. A BUILD option's variable name (KC_DB, KC_HEALTH_ENABLED, KC_METRICS_ENABLED, KC_FEATURES, ...) is refused by the template with a fail: on the serving container it would disagree with what the init container built, and Keycloak answers that by re-augmenting onto a read-only path.",
|
|
"type": ["array", "null"],
|
|
"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). This chart's fallback reproduces ITS defaults, which deviate from the repo's usual 20000/1000 pair: the published image declares keycloak:x:1000:0 in /etc/passwd and copies the whole install tree with --chown=1000:0, so uid 1000 is the OWNER of everything the process reads. The GROUP is 1000 and not the image's own 0 - counted across the image layer, 521 of 522 entries are owned by 1000:0 with every mode granting the owner full access, so group 0 (upstream's OpenShift arbitrary-uid pattern) buys nothing once the owning uid is pinned, and leaving it out keeps a root GID out of the pod. Every container in the pod, both init containers included, uses the same container context: readOnlyRootFilesystem: true holds throughout, which is the reason the chart has two init containers rather than one writable one.",
|
|
"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 securityContext of every container in the pod; 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 defaults carry the probe's CONTEXT: the port NAME 'management' for all three, and three DIFFERENT paths - /health/live for liveness (no database check), /health/ready for readiness (with one), /health/started for startup. Pointing liveness at /health or /health/ready would restart every pod of the cluster for as long as the database is away.",
|
|
"type": ["object", "null"]
|
|
}
|
|
}
|
|
}
|