From 351b5e8fed051875b2fcbf23c4dfa97a41fd94e0 Mon Sep 17 00:00:00 2001 From: igrikus Date: Tue, 19 Aug 2025 15:27:10 +0200 Subject: [PATCH] Add helm chart add README --- README.md | 2 + doc/helm.md | 124 +++++++++++++ helm-chart/.gitignore | 1 + helm-chart/.helmignore | 23 +++ helm-chart/Chart.yaml | 9 + helm-chart/templates/NOTES.txt | 22 +++ helm-chart/templates/_helpers.tpl | 72 ++++++++ .../templates/configmap-init-script.yaml | 89 +++++++++ helm-chart/templates/configmap-providers.yaml | 17 ++ helm-chart/templates/configmap.yaml | 47 +++++ helm-chart/templates/deployment.yaml | 166 +++++++++++++++++ helm-chart/templates/ingress.yaml | 33 ++++ helm-chart/templates/pvc.yaml | 23 +++ helm-chart/templates/secret.yaml | 31 ++++ helm-chart/templates/service.yaml | 19 ++ helm-chart/templates/serviceaccount.yaml | 13 ++ helm-chart/values.yaml | 169 ++++++++++++++++++ 17 files changed, 860 insertions(+) create mode 100644 doc/helm.md create mode 100644 helm-chart/.gitignore create mode 100644 helm-chart/.helmignore create mode 100644 helm-chart/Chart.yaml create mode 100644 helm-chart/templates/NOTES.txt create mode 100644 helm-chart/templates/_helpers.tpl create mode 100644 helm-chart/templates/configmap-init-script.yaml create mode 100644 helm-chart/templates/configmap-providers.yaml create mode 100644 helm-chart/templates/configmap.yaml create mode 100644 helm-chart/templates/deployment.yaml create mode 100644 helm-chart/templates/ingress.yaml create mode 100644 helm-chart/templates/pvc.yaml create mode 100644 helm-chart/templates/secret.yaml create mode 100644 helm-chart/templates/service.yaml create mode 100644 helm-chart/templates/serviceaccount.yaml create mode 100644 helm-chart/values.yaml diff --git a/README.md b/README.md index 24fbbcc4..65308356 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ Check out the [quickstart](/doc/quickstart.md) document for instructions on how Thanks to the efforts of the amazing folks at [@mercedes-benz](https://github.com/mercedes-benz/), GARM can now be integrated into k8s via their operator. Check out the [GARM operator](https://github.com/mercedes-benz/garm-operator/) for more details. +In addition to the operator, GARM can also be deployed on Kubernetes using the provided Helm chart. For detailed instructions, please see the [Helm deployment guide](/doc/helm.md). + ## Configuring GARM for GHES GARM supports creating pools and scale sets in either GitHub itself or in your own deployment of [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@3.10/admin/overview/about-github-enterprise-server). For instructions on how to use ```GARM``` with GHE, see the [credentials](/doc/github_credentials.md) section of the documentation. diff --git a/doc/helm.md b/doc/helm.md new file mode 100644 index 00000000..b5b33ba8 --- /dev/null +++ b/doc/helm.md @@ -0,0 +1,124 @@ +# Deploying GARM with Helm + +This document provides instructions on how to deploy GARM on a Kubernetes cluster using the provided Helm chart. + +As this chart is not currently published to a public Helm repository, you will need to clone the GARM git repository to deploy it. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ +- `kubectl` configured to connect to your cluster. + +### Admin Credentials + +By default, the chart creates a Kubernetes secret to store the admin user\'s credentials (`username`, `password`, `email`), the JWT secret, and the database passphrase. + +If you want to use an existing secret, you can specify its name using `garm.admin.secretName`. Ensure the secret contains the necessary keys as defined by `garm.admin.usernameKey`, `garm.admin.passwordKey`, etc. + +### Persistence + +The chart uses a PersistentVolumeClaim (PVC) to store the GARM database. By default, persistence is enabled. You can disable it by setting `persistence.enabled` to `false`, but this is not recommended for production environments. + +### Providers Configuration + +You can configure external providers by adding them to the `providers` list in your `values.yaml` file. Each provider entry requires a `name`, `description`, `executable` path, and a `config` block with the provider-specific settings. + +Example for a GCP provider: + +```yaml +providers: + - name: "gcp" + type: "external" + description: "GCP provider" + executable: "/opt/garm/providers.d/garm-provider-gcp" + config: + project_id: "my-gcp-project" + zone: "us-central1-a" +``` + +### Forge Credentials (GitHub/Gitea) + +The chart\'s initialization script can automatically configure GARM with your GitHub and/or Gitea credentials. To use this feature, you must first create Kubernetes secrets containing your forge credentials. + +Then, configure the `forges.github.credentials` or `forges.gitea.credentials` sections in your `values.yaml`. + +**GitHub Example:** + +First, create a secret: + +```bash +kubectl create secret generic my-github-secret \ + --from-literal=token='ghp_xxxxxxxx' +``` + +Then, configure `values.yaml`: + +```yaml +forges: + github: + credentials: + - name: "my-github" + secretName: "my-github-secret" + authType: "pat" + tokenKey: "token" +``` + +**Gitea Example:** + +First, create a secret: + +```bash +kubectl create secret generic garm-gitea-config \ + --from-literal=server-url='https://gitea.example.com' \ + --from-literal=access-token='xxxxxxxx' +``` + +Then, configure `values.yaml`: + +```yaml +forges: + gitea: + credentials: + - name: "my-gitea" + secretName: "garm-gitea-config" + urlKey: "server-url" + tokenKey: "access-token" +``` + +## Installation Steps + +The `helm-chart/values.yaml` file serves as a template and is not intended for direct use, as it contains placeholder values. To properly deploy the chart, follow these steps: + +1. **Create a custom values file:** + Clone the repository and copy the `values.yaml` to a new file. For example, `my-values.yaml`. + + ```bash + git clone https://github.com/cloudbase/garm.git + cd garm + cp helm-chart/values.yaml my-values.yaml + ``` + +2. **Configure your deployment:** + Edit `my-values.yaml` and modify the parameters to match your environment. At a minimum, you will likely need to configure: + - `garm.url` (or the individual `callbackUrl`, `metadataUrl`, `webhookUrl`) + - `ingress.host` if you are using Ingress. + - `providers` to set up at least one compute provider. + - `forges` to configure credentials for GitHub or Gitea. + +3. **Install the chart:** + Once you have configured your `my-values.yaml`, install the chart using the `-f` flag to specify your custom values file. + + ```bash + helm install my-garm ./helm-chart -f my-values.yaml + ``` + +## Uninstallation + +To uninstall the `my-garm` deployment: + +```bash +helm uninstall my-garm +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. diff --git a/helm-chart/.gitignore b/helm-chart/.gitignore new file mode 100644 index 00000000..ebf1d3dc --- /dev/null +++ b/helm-chart/.gitignore @@ -0,0 +1 @@ +charts diff --git a/helm-chart/.helmignore b/helm-chart/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/helm-chart/.helmignore @@ -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/ diff --git a/helm-chart/Chart.yaml b/helm-chart/Chart.yaml new file mode 100644 index 00000000..f5499341 --- /dev/null +++ b/helm-chart/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: garm +description: GARM (Github/Gitea Actions Runner Manager) Helm Chart + +type: application + +version: 0.1.0 + +appVersion: "nightly" diff --git a/helm-chart/templates/NOTES.txt b/helm-chart/templates/NOTES.txt new file mode 100644 index 00000000..48fcfdfc --- /dev/null +++ b/helm-chart/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} + {{- $protocol := "http" -}} + {{- if .Values.ingress.tls.secretName -}} + {{- $protocol = "https" -}} + {{- end -}} + echo {{ $protocol }}://{{ .Values.ingress.host }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "garm.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "garm.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "garm.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "garm.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm-chart/templates/_helpers.tpl b/helm-chart/templates/_helpers.tpl new file mode 100644 index 00000000..7036b72a --- /dev/null +++ b/helm-chart/templates/_helpers.tpl @@ -0,0 +1,72 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "garm.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "garm.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 }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "garm.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "garm.labels" -}} +helm.sh/chart: {{ include "garm.chart" . }} +{{ include "garm.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "garm.selectorLabels" -}} +app.kubernetes.io/name: {{ include "garm.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "garm.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "garm.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + + + +{{- define "garm.admin-secret-name" -}} +{{- .Values.garm.admin.secretName | default (printf "%s-admin-credentials" (include "garm.fullname" .)) -}} +{{- end -}} + +{{- define "garm.db-path" -}} +/var/lib/garm +{{- end -}} diff --git a/helm-chart/templates/configmap-init-script.yaml b/helm-chart/templates/configmap-init-script.yaml new file mode 100644 index 00000000..4a6c51b8 --- /dev/null +++ b/helm-chart/templates/configmap-init-script.yaml @@ -0,0 +1,89 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ include "garm.fullname" . }}-init-script" + labels: + {{- include "garm.labels" . | nindent 4 }} +data: + init.sh: |- + #!/bin/sh + set -e + + INIT_MARKER="{{ include "garm.db-path" . }}/.initialized" + if [ -f "$INIT_MARKER" ]; then + echo "GARM already initialized, skipping setup." + exit 0 + fi + + # Start GARM in the background + /bin/garm -config /etc/garm/config.toml & + GARM_PID=$! + + # Wait for the server to be ready + sleep 5 + + # Run GARM setup + garm-cli init \ + --name "{{ include "garm.fullname" . }}" \ + --url "http://localhost:{{ .Values.service.port }}" \ + --callback-url "{{- if .Values.garm.callbackUrl }}{{ .Values.garm.callbackUrl }}{{- else }}{{ .Values.garm.url | trimSuffix "/" }}/api/v1/callbacks{{- end }}" \ + --metadata-url "{{- if .Values.garm.metadataUrl }}{{ .Values.garm.metadataUrl }}{{- else }}{{ .Values.garm.url | trimSuffix "/" }}/api/v1/metadata{{- end }}" \ + --webhook-url "{{- if .Values.garm.webhookUrl }}{{ .Values.garm.webhookUrl }}{{- else }}{{ .Values.garm.url | trimSuffix "/" }}/webhooks{{- end }}" \ + --username "$GARM_ADMIN_USERNAME" \ + --email "$GARM_ADMIN_EMAIL" \ + --password "$GARM_ADMIN_PASSWORD" + + {{- if .Values.forges.gitea.credentials }} + {{- range $i, $cred := .Values.forges.gitea.credentials }} + GITEA_URL=$(cat /gitea-secrets/{{ $cred.name }}/{{ $cred.urlKey }}) + GITEA_TOKEN=$(cat /gitea-secrets/{{ $cred.name }}/{{ $cred.tokenKey }}) + + garm-cli gitea endpoint create --api-base-url "$GITEA_URL" --base-url "$GITEA_URL" --description "{{ $cred.name }}" --name "{{ $cred.name }}" + garm-cli gitea credentials add --endpoint "{{ $cred.name }}" --auth-type pat --pat-oauth-token "$GITEA_TOKEN" --name "{{ $cred.name }}-token" --description "{{ $cred.name }} access token" + {{- end }} + {{- end }} + + {{- if .Values.forges.github.credentials }} + {{- range $cred := .Values.forges.github.credentials }} + {{- if $cred.urlKey }} + GITHUB_URL=$(cat /github-secrets/{{ $cred.name }}/{{ $cred.urlKey }}) + garm-cli github endpoint create \ + --api-base-url "$GITHUB_URL" \ + --base-url "$GITHUB_URL" \ + --description "{{ $cred.name }}" \ + --name "{{ $cred.name }}" + {{- end }} + + {{- if eq $cred.authType "pat" }} + GITHUB_TOKEN=$(cat /github-secrets/{{ $cred.name }}/{{ $cred.tokenKey }}) + garm-cli github credentials add \ + --name "{{ $cred.name }}" \ + {{- if $cred.urlKey }} + --endpoint "{{ $cred.name }}" \ + {{- end }} + --auth-type pat \ + --pat-oauth-token "$GITHUB_TOKEN" + {{- else if eq $cred.authType "app" }} + APP_ID=$(cat /github-secrets/{{ $cred.name }}/{{ $cred.appIdKey }}) + INSTALL_ID=$(cat /github-secrets/{{ $cred.name }}/{{ $cred.appInstallationIdKey }}) + PRIVATE_KEY_PATH="/github-secrets/{{ $cred.name }}/{{ $cred.privateKeyKey }}" + garm-cli github credentials add \ + --name "{{ $cred.name }}" \ + {{- if $cred.urlKey }} + --endpoint "{{ $cred.name }}" \ + {{- end }} + --auth-type app \ + --app-id "$APP_ID" \ + --app-installation-id "$INSTALL_ID" \ + --private-key-path "$PRIVATE_KEY_PATH" + {{- end }} + {{- end }} + {{- end }} + + echo "GARM initialization complete." + + # Stop the background GARM server + kill $GARM_PID + + # Create the marker file to indicate that initialization is done + touch "$INIT_MARKER" diff --git a/helm-chart/templates/configmap-providers.yaml b/helm-chart/templates/configmap-providers.yaml new file mode 100644 index 00000000..c2475341 --- /dev/null +++ b/helm-chart/templates/configmap-providers.yaml @@ -0,0 +1,17 @@ +{{- /* +Create a ConfigMap for each provider defined in values.yaml +*/}} +{{- range $provider := .Values.providers }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ include "garm.fullname" $ }}-provider-{{ .name }}" + labels: + {{- include "garm.labels" $ | nindent 4 }} +data: + garm-provider-{{ .name }}.toml: | + {{- range $key, $value := .config }} + {{ $key }} = {{ $value | toToml }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm-chart/templates/configmap.yaml b/helm-chart/templates/configmap.yaml new file mode 100644 index 00000000..b77c0fdd --- /dev/null +++ b/helm-chart/templates/configmap.yaml @@ -0,0 +1,47 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ include "garm.fullname" . }}-config" + labels: + {{- include "garm.labels" . | nindent 4 }} +data: + config.toml: |- + [default] + enable_webhook_management = true + + [logging] + enable_log_streamer = true + log_format = "text" + log_level = "info" + log_source = false + + [metrics] + enable = true + disable_auth = false + + [jwt_auth] + secret = "$GARM_JWT_SECRET" + time_to_live = "8760h" + + [apiserver] + bind = "0.0.0.0" + port = {{ .Values.service.port }} + use_tls = false + [apiserver.webui] + enable = {{ .Values.garm.ui.enabled }} + + [database] + backend = "sqlite3" + passphrase = "$GARM_DB_PASSPHRASE" + [database.sqlite3] + db_file = "{{ include "garm.db-path" . }}/garm.db" + + {{- range .Values.providers }} + [[provider]] + name = "{{ .name }}" + provider_type = "{{ .type | default "external" }}" + description = "{{ .description }}" + [provider.external] + provider_executable = "{{ .executable }}" + config_file = "/etc/garm/provider-configs/garm-provider-{{ .name }}.toml" + {{- end }} diff --git a/helm-chart/templates/deployment.yaml b/helm-chart/templates/deployment.yaml new file mode 100644 index 00000000..aafba788 --- /dev/null +++ b/helm-chart/templates/deployment.yaml @@ -0,0 +1,166 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "garm.fullname" . }} + labels: + {{- include "garm.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "garm.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "garm.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "garm.serviceAccountName" . }} + volumes: + - name: garm-config + emptyDir: {} + {{- if .Values.persistence.enabled }} + - name: garm-db + persistentVolumeClaim: + claimName: {{ include "garm.fullname" . }}-db + {{- end }} + - name: config-templates + configMap: + name: "{{ include "garm.fullname" . }}-config" + - name: init-script + configMap: + name: "{{ include "garm.fullname" . }}-init-script" + defaultMode: 0755 + {{- range .Values.providers }} + - name: provider-config-{{ .name }} + configMap: + name: {{ include "garm.fullname" $ }}-provider-{{ .name }} + {{- end }} + {{- if .Values.forges.gitea.credentials }} + {{- range .Values.forges.gitea.credentials }} + - name: gitea-secret-{{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- end }} + {{- if .Values.forges.github.credentials }} + {{- range .Values.forges.github.credentials }} + - name: github-secret-{{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + + initContainers: + - name: substitute-config + image: envsubst/envsubst + command: ["/bin/sh", "-c"] + args: + - | + envsubst < /config-templates/config.toml > /etc/garm/config.toml + env: + - name: GARM_JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "garm.admin-secret-name" . }} + key: {{ .Values.garm.admin.jwtSecretKey }} + - name: GARM_DB_PASSPHRASE + valueFrom: + secretKeyRef: + name: {{ include "garm.admin-secret-name" . }} + key: {{ .Values.garm.admin.dbPassphraseKey }} + volumeMounts: + - name: garm-config + mountPath: /etc/garm + - name: config-templates + mountPath: /config-templates + + - name: config-init + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/init/init.sh"] + env: + - name: GARM_ADMIN_USERNAME + valueFrom: + secretKeyRef: + name: {{ include "garm.admin-secret-name" . }} + key: {{ .Values.garm.admin.usernameKey }} + - name: GARM_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "garm.admin-secret-name" . }} + key: {{ .Values.garm.admin.passwordKey }} + - name: GARM_ADMIN_EMAIL + valueFrom: + secretKeyRef: + name: {{ include "garm.admin-secret-name" . }} + key: {{ .Values.garm.admin.emailKey }} + volumeMounts: + - name: garm-config + mountPath: /etc/garm + {{- if .Values.persistence.enabled }} + - name: garm-db + mountPath: {{ include "garm.db-path" . }} + {{- end }} + - name: config-templates + mountPath: /config-templates + - name: init-script + mountPath: /init + {{- if .Values.forges.gitea.credentials }} + {{- range .Values.forges.gitea.credentials }} + - name: gitea-secret-{{ .name }} + mountPath: /gitea-secrets/{{ .name }} + readOnly: true + {{- end }} + {{- end }} + {{- if .Values.forges.github.credentials }} + {{- range .Values.forges.github.credentials }} + - name: github-secret-{{ .name }} + mountPath: /github-secrets/{{ .name }} + readOnly: true + {{- end }} + {{- end }} + {{- range .Values.providers }} + - name: provider-config-{{ .name }} + mountPath: "/etc/garm/provider-configs/garm-provider-{{ .name }}.toml" + subPath: "garm-provider-{{ .name }}.toml" + readOnly: true + {{- end }} + + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + volumeMounts: + - name: garm-config + mountPath: /etc/garm + {{- if .Values.persistence.enabled }} + - name: garm-db + mountPath: {{ include "garm.db-path" . }} + {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- range .Values.providers }} + - name: provider-config-{{ .name }} + mountPath: "/etc/garm/provider-configs/garm-provider-{{ .name }}.toml" + subPath: "garm-provider-{{ .name }}.toml" + readOnly: true + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/helm-chart/templates/ingress.yaml b/helm-chart/templates/ingress.yaml new file mode 100644 index 00000000..84ddb601 --- /dev/null +++ b/helm-chart/templates/ingress.yaml @@ -0,0 +1,33 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "garm.fullname" . }} + labels: + {{- include "garm.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls.secretName }} + tls: + - hosts: + - {{ .Values.ingress.host | quote }} + secretName: {{ .Values.ingress.tls.secretName }} + {{- end }} + rules: + - host: {{ .Values.ingress.host | quote }} + http: + paths: + - path: "/" + pathType: "Prefix" + backend: + service: + name: {{ include "garm.fullname" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/helm-chart/templates/pvc.yaml b/helm-chart/templates/pvc.yaml new file mode 100644 index 00000000..5a4fc0ed --- /dev/null +++ b/helm-chart/templates/pvc.yaml @@ -0,0 +1,23 @@ +{{- if .Values.persistence.enabled -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "garm.fullname" . }}-db + labels: + {{- include "garm.labels" . | nindent 4 }} + {{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size }} + {{- if .Values.persistence.storageClassName }} + storageClassName: {{ .Values.persistence.storageClassName }} + {{- end }} +{{- end }} diff --git a/helm-chart/templates/secret.yaml b/helm-chart/templates/secret.yaml new file mode 100644 index 00000000..e2ccfd5b --- /dev/null +++ b/helm-chart/templates/secret.yaml @@ -0,0 +1,31 @@ +{{- if not .Values.garm.admin.secretName -}} +{{- $secretName := include "garm.admin-secret-name" . -}} +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace $secretName -}} + +{{- $password := "" -}} +{{- $jwtSecret := "" -}} +{{- $dbPassphrase := "" -}} + +{{- if $existingSecret -}} + {{- $password = index $existingSecret.data .Values.garm.admin.passwordKey | b64dec -}} + {{- $jwtSecret = index $existingSecret.data .Values.garm.admin.jwtSecretKey | b64dec -}} + {{- $dbPassphrase = index $existingSecret.data .Values.garm.admin.dbPassphraseKey | b64dec -}} +{{- else -}} + {{- $password = randAlphaNum 40 -}} + {{- $jwtSecret = randAlphaNum 64 -}} + {{- $dbPassphrase = randAlphaNum 32 -}} +{{- end -}} + +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + labels: + {{- include "garm.labels" . | nindent 4 }} +stringData: + {{ .Values.garm.admin.usernameKey }}: {{ .Values.garm.admin.defaultUsername | quote }} + {{ .Values.garm.admin.passwordKey }}: {{ $password | quote }} + {{ .Values.garm.admin.emailKey }}: {{ .Values.garm.admin.defaultEmail | quote }} + {{ .Values.garm.admin.jwtSecretKey }}: {{ $jwtSecret | quote }} + {{ .Values.garm.admin.dbPassphraseKey }}: {{ $dbPassphrase | quote }} +{{- end -}} diff --git a/helm-chart/templates/service.yaml b/helm-chart/templates/service.yaml new file mode 100644 index 00000000..ddd3ca54 --- /dev/null +++ b/helm-chart/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "garm.fullname" . }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "garm.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "garm.selectorLabels" . | nindent 4 }} diff --git a/helm-chart/templates/serviceaccount.yaml b/helm-chart/templates/serviceaccount.yaml new file mode 100644 index 00000000..193bc3f4 --- /dev/null +++ b/helm-chart/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "garm.serviceAccountName" . }} + labels: + {{- include "garm.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml new file mode 100644 index 00000000..a527f661 --- /dev/null +++ b/helm-chart/values.yaml @@ -0,0 +1,169 @@ +# -- GARM application configuration +garm: + # -- Enable the GARM web UI. + ui: + enabled: true + + # -- GARM admin user credentials. These will be stored in a Kubernetes secret. + admin: + # -- The name of the secret containing the admin credentials. + # -- If not set, a secret will be created with a generated password. + secretName: "" + # -- The default admin username to use when generating the secret. + defaultUsername: "admin" + # -- The default admin email address to use when generating the secret. + defaultEmail: "admin@example.com" + # -- The key in the secret for the admin username. + usernameKey: "username" + # -- The key in the secret for the admin password. + passwordKey: "password" + # -- The key in the secret for the admin email. + emailKey: "email" + # -- The key in the secret for the JWT secret. + jwtSecretKey: "jwt-secret" + # -- The key in the secret for the database passphrase. + dbPassphraseKey: "db-passphrase" + + # -- The base URL for the GARM service. This will be used in garm init process. + # -- You can omit this variable if URLs below are set. + url: "http://garm.example.com:9997" + # -- Specify callback URL (with correct URI path). If empty, garm.url will be used. + callbackUrl: "" + # -- Specify metadata URL (with correct URI path). If empty, garm.url will be used. + metadataUrl: "" + # -- Specify webhook URL (with correct URI path). If empty, garm.url will be used. + webhookUrl: "" + +# -- Image configuration for GARM and its components. +image: + # -- GARM image repository. + repository: ghcr.io/cloudbase/garm + # -- GARM image pull policy. + pullPolicy: Always + # -- GARM image tag. Overrides the chart's appVersion. + tag: "" + +# -- Overrides for the chart name. +nameOverride: "" +# -- Overrides for the full chart name. +fullnameOverride: "" + +# -- ServiceAccount configuration. +serviceAccount: + # -- Specifies whether a service account should be created. + create: true + # -- Automatically mount a ServiceAccount's API credentials. + automount: true + # -- Annotations to add to the service account. + annotations: {} + # -- The name of the service account to use. + # -- If not set and create is true, a name is generated using the fullname template. + name: "" + +# -- Annotations for the Pod. +podAnnotations: {} +# -- Labels for the Pod. +podLabels: {} + +# -- Service configuration. +service: + # -- Kubernetes service type. + type: ClusterIP + # -- The port for the GARM service. + port: 9997 + annotations: {} + +# -- Ingress configuration. +ingress: + # -- Enable or disable the Ingress resource. + enabled: false + # -- The public-facing host for the Ingress resource. Required if ingress.enabled is true. + host: "" + # -- IngressClassName for the Ingress resource. + className: "" + # -- Annotations for the Ingress resource. + annotations: {} + # -- TLS configuration for the Ingress. + tls: + # -- The name of the secret containing the TLS certificate. + secretName: "" + +# -- Resource requests and limits for the GARM container. +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + +# -- Additional volumes to mount in the Pod. +extraVolumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# -- Additional volume mounts for the GARM container. +extraVolumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +# -- Persistence configuration for the GARM database. +persistence: + # -- Enable or disable persistence. + enabled: true + # -- Annotations for the PVC. + annotations: {} + # -- The storage class to use for the PVC. + storageClassName: "" + # -- The access modes for the PVC. + accessModes: + - ReadWriteOnce + # -- The size of the PVC. + size: 1Gi + +# -- Configuration for different git forges. +forges: + # -- Gitea server configurations. + gitea: + # -- A list of Gitea credentials. If empty, Gitea integration is not initialized. + credentials: [] + # - name: "my-gitea" + # secretName: "garm-gitea-config" + # urlKey: "server-url" + # tokenKey: "access-token" + + # -- GitHub server configurations. + github: + # -- A list of GitHub servers/credentials. If empty, GitHub integration is not initialized. + credentials: [] + # - name: "my-github" + # secretName: "my-github-secret" + # -- If this field is empty, the default GitHub endpoint will be used. + # urlKey: "" + # -- Allowed values: 'pat' or 'app' + # authType: "pat" + # -- Specify this key only if authType is 'pat' + # tokenKey: "token" + # -- Specify values below only if authType is 'app' + # appIdKey: "app-id" + # appInstallationIdKey: "app-installation-id" + # privateKeyKey: "private-key" + +# -- Configuration for different providers. +providers: [] + # - name: "gcp" # Unique name for this provider + # type: "external" # Provider type + # description: "GCP provider" + # executable: "/opt/garm/providers.d/garm-provider-gcp" # Path to the executable + # + # # -- Dynamic configuration for the provider's .toml file. + # # -- You can add any keys here, and they will be added to the ConfigMap. + # config: + # project_id: "my-gcp-project" + # zone: "us-central1-a" + # network_id: "projects/my-gcp-project/global/networks/default" + # subnetwork_id: "projects/my-gcp-project/regions/us-central1/subnetworks/default" + # external_ip_access: true