Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 23 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Lint Chart

on:
pull_request:

env:
CHART_NAME: garage

jobs:
lint:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v5
- name: Set up Helm
uses: azure/setup-helm@v5
with:
version: 4.2.3
# admin.token is a required value; a dummy is passed so all templates
# render during linting instead of being skipped with a warning.
- name: Lint Helm chart
run: helm lint ${{ env.CHART_NAME }} --strict --set admin.token=ci-lint-only
shell: bash
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@v7
with:
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
- uses: aboutbits/github-actions-base/git-setup@v2
- name: Set up Helm
uses: azure/setup-helm@v5
with:
version: 4.2.3
- 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/
14 changes: 14 additions & 0 deletions garage/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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"
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.

- https://github.com/deuxfleurs-org/garage
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 }}
50 changes: 50 additions & 0 deletions garage/templates/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{{- $rpcSecret := include "garage.rpcSecret" . -}}
{{- $adminToken := required "admin.token is required" .Values.admin.token -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "garage.fullname" . }}
labels:
{{- include "garage.labels" . | nindent 4 }}
type: Opaque
stringData:
rpc_secret: {{ $rpcSecret | quote }}
admin_token: {{ $adminToken | quote }}
{{- 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 }}


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