Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 9s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 17s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 19s
Validate Helm Charts / Build garage (pull_request) Successful in 8s
Validate Helm Charts / Build patchmon (pull_request) Successful in 7s
Validate Helm Charts / Build rallly (pull_request) Successful in 7s
Validate Helm Charts / Build searxng (pull_request) Successful in 6s
Validate Helm Charts / Build sure (pull_request) Successful in 7s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 6s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 18s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 7s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 16s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 18s
Install-test / Install-test bentopdf (pull_request) Successful in 1m10s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 22s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 18s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 17s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 19s
Install-test / Install-test garage (pull_request) Successful in 1m17s
Install-test / Install-test patchmon (pull_request) Successful in 1m24s
Install-test / Install-test rallly (pull_request) Successful in 1m39s
Install-test / Install-test searxng (pull_request) Successful in 1m7s
Install-test / Install-test sure (pull_request) Successful in 2m39s
Install-test / Install-test voucher-vault (pull_request) Successful in 2m9s
Validate Helm Charts / Install-test (pull_request) Successful in 13m15s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m50s
Publish BentoPDF Helm Chart / Publish BentoPDF Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Garage Helm Chart / Publish Garage Helm Chart (push) Successful in 9s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Rallly Helm Chart / Publish Rallly Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish SearXNG Helm Chart / Publish SearXNG Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Sure Helm Chart / Publish Sure Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Urlaubsverwaltung Helm Chart / Publish Urlaubsverwaltung Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Patchmon Helm Chart / Publish Patchmon Helm Chart (push) Successful in 9s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish VoucherVault Helm Chart / Publish VoucherVault Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
SonarQube Analysis / SonarQube Analysis (push) Successful in 43s
Group 1 of the #114 sweep: the charts that render exactly one Ingress object and set no behaviour-bearing nginx annotation - bentopdf, garage, patchmon, rallly, searxng, sure, urlaubsverwaltung, voucher-vault. Each gets the form the pilot (cyberchef, PR #115) fixed and template-chart carries: an httpRoute block (enabled/parentRefs/hostnames, off by default), a template rendering ONLY an HTTPRoute with parentRefs - no Gateway, no gatewayClassName, no Certificate, no implementation-specific policy object - render aborts on both modes at once (naming both value paths), on an empty parentRefs and on a missing hostname, empty hostnames meaning [ingress.domain], a schema entry closed against unknown keys with an open parentRef definition, and a NOTES.txt that names the active mode and what the mode costs. Minor, additive, defaults unchanged: `helm template` with each chart's CI values is byte-identical to origin/main for seven of the eight, and for sure identical in every rendered value (the diff is two YAML comment lines). Three findings that were not in the plan, each argued in the file it changes: sure derived RAILS_ASSUME_SSL/RAILS_FORCE_SSL from ingress.enabled alone. Gateway mode sets that false by construction while TLS is still terminated at the Gateway, so both flags would have gone false: insecure cookies, no HSTS, http:// links, with a Ready pod and a working UI as the only symptom. They now read sure.publishedOverTls (ingress.enabled OR httpRoute.enabled), which is ingress.enabled while httpRoute.enabled is false - hence still Minor. garage claimed, in its Ingress template and its schema, that nothing outside that template reads ingress.domain. The HTTPRoute reads it, so the claim now holds only while both modes are off. Corrected rather than left standing. garage also depends on an upload limit it does not set: its annotations comment tells the operator to set proxy-body-size: "0" because ingress-nginx truncates S3 uploads at 1 MB. proxy-body-size has no Gateway-API counterpart, so its Gateway-mode NOTES prints that the limit is the Gateway's. README's list of affected charts named five and now names garage as a sixth. clusterIssuer needed no change in any of the eight: its only required() sits inside the Ingress template behind ingress.enabled, the same no-op the pilot found. Annotation guidance that stops applying in Gateway mode moved into the Gateway branch of NOTES.txt for bentopdf (X-Robots-Tag) and searxng (forward-auth). CI: one render overlay per chart under ci/httproute/, so the assertion is not vacuum-green - ci/run-httproute-match-test.sh goes from 4 held assertions over 2 charts to 20 over 10. The install-test fixtures stay in Ingress mode: the install runs one configuration per chart, cyberchef already pays that price against the real Gateway-API CRDs, and for garage the Ingress is the only place its Service port is referenced by NAME. Not proven, by construction: that traffic flows. No Gateway controller runs in CI. Not run locally either: k3d, Docker, Trivy. Part of #114
338 lines
16 KiB
JSON
338 lines
16 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
|
"title": "garage 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.rpcSecretRef ships as null and JSON Schema's 'required' tests presence, not null-ness, so it would never fire - the enforcement is required() in the template (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 StatefulSet with replicas: 0 and takes precedence over garage.replicas.",
|
|
"type": "boolean"
|
|
},
|
|
"secrets": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"rpcSecretRef": {
|
|
"description": "op:// item path. The item must carry the field 'rpc-secret' with 32 bytes of random hex. Required always - Garage refuses to start without an RPC secret even as a single node.",
|
|
"type": ["string", "null"]
|
|
},
|
|
"s3SecretRef": {
|
|
"description": "op:// item path. The item must carry the fields 'access-key', 'secret-key' and 'bucket'. Required while garage.singleNode is true together with defaultAccessKey or defaultBucket, because Garage imports those values on start.",
|
|
"type": ["string", "null"]
|
|
}
|
|
}
|
|
},
|
|
"garage": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"replicas": {
|
|
"description": "0 or 1. Capped at 1 because more than one replica is not a cluster: this chart renders no peer bootstrapping and no discovery CRD, so a second pod would be a lone node with its own empty metadata database.",
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"maximum": 1
|
|
},
|
|
"singleNode": {
|
|
"description": "Run 'garage server --single-node', which creates the cluster layout in-process before the listeners open. Idempotent and guarded by the layout version; requires replicationFactor 1.",
|
|
"type": "boolean"
|
|
},
|
|
"defaultAccessKey": {
|
|
"description": "Import the S3 access key from the vault item on start (--default-access-key). Requires singleNode; the chart fails the render on the mixed combination, because Garage refuses it at start.",
|
|
"type": "boolean"
|
|
},
|
|
"defaultBucket": {
|
|
"description": "Create the bucket from the vault item's 'bucket' field on start (--default-bucket) and grant the imported key access to it. Requires singleNode.",
|
|
"type": "boolean"
|
|
},
|
|
"replicationFactor": {
|
|
"description": "1 for this deployment, and raising it is not an upgrade path: the factor is part of the cluster layout, so increasing it is a layout operation with data movement on a cluster that would first need more nodes.",
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"dbEngine": {
|
|
"description": "Metadata engine. Changing it on an existing release needs 'garage convert-db', not a values change.",
|
|
"type": "string",
|
|
"enum": ["lmdb", "sqlite", "fjall"]
|
|
},
|
|
"metadataFsync": {
|
|
"description": "true here, against Garage's own default of false: the volume layer cannot help with a write the engine never handed to the kernel.",
|
|
"type": "boolean"
|
|
},
|
|
"dataFsync": {
|
|
"description": "false, Garage's default: a lost block is one object and is replaceable, a lost metadata write can take the index with it.",
|
|
"type": "boolean"
|
|
},
|
|
"metadataAutoSnapshotInterval": {
|
|
"description": "Go-style duration, e.g. '6h'. The only consistent recovery point this deployment has; an empty string is rejected because switching the snapshots off is not a configuration this chart offers at replication factor 1.",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"blockSize": {
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"compressionLevel": {
|
|
"description": "zstd level for stored blocks, or the literal string \"none\" to disable compression. NOT 0 for disabling: Garage accepts 0 as an integer and zstd reads it as its default level (3), i.e. more compression than 1, not none. Negative integers are zstd's fast modes.",
|
|
"oneOf": [
|
|
{ "type": "integer" },
|
|
{ "type": "string", "enum": ["none"] }
|
|
]
|
|
},
|
|
"bindAddress": {
|
|
"description": "Host part of the S3, web and RPC listeners. '[::]' accepts IPv4 as well while net.ipv6.bindv6only is 0.",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"adminBindAddress": {
|
|
"description": "Host part of the admin listener, separate from the others because all three probes use it.",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"ports": {
|
|
"description": "May be null / empty: the chart then falls back to its defaults. The ConfigMap renders these into garage.toml's bind addresses and the StatefulSet renders them as containerPorts, so both files follow one source.",
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"s3": { "$ref": "#/definitions/port" },
|
|
"rpc": { "$ref": "#/definitions/port" },
|
|
"web": { "$ref": "#/definitions/port" },
|
|
"admin": { "$ref": "#/definitions/port" }
|
|
}
|
|
},
|
|
"s3": {
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"region": {
|
|
"description": "A label both sides must agree on; a consumer configured for a different one gets a signature error.",
|
|
"type": "string",
|
|
"minLength": 1
|
|
},
|
|
"rootDomain": {
|
|
"description": "Optional suffix for vhost-style bucket access. Deliberately not coupled to ingress.domain or httpRoute.hostnames - a wildcard vhost domain and the single host a route answers on are different things.",
|
|
"type": ["string", "null"]
|
|
}
|
|
}
|
|
},
|
|
"web": {
|
|
"type": ["object", "null"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": {
|
|
"description": "Static website hosting (the s3_web endpoint). Off: a second HTTP listener that serves nothing without a rootDomain.",
|
|
"type": "boolean"
|
|
},
|
|
"rootDomain": {
|
|
"type": ["string", "null"]
|
|
}
|
|
}
|
|
},
|
|
"resources": {
|
|
"$ref": "#/definitions/resources"
|
|
},
|
|
"livenessProbe": {
|
|
"$ref": "#/definitions/probe"
|
|
},
|
|
"readinessProbe": {
|
|
"$ref": "#/definitions/probe"
|
|
},
|
|
"startupProbe": {
|
|
"$ref": "#/definitions/probe"
|
|
},
|
|
"securityContext": {
|
|
"$ref": "#/definitions/securityContext"
|
|
},
|
|
"env": {
|
|
"$ref": "#/definitions/env"
|
|
}
|
|
}
|
|
},
|
|
"storage": {
|
|
"description": "The two PVCs. Deliberately NOT null-safe: an erased block yields an invalid PVC, which fails loudly at apply time. Issue #99 covers only the silent class, and on an existing release a chart-side fallback could not help anyway, because a PVC cannot shrink.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"data": {
|
|
"description": "metadata_dir AND data_dir as two subdirectories of one volume, so a volume snapshot catches index and blocks atomically.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"amount": { "type": ["string", "null"] },
|
|
"storageClass": { "type": ["string", "null"] }
|
|
}
|
|
},
|
|
"snapshots": {
|
|
"description": "metadata_snapshots_dir, on a second volume so the recovery point does not die with the volume whose corruption it should repair.",
|
|
"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), and ships false. Mutually exclusive with httpRoute.enabled - the render aborts when both are true. Checked in both directions for this chart, and the answer moved with Gitea #114: the HTTPRoute reads ingress.domain too (as its single hostname), so domain is free only while BOTH routing modes are off; clusterIssuer is read by the Ingress alone and ingress.enabled: false still frees it.",
|
|
"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 and no notion of a body size, so the proxy-body-size an S3 endpoint needs cannot be set here when httpRoute.enabled is used - it has to be raised at the Gateway.",
|
|
"type": "object"
|
|
}
|
|
}
|
|
},
|
|
"httpRoute": {
|
|
"description": "Gateway-API alternative to the Ingress (Gitea #114), and off by default like the Ingress here. Mutually exclusive with ingress.enabled - the render aborts when both are true. The chart renders only an HTTPRoute with parentRefs, pointing at the Service's s3 port; 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, 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. A wildcard for vhost-style bucket access goes here, alongside garage.s3.rootDomain.",
|
|
"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.",
|
|
"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, deviations included - fsGroupChangePolicy: OnRootMismatch rather than the repo's Always, because an object store's data directory holds one file per block and a recursive chown on every pod start turns a restart into a multi-minute outage that grows with the store.",
|
|
"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 defaults carry the probe's CONTEXT: the port NAME 'admin' for all three, /health for readiness only, and a TCP connect for liveness and startup - because /health answers 503 until a cluster layout exists, and a probe that can only fail would restart a node forever.",
|
|
"type": ["object", "null"]
|
|
}
|
|
}
|
|
}
|