From ac02458d9b08b651b83acb5d620832e51d67eae8 Mon Sep 17 00:00:00 2001 From: rome-user Date: Fri, 14 Jul 2023 02:11:41 -0700 Subject: [PATCH] [FEAT] Initial version of automatic Actions Runner deployment This commit addresses issue #90 by adding options to enable Forgejo Actions and, if desired, automatically deploy actions runners with a specified number of replicas. Due to the complexity of building the app.ini, we copy most of setup done for Forgejo itself, but we do not attempt any database migration or run Forgejo itself. Instead we take the (possibly partial) built app.ini file and use it in order to register Actions Runner tokens. Once these tokens are generated, we pass them to the Actions Runner images and create a runner file. Finally, we invoke the Actions Runner. Note that this setup process only happens once -- we generate new runner tokens for a replica if and only if a runner file does not already exist in the persistent volume claimed by the replica. Note that these changes do NOT add support for custom runner labels. These can probably come later. --- templates/_helpers.tpl | 41 ++++++ templates/gitea/init.yaml | 59 ++++++++ templates/gitea/runner.yaml | 260 ++++++++++++++++++++++++++++++++++++ values.yaml | 20 +++ 4 files changed, 380 insertions(+) create mode 100644 templates/gitea/runner.yaml diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 97c286c..74cd89d 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -46,6 +46,21 @@ Create image name and tag used by the deployment. {{- end -}} {{- end -}} +{{/* +Create image name and tag used by the actions runner. +*/}} +{{- define "gitea.actions-image" -}} +{{- $registry := .Values.gitea.actions.runner.image.registry | default (.Values.global.imageRegistry | default .Values.image.registry) -}} +{{- $name := .Values.gitea.actions.runner.image.repository -}} +{{- $tag := .Values.gitea.actions.runner.image.tag -}} +{{- $rootless := ternary "-rootless" "" (or .Values.image.rootless .Values.gitea.actions.runner.image.rootless) -}} +{{- if $registry -}} + {{- printf "%s/%s:%s%s" $registry $name $tag $rootless -}} +{{- else -}} + {{- printf "%s:%s%s" $name $tag $rootless -}} +{{- end -}} +{{- end -}} + {{/* Docker Image Registry Secret Names evaluating values as templates */}} @@ -91,6 +106,26 @@ app.kubernetes.io/name: {{ include "gitea.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} +{{/* +Actions runner labels +*/}} +{{- define "gitea.actions.runner.labels" -}} +helm.sh/chart: {{ include "gitea.chart" . }} +app: actions-runner +{{ include "gitea.actions.runner.selectorLabels" . }} +app.kubernetes.io/version: {{ .Values.gitea.actions.runner.image.tag | quote }} +version: {{ .Values.gitea.actions.runner.image.tag | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Actions runner selector labels +*/}} +{{- define "gitea.actions.runner.selectorLabels" -}} +app.kubernetes.io/name: actions-runner +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + {{- define "postgresql.dns" -}} {{- printf "%s-postgresql.%s.svc.%s:%g" .Release.Name .Release.Namespace .Values.clusterDomain .Values.postgresql.global.postgresql.service.ports.postgresql -}} {{- end -}} @@ -199,6 +234,9 @@ https {{- if not (hasKey .Values.gitea.config "metrics") -}} {{- $_ := set .Values.gitea.config "metrics" dict -}} {{- end -}} + {{- if not (hasKey .Values.gitea.config "actions") -}} + {{- $_ := set .Values.gitea.config "actions" dict -}} + {{- end -}} {{- if not (hasKey .Values.gitea.config "database") -}} {{- $_ := set .Values.gitea.config "database" dict -}} {{- end -}} @@ -226,6 +264,9 @@ https {{- if not (hasKey .Values.gitea.config.metrics "ENABLED") -}} {{- $_ := set .Values.gitea.config.metrics "ENABLED" .Values.gitea.metrics.enabled -}} {{- end -}} + {{- if not (hasKey .Values.gitea.config.actions "ENABLED") -}} + {{- $_ := set .Values.gitea.config.actions "ENABLED" .Values.gitea.actions.enabled -}} + {{- end -}} {{- if .Values.memcached.enabled -}} {{- $_ := set .Values.gitea.config.cache "ENABLED" "true" -}} {{- $_ := set .Values.gitea.config.cache "ADAPTER" "memcache" -}} diff --git a/templates/gitea/init.yaml b/templates/gitea/init.yaml index 838460b..412f694 100644 --- a/templates/gitea/init.yaml +++ b/templates/gitea/init.yaml @@ -127,3 +127,62 @@ stringData: configure_oauth echo '==== END GITEA CONFIGURATION ====' + + generate_token.sh: |- + #!/usr/bin/env bash + + set -euo pipefail + + status=0 + + if [ -f /data/.runner ]; + then + echo 'Runner seems to already be configured. We are not creating a new token.' + else + echo 'Generating runner secret...' + forgejo forgejo-cli actions generate-secret > /tmp/runner-secret || { + echo 'Failed to write secret to /tmp/runner-secret!' + exit 1 + } + + # try + { + echo 'Registering generated secret...' + forgejo forgejo-cli actions register \ + --secret-file /tmp/runner-secret \ + --config /data/gitea/conf/app.ini + } || + # catch + { + echo 'Failed to register!' + status=1 + } + fi + + exit $status + + configure_runner.sh: |- + #!/bin/ash + + set -euo pipefail + + echo '==== BEGIN RUNNER CONFIGURATION ====' + + if [ -f /data/.runner ]; + then + echo 'Runner seems to already be configured. We are not creating a new token.' + else + forgejo-runner create-runner-file \ + --secret $(cat /tmp/runner-secret) \ + --instance http://{{ .Release.Name }}-http.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.http.port }} + + echo 'Moving runner file to /data directory' + + mv /.runner /data/.runner + + echo 'Creating symlink from /data/.runner to /.runner' + + ln -sf /data/.runner /.runner + fi + + echo '==== END RUNNER CONFIGURATION ====' diff --git a/templates/gitea/runner.yaml b/templates/gitea/runner.yaml new file mode 100644 index 0000000..c2662cf --- /dev/null +++ b/templates/gitea/runner.yaml @@ -0,0 +1,260 @@ +{{- if .Values.gitea.actions.runner.enabled -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: actions-runner + labels: + {{- include "gitea.actions.runner.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.gitea.actions.runner.replicas }} + selector: + matchLabels: + {{- include "gitea.actions.runner.selectorLabels" . | nindent 6 }} + serviceName: actions-runner + template: + metadata: + labels: + {{- include "gitea.actions.runner.labels" . | nindent 8 }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" + {{- end }} + {{- include "gitea.images.pullSecrets" . | nindent 6 }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + # First wait for a Forgejo instance to come online. This is to guard + # against race conditions between forgejo and the actions runner when + # both are being set up for the first time. + - name: await-forgejo + image: alpine/curl + command: + - sh + args: + - -c + - | + retries=0 + max=3 + echo 'Waiting for Forgejo to come online before proceeding' + until $(curl --output /dev/null --silent --head --fail http://{{ include "gitea.fullname" . }}-http.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.http.port }}); + do + retries=$((retries+1)) + if [ $retries -gt $max ]; then + echo 'Max retries reached, exiting' + exit 1 + fi + echo "Checking again in 5 seconds. Retry count: ${retries} of ${max}"; + sleep 5; + done; + # We are recreating just enough of the app.ini so that we can invoke + # the forgejo-cli to register secrets and create runner files out the + # newly registered secrets. + - name: init-directories + image: "{{ include "gitea.image" . }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/usr/sbin/init_directory_structure.sh"] + env: + - name: GITEA_APP_INI + value: /data/gitea/conf/app.ini + - name: GITEA_CUSTOM + value: /data/gitea + - name: GITEA_WORK_DIR + value: /data + - name: GITEA_TEMP + value: /tmp/gitea + {{- if .Values.statefulset.env }} + {{- toYaml .Values.statefulset.env | nindent 12 }} + {{- end }} + volumeMounts: + - name: init + mountPath: /usr/sbin + - name: temp + mountPath: /tmp + - name: data + mountPath: /data + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + resources: + {{- toYaml .Values.initContainers.resources | nindent 12 }} + - name: init-app-ini + image: "{{ include "gitea.image" . }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/usr/sbin/config_environment.sh"] + env: + - name: GITEA_APP_INI + value: /data/gitea/conf/app.ini + - name: GITEA_CUSTOM + value: /data/gitea + - name: GITEA_WORK_DIR + value: /data + - name: GITEA_TEMP + value: /tmp/gitea + {{- if .Values.statefulset.env }} + {{- toYaml .Values.statefulset.env | nindent 12 }} + {{- end }} + {{- if .Values.gitea.additionalConfigFromEnvs }} + {{- toYaml .Values.gitea.additionalConfigFromEnvs | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: /usr/sbin + - name: temp + mountPath: /tmp + - name: data + mountPath: /data + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - name: inline-config-sources + mountPath: /env-to-ini-mounts/inlines/ + {{- range $idx, $value := .Values.gitea.additionalConfigSources }} + - name: additional-config-sources-{{ $idx }} + mountPath: "/env-to-ini-mounts/additionals/{{ $idx }}/" + {{- end }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + resources: + {{- toYaml .Values.initContainers.resources | nindent 12 }} + - name: configure-gitea + image: "{{ include "gitea.image" . }}" + command: + - sh + args: + - -c + - /usr/sbin/generate_token.sh + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- /* By default this container runs as user 1000 unless otherwise stated */ -}} + {{- $csc := deepCopy .Values.containerSecurityContext -}} + {{- if not (hasKey $csc "runAsUser") -}} + {{- $_ := set $csc "runAsUser" 1000 -}} + {{- end -}} + {{- toYaml $csc | nindent 12 }} + env: + - name: GITEA_APP_INI + value: /data/gitea/conf/app.ini + - name: GITEA_CUSTOM + value: /data/gitea + - name: GITEA_WORK_DIR + value: /data + - name: GITEA_TEMP + value: /tmp/gitea + volumeMounts: + - name: init + mountPath: /usr/sbin + - name: temp + mountPath: /tmp + - name: data + mountPath: /data + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + resources: + {{- toYaml .Values.initContainers.resources | nindent 12 }} + - name: configure-runner + image: "{{ include "gitea.actions-image" . }}" + imagePullPolicy: {{ default .Values.image.pullPolicy .Values.gitea.actions.runner.image.pullPolicy }} + command: ["/usr/sbin/configure_runner.sh"] + volumeMounts: + - name: init + mountPath: /usr/sbin + - name: data + mountPath: /data + - name: temp + mountPath: /tmp + containers: + - name: {{ .Chart.Name }} + image: "{{ include "gitea.actions-image" . }}" + imagePullPolicy: {{ default .Values.image.pullPolicy .Values.gitea.actions.runner.image.pullPolicy }} + env: + - name: DOCKER_HOST + value: tcp://localhost:2376 + - name: DOCKER_CERT_PATH + value: /certs/client + - name: DOCKER_TLS_VERIFY + value: "1" + command: + - sh + args: + - -c + - | + if [ ! -L /.runner ]; then + ln -s /data/.runner /.runner; + fi; + /bin/forgejo-runner daemon + volumeMounts: + - name: certs + mountPath: /certs + - name: data + mountPath: /data + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - name: temp + mountPath: /tmp + - name: daemon + image: docker:23.0.6-dind-rootless + env: + - name: DOCKER_TLS_CERTDIR + value: /certs + # NOTE: the container needs to run as root to configure dockerd, but + # dockerd itself is NOT run as root + securityContext: + privileged: true + volumeMounts: + - name: certs + mountPath: /certs + volumes: + - name: init + secret: + secretName: {{ include "gitea.fullname" . }}-init + defaultMode: 110 + - name: config + secret: + secretName: {{ include "gitea.fullname" . }} + defaultMode: 110 + {{- if gt (len .Values.extraVolumes) 0 }} + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + - name: inline-config-sources + secret: + secretName: {{ include "gitea.fullname" . }}-inline-config + {{- range $idx, $value := .Values.gitea.additionalConfigSources }} + - name: additional-config-sources-{{ $idx }} + {{- toYaml $value | nindent 10 }} + {{- end }} + - name: certs + emptyDir: {} + - name: temp + emptyDir: {} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + {{- with .Values.persistence.labels }} + labels: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + {{- include "gitea.persistence.storageClass" . | indent 8 }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- end }} diff --git a/values.yaml b/values.yaml index 8b615a7..2a99fed 100644 --- a/values.yaml +++ b/values.yaml @@ -299,6 +299,26 @@ gitea: password: r8sA8CPHD9!bt6d email: "gitea@local.domain" + ## @param gitea.actions.enabled Enable Forgejo Actions + ## @param gitea.actions.runner.enabled Enable automatic deployment of a runner + ## @param gitea.actions.runner.image.registry Image registry, e.g. gcr.io,docker.io + ## @param gitea.actions.runner.image.repository Image to start for this pod + ## @param gitea.actions.runner.image.tag Visit: [Image tag](https://code.forgejo.org/forgejo/-/packages/container/runner/versions). + ## @param gitea.actions.runner.image.pullPolicy Overrides the pull policy set globally for actions runners + ## @param gitea.actions.runner.image.rootless Determines whether we use a rootless container or not + ## @param gitea.actions.runner.replicas Number of replicas to automatically deploy + actions: + enabled: false + runner: + enabled: false + image: + registry: "code.forgejo.org" + repository: forgejo/runner + tag: "2.3.0" + pullPolicy: Always + rootless: false + replicas: 1 + ## @param gitea.metrics.enabled Enable Forgejo metrics ## @param gitea.metrics.serviceMonitor.enabled Enable Forgejo metrics service monitor metrics: