Skip to content

Commit cd7311d

Browse files
stplasimThoSap
andauthored
Setup helm chart for garage (#1)
* init helm chart for garage * update readme * fix comments * align helpers * update helm version * fix missing ref * remove flag to disable admin api * add github mirror to Chart.yaml * apply suggestions from code review Co-authored-by: Thomas Sapelza <sapelza.thomas@gmail.com> * update to helm v4 * add lint ci * update path type from Prefix to ImplementationSpecific --------- Co-authored-by: Thomas Sapelza <sapelza.thomas@gmail.com>
1 parent fd86005 commit cd7311d

18 files changed

Lines changed: 1054 additions & 0 deletions

.github/workflows/lint.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Lint Chart
2+
3+
on:
4+
pull_request:
5+
6+
env:
7+
CHART_NAME: garage
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-24.04
12+
timeout-minutes: 5
13+
steps:
14+
- uses: actions/checkout@v5
15+
- name: Set up Helm
16+
uses: azure/setup-helm@v5
17+
with:
18+
version: 4.2.3
19+
# admin.token is a required value; a dummy is passed so all templates
20+
# render during linting instead of being skipped with a warning.
21+
- name: Lint Helm chart
22+
run: helm lint ${{ env.CHART_NAME }} --strict --set admin.token=ci-lint-only
23+
shell: bash

.github/workflows/release.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Release Package
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Version (eg: x.x.x)"
8+
type: string
9+
required: true
10+
11+
env:
12+
CHART_NAME: garage
13+
OCI_IMAGE: oci://ghcr.io/${{ github.repository }}
14+
15+
jobs:
16+
build-and-publish:
17+
runs-on: ubuntu-24.04
18+
timeout-minutes: 15
19+
steps:
20+
- uses: actions/checkout@v7
21+
with:
22+
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
23+
- uses: aboutbits/github-actions-base/git-setup@v2
24+
- name: Set up Helm
25+
uses: azure/setup-helm@v5
26+
with:
27+
version: 4.2.3
28+
- name: Set version of Chart.yaml file
29+
run: yq -i '.version = "${{ github.event.inputs.version }}"' ${{ env.CHART_NAME }}/Chart.yaml
30+
shell: bash
31+
- name: Package Helm chart
32+
run: helm package ${{ env.CHART_NAME }}
33+
shell: bash
34+
- name: Push to OCI registry
35+
run: |
36+
helm registry login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
37+
helm push ${{ env.CHART_NAME }}-${{ github.event.inputs.version }}.tgz ${{ env.OCI_IMAGE }}
38+
shell: bash
39+
- uses: aboutbits/github-actions-base/git-commit-and-push-all@v2
40+
with:
41+
message: '${{ github.event.inputs.version }}'
42+
- uses: aboutbits/github-actions-base/git-create-or-update-tag@v2
43+
with:
44+
tag-name: 'v${{ github.event.inputs.version }}'
45+
- uses: aboutbits/github-actions-base/github-create-release@v2
46+
with:
47+
tag-name: 'v${{ github.event.inputs.version }}'
48+
release-notes-generation: 'true'

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
*.iml
3+
*.tgz
4+
.DS_Store

garage/.helmignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Patterns to ignore when building packages.
2+
# This supports shell glob matching, relative path matching, and
3+
# negation (prefixed with !). Only one pattern per line.
4+
.DS_Store
5+
# Common VCS dirs
6+
.git/
7+
.gitignore
8+
.bzr/
9+
.bzrignore
10+
.hg/
11+
.hgignore
12+
.svn/
13+
# Common backup files
14+
*.swp
15+
*.bak
16+
*.tmp
17+
*.orig
18+
*~
19+
# Various IDEs
20+
.project
21+
.idea/
22+
*.tmproj
23+
.vscode/

garage/Chart.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v2
2+
name: garage
3+
description: AboutBits single-node Garage (S3-compatible object storage) Helm chart
4+
type: application
5+
version: "0.1.0"
6+
appVersion: "v2.3.0"
7+
keywords:
8+
- garage
9+
- s3
10+
- object-storage
11+
home: https://garagehq.deuxfleurs.fr
12+
sources:
13+
- https://git.deuxfleurs.fr/Deuxfleurs/garage
14+
- https://github.com/deuxfleurs-org/garage

garage/templates/NOTES.txt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Garage single-node deployment "{{ include "garage.fullname" . }}" is starting up.
2+
3+
{{- if .Values.bootstrap.enabled }}
4+
5+
It bootstraps itself on first boot via `garage server --single-node --default-bucket`
6+
(no manual layout/bucket setup needed). This is a one-time bootstrap: adding a
7+
second bucket or rotating the default key later means calling the Admin API
8+
directly, not editing values.yaml.
9+
10+
Bucket: {{ .Values.bootstrap.defaultBucket }}
11+
Access key: (value of bootstrap.accessKey in your values)
12+
Secret key: (value of bootstrap.secretKey in your values)
13+
{{- else }}
14+
15+
No bucket/key is auto-created (bootstrap.enabled=false). Create buckets and
16+
keys yourself via the Admin API or the `garage` CLI, e.g.:
17+
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage layout assign -z dc1 -c 1G <node-id>
18+
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage layout apply --version 1
19+
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket create <bucket-name>
20+
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage key create <key-name>
21+
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket allow --read --write --owner <bucket-name> --key <key-name>
22+
{{- end }}
23+
24+
S3 endpoint (from inside the cluster):
25+
http://{{ include "garage.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.s3.port }}
26+
27+
Region: {{ .Values.s3.region }}
28+
29+
{{- if .Values.ingress.enabled }}
30+
31+
External S3 endpoint (via Ingress):
32+
{{- range .Values.ingress.hosts }}
33+
{{- $host := .host }}
34+
{{- range .paths }}
35+
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ .path }}
36+
{{- end }}
37+
{{- end }}
38+
{{- end }}
39+
{{- if .Values.httpRoute.enabled }}
40+
41+
External S3 endpoint (via Gateway API HTTPRoute):
42+
{{- if .Values.httpRoute.hostnames }}
43+
{{- range .Values.httpRoute.hostnames }}
44+
https://{{ . }}/
45+
{{- end }}
46+
{{- else }}
47+
(matches all hostnames on the referenced Gateway)
48+
{{- end }}
49+
{{- end }}
50+
51+
Check status:
52+
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage status
53+
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket list

garage/templates/_helpers.tpl

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{{/*
2+
Chart name and fullname
3+
*/}}
4+
{{- define "garage.name" -}}
5+
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6+
{{- end }}
7+
8+
{{- define "garage.fullname" -}}
9+
{{- if .Values.fullnameOverride }}
10+
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
11+
{{- else }}
12+
{{- $name := default .Chart.Name .Values.nameOverride }}
13+
{{- if contains $name .Release.Name }}
14+
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
15+
{{- else }}
16+
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
17+
{{- end }}
18+
{{- end }}
19+
{{- end }}
20+
21+
{{- define "garage.chart" -}}
22+
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
23+
{{- end }}
24+
25+
{{- define "garage.labels" -}}
26+
helm.sh/chart: {{ include "garage.chart" . }}
27+
{{ include "garage.selectorLabels" . }}
28+
{{- if .Chart.AppVersion }}
29+
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
30+
{{- end }}
31+
app.kubernetes.io/managed-by: {{ .Release.Service }}
32+
{{- end }}
33+
34+
{{- define "garage.selectorLabels" -}}
35+
app.kubernetes.io/name: {{ include "garage.name" . }}
36+
app.kubernetes.io/instance: {{ .Release.Name }}
37+
{{- end }}
38+
39+
{{/*
40+
Name of the ServiceAccount to use.
41+
*/}}
42+
{{- define "garage.serviceAccountName" -}}
43+
{{- if .Values.serviceAccount.create }}
44+
{{- $baseName := .Values.serviceAccount.name | default (printf "sa-%s" (include "garage.fullname" .)) -}}
45+
{{- $baseName | trunc 63 | trimSuffix "-" }}
46+
{{- else }}
47+
{{- default "default" .Values.serviceAccount.name }}
48+
{{- end }}
49+
{{- end }}
50+
51+
{{/*
52+
Names for the ingress / HTTPRoute objects (prefixed for readability, overridable
53+
via .Values.<kind>.name). These name the routing object only — the backend still
54+
targets the Service (garage.fullname).
55+
*/}}
56+
{{- define "garage.ingressName" -}}
57+
{{- $baseName := .Values.ingress.name | default (printf "ingress-%s" (include "garage.fullname" .)) -}}
58+
{{- $baseName | trunc 63 | trimSuffix "-" }}
59+
{{- end }}
60+
61+
{{- define "garage.httpRouteName" -}}
62+
{{- $baseName := .Values.httpRoute.name | default (printf "httproute-%s" (include "garage.fullname" .)) -}}
63+
{{- $baseName | trunc 63 | trimSuffix "-" }}
64+
{{- end }}
65+
66+
{{/*
67+
Auto-generated RPC secret (inter-node clustering auth; loopback-only on a single
68+
node, so it is never consumed externally). `lookup` reuses the previously stored
69+
value on `helm upgrade` to avoid a needless Secret diff — this is churn
70+
avoidance, not a correctness requirement: the node's identity and cluster layout
71+
live in the metadata volume, independent of this secret, so regenerating it does
72+
not lose data or orphan the layout. The admin_token, by contrast, is a required,
73+
operator-provided value (see admin.token) so it is known up front.
74+
*/}}
75+
{{- define "garage.rpcSecret" -}}
76+
{{- $existing := (lookup "v1" "Secret" .Release.Namespace (include "garage.fullname" .)) -}}
77+
{{- if $existing -}}
78+
{{- index $existing.data "rpc_secret" | b64dec -}}
79+
{{- else -}}
80+
{{- sha256sum (printf "%s-rpc-%s" .Release.Name (randAlphaNum 32)) -}}
81+
{{- end -}}
82+
{{- end }}

garage/templates/httproute.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{{- if and .Values.ingress.enabled .Values.httpRoute.enabled -}}
2+
{{- fail "Enable only one of ingress.enabled or httpRoute.enabled, not both" -}}
3+
{{- end -}}
4+
{{- if .Values.httpRoute.enabled -}}
5+
{{- if not .Values.httpRoute.parentRefs -}}
6+
{{- fail "httpRoute.parentRefs is required when httpRoute.enabled=true" -}}
7+
{{- end -}}
8+
{{- $fullName := include "garage.fullname" . -}}
9+
apiVersion: gateway.networking.k8s.io/v1
10+
kind: HTTPRoute
11+
metadata:
12+
name: {{ include "garage.httpRouteName" . }}
13+
labels:
14+
{{- include "garage.labels" . | nindent 4 }}
15+
{{- with .Values.httpRoute.labels }}
16+
{{- toYaml . | nindent 4 }}
17+
{{- end }}
18+
{{- with .Values.httpRoute.annotations }}
19+
annotations:
20+
{{- toYaml . | nindent 4 }}
21+
{{- end }}
22+
spec:
23+
parentRefs:
24+
{{- toYaml .Values.httpRoute.parentRefs | nindent 4 }}
25+
{{- with .Values.httpRoute.hostnames }}
26+
hostnames:
27+
{{- toYaml . | nindent 4 }}
28+
{{- end }}
29+
rules:
30+
- matches:
31+
{{- toYaml .Values.httpRoute.matches | nindent 8 }}
32+
backendRefs:
33+
- name: {{ $fullName }}
34+
port: {{ .Values.service.s3.port }}
35+
{{- end }}

garage/templates/ingress.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{{- if .Values.ingress.enabled -}}
2+
{{- $fullName := include "garage.fullname" . -}}
3+
{{- $svcPort := .Values.service.s3.port -}}
4+
apiVersion: networking.k8s.io/v1
5+
kind: Ingress
6+
metadata:
7+
name: {{ include "garage.ingressName" . }}
8+
labels:
9+
{{- include "garage.labels" . | nindent 4 }}
10+
{{- with .Values.ingress.labels }}
11+
{{- toYaml . | nindent 4 }}
12+
{{- end }}
13+
{{- with .Values.ingress.annotations }}
14+
annotations:
15+
{{- toYaml . | nindent 4 }}
16+
{{- end }}
17+
spec:
18+
{{- with .Values.ingress.className }}
19+
ingressClassName: {{ . }}
20+
{{- end }}
21+
{{- if .Values.ingress.tls }}
22+
tls:
23+
{{- range .Values.ingress.tls }}
24+
- hosts:
25+
{{- range .hosts }}
26+
- {{ . | quote }}
27+
{{- end }}
28+
secretName: {{ .secretName }}
29+
{{- end }}
30+
{{- end }}
31+
rules:
32+
{{- range .Values.ingress.hosts }}
33+
- host: {{ .host | quote }}
34+
http:
35+
paths:
36+
{{- range .paths }}
37+
- path: {{ .path }}
38+
pathType: {{ .pathType }}
39+
backend:
40+
service:
41+
name: {{ $fullName }}
42+
port:
43+
number: {{ $svcPort }}
44+
{{- end }}
45+
{{- end }}
46+
{{- end }}

garage/templates/secret.yaml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{{- $rpcSecret := include "garage.rpcSecret" . -}}
2+
{{- $adminToken := required "admin.token is required" .Values.admin.token -}}
3+
apiVersion: v1
4+
kind: Secret
5+
metadata:
6+
name: {{ include "garage.fullname" . }}
7+
labels:
8+
{{- include "garage.labels" . | nindent 4 }}
9+
type: Opaque
10+
stringData:
11+
rpc_secret: {{ $rpcSecret | quote }}
12+
admin_token: {{ $adminToken | quote }}
13+
{{- if .Values.bootstrap.enabled }}
14+
default_access_key: {{ required "bootstrap.accessKey is required when bootstrap.enabled=true (e.g. GK + 16 hex chars)" .Values.bootstrap.accessKey | quote }}
15+
default_secret_key: {{ required "bootstrap.secretKey is required when bootstrap.enabled=true" .Values.bootstrap.secretKey | quote }}
16+
{{- end }}
17+
garage.toml: |
18+
metadata_dir = "/var/lib/garage/meta"
19+
data_dir = "/var/lib/garage/data"
20+
db_engine = {{ .Values.garage.dbEngine | quote }}
21+
block_size = {{ .Values.garage.blockSize | quote }}
22+
{{- if eq (toString .Values.garage.compressionLevel) "none" }}
23+
compression_level = "none"
24+
{{- else }}
25+
compression_level = {{ .Values.garage.compressionLevel | int }}
26+
{{- end }}
27+
28+
replication_factor = 1
29+
30+
{{- if .Values.garage.metadataAutoSnapshotInterval }}
31+
metadata_auto_snapshot_interval = {{ .Values.garage.metadataAutoSnapshotInterval | quote }}
32+
{{- end }}
33+
34+
rpc_bind_addr = "[::]:3901"
35+
rpc_public_addr = "127.0.0.1:3901"
36+
rpc_secret = {{ $rpcSecret | quote }}
37+
38+
{{- if .Values.garage.additionalTopLevelConfig }}
39+
{{ .Values.garage.additionalTopLevelConfig | nindent 4 }}
40+
{{- end }}
41+
42+
[s3_api]
43+
s3_region = {{ .Values.s3.region | quote }}
44+
api_bind_addr = "[::]:{{ .Values.service.s3.port }}"
45+
root_domain = {{ .Values.s3.rootDomain | quote }}
46+
47+
48+
[admin]
49+
api_bind_addr = "[::]:{{ .Values.admin.port }}"
50+
admin_token = {{ $adminToken | quote }}

0 commit comments

Comments
 (0)