Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 12s
Validate Helm Charts / Build garage (pull_request) Successful in 7s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 16s
Install-test / Install-test garage (pull_request) Successful in 1m17s
Validate Helm Charts / Install-test (pull_request) Successful in 1m17s
My own values comment claimed 0 switches compression off. It does not: Garage's deserializer takes an integer OR the literal string "none" (src/util/config.rs, deserialize_compression), and 0 is a valid integer that zstd reads as its default level - 3, i.e. MORE compression than the chart's 1, not none. A comment that invites an operator to write 0 for "off" is the values-that-must-not-lie shape with the lie in the documentation instead of in the template. The schema now accepts an integer (negative included, those are zstd's fast modes) or exactly "none", and the ConfigMap quotes the string form so the rendered TOML stays valid. Verified: 1, 7, -3 render unquoted, "none" renders quoted and the result still parses as TOML, and any other string is rejected by the schema. No version bump: the chart is not published yet.
309 lines
14 KiB
JSON
309 lines
14 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 - the two 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). Checked in both directions for this chart: nothing outside that template reads ingress.domain, so switching the Ingress off frees domain as well as clusterIssuer.",
|
|
"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.",
|
|
"type": "object"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"definitions": {
|
|
"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"]
|
|
}
|
|
}
|
|
}
|