Compare commits

..

23 Commits

Author SHA1 Message Date
Alex Auvolat 7b795e38e9 post-v2.4.0 release dependency upgrades 2026-09-07 09:31:22 +02:00
maximilien ceadd2c0d2 Merge pull request 'helm: update doc' (#1530) from helm-docs-update-2 into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1530
2026-09-07 07:19:00 +00:00
maximilien 67ada6ee78 helm: update doc 2026-09-07 07:19:00 +00:00
Alex Auvolat 229ec977d5 reintroduce full rustls dependency in garage_rpc (fix #1526) (#1527)
also fix #1416

issue introduced in #1412

other potential solution #1473

Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1527
2026-09-07 07:16:44 +00:00
maximilien 13b264afce Merge pull request 'helm: update helm-docs for chart 0.10.1' (#1529) from helm-docs-update into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1529
2026-09-07 06:29:29 +00:00
maximilien 6b50841d00 helm: update helm-docs for chart 0.10.1 2026-09-07 08:27:28 +02:00
maximilien 6dd5a6e823 Merge pull request 'helm: Add singleNode parameter' (#1458) from kirmanak/garage:main-v2 into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1458
2026-09-07 06:23:25 +00:00
maximilien 683b419047 helm: add single-node unit tests 2026-09-07 08:19:13 +02:00
Kirill Kamakin 4d83477bb6 Add garage.singleNode paramter 2026-09-07 08:17:31 +02:00
Kirill Kamakin b69cfcb857 Revert "Enable container command override"
This reverts commit ca22543b50ee58dff27cddbe229a0fd87689e95c.
2026-09-07 08:17:22 +02:00
Kirill Kamakin b934548c47 Enable container command override 2026-09-07 08:16:43 +02:00
maximilien 69a3608300 Merge pull request 'Allow creation of roles instead of cluster roles' (#1478) from hmerat/garage:disable-cluster-roles into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1478
2026-09-07 06:05:03 +00:00
maximilien a3bd785fb2 Bump chart version 2026-09-07 08:02:28 +02:00
Hervé MERAT 8a85013e8c Allow creation of roles instead of cluster roles 2026-09-06 22:08:39 +02:00
maximilien 2e11d15532 Merge pull request 'helm: add unit tests, fix edge cases' (#1519) from helm-unit-tests into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1519
2026-09-06 19:57:04 +00:00
maximilien 1907c7cf69 helm: fix test 2026-09-06 21:45:40 +02:00
maximilien 0d53abd139 helm: add unit test suite and complex valuefile example 2026-09-06 21:45:34 +02:00
maximilien fbdb0cb174 helm: Ingress: only include yaml document delimiter when followed by content 2026-09-06 21:44:30 +02:00
maximilien 7baabe5521 helm: ServiceMonitor: validate tlsConfig and relabeling config before injecting it 2026-09-06 21:44:29 +02:00
Alex Auvolat 073dd715ce bump version to v2.4.0 and update api json schema 2026-09-06 19:43:04 +02:00
Alex 845db1afa1 Merge pull request 'api/s3: don't panic when a version has no blocks' (#1522) from rajsinghtech/garage:fix/multipart-empty-blocks into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1522
2026-09-06 07:49:47 +00:00
Raj Singh c54077af73 api/s3: don't panic when reading a version with no blocks
body_from_blocks_range indexed all_blocks[0] to size a capacity hint. A
version with zero blocks made that index panic and kill the process on a
plain GET, the read-side twin of the CompleteMultipartUpload panic at
multipart.rs:479. Fall back to the existing 1024 floor when the slice is
empty; with a non-empty slice the hint is unchanged.
2026-09-04 03:19:07 +00:00
Raj Singh f38f001bad api/s3: don't panic when final multipart version has no blocks
CompleteMultipartUpload builds the final version and then reads
`final_version.blocks.items()[0]` unconditionally. When the assembled
version ends up with an empty block list the index panics and the
process dies before it can answer the request:

    panicked at src/api/s3/multipart.rs:479:3:
    index out of bounds: the len is 0 but the index is 0

Use `.first()` and return an internal error instead, matching how the
rest of this file handles missing data. The empty-block case is an
internal consistency condition rather than a malformed request, so it
maps to an internal error rather than a 4xx.

The same panic was reported earlier in #1403, at multipart.rs:483 on
v2.2.0. That report was closed without a fix after the reporter moved
off consistency_mode = "dangerous".

Closes #1521
2026-09-04 00:01:32 +00:00
58 changed files with 2419 additions and 1039 deletions
+12
View File
@@ -53,3 +53,15 @@ steps:
- nix-build -j4 --attr flakePackages.dev
- nix-shell --attr ci --run ./script/test-smoke.sh || (cat /tmp/garage.log; false)
depends_on: [ build ]
- name: helm chart tests
image: helmunittest/helm-unittest:4.2.3-1.1.2
commands:
- helm lint --strict script/helm/garage
- helm lint --strict script/helm/garage -f script/helm/garage/tests/values/daemonset.yaml
- helm lint --strict script/helm/garage -f script/helm/garage/tests/values/ingress.yaml
- helm lint --strict script/helm/garage -f script/helm/garage/tests/values/existing-secret.yaml
- helm lint --strict script/helm/garage -f script/helm/garage/tests/values/monitoring.yaml
- helm lint --strict script/helm/garage -f script/helm/garage/tests/values/minimal.yaml
- helm lint --strict script/helm/garage -f script/helm/garage/complex-values.yaml
- helm unittest --strict script/helm/garage
Generated
+893 -910
View File
File diff suppressed because it is too large Load Diff
+13 -18
View File
@@ -25,18 +25,18 @@ default-members = ["src/garage"]
# Internal Garage crates
format_table = { version = "0.1.1", path = "src/format-table" }
garage_api_common = { version = "2.3.0", path = "src/api/common" }
garage_api_admin = { version = "2.3.0", path = "src/api/admin" }
garage_api_s3 = { version = "2.3.0", path = "src/api/s3" }
garage_api_k2v = { version = "2.3.0", path = "src/api/k2v" }
garage_block = { version = "2.3.0", path = "src/block" }
garage_db = { version = "2.3.0", path = "src/db", default-features = false }
garage_model = { version = "2.3.0", path = "src/model", default-features = false }
garage_net = { version = "2.3.0", path = "src/net" }
garage_rpc = { version = "2.3.0", path = "src/rpc" }
garage_table = { version = "2.3.0", path = "src/table" }
garage_util = { version = "2.3.0", path = "src/util" }
garage_web = { version = "2.3.0", path = "src/web" }
garage_api_common = { version = "2.4.0", path = "src/api/common" }
garage_api_admin = { version = "2.4.0", path = "src/api/admin" }
garage_api_s3 = { version = "2.4.0", path = "src/api/s3" }
garage_api_k2v = { version = "2.4.0", path = "src/api/k2v" }
garage_block = { version = "2.4.0", path = "src/block" }
garage_db = { version = "2.4.0", path = "src/db", default-features = false }
garage_model = { version = "2.4.0", path = "src/model", default-features = false }
garage_net = { version = "2.4.0", path = "src/net" }
garage_rpc = { version = "2.4.0", path = "src/rpc" }
garage_table = { version = "2.4.0", path = "src/table" }
garage_util = { version = "2.4.0", path = "src/util" }
garage_web = { version = "2.4.0", path = "src/web" }
k2v-client = { version = "0.0.4", path = "src/k2v-client" }
# External crates from crates.io
@@ -76,7 +76,6 @@ pnet_datalink = "0.35"
rand = "0.9"
sha1 = "0.10"
sha2 = "0.10"
subtle = "2.6.1"
timeago = { version = "0.5", default-features = false }
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
@@ -118,11 +117,7 @@ kube = { version = "3.0", default-features = false, features = [
"rustls-tls",
] }
schemars = "1.2"
reqwest = { version = "0.13", default-features = false, features = [
"rustls-no-provider",
"json",
] }
reqwest = { version = "0.13", default-features = false }
form_urlencoded = "1.2"
http = "1.4"
httpdate = "1.0"
+109 -1
View File
@@ -12,7 +12,7 @@
"name": "AGPL-3.0",
"identifier": "AGPL-3.0"
},
"version": "v2.3.0"
"version": "v2.4.0"
},
"servers": [
{
@@ -3321,10 +3321,35 @@
"dbEngine"
],
"properties": {
"addr": {
"type": [
"string",
"null"
],
"description": "Socket address used by other nodes to connect to this node for RPC"
},
"dataPartition": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/FreeSpaceResp",
"description": "Total and available space on the disk partition(s) containing the data\ndirectory(ies)"
}
]
},
"dbEngine": {
"type": "string",
"description": "database engine used for metadata"
},
"draining": {
"type": [
"boolean",
"null"
],
"description": "Whether this node is part of an older layout version and is draining data."
},
"garageFeatures": {
"type": [
"array",
@@ -3346,9 +3371,38 @@
],
"description": "hostname of this node"
},
"isUp": {
"type": [
"boolean",
"null"
],
"description": "Whether this node is connected in the cluster"
},
"metadataPartition": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/FreeSpaceResp",
"description": "Total and available space on the disk partition containing the\nmetadata directory"
}
]
},
"nodeId": {
"type": "string"
},
"role": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/NodeAssignedRole",
"description": "Role assigned to this node in the current cluster layout"
}
]
},
"rustVersion": {
"type": "string",
"description": "rustc version with which this garage release was compiled"
@@ -3684,10 +3738,35 @@
"dbEngine"
],
"properties": {
"addr": {
"type": [
"string",
"null"
],
"description": "Socket address used by other nodes to connect to this node for RPC"
},
"dataPartition": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/FreeSpaceResp",
"description": "Total and available space on the disk partition(s) containing the data\ndirectory(ies)"
}
]
},
"dbEngine": {
"type": "string",
"description": "database engine used for metadata"
},
"draining": {
"type": [
"boolean",
"null"
],
"description": "Whether this node is part of an older layout version and is draining data."
},
"garageFeatures": {
"type": [
"array",
@@ -3709,9 +3788,38 @@
],
"description": "hostname of this node"
},
"isUp": {
"type": [
"boolean",
"null"
],
"description": "Whether this node is connected in the cluster"
},
"metadataPartition": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/FreeSpaceResp",
"description": "Total and available space on the disk partition containing the\nmetadata directory"
}
]
},
"nodeId": {
"type": "string"
},
"role": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/NodeAssignedRole",
"description": "Role assigned to this node in the current cluster layout"
}
]
},
"rustVersion": {
"type": "string",
"description": "rustc version with which this garage release was compiled"
+16 -1
View File
@@ -33,12 +33,27 @@ kubectl apply -k ../k8s/crd
helm install --create-namespace --namespace garage garage ./garage -f values.override.yaml
```
After deploying, cluster layout must be configured manually as described in [Creating a cluster layout](@/documentation/quick-start/_index.md#creating-a-cluster-layout). Use the following command to access garage CLI:
For multi-node deployments, cluster layout must be configured manually after deploying, as described in [Creating a cluster layout](@/documentation/quick-start/_index.md#creating-a-cluster-layout). Use the following command to access garage CLI:
```bash
kubectl exec --stdin --tty -n garage garage-0 -- ./garage status
```
## Single-node deployments
For a fresh single-node deployment, enable Garage's automatic single-node setup:
```yaml
garage:
singleNode: true
```
With the chart-generated `garage.toml`, this starts Garage with `--single-node`, runs one StatefulSet replica, and sets `replication_factor = 1`.
If you use `garage.garageTomlString` or `garage.existingConfigMap`, the chart cannot update your configuration file. In that case, make sure your Garage configuration sets `replication_factor = 1`; otherwise Garage will refuse to start in single-node mode.
Garage refuses to run with `--single-node` if the cluster already has a multi-node layout, so this option is intended for new single-node deployments.
## Overriding default values
All possible configuration values can be found with:
+5 -5
View File
@@ -96,14 +96,14 @@ to store 2 TB of data in total.
## Get a Docker image
Our docker image is currently named `dxflrs/garage` and is stored on the [Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated).
We encourage you to use a fixed tag (eg. `v2.3.0`) and not the `latest` tag.
For this example, we will use the latest published version at the time of the writing which is `v2.3.0` but it's up to you
We encourage you to use a fixed tag (eg. `v2.4.0`) and not the `latest` tag.
For this example, we will use the latest published version at the time of the writing which is `v2.4.0` but it's up to you
to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated).
For example:
```
docker pull dxflrs/garage:v2.3.0
docker pull dxflrs/garage:v2.4.0
```
## Deploying and configuring Garage
@@ -171,7 +171,7 @@ docker run \
-v /etc/garage.toml:/etc/garage.toml \
-v /var/lib/garage/meta:/var/lib/garage/meta \
-v /var/lib/garage/data:/var/lib/garage/data \
dxflrs/garage:v2.3.0
dxflrs/garage:v2.4.0
```
With this command line, Garage should be started automatically at each boot.
@@ -185,7 +185,7 @@ If you want to use `docker-compose`, you may use the following `docker-compose.y
version: "3"
services:
garage:
image: dxflrs/garage:v2.3.0
image: dxflrs/garage:v2.4.0
network_mode: "host"
restart: unless-stopped
volumes:
+3 -3
View File
@@ -111,7 +111,7 @@ your data to be persisted properly.
### Configuring initial access credentials
Since `v2.3.0`, Garage can automatically create a default access key and a default storage bucket,
Since `v2.n.0`, Garage can automatically create a default access key and a default storage bucket,
based on values provided in environment variables.
To use this feature, export the following environment variables:
@@ -157,7 +157,7 @@ docker run \
-e GARAGE_DEFAULT_ACCESS_KEY \
-e GARAGE_DEFAULT_SECRET_KEY \
-e GARAGE_DEFAULT_BUCKET \
dxflrs/garage:v2.3.0
dxflrs/garage:v2.4.0
/garage server --single-node --default-bucket
```
@@ -337,7 +337,7 @@ When you first start a cluster without automatic configuration, the output of `g
```
==== HEALTHY NODES ====
ID Hostname Address Tags Zone Capacity DataAvail Version
563e1ac825ee3323 linuxbox 127.0.0.1:3901 NO ROLE ASSIGNED v2.3.0
563e1ac825ee3323 linuxbox 127.0.0.1:3901 NO ROLE ASSIGNED v2.4.0
```
Creating a cluster layout for a Garage deployment means informing Garage of the
+2
View File
@@ -21,3 +21,5 @@
.idea/
*.tmproj
.vscode/
# helm-unittest test suites
tests/
+5 -3
View File
@@ -2,8 +2,8 @@ apiVersion: v2
name: garage
description: S3-compatible object store for small self-hosted geo-distributed deployments
type: application
version: 0.9.3
appVersion: "v2.3.0"
version: 0.10.1
appVersion: "v2.4.0"
home: https://garagehq.deuxfleurs.fr/
icon: https://garagehq.deuxfleurs.fr/images/garage-logo.svg
@@ -15,4 +15,6 @@ keywords:
sources:
- https://git.deuxfleurs.fr/Deuxfleurs/garage.git
maintainers: []
maintainers:
- name: Garage maintainer team
email: garagehq@deuxfleurs.fr
+28 -13
View File
@@ -1,11 +1,17 @@
# garage
![Version: 0.9.3](https://img.shields.io/badge/Version-0.9.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v2.3.0](https://img.shields.io/badge/AppVersion-v2.3.0-informational?style=flat-square)
![Version: 0.10.1](https://img.shields.io/badge/Version-0.10.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v2.4.0](https://img.shields.io/badge/AppVersion-v2.4.0-informational?style=flat-square)
S3-compatible object store for small self-hosted geo-distributed deployments
**Homepage:** <https://garagehq.deuxfleurs.fr/>
## Maintainers
| Name | Email | Url |
| ---- | ------ | --- |
| Garage maintainer team | <garagehq@deuxfleurs.fr> | |
## Source Code
* <https://git.deuxfleurs.fr/Deuxfleurs/garage.git>
@@ -15,36 +21,42 @@ S3-compatible object store for small self-hosted geo-distributed deployments
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| commonLabels | object | `{}` | Extra labels for all resources |
| args | list | `[]` | Override the container arguments. |
| command | list | `[]` | Override the container entrypoint. |
| commonLabels | object | `{}` | Additional labels to add to all resources created by this chart |
| deployment.kind | string | `"StatefulSet"` | Switchable to DaemonSet |
| deployment.podManagementPolicy | string | `"OrderedReady"` | If using statefulset, allow Parallel or OrderedReady (default) |
| deployment.replicaCount | int | `3` | Number of StatefulSet replicas/garage nodes to start |
| environment | object | `{}` | |
| extraVolumeMounts | object | `{}` | |
| extraVolumes | object | `{}` | |
| environment | object | `{}` | Extra container env vars, as a list of {name, value} objects (same shape as a Pod container's env) |
| extraVolumeMounts | object | `{}` | Extra volume mounts, as a list of mount objects (same shape as a container's volumeMounts) |
| extraVolumes | object | `{}` | Extra volumes, as a list of volume objects (same shape as a PodSpec's volumes) |
| fullnameOverride | string | `""` | |
| garage.blockSize | string | `"1048576"` | Defaults is 1MB An increase can result in better performance in certain scenarios https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#block_size |
| garage.additionalTopLevelConfig | string | `""` | Additional configuration to append to garage.toml. Use a multi-line string for custom config. Example: additionalTopLevelConfig: |- data_fsync = true |
| garage.admin.apiBindAddr | string | `"[::]:3903"` | |
| garage.blockSize | string | `"1048576"` | Defaults is 1MB, an increase can result in better performance in certain scenarios https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#block_size |
| garage.bootstrapPeers | list | `[]` | This is not required if you use the integrated kubernetes discovery |
| garage.compressionLevel | string | `"1"` | zstd compression level of stored blocks https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#compression_level |
| garage.dbEngine | string | `"lmdb"` | Can be changed for better performance on certain systems https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#db_engine |
| garage.consistencyMode | string | `"consistent"` | By default, enable read-after-write consistency guarantees, see the consistency_mode section at https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#consistency_mode |
| garage.dbEngine | string | `"lmdb"` | Can be changed for better performance on certain systems, use "sqlite" to prioritize durability https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#db_engine |
| garage.existingConfigMap | string | `""` | if not empty string, allow using an existing ConfigMap for the garage.toml, if set, ignores garage.toml |
| garage.existingRpcSecret | string | `""` | If you want to provide an rpcSecret within an existing k8s secret, specify the secret name here, and store the value under the secret key `rpcSecret` the default secret will not be created |
| garage.garageTomlString | string | `""` | String Template for the garage configuration if set, ignores above values. Values can be templated, see https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/ |
| garage.kubernetesSkipCrd | bool | `false` | Set to true if you want to use k8s discovery but install the CRDs manually outside of the helm chart, for example if you operate at namespace level without cluster resources |
| garage.replicationFactor | string | `"3"` | Default to 3 replicas, see the replication_factor section at https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#replication_factor |
| garage.consistencyMode | string | `"consistent"` | Default to read-after-write consistency, see the consistency_mode section at https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#consistency_mode |
| garage.metadataAutoSnapshotInterval | string | `""` | If this value is set, Garage will automatically take a snapshot of the metadata DB file at a regular interval and save it in the metadata directory. https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#metadata_auto_snapshot_interval |
| garage.admin.apiBindAddr | string | `"[::]:3903"` | |
| garage.rpcBindAddr | string | `"[::]:3901"` | |
| garage.noClusterRole | bool | `false` | Set to true if you want to use roles instead of cluster roles |
| garage.replicationFactor | string | `"3"` | Default to 3 replicas, see the replication_factor section at https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#replication_factor |
| garage.rpcBindAddr | string | `"[::]:3901"` | Port used for node-to-node RPC |
| garage.rpcSecret | string | `""` | If not given, a random secret will be generated and stored in a Secret object |
| garage.s3.api.bindAddr | string | `"[::]:3900"` | |
| garage.s3.api.region | string | `"garage"` | |
| garage.s3.api.rootDomain | string | `".s3.garage.tld"` | |
| garage.s3.web.index | string | `"index.html"` | |
| garage.s3.web.bindAddr | string | `"[::]:3902"` | |
| garage.s3.web.index | string | `"index.html"` | |
| garage.s3.web.rootDomain | string | `".web.garage.tld"` | |
| garage.singleNode | bool | `false` | Start Garage with `--single-node`, run one StatefulSet replica, and render replication_factor = 1 in the generated garage.toml, if using garageTomlString or existingConfigMap, set replication_factor = 1 yourself. |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"dxflrs/amd64_garage"` | default to amd64 docker image |
| image.tag | string | `""` | set the image tag, please prefer using the chart version and not this to avoid compatibility issues |
| image.tag | string | `""` | set the image tag, please prefer using the chart version and not this, to avoid compatibility issues |
| imagePullSecrets | list | `[]` | set if you need credentials to pull your custom image |
| ingress.s3.api.annotations | object | `{}` | Rely _either_ on the className or the annotation below but not both! If you want to use the className, set className: "nginx" and replace "nginx" by an Ingress controller name, examples [here](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers). |
| ingress.s3.api.enabled | bool | `false` | |
@@ -81,13 +93,16 @@ S3-compatible object store for small self-hosted geo-distributed deployments
| persistence.meta.size | string | `"100Mi"` | |
| podAnnotations | object | `{}` | additional pod annotations |
| podSecurityContext.fsGroup | int | `1000` | |
| podSecurityContext.fsGroupChangePolicy | string | `"OnRootMismatch"` | |
| podSecurityContext.runAsGroup | int | `1000` | |
| podSecurityContext.runAsNonRoot | bool | `true` | |
| podSecurityContext.runAsUser | int | `1000` | |
| priorityClassName | string | `""` | Optional priority class name to assign to the pods. See https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ |
| readinessProbe | object | `{}` | Specifies a readinessProbe |
| resources | object | `{}` | |
| securityContext.capabilities | object | `{"drop":["ALL"]}` | The default security context is heavily restricted, feel free to tune it to your requirements |
| securityContext.readOnlyRootFilesystem | bool | `true` | |
| service.annotations | object | `{}` | Annotations to add to the service |
| service.s3.api.port | int | `3900` | |
| service.s3.web.port | int | `3902` | |
| service.type | string | `"ClusterIP"` | You can rely on any service to expose your cluster - ClusterIP (+ Ingress) - NodePort (+ Ingress) - LoadBalancer |
+331
View File
@@ -0,0 +1,331 @@
# An "everything and the kitchen sink" values file for the helm chart: combines many non-default
# settings at once, including examples for the fields that default to empty in
# values.yaml and are therefore hard to guess the expected shape of.
#
# Aside the documentation value, it doubles as an integration-test fixture:
# CI renders and lints the chart with this file (see .woodpecker/debug.yaml)
# to catch feature interactions that per-feature fixtures wouldn't exercise together
# (e.g. both ingresses enabled at once, monitoring + custom service account,
# a DaemonSet-incompatible field set alongside a StatefulSet, ...).
#
# Try it locally with:
# helm template script/helm/garage -f script/helm/garage/complex-values.yaml
# helm lint --strict script/helm/garage -f script/helm/garage/complex-values.yaml
# -- Additional labels to add to all resources created by this chart
commonLabels:
app.kubernetes.io/part-of: storage
team: platform-infrastructure
# Garage configuration. Values under this are written to garage.toml
garage:
# -- sqlite for durability, lmdb for performance
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#db_engine
dbEngine: "sqlite"
# -- Here set to 10MiB
# An increase can result in better performance in certain scenarios
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#block_size
blockSize: "10485760"
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#replication_factor
replicationFactor: "5"
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#consistency_mode
consistencyMode: "dangerous"
# -- zstd compression level of stored blocks
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#compression_level
compressionLevel: "5"
# -- If this value is set, Garage will automatically take a snapshot of the metadata DB file and save it in the metadata directory.
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#metadata_auto_snapshot_interval
metadataAutoSnapshotInterval: "30 days"
rpcBindAddr: "[::]:3901"
# -- If not given, a random secret will be generated and stored in a Secret object
rpcSecret: ""
# -- If you want to provide an rpcSecret within an existing k8s secret,
# specify the secret name here, and store the value under the secret key `rpcSecret`
# ! the default secret will not be created
existingRpcSecret: ""
# -- This is not required if you use the integrated kubernetes discovery. Each
# entry is "<garage_node_id>@<host>:<port>", where <garage_node_id> is the node's public key
# (shown by `garage node id` on that node).
bootstrapPeers:
- "563e1ac825ee3323aa441e72c26d1030d6d4222c43c986812dbf7cd47d18aef@garage-0.garage-headless:3901"
- "86f0f26ae4afbd59aaf9cfb302af3fe0464f2f7b5b21f80f7e6f4e9989b5c1f8@garage-1.garage-headless:3901"
# -- Set to true if you want to use k8s discovery but install the CRDs manually outside
# of the helm chart, for example if you operate at namespace level without cluster resources
kubernetesSkipCrd: true
s3:
api:
bindAddr: "[::]:3900"
region: "garage"
rootDomain: ".s3.garage.tld"
web:
bindAddr: "[::]:3902"
rootDomain: ".web.garage.tld"
index: "index.html"
admin:
apiBindAddr: "[::]:3903"
# -- Additional configuration to append to garage.toml. Use a multi-line string for custom config.
additionalTopLevelConfig: |-
data_fsync = true
# -- if not empty string, allow using an existing ConfigMap for the garage.toml,
# if set, ignores garage.toml
existingConfigMap: ""
# -- String Template for the garage configuration.
# if set, ignores every other garage.* value above and is rendered with `tpl`,
# so it can reference .Values/.Release/.Chart, e.g.:
# garageTomlString: |-
# metadata_dir = "/mnt/meta"
# data_dir = "/mnt/data"
# replication_factor = {{ .Values.garage.replicationFactor }}
# rpc_bind_addr = "{{ .Values.garage.rpcBindAddr }}"
# rpc_secret = "__RPC_SECRET_REPLACE__"
# [kubernetes_discovery]
# namespace = "{{ .Release.Namespace }}"
# service_name = "{{ include "garage.fullname" . }}"
# A rendering-verified version of this example lives in tests/configmap_test.yaml.
garageTomlString: ""
# Data persistence
persistence:
enabled: true
meta:
storageClass: "fast-ssd"
size: 100Mi
# used only for daemon sets
hostPath: /var/lib/garage/meta
data:
storageClass: "standard"
size: 100Mi
# used only for daemon sets
hostPath: /var/lib/garage/data
# Deployment configuration
deployment:
# -- Switchable to DaemonSet
kind: StatefulSet
# -- Number of StatefulSet replicas/garage nodes to start
replicaCount: 3
# -- If using statefulset, allow Parallel or OrderedReady (default)
podManagementPolicy: OrderedReady
image:
# -- default to amd64 docker image
repository: dxflrs/amd64_garage
# -- set the image tag, please prefer using the chart version and not this
# to avoid compatibility issues
tag: ""
pullPolicy: IfNotPresent
initImage:
repository: busybox
tag: stable
pullPolicy: IfNotPresent
# -- set if you need credentials to pull your custom image. Each entry needs a
# `name:` key, matching a Secret of type kubernetes.io/dockerconfigjson.
imagePullSecrets:
- name: my-pull-secret
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# -- Specifies whether a service account should be created
create: true
# -- Annotations to add to the service account. Example below is for AWS IRSA.
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/garage-s3"
# -- The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# -- additional pod annotations
podAnnotations:
example.com/has-an-annotation: "true"
podSecurityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: "OnRootMismatch"
runAsNonRoot: true
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
service:
# -- You can rely on any service to expose your cluster
# - ClusterIP (+ Ingress)
# - NodePort (+ Ingress)
# - LoadBalancer
type: ClusterIP
# -- Annotations to add to the service. Example below is for an AWS NLB.
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
s3:
api:
port: 3900
web:
port: 3902
# NOTE: the admin API is excluded for now as it is not consistent across nodes
ingress:
s3:
api:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
labels: {}
hosts:
# -- garage S3 API endpoint, to be used with awscli for example
- host: "s3.garage.tld"
paths:
- path: /
pathType: Prefix
# -- garage S3 API endpoint, DNS style bucket access
- host: "*.s3.garage.tld"
paths:
- path: /
pathType: Prefix
tls:
- secretName: garage-s3-api-tls
hosts:
- s3.garage.tld
- "*.s3.garage.tld"
web:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
labels: {}
hosts:
# -- wildcard website access with bucket name prefix
- host: "*.web.garage.tld"
paths:
- path: /
pathType: Prefix
# -- specific bucket access with FQDN bucket
- host: "mywebpage.example.com"
paths:
- path: /
pathType: Prefix
tls:
- secretName: garage-s3-web-tls
hosts:
- "*.web.garage.tld"
- mywebpage.example.com
# The following are indicative for a small-size deployment, for anything serious double them.
resources:
limits:
cpu: 200m
memory: 2048Mi
requests:
cpu: 100m
memory: 1024Mi
# -- Specifies a livenessProbe
livenessProbe:
httpGet:
path: /health
port: 3903 # or the port from garage.admin.apiBindAddr
initialDelaySeconds: 5
periodSeconds: 30
# -- Specifies a readinessProbe
readinessProbe:
httpGet:
path: /health
port: 3903 # or the port from garage.admin.apiBindAddr
initialDelaySeconds: 5
periodSeconds: 30
# -- Example: pin pods to a dedicated storage node pool, paired with the
# toleration below.
nodeSelector:
node-role.kubernetes.io/storage: "true"
tolerations:
- key: "dedicated"
operator: "Equal"
value: "storage"
effect: "NoSchedule"
# -- Example: spread garage replicas across different nodes, since it is a
# geo-distributed store that only helps availability if replicas don't share
# a failure domain or availability zone.
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: garage
topologyKey: kubernetes.io/hostname
# -- Optional priority class name to assign to the pods.
# See https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
# This is expected to reference a PriorityClass you define yourself.
priorityClassName: "high-priority-storage"
# -- Extra container env vars. Note this is a [] of {name, value} objects (ie. a pod env stanza)
# GARAGE_ADMIN_TOKEN_FILE below points garage at the token file mounted by
# extraVolumes/extraVolumeMounts, see below.
environment:
- name: RUST_LOG
value: "garage=debug"
- name: GARAGE_ADMIN_TOKEN_FILE
value: /mnt/secrets-store/admin-token
# -- Extra volumes/volumeMounts. Both are []. Example here mounts the admin API
# token from an external secrets manager via the Secrets Store CSI driver
# (https://secrets-store-csi-driver.sigs.k8s.io/) instead of a Secret volume.
# This allows, for example, providing the tokens without creating a Kubernetes
# secret. garage reads the mounted file through GARAGE_ADMIN_TOKEN_FILE above.
extraVolumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: garage-admin-token
extraVolumeMounts:
- name: secrets-store
mountPath: /mnt/secrets-store
readOnly: true
monitoring:
metrics:
# -- If true, a service for monitoring is created with a prometheus.io/scrape annotation
enabled: true
serviceMonitor:
# -- If true, a ServiceMonitor CRD is created for a prometheus operator
# https://github.com/coreos/prometheus-operator
enabled: true
path: /metrics
# -- Defaults to the namespace the chart is deployed to; this field is
# templated, so it can also reference .Release.Namespace itself.
namespace: "monitoring"
labels:
release: prometheus
interval: 30s
scheme: http
tlsConfig: {}
scrapeTimeout: 10s
relabelings:
- sourceLabels: ["__meta_kubernetes_pod_node_name"]
targetLabel: node
tracing:
# -- specify a sink endpoint for OpenTelemetry Traces, eg. `http://localhost:4317`
sink: "http://otel-collector.monitoring.svc:4317"
@@ -1,3 +1,4 @@
{{- if eq .Values.garage.noClusterRole false }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
@@ -28,3 +29,4 @@ roleRef:
kind: ClusterRole
name: manage-crds-{{ .Release.Namespace }}-{{ .Release.Name }}
apiGroup: rbac.authorization.k8s.io
{{ end }}
+1 -1
View File
@@ -15,7 +15,7 @@ data:
block_size = "{{ .Values.garage.blockSize }}"
replication_factor = {{ .Values.garage.replicationFactor }}
replication_factor = {{ ternary 1 .Values.garage.replicationFactor .Values.garage.singleNode }}
consistency_mode = "{{ .Values.garage.consistencyMode }}"
compression_level = {{ .Values.garage.compressionLevel }}
@@ -62,7 +62,9 @@ spec:
{{- end }}
{{- end }}
{{- end }}
{{- if and .Values.ingress.s3.api.enabled .Values.ingress.s3.web.enabled }}
---
{{ end }}
{{- if .Values.ingress.s3.web.enabled -}}
{{- $fullName := include "garage.fullname" . -}}
{{- $svcPort := .Values.service.s3.web.port -}}
+27
View File
@@ -0,0 +1,27 @@
{{- if eq .Values.garage.noClusterRole true }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: manage-crds-{{ .Release.Namespace }}-{{ .Release.Name }}
labels:
{{- include "garage.labels" . | nindent 4 }}
rules:
- apiGroups: ["deuxfleurs.fr"]
resources: ["garagenodes"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: allow-crds-for-{{ .Release.Namespace }}-{{ .Release.Name }}
labels:
{{- include "garage.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ include "garage.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
roleRef:
kind: Role
name: manage-crds-{{ .Release.Namespace }}-{{ .Release.Name }}
apiGroup: rbac.authorization.k8s.io
{{ end }}
@@ -28,11 +28,11 @@ spec:
scheme: {{ .Values.monitoring.metrics.serviceMonitor.scheme }}
{{- with .Values.monitoring.metrics.serviceMonitor.tlsConfig }}
tlsConfig:
{{- toYaml . | nindent 6 }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.monitoring.metrics.serviceMonitor.relabelings }}
relabelings:
{{- toYaml . | nindent 6 }}
{{- toYaml . | nindent 8 }}
{{- end }}
jobLabel: "{{ .Release.Name }}"
selector:
@@ -41,4 +41,4 @@ spec:
namespaceSelector:
matchNames:
- {{ .Release.Namespace }}
{{- end }}
{{- end }}
+8 -1
View File
@@ -9,7 +9,7 @@ spec:
matchLabels:
{{- include "garage.selectorLabels" . | nindent 6 }}
{{- if eq .Values.deployment.kind "StatefulSet" }}
replicas: {{ .Values.deployment.replicaCount }}
replicas: {{ ternary 1 .Values.deployment.replicaCount .Values.garage.singleNode }}
serviceName: {{ include "garage.fullname" . }}-headless
podManagementPolicy: {{ .Values.deployment.podManagementPolicy }}
{{- end }}
@@ -59,6 +59,13 @@ spec:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.garage.singleNode }}
command:
- /garage
args:
- server
- --single-node
{{- end }}
ports:
- containerPort: {{ include "garage.portFromBindAddr" .Values.garage.s3.api.bindAddr | int }}
name: s3-api
@@ -0,0 +1,31 @@
suite: rbac
templates:
- templates/clusterrole.yaml
tests:
- it: allows managing the garage CRD by default
asserts:
- hasDocuments:
count: 2
- documentIndex: 0
isKind:
of: ClusterRole
- documentIndex: 0
contains:
path: rules[0].resources
content: customresourcedefinitions
- documentIndex: 1
isKind:
of: ClusterRoleBinding
- documentIndex: 1
equal:
path: subjects[0].name
value: RELEASE-NAME-garage
- it: skips the CRD management rule when the CRD is installed manually
set:
garage.kubernetesSkipCrd: true
asserts:
- documentIndex: 0
notContains:
path: rules[0].resources
content: customresourcedefinitions
@@ -0,0 +1,82 @@
# Integration-style suite: renders the whole chart with complex-values.yaml
# (many non-default features combined at once) and checks that they don't
# clobber each other, rather than testing any single feature in isolation
# (that's what the other tests/*_test.yaml suites are for).
suite: complex-values integration
templates:
- templates/workload.yaml
- templates/service.yaml
- templates/service-headless.yaml
- templates/ingress.yaml
- templates/servicemonitor.yaml
- templates/serviceaccount.yaml
- templates/configmap.yaml
- templates/clusterrole.yaml
tests:
- it: renders a self-consistent deployment with every optional feature enabled
values:
- ../complex-values.yaml
asserts:
- template: templates/workload.yaml
isKind:
of: StatefulSet
- template: templates/workload.yaml
equal:
path: metadata.labels.team
value: platform-infrastructure
- template: templates/workload.yaml
equal:
path: spec.template.spec.containers[0].env[0].name
value: RUST_LOG
- template: templates/workload.yaml
contains:
path: spec.template.spec.volumes
content:
name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: garage-admin-token
- template: templates/workload.yaml
equal:
path: spec.template.spec.containers[0].env[1].name
value: GARAGE_ADMIN_TOKEN_FILE
- template: templates/workload.yaml
equal:
path: spec.volumeClaimTemplates[0].spec.storageClassName
value: fast-ssd
- template: templates/workload.yaml
contains:
path: spec.template.spec.imagePullSecrets
content:
name: my-pull-secret
- template: templates/service.yaml
hasDocuments:
count: 2 # main service + metrics service, since monitoring.metrics.enabled is true here
- template: templates/service-headless.yaml
hasDocuments:
count: 1 # StatefulSet still gets a headless service
- template: templates/ingress.yaml
hasDocuments:
count: 2 # both s3 api and s3 web ingresses enabled together
- template: templates/servicemonitor.yaml
hasDocuments:
count: 1
- template: templates/servicemonitor.yaml
equal:
path: spec.endpoints[0].relabelings[0].targetLabel
value: node
- template: templates/serviceaccount.yaml
equal:
path: metadata.annotations["eks.amazonaws.com/role-arn"]
value: "arn:aws:iam::123456789012:role/garage-s3"
- template: templates/configmap.yaml
matchRegex:
path: data["garage.toml"]
pattern: 'data_fsync = true'
- template: templates/clusterrole.yaml
documentIndex: 0
notContains:
path: rules[0].resources
content: customresourcedefinitions # garage.kubernetesSkipCrd is true here
@@ -0,0 +1,145 @@
suite: configmap
templates:
- templates/configmap.yaml
tests:
- it: renders garage.toml with the default configuration
asserts:
- hasDocuments:
count: 1
- isKind:
of: ConfigMap
- equal:
path: metadata.name
value: RELEASE-NAME-garage-config
- matchRegex:
path: data["garage.toml"]
pattern: 'metadata_dir = "/mnt/meta"'
- matchRegex:
path: data["garage.toml"]
pattern: 'data_dir = "/mnt/data"'
- matchRegex:
path: data["garage.toml"]
pattern: 'db_engine = "lmdb"'
- matchRegex:
path: data["garage.toml"]
pattern: 'block_size = "1048576"'
- matchRegex:
path: data["garage.toml"]
pattern: 'replication_factor = 3'
- matchRegex:
path: data["garage.toml"]
pattern: 'consistency_mode = "consistent"'
- matchRegex:
path: data["garage.toml"]
pattern: 'compression_level = 1'
- matchRegex:
path: data["garage.toml"]
pattern: 'rpc_bind_addr = "\[::\]:3901"'
- matchRegex:
path: data["garage.toml"]
pattern: 'rpc_secret = "__RPC_SECRET_REPLACE__"'
- matchRegex:
path: data["garage.toml"]
pattern: '(?s)\[kubernetes_discovery\]\s*namespace = "NAMESPACE"\s*service_name = "RELEASE-NAME-garage"\s*skip_crd = false'
- matchRegex:
path: data["garage.toml"]
pattern: '(?s)\[s3_api\]\s*s3_region = "garage"\s*api_bind_addr = "\[::\]:3900"\s*root_domain = "\.s3\.garage\.tld"'
- matchRegex:
path: data["garage.toml"]
pattern: '(?s)\[s3_web\]\s*bind_addr = "\[::\]:3902"\s*root_domain = "\.web\.garage\.tld"\s*index = "index.html"'
- matchRegex:
path: data["garage.toml"]
pattern: '(?s)\[admin\]\s*api_bind_addr = "\[::\]:3903"'
- notMatchRegex:
path: data["garage.toml"]
pattern: 'metadata_auto_snapshot_interval'
- notMatchRegex:
path: data["garage.toml"]
pattern: 'trace_sink'
- it: reflects custom garage settings, bootstrap peers and additional config
set:
garage.dbEngine: sqlite
garage.blockSize: "2097152"
garage.replicationFactor: "5"
garage.consistencyMode: degraded
garage.compressionLevel: "3"
garage.metadataAutoSnapshotInterval: 6h
garage.bootstrapPeers:
- abc@peer1:3901
- def@peer2:3901
garage.additionalTopLevelConfig: "data_fsync = true"
monitoring.tracing.sink: http://otel:4317
asserts:
- matchRegex:
path: data["garage.toml"]
pattern: 'db_engine = "sqlite"'
- matchRegex:
path: data["garage.toml"]
pattern: 'block_size = "2097152"'
- matchRegex:
path: data["garage.toml"]
pattern: 'replication_factor = 5'
- matchRegex:
path: data["garage.toml"]
pattern: 'consistency_mode = "degraded"'
- matchRegex:
path: data["garage.toml"]
pattern: 'compression_level = 3'
- matchRegex:
path: data["garage.toml"]
pattern: 'metadata_auto_snapshot_interval = "6h"'
- matchRegex:
path: data["garage.toml"]
pattern: 'bootstrap_peers = \["abc@peer1:3901"\s*, "def@peer2:3901"'
- matchRegex:
path: data["garage.toml"]
pattern: 'data_fsync = true'
- matchRegex:
path: data["garage.toml"]
pattern: 'trace_sink = "http://otel:4317"'
- it: uses garageTomlString verbatim when set, ignoring the structured values
set:
garage.garageTomlString: |-
metadata_dir = "/custom/meta"
replication_factor = 1
garage.dbEngine: sqlite
asserts:
- equal:
path: data["garage.toml"]
value: |-
metadata_dir = "/custom/meta"
replication_factor = 1
- notMatchRegex:
path: data["garage.toml"]
pattern: 'db_engine'
- it: templates garageTomlString against the release and values context
set:
garage.garageTomlString: |-
# namespace: {{ .Release.Namespace }}
replication_factor = {{ .Values.garage.replicationFactor }}
garage.replicationFactor: "7"
asserts:
- equal:
path: data["garage.toml"]
value: |-
# namespace: NAMESPACE
replication_factor = 7
- it: forces replication_factor to 1 when garage.singleNode is enabled
set:
garage.singleNode: true
garage.replicationFactor: "5"
asserts:
- matchRegex:
path: data["garage.toml"]
pattern: 'replication_factor = 1'
- it: does not render a ConfigMap when an existing one is referenced
set:
garage.existingConfigMap: my-external-cm
asserts:
- hasDocuments:
count: 0
@@ -0,0 +1,95 @@
suite: ingress
templates:
- templates/ingress.yaml
tests:
- it: renders no ingress by default
asserts:
- hasDocuments:
count: 0
- it: renders api and web ingresses with tls when enabled
values:
- ./values/ingress.yaml
asserts:
- hasDocuments:
count: 2
- isKind:
of: Ingress
- documentIndex: 0
equal:
path: metadata.name
value: RELEASE-NAME-garage-s3-api
- documentIndex: 0
equal:
path: spec.ingressClassName
value: nginx
- documentIndex: 0
equal:
path: spec.rules[0].host
value: s3.example.com
- documentIndex: 0
equal:
path: spec.tls[0].secretName
value: garage-s3-api-tls
- documentIndex: 1
equal:
path: metadata.name
value: RELEASE-NAME-garage-s3-web
- documentIndex: 1
equal:
path: spec.rules[0].host
value: "*.web.example.com"
- documentIndex: 1
equal:
path: spec.tls[0].secretName
value: garage-s3-web-tls
- it: can enable only the s3 api ingress
set:
ingress.s3.api.enabled: true
ingress.s3.api.hosts[0].host: s3.example.com
ingress.s3.api.hosts[0].paths[0].path: /
ingress.s3.api.hosts[0].paths[0].pathType: Prefix
asserts:
- hasDocuments:
count: 1
- equal:
path: metadata.name
value: RELEASE-NAME-garage-s3-api
- it: omits ingressClassName and tls when neither is configured
set:
ingress.s3.api.enabled: true
ingress.s3.api.hosts[0].host: s3.example.com
ingress.s3.api.hosts[0].paths[0].path: /
ingress.s3.api.hosts[0].paths[0].pathType: Prefix
asserts:
- isNull:
path: spec.ingressClassName
- isNull:
path: spec.tls
- it: renders multiple hosts on the same ingress
set:
ingress.s3.api.enabled: true
ingress.s3.api.hosts:
- host: s3.example.com
paths:
- path: /
pathType: Prefix
- host: s3-alt.example.com
paths:
- path: /
pathType: Prefix
asserts:
- hasDocuments:
count: 1
- lengthEqual:
path: spec.rules
count: 2
- equal:
path: spec.rules[0].host
value: s3.example.com
- equal:
path: spec.rules[1].host
value: s3-alt.example.com
+56
View File
@@ -0,0 +1,56 @@
suite: naming and common labels
templates:
- templates/workload.yaml
- templates/configmap.yaml
tests:
- it: applies commonLabels alongside the default chart labels
template: templates/workload.yaml
set:
commonLabels:
team: storage
asserts:
- equal:
path: metadata.labels.team
value: storage
- equal:
path: metadata.labels["app.kubernetes.io/managed-by"]
value: Helm
- it: uses fullnameOverride verbatim for resource names
template: templates/workload.yaml
set:
fullnameOverride: my-garage-cluster
asserts:
- equal:
path: metadata.name
value: my-garage-cluster
- equal:
path: spec.serviceName
value: my-garage-cluster-headless
- it: does not double-prefix when the release name already contains the chart name
template: templates/workload.yaml
release:
name: garage
asserts:
- equal:
path: metadata.name
value: garage
- it: prefixes the release name with the chart name otherwise
template: templates/workload.yaml
release:
name: prod
asserts:
- equal:
path: metadata.name
value: prod-garage
- it: truncates an overly long fullname to 63 characters and trims a trailing dash
template: templates/workload.yaml
set:
fullnameOverride: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-suffix-that-will-be-cut-off
asserts:
- equal:
path: metadata.name
value: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+33
View File
@@ -0,0 +1,33 @@
suite: rpc secret
templates:
- templates/secret.yaml
tests:
- it: generates a Secret holding the rpc secret by default
asserts:
- hasDocuments:
count: 1
- isKind:
of: Secret
- equal:
path: metadata.name
value: RELEASE-NAME-garage-rpc-secret
- equal:
path: type
value: Opaque
- isNotNull:
path: data.rpcSecret
- it: does not render a Secret when an existing one is referenced
values:
- ./values/existing-secret.yaml
asserts:
- hasDocuments:
count: 0
- it: base64-encodes an explicitly provided rpc secret
set:
garage.rpcSecret: my-plain-secret
asserts:
- equal:
path: data.rpcSecret
value: bXktcGxhaW4tc2VjcmV0
@@ -0,0 +1,26 @@
suite: headless service
templates:
- templates/service-headless.yaml
tests:
- it: creates a headless service for a StatefulSet by default
asserts:
- hasDocuments:
count: 1
- isKind:
of: Service
- equal:
path: metadata.name
value: RELEASE-NAME-garage-headless
- equal:
path: spec.clusterIP
value: None
- equal:
path: spec.type
value: ClusterIP
- it: does not create a headless service for a DaemonSet
values:
- ./values/daemonset.yaml
asserts:
- hasDocuments:
count: 0
@@ -0,0 +1,61 @@
suite: service
templates:
- templates/service.yaml
tests:
- it: creates a ClusterIP service with s3-api and s3-web ports by default
asserts:
- hasDocuments:
count: 1
- isKind:
of: Service
- equal:
path: spec.type
value: ClusterIP
- equal:
path: spec.ports[0].name
value: s3-api
- equal:
path: spec.ports[0].port
value: 3900
- equal:
path: spec.ports[1].name
value: s3-web
- equal:
path: spec.ports[1].port
value: 3902
- it: honors a custom service type and port
set:
service.type: LoadBalancer
service.s3.api.port: 9000
asserts:
- equal:
path: spec.type
value: LoadBalancer
- equal:
path: spec.ports[0].port
value: 9000
- it: does not create a metrics service by default
asserts:
- hasDocuments:
count: 1
- it: adds a headless metrics service when monitoring is enabled
values:
- ./values/monitoring.yaml
asserts:
- hasDocuments:
count: 2
- documentIndex: 1
equal:
path: metadata.name
value: RELEASE-NAME-garage-metrics
- documentIndex: 1
equal:
path: spec.clusterIP
value: None
- documentIndex: 1
equal:
path: metadata.annotations["prometheus.io/scrape"]
value: "true"
@@ -0,0 +1,28 @@
suite: service account
templates:
- templates/serviceaccount.yaml
tests:
- it: creates a ServiceAccount by default
asserts:
- hasDocuments:
count: 1
- isKind:
of: ServiceAccount
- equal:
path: metadata.name
value: RELEASE-NAME-garage
- it: does not create a ServiceAccount when disabled
values:
- ./values/minimal.yaml
asserts:
- hasDocuments:
count: 0
- it: honors a custom service account name
set:
serviceAccount.name: my-garage-sa
asserts:
- equal:
path: metadata.name
value: my-garage-sa
@@ -0,0 +1,65 @@
suite: service monitor
templates:
- templates/servicemonitor.yaml
tests:
- it: renders no ServiceMonitor by default
asserts:
- hasDocuments:
count: 0
- it: renders no ServiceMonitor when only metrics are enabled
set:
monitoring.metrics.enabled: true
asserts:
- hasDocuments:
count: 0
- it: renders a ServiceMonitor when explicitly enabled
values:
- ./values/monitoring.yaml
asserts:
- hasDocuments:
count: 1
- isKind:
of: ServiceMonitor
- equal:
path: metadata.name
value: RELEASE-NAME-garage
- equal:
path: metadata.namespace
value: NAMESPACE
- equal:
path: spec.endpoints[0].interval
value: 30s
- it: templates a custom namespace against the release context
values:
- ./values/monitoring.yaml
set:
monitoring.metrics.serviceMonitor.namespace: "{{ .Release.Namespace }}-monitoring"
asserts:
- equal:
path: metadata.namespace
value: NAMESPACE-monitoring
- it: applies custom labels, tlsConfig and relabelings
values:
- ./values/monitoring.yaml
set:
monitoring.metrics.serviceMonitor.labels:
team: storage
monitoring.metrics.serviceMonitor.tlsConfig:
insecureSkipVerify: true
monitoring.metrics.serviceMonitor.relabelings:
- sourceLabels: ["__meta_kubernetes_pod_name"]
targetLabel: pod
asserts:
- equal:
path: metadata.labels.team
value: storage
- equal:
path: spec.endpoints[0].tlsConfig.insecureSkipVerify
value: true
- equal:
path: spec.endpoints[0].relabelings[0].targetLabel
value: pod
@@ -0,0 +1,10 @@
# Run garage as a DaemonSet (one pod per node) instead of the default StatefulSet,
# using hostPath volumes for meta/data persistence.
deployment:
kind: DaemonSet
persistence:
enabled: true
meta:
hostPath: /var/lib/garage/meta
data:
hostPath: /var/lib/garage/data
@@ -0,0 +1,5 @@
# Use a pre-existing Kubernetes Secret for the RPC secret instead of letting
# the chart generate/manage one.
garage:
rpcSecret: ""
existingRpcSecret: "garage-rpc-secret-external"
@@ -0,0 +1,27 @@
# Expose both the S3 API and website endpoints through Ingress, with TLS.
ingress:
s3:
api:
enabled: true
className: "nginx"
hosts:
- host: "s3.example.com"
paths:
- path: /
pathType: Prefix
tls:
- secretName: garage-s3-api-tls
hosts:
- s3.example.com
web:
enabled: true
className: "nginx"
hosts:
- host: "*.web.example.com"
paths:
- path: /
pathType: Prefix
tls:
- secretName: garage-s3-web-tls
hosts:
- "*.web.example.com"
@@ -0,0 +1,8 @@
# Minimal single-node deployment without persistent storage or a dedicated
# service account, e.g. for local testing.
deployment:
replicaCount: 1
persistence:
enabled: false
serviceAccount:
create: false
@@ -0,0 +1,7 @@
# Enable Prometheus metrics scraping and a ServiceMonitor for the prometheus-operator.
monitoring:
metrics:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
+214
View File
@@ -0,0 +1,214 @@
suite: workload (StatefulSet/DaemonSet)
templates:
- templates/workload.yaml
- templates/configmap.yaml
tests:
- it: defaults to a StatefulSet with 3 replicas and 2 volumes
template: templates/workload.yaml
asserts:
- isKind:
of: StatefulSet
- equal:
path: spec.replicas
value: 3
- equal:
path: spec.podManagementPolicy
value: OrderedReady
- equal:
path: spec.template.spec.volumes[1].name
value: etc
- lengthEqual:
path: spec.template.spec.volumes
count: 2
- isNotNull:
path: spec.volumeClaimTemplates
- it: uses a StatefulSet with PVC-backed volumeClaimTemplates by default
template: templates/workload.yaml
asserts:
- isKind:
of: StatefulSet
- isNotNull:
path: spec.volumeClaimTemplates
- equal:
path: spec.volumeClaimTemplates[0].spec.resources.requests.storage
value: 100Mi
- it: switches to a DaemonSet with hostPath volumes when requested
template: templates/workload.yaml
values:
- ./values/daemonset.yaml
asserts:
- isKind:
of: DaemonSet
- isNull:
path: spec.replicas
- isNull:
path: spec.volumeClaimTemplates
- contains:
path: spec.template.spec.volumes
content:
name: meta
hostPath:
path: /var/lib/garage/meta
type: DirectoryOrCreate
- contains:
path: spec.template.spec.volumes
content:
name: data
hostPath:
path: /var/lib/garage/data
type: DirectoryOrCreate
- it: renders emptyDir volumes when persistence is disabled
template: templates/workload.yaml
values:
- ./values/minimal.yaml
asserts:
- contains:
path: spec.template.spec.volumes
content:
name: meta
emptyDir: {}
- contains:
path: spec.template.spec.volumes
content:
name: data
emptyDir: {}
- isNull:
path: spec.volumeClaimTemplates
- it: honors a custom replicaCount
template: templates/workload.yaml
set:
deployment.replicaCount: 5
asserts:
- equal:
path: spec.replicas
value: 5
- it: points the init container at the configured rpc secret
template: templates/workload.yaml
asserts:
- equal:
path: spec.template.spec.initContainers[0].env[0].valueFrom.secretKeyRef.name
value: RELEASE-NAME-garage-rpc-secret
- it: points the init container at an existing rpc secret when configured
template: templates/workload.yaml
values:
- ./values/existing-secret.yaml
asserts:
- equal:
path: spec.template.spec.initContainers[0].env[0].valueFrom.secretKeyRef.name
value: garage-rpc-secret-external
- it: sets the container image from repository and tag
template: templates/workload.yaml
set:
image.repository: dxflrs/amd64_garage
image.tag: v1.2.3
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: dxflrs/amd64_garage:v1.2.3
- it: falls back to the chart appVersion when no image tag is set
template: templates/workload.yaml
asserts:
- matchRegex:
path: spec.template.spec.containers[0].image
pattern: ^dxflrs/amd64_garage:v
- it: omits storageClassName from volumeClaimTemplates by default
template: templates/workload.yaml
asserts:
- isNull:
path: spec.volumeClaimTemplates[0].spec.storageClassName
- isNull:
path: spec.volumeClaimTemplates[1].spec.storageClassName
- it: sets storageClassName in volumeClaimTemplates when configured
template: templates/workload.yaml
set:
persistence.meta.storageClass: fast-storage
persistence.data.storageClass: slow-storage
asserts:
- equal:
path: spec.volumeClaimTemplates[0].spec.storageClassName
value: fast-storage
- equal:
path: spec.volumeClaimTemplates[1].spec.storageClassName
value: slow-storage
- it: renders emptyDir volumes for a DaemonSet when persistence is disabled
template: templates/workload.yaml
set:
deployment.kind: DaemonSet
persistence.enabled: false
asserts:
- contains:
path: spec.template.spec.volumes
content:
name: meta
emptyDir: {}
- contains:
path: spec.template.spec.volumes
content:
name: data
emptyDir: {}
- it: mounts the existing ConfigMap volume when configured
template: templates/workload.yaml
set:
garage.existingConfigMap: my-external-cm
asserts:
- equal:
path: spec.template.spec.volumes[0].configMap.name
value: my-external-cm
- it: runs a single replica with --single-node when garage.singleNode is enabled
template: templates/workload.yaml
set:
garage.singleNode: true
deployment.replicaCount: 3
asserts:
- equal:
path: spec.replicas
value: 1
- equal:
path: spec.template.spec.containers[0].command
value:
- /garage
- equal:
path: spec.template.spec.containers[0].args
value:
- server
- --single-node
- it: does not set a custom command/args when garage.singleNode is disabled
template: templates/workload.yaml
asserts:
- isNull:
path: spec.template.spec.containers[0].command
- isNull:
path: spec.template.spec.containers[0].args
- it: uses a custom service account name without creating one when disabled
template: templates/workload.yaml
set:
serviceAccount.create: false
serviceAccount.name: my-external-sa
asserts:
- equal:
path: spec.template.spec.serviceAccountName
value: my-external-sa
- it: falls back to the default service account when disabled without a custom name
template: templates/workload.yaml
set:
serviceAccount.create: false
asserts:
- equal:
path: spec.template.spec.serviceAccountName
value: default
+21 -5
View File
@@ -7,14 +7,13 @@ commonLabels: {}
# app.kubernetes.io/part-of: storage
# team: platform
# Garage configuration. These values go to garage.toml
# Garage configuration. These values configure Garage and render to garage.toml unless noted otherwise.
garage:
# -- Can be changed for better performance on certain systems
# -- Can be changed for better performance on certain systems, use "sqlite" to prioritize durability
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#db_engine
dbEngine: "lmdb"
# -- Defaults is 1MB
# An increase can result in better performance in certain scenarios
# -- Defaults is 1MB, an increase can result in better performance in certain scenarios
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#block_size
blockSize: "1048576"
@@ -22,6 +21,10 @@ garage:
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#replication_factor
replicationFactor: "3"
# -- Start Garage with `--single-node`, run one StatefulSet replica, and render replication_factor = 1 in the generated garage.toml,
# if using garageTomlString or existingConfigMap, set replication_factor = 1 yourself.
singleNode: false
# -- By default, enable read-after-write consistency guarantees, see the consistency_mode section at
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#consistency_mode
consistencyMode: "consistent"
@@ -34,6 +37,7 @@ garage:
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#metadata_auto_snapshot_interval
metadataAutoSnapshotInterval: ""
# -- Port used for node-to-node RPC
rpcBindAddr: "[::]:3901"
# -- If not given, a random secret will be generated and stored in a Secret object
rpcSecret: ""
@@ -46,6 +50,8 @@ garage:
# -- Set to true if you want to use k8s discovery but install the CRDs manually outside
# of the helm chart, for example if you operate at namespace level without cluster resources
kubernetesSkipCrd: false
# -- Set to true if you want to use roles instead of cluster roles
noClusterRole: false
s3:
api:
bindAddr: "[::]:3900"
@@ -100,7 +106,7 @@ deployment:
image:
# -- default to amd64 docker image
repository: dxflrs/amd64_garage
# -- set the image tag, please prefer using the chart version and not this
# -- set the image tag, please prefer using the chart version and not this,
# to avoid compatibility issues
tag: ""
pullPolicy: IfNotPresent
@@ -246,10 +252,20 @@ affinity: {}
# See https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
priorityClassName: ""
# -- Extra container env vars, as a list of {name, value} objects (same shape
# as a Pod container's env)
environment: {}
# -- Override the container entrypoint.
command: []
# -- Override the container arguments.
args: []
# -- Extra volumes, as a list of volume objects (same shape as a PodSpec's volumes)
extraVolumes: {}
# -- Extra volume mounts, as a list of mount objects (same shape as a container's volumeMounts)
extraVolumeMounts: {}
monitoring:
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_api_admin"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+2 -2
View File
@@ -191,7 +191,7 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
.admin
.metrics_token
.as_ref()
.is_some_and(|s| s.eq_ct(&self.admin_token))
.is_some_and(|s| s == &self.admin_token)
{
return Ok(GetCurrentAdminTokenInfoResponse(
GetAdminTokenInfoResponse {
@@ -210,7 +210,7 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
.admin
.admin_token
.as_ref()
.is_some_and(|s| s.eq_ct(&self.admin_token))
.is_some_and(|s| s == &self.admin_token)
{
return Ok(GetCurrentAdminTokenInfoResponse(
GetAdminTokenInfoResponse {
+2 -8
View File
@@ -117,14 +117,8 @@ impl AdminApiServer {
#[cfg(feature = "metrics")] exporter: PrometheusExporter,
) -> Arc<Self> {
let cfg = &garage.config.admin;
let metrics_token = cfg
.metrics_token
.as_ref()
.map(|token| hash_bearer_token(token.extract_secret()));
let admin_token = cfg
.admin_token
.as_ref()
.map(|token| hash_bearer_token(token.extract_secret()));
let metrics_token = cfg.metrics_token.as_deref().map(hash_bearer_token);
let admin_token = cfg.admin_token.as_deref().map(hash_bearer_token);
let metrics_require_token = cfg.metrics_require_token;
let endpoint = garage.system.netapp.endpoint(ADMIN_RPC_PATH.into());
+1 -1
View File
@@ -876,7 +876,7 @@ impl Modify for SecurityAddon {
#[derive(OpenApi)]
#[openapi(
info(
version = "v2.3.0",
version = "v2.4.0",
title = "Garage administration API",
description = "Administrate your Garage cluster programmatically, including status, layout, keys, buckets, and maintenance tasks.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_api_common"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_api_k2v"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_api_s3"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+6 -1
View File
@@ -698,9 +698,14 @@ fn body_from_blocks_range(
// range, as well as their "true offset", which is their actual offset in the complete
// file (whereas block.offset designates the offset of the block WITHIN THE PART
// block.part_number, which is not the same in the case of a multipart upload)
// A version with no blocks yields no data, so the capacity hint must not index
// into an empty slice.
let capacity_block_size = all_blocks
.first()
.map_or(1024, |(_, b)| std::cmp::max(b.size, 1024));
let mut blocks: Vec<(VersionBlock, u64)> = Vec::with_capacity(std::cmp::min(
all_blocks.len(),
4 + ((end - begin) / std::cmp::max(all_blocks[0].1.size, 1024)) as usize,
4 + ((end - begin) / capacity_block_size) as usize,
));
let mut block_offset: u64 = 0;
for (_, b) in all_blocks.iter() {
+7 -1
View File
@@ -476,7 +476,13 @@ pub async fn handle_complete_multipart_upload(
size: total_size,
etag: etag.clone(),
},
final_version.blocks.items()[0].1.hash,
final_version
.blocks
.items()
.first()
.ok_or_internal_error("Multipart completion produced a final version with no blocks")?
.1
.hash,
));
let final_object = Object::new(*bucket_id, key.clone(), vec![object_version]);
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_block"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_db"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -2
View File
@@ -307,8 +307,7 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
let net_key_hex_str = rpc_secret.ok_or("No RPC secret provided")?;
let network_key = NetworkKey::from_slice(
&hex::decode(net_key_hex_str.extract_secret())
.err_context("Invalid RPC secret key (bad hex)")?[..],
&hex::decode(&net_key_hex_str).err_context("Invalid RPC secret key (bad hex)")?[..],
)
.ok_or("Invalid RPC secret provided (wrong length)")?;
+5 -8
View File
@@ -2,7 +2,7 @@ use std::path::PathBuf;
use structopt::StructOpt;
use garage_util::config::{Config, Secret};
use garage_util::config::Config;
use garage_util::error::Error;
/// Structure for secret values or paths that are passed as CLI arguments or environment
@@ -99,7 +99,7 @@ pub fn fill_secrets(mut config: Config, secrets: Secrets) -> Result<Config, Erro
}
pub(crate) fn fill_secret(
config_secret: &mut Option<Secret<String>>,
config_secret: &mut Option<String>,
config_secret_file: &Option<PathBuf>,
cli_secret: &Option<String>,
cli_secret_file: &Option<PathBuf>,
@@ -110,7 +110,7 @@ pub(crate) fn fill_secret(
(Some(_), Some(_)) => {
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
}
(Some(secret), None) => Some(Secret::new(secret.to_string())),
(Some(secret), None) => Some(secret.to_string()),
(None, Some(file)) => Some(read_secret_file(file, allow_world_readable)?),
(None, None) => None,
};
@@ -132,10 +132,7 @@ pub(crate) fn fill_secret(
Ok(())
}
fn read_secret_file(
file_path: &PathBuf,
allow_world_readable: bool,
) -> Result<Secret<String>, Error> {
fn read_secret_file(file_path: &PathBuf, allow_world_readable: bool) -> Result<String, Error> {
if !allow_world_readable {
#[cfg(unix)]
{
@@ -155,7 +152,7 @@ fn read_secret_file(
// trim_end: allows for use case such as `echo "$(openssl rand -hex 32)" > somefile`.
// also editors sometimes add a trailing newline
Ok(Secret::new(String::from(secret_buf.trim_end())))
Ok(String::from(secret_buf.trim_end()))
}
#[cfg(test)]
+1
View File
@@ -24,6 +24,7 @@ percent-encoding.workspace = true
hyper = { workspace = true, default-features = false, features = ["http1", "http2"] }
hyper-util.workspace = true
hyper-rustls.workspace = true
reqwest = { workspace = true, default-features = false, features = ["json", "rustls-no-provider"] }
serde = { workspace = true, default-features = false, features = ["derive", "std"] }
serde_json.workspace = true
thiserror.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_model"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -137,7 +137,7 @@ impl Garage {
info!("Initializing RPC...");
let network_key = hex::decode(config.rpc_secret.as_ref().ok_or_message(
"rpc_secret value is missing, not present in config file or in environment",
)?.extract_secret())
)?)
.ok()
.and_then(|x| NetworkKey::from_slice(&x))
.ok_or_message("Invalid RPC secret key: expected 32 bytes of random hex, please check the documentation for requirements")?;
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_net"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_rpc"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
@@ -40,7 +40,7 @@ thiserror = { workspace = true, optional = true }
kube = { workspace = true, optional = true }
k8s-openapi = { workspace = true, optional = true }
schemars = { workspace = true, optional = true }
reqwest = { workspace = true, optional = true }
reqwest = { workspace = true, optional = true, features = ["json", "rustls"] }
pnet_datalink.workspace = true
+1 -1
View File
@@ -115,7 +115,7 @@ impl ConsulDiscovery {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
"x-consul-token",
reqwest::header::HeaderValue::from_str(token.extract_secret())?,
reqwest::header::HeaderValue::from_str(token)?,
);
builder = builder.default_headers(headers);
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_table"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_util"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
@@ -32,7 +32,6 @@ lazy_static.workspace = true
tracing.workspace = true
rand.workspace = true
sha2.workspace = true
subtle.workspace = true
chrono.workspace = true
rmp-serde.workspace = true
+4 -35
View File
@@ -90,7 +90,7 @@ pub struct Config {
pub allow_world_readable_secrets: bool,
/// RPC secret key: 32 bytes hex encoded
pub rpc_secret: Option<Secret<String>>,
pub rpc_secret: Option<String>,
/// Optional file where RPC secret key is read from
pub rpc_secret_file: Option<PathBuf>,
/// Address to bind for RPC
@@ -205,37 +205,6 @@ pub struct WebConfig {
pub add_host_to_metrics: bool,
}
#[derive(Deserialize, Clone)]
#[serde(transparent)]
pub struct Secret<T>(T);
impl<T> Secret<T> {
pub fn new(secret: T) -> Self {
Secret(secret)
}
pub fn extract_secret(&self) -> &T {
&self.0
}
}
impl<T: std::ops::Deref<Target = str>> Secret<T> {
pub fn eq_ct(&self, other: &T) -> bool {
use subtle::ConstantTimeEq;
self.0
.deref()
.as_bytes()
.ct_eq(other.deref().as_bytes())
.into()
}
}
impl<T> std::fmt::Debug for Secret<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Secret").finish_non_exhaustive()
}
}
/// Configuration for the admin and monitoring HTTP API
#[derive(Deserialize, Debug, Clone, Default)]
pub struct AdminConfig {
@@ -243,7 +212,7 @@ pub struct AdminConfig {
pub api_bind_addr: Option<UnixOrTCPSocketAddress>,
/// Bearer token to use to scrape metrics
pub metrics_token: Option<Secret<String>>,
pub metrics_token: Option<String>,
/// File to read metrics token from
pub metrics_token_file: Option<PathBuf>,
/// Whether to require an access token for accessing the metrics endpoint
@@ -251,7 +220,7 @@ pub struct AdminConfig {
pub metrics_require_token: bool,
/// Bearer token to use to access Admin API endpoints
pub admin_token: Option<Secret<String>>,
pub admin_token: Option<String>,
/// File to read admin token from
pub admin_token_file: Option<PathBuf>,
@@ -283,7 +252,7 @@ pub struct ConsulDiscoveryConfig {
/// Client TLS key to use when connecting to Consul
pub client_key: Option<String>,
/// /// Token to use for connecting to consul
pub token: Option<Secret<String>>,
pub token: Option<String>,
/// Skip TLS hostname verification
#[serde(default)]
pub tls_skip_verify: bool,
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_web"
version = "2.3.0"
version = "2.4.0"
authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
edition = "2018"
license = "AGPL-3.0"