Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/release.yml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add an additional helm lint workflow for the PR CI.
https://helm.sh/docs/helm/helm_lint/

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Release Package

on:
workflow_dispatch:
inputs:
version:
description: "Version (eg: x.x.x)"
type: string
required: true

env:
CHART_NAME: garage
OCI_IMAGE: oci://ghcr.io/${{ github.repository }}

jobs:
build-and-publish:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
Comment thread
stplasim marked this conversation as resolved.
Outdated
with:
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
- uses: aboutbits/github-actions-base/git-setup@v2
- name: Set up Helm
uses: azure/setup-helm@v4
Comment thread
stplasim marked this conversation as resolved.
Outdated
with:
version: 3.21.3

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As Helm v4 is out since November 2025, we should think of upgrading to v4 in the future.

v3 is fine for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can go with helm 4. There are no breaking changes that we care about

- name: Set version of Chart.yaml file
run: yq -i '.version = "${{ github.event.inputs.version }}"' ${{ env.CHART_NAME }}/Chart.yaml
shell: bash
- name: Package Helm chart
run: helm package ${{ env.CHART_NAME }}
shell: bash
- name: Push to OCI registry
run: |
helm registry login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
helm push ${{ env.CHART_NAME }}-${{ github.event.inputs.version }}.tgz ${{ env.OCI_IMAGE }}
shell: bash
- uses: aboutbits/github-actions-base/git-commit-and-push-all@v2
with:
message: '${{ github.event.inputs.version }}'
- uses: aboutbits/github-actions-base/git-create-or-update-tag@v2
with:
tag-name: 'v${{ github.event.inputs.version }}'
- uses: aboutbits/github-actions-base/github-create-release@v2
with:
tag-name: 'v${{ github.event.inputs.version }}'
release-notes-generation: 'true'
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea
*.iml
*.tgz
.DS_Store
23 changes: 23 additions & 0 deletions garage/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
13 changes: 13 additions & 0 deletions garage/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v2
name: garage
description: AboutBits single-node Garage (S3-compatible object storage) Helm chart
type: application
version: 0.1.0
appVersion: "v2.3.0"
Comment thread
stplasim marked this conversation as resolved.
Outdated
keywords:
- garage
- s3
- object-storage
home: https://garagehq.deuxfleurs.fr
sources:
- https://git.deuxfleurs.fr/Deuxfleurs/garage
Comment on lines +12 to +13

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also add the GitHub URL.

Suggested change
sources:
- https://git.deuxfleurs.fr/Deuxfleurs/garage
sources:
- https://git.deuxfleurs.fr/Deuxfleurs/garage
- https://github.com/datahub-local/garage-helm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, even though datahub-local is the wrong repo, we can put their GitHub mirror here.

53 changes: 53 additions & 0 deletions garage/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
Garage single-node deployment "{{ include "garage.fullname" . }}" is starting up.

{{- if .Values.bootstrap.enabled }}

It bootstraps itself on first boot via `garage server --single-node --default-bucket`
(no manual layout/bucket setup needed). This is a one-time bootstrap: adding a
second bucket or rotating the default key later means calling the Admin API
directly, not editing values.yaml.

Bucket: {{ .Values.bootstrap.defaultBucket }}
Access key: (value of bootstrap.accessKey in your values)
Secret key: (value of bootstrap.secretKey in your values)
{{- else }}

No bucket/key is auto-created (bootstrap.enabled=false). Create buckets and
keys yourself via the Admin API or the `garage` CLI, e.g.:
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage layout assign -z dc1 -c 1G <node-id>
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage layout apply --version 1
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket create <bucket-name>
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage key create <key-name>
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket allow --read --write --owner <bucket-name> --key <key-name>
{{- end }}

S3 endpoint (from inside the cluster):
http://{{ include "garage.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.s3.port }}

Region: {{ .Values.s3.region }}

{{- if .Values.ingress.enabled }}

External S3 endpoint (via Ingress):
{{- range .Values.ingress.hosts }}
{{- $host := .host }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ .path }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.httpRoute.enabled }}

External S3 endpoint (via Gateway API HTTPRoute):
{{- if .Values.httpRoute.hostnames }}
{{- range .Values.httpRoute.hostnames }}
https://{{ . }}/
{{- end }}
{{- else }}
(matches all hostnames on the referenced Gateway)
{{- end }}
{{- end }}

Check status:
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage status
kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket list
82 changes: 82 additions & 0 deletions garage/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{{/*
Chart name and fullname
*/}}
{{- define "garage.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "garage.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{- define "garage.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "garage.labels" -}}
helm.sh/chart: {{ include "garage.chart" . }}
{{ include "garage.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "garage.selectorLabels" -}}
app.kubernetes.io/name: {{ include "garage.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Name of the ServiceAccount to use.
*/}}
{{- define "garage.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- $baseName := .Values.serviceAccount.name | default (printf "sa-%s" (include "garage.fullname" .)) -}}
{{- $baseName | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Names for the ingress / HTTPRoute objects (prefixed for readability, overridable
via .Values.<kind>.name). These name the routing object only — the backend still
targets the Service (garage.fullname).
*/}}
{{- define "garage.ingressName" -}}
{{- $baseName := .Values.ingress.name | default (printf "ingress-%s" (include "garage.fullname" .)) -}}
{{- $baseName | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "garage.httpRouteName" -}}
{{- $baseName := .Values.httpRoute.name | default (printf "httproute-%s" (include "garage.fullname" .)) -}}
{{- $baseName | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Auto-generated RPC secret (inter-node clustering auth; loopback-only on a single
node, so it is never consumed externally). `lookup` reuses the previously stored
value on `helm upgrade` to avoid a needless Secret diff — this is churn
avoidance, not a correctness requirement: the node's identity and cluster layout
live in the metadata volume, independent of this secret, so regenerating it does
not lose data or orphan the layout. The admin_token, by contrast, is a required,
operator-provided value (see admin.token) so it is known up front.
*/}}
{{- define "garage.rpcSecret" -}}
{{- $existing := (lookup "v1" "Secret" .Release.Namespace (include "garage.fullname" .)) -}}
{{- if $existing -}}
{{- index $existing.data "rpc_secret" | b64dec -}}
{{- else -}}
{{- sha256sum (printf "%s-rpc-%s" .Release.Name (randAlphaNum 32)) -}}
{{- end -}}
{{- end }}
35 changes: 35 additions & 0 deletions garage/templates/httproute.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{{- if and .Values.ingress.enabled .Values.httpRoute.enabled -}}
{{- fail "Enable only one of ingress.enabled or httpRoute.enabled, not both" -}}
{{- end -}}
{{- if .Values.httpRoute.enabled -}}
{{- if not .Values.httpRoute.parentRefs -}}
{{- fail "httpRoute.parentRefs is required when httpRoute.enabled=true" -}}
{{- end -}}
{{- $fullName := include "garage.fullname" . -}}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "garage.httpRouteName" . }}
labels:
{{- include "garage.labels" . | nindent 4 }}
{{- with .Values.httpRoute.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.httpRoute.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRefs:
{{- toYaml .Values.httpRoute.parentRefs | nindent 4 }}
{{- with .Values.httpRoute.hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- matches:
{{- toYaml .Values.httpRoute.matches | nindent 8 }}
backendRefs:
- name: {{ $fullName }}
port: {{ .Values.service.s3.port }}
{{- end }}
46 changes: 46 additions & 0 deletions garage/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "garage.fullname" . -}}
{{- $svcPort := .Values.service.s3.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "garage.ingressName" . }}
labels:
{{- include "garage.labels" . | nindent 4 }}
{{- with .Values.ingress.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
57 changes: 57 additions & 0 deletions garage/templates/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{{- $rpcSecret := include "garage.rpcSecret" . -}}
{{- $adminToken := "" -}}
{{- if .Values.admin.enabled -}}
{{- $adminToken = required "admin.token is required when admin.enabled=true" .Values.admin.token -}}
{{- end -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "garage.fullname" . }}
labels:
{{- include "garage.labels" . | nindent 4 }}
type: Opaque
stringData:
rpc_secret: {{ $rpcSecret | quote }}
{{- if .Values.admin.enabled }}
admin_token: {{ $adminToken | quote }}
{{- end }}
{{- if .Values.bootstrap.enabled }}
default_access_key: {{ required "bootstrap.accessKey is required when bootstrap.enabled=true (e.g. GK + 16 hex chars)" .Values.bootstrap.accessKey | quote }}
default_secret_key: {{ required "bootstrap.secretKey is required when bootstrap.enabled=true" .Values.bootstrap.secretKey | quote }}
{{- end }}
garage.toml: |
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = {{ .Values.garage.dbEngine | quote }}
block_size = {{ .Values.garage.blockSize | quote }}
{{- if eq (toString .Values.garage.compressionLevel) "none" }}
compression_level = "none"
{{- else }}
compression_level = {{ .Values.garage.compressionLevel | int }}
{{- end }}

replication_factor = 1

{{- if .Values.garage.metadataAutoSnapshotInterval }}
metadata_auto_snapshot_interval = {{ .Values.garage.metadataAutoSnapshotInterval | quote }}
{{- end }}

rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = {{ $rpcSecret | quote }}

{{- if .Values.garage.additionalTopLevelConfig }}
{{ .Values.garage.additionalTopLevelConfig | nindent 4 }}
{{- end }}

[s3_api]
s3_region = {{ .Values.s3.region | quote }}
api_bind_addr = "[::]:{{ .Values.service.s3.port }}"
root_domain = {{ .Values.s3.rootDomain | quote }}

{{- if .Values.admin.enabled }}

[admin]
api_bind_addr = "[::]:{{ .Values.admin.port }}"
admin_token = {{ $adminToken | quote }}
{{- end }}
Loading