diff --git a/scripts/token.sh b/scripts/token.sh new file mode 100644 index 0000000..8186ddd --- /dev/null +++ b/scripts/token.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +set -eu + +check_token() { + set +e + + echo "Checking for existing token..." + token="$(kubectl get secret "$SECRET_NAME" -o jsonpath="{.data['token']}" 2> /dev/null)" + [ $? -ne 0 ] && return 1 + [ -z "$token" ] && return 2 + return 0 +} + +create_token() { + echo "Waiting for new token to be generated..." + begin=$(date +%s) + end=$((begin + 300)) # 5 minutes + while true; do + [ -f /data/actions/token ] && return 0 + [ "$(date +%s)" -gt $end ] && return 1 + sleep 5 + done +} + +store_token() { + echo "Storing the token in Kubernetes secret..." + kubectl patch secret "$SECRET_NAME" -p "{\"data\":{\"token\":\"$(base64 /data/actions/token | tr -d '\n')\"}}" +} + +if check_token; then + echo "Key already in place, exiting." + exit +fi + +if ! create_token; then + echo "Timed out waiting for a token to appear." + exit 1 +fi + +store_token diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index c7d13d9..f000723 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -25,6 +25,14 @@ If release name contains chart name it will be used as a full name. {{- end -}} {{- end -}} +{{/* +Create a default worker name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "gitea.workername" -}} +{{- printf "%s-%s" .global.Release.Name .worker | trunc 63 | trimSuffix "-" -}} +{{- end -}} + {{/* Create chart name and version as used by the chart label. */}} diff --git a/templates/gitea/actions-job.yaml b/templates/gitea/actions-job.yaml new file mode 100644 index 0000000..4e3e130 --- /dev/null +++ b/templates/gitea/actions-job.yaml @@ -0,0 +1,176 @@ +{{- if and (and .Values.actions.job.enabled .Values.persistence.enabled) .Values.persistence.mount }} +{{- if .Values.actions.existingSecret }} +{{- fail "Can't specify both actions.job.enabled and actions.existingSecret" }} +{{- end }} +{{- $name := include "gitea.workername" (dict "global" . "worker" "actions-token-job") }} +{{- $secretName := include "gitea.workername" (dict "global" . "worker" "actions-token") }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "gitea.fullname" . }}-scripts + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +data: +{{ (.Files.Glob "scripts/*.sh").AsConfig | indent 2 }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +rules: + - apiGroups: + - "" + resources: + - secrets + resourceNames: + - {{ $secretName }} + verbs: + - get + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $name }} +subjects: + - kind: ServiceAccount + name: {{ $name }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded + {{- with .Values.actions.job.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ttlSecondsAfterFinished: 0 + template: + metadata: + labels: + {{- include "gitea.labels" . | nindent 8 }} + app.kubernetes.io/component: token-job + spec: + containers: + - name: actions-token-create + image: "{{ .Values.actions.job.tokenImage.repository }}:{{ .Values.actions.job.tokenImage.tag | default "latest-rootless" }}" + imagePullPolicy: {{ .Values.actions.job.tokenImage.pullPolicy }} + env: + - name: GITEA_APP_INI + value: /data/gitea/conf/app.ini + command: + - sh + - -c + - | + while ! nc -z gitea-http 3000; do + sleep 5 + done + + echo "Generating token..." + mkdir -p /data/actions/ + gitea actions generate-runner-token | grep -E '^.{40}$' | tr -d '\n' > /data/actions/token + resources: + {{- toYaml .Values.actions.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /data + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - name: actions-token-upload + image: "{{ .Values.actions.job.publishImage.repository }}:{{ .Values.actions.job.publishImage.tag | default "latest" }}" + imagePullPolicy: {{ .Values.actions.job.publishImage.pullPolicy }} + env: + - name: SECRET_NAME + value: {{ $secretName }} + command: + - sh + - -c + - | + printf "Checking rights to update secret... " + kubectl auth can-i update secret/${SECRET_NAME} + /scripts/token.sh + resources: + {{- toYaml .Values.actions.resources | nindent 12 }} + volumeMounts: + - mountPath: /scripts + name: scripts + readOnly: true + - mountPath: /data + name: data + readOnly: true + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + restartPolicy: Never + serviceAccount: {{ $name }} + volumes: + - name: scripts + configMap: + name: {{ include "gitea.fullname" . }}-scripts + defaultMode: 0755 + - name: data + persistentVolumeClaim: + claimName: {{ .Values.persistence.claimName }} + parallelism: 1 + completions: 1 + backoffLimit: 1 +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: never + helm.sh/resource-policy: keep + argocd.argoproj.io/hook: Skip + argocd.argoproj.io/hook-delete-policy: Never + name: {{ $secretName }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job +{{ $secret := (lookup "v1" "Secret" .Release.Namespace $secretName) -}} +{{ if $secret -}} +data: + signing.key: {{ (b64dec (index $secret.data "signing.key")) | b64enc }} +{{ end -}} +{{- end }} diff --git a/templates/gitea/actions.yaml b/templates/gitea/actions.yaml new file mode 100644 index 0000000..4e3e130 --- /dev/null +++ b/templates/gitea/actions.yaml @@ -0,0 +1,176 @@ +{{- if and (and .Values.actions.job.enabled .Values.persistence.enabled) .Values.persistence.mount }} +{{- if .Values.actions.existingSecret }} +{{- fail "Can't specify both actions.job.enabled and actions.existingSecret" }} +{{- end }} +{{- $name := include "gitea.workername" (dict "global" . "worker" "actions-token-job") }} +{{- $secretName := include "gitea.workername" (dict "global" . "worker" "actions-token") }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "gitea.fullname" . }}-scripts + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +data: +{{ (.Files.Glob "scripts/*.sh").AsConfig | indent 2 }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +rules: + - apiGroups: + - "" + resources: + - secrets + resourceNames: + - {{ $secretName }} + verbs: + - get + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $name }} +subjects: + - kind: ServiceAccount + name: {{ $name }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $name }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: hook-succeeded + {{- with .Values.actions.job.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ttlSecondsAfterFinished: 0 + template: + metadata: + labels: + {{- include "gitea.labels" . | nindent 8 }} + app.kubernetes.io/component: token-job + spec: + containers: + - name: actions-token-create + image: "{{ .Values.actions.job.tokenImage.repository }}:{{ .Values.actions.job.tokenImage.tag | default "latest-rootless" }}" + imagePullPolicy: {{ .Values.actions.job.tokenImage.pullPolicy }} + env: + - name: GITEA_APP_INI + value: /data/gitea/conf/app.ini + command: + - sh + - -c + - | + while ! nc -z gitea-http 3000; do + sleep 5 + done + + echo "Generating token..." + mkdir -p /data/actions/ + gitea actions generate-runner-token | grep -E '^.{40}$' | tr -d '\n' > /data/actions/token + resources: + {{- toYaml .Values.actions.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /data + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - name: actions-token-upload + image: "{{ .Values.actions.job.publishImage.repository }}:{{ .Values.actions.job.publishImage.tag | default "latest" }}" + imagePullPolicy: {{ .Values.actions.job.publishImage.pullPolicy }} + env: + - name: SECRET_NAME + value: {{ $secretName }} + command: + - sh + - -c + - | + printf "Checking rights to update secret... " + kubectl auth can-i update secret/${SECRET_NAME} + /scripts/token.sh + resources: + {{- toYaml .Values.actions.resources | nindent 12 }} + volumeMounts: + - mountPath: /scripts + name: scripts + readOnly: true + - mountPath: /data + name: data + readOnly: true + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + restartPolicy: Never + serviceAccount: {{ $name }} + volumes: + - name: scripts + configMap: + name: {{ include "gitea.fullname" . }}-scripts + defaultMode: 0755 + - name: data + persistentVolumeClaim: + claimName: {{ .Values.persistence.claimName }} + parallelism: 1 + completions: 1 + backoffLimit: 1 +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + # helm.sh/hook: post-install + # helm.sh/hook-delete-policy: never + helm.sh/resource-policy: keep + argocd.argoproj.io/hook: Skip + argocd.argoproj.io/hook-delete-policy: Never + name: {{ $secretName }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app.kubernetes.io/component: token-job +{{ $secret := (lookup "v1" "Secret" .Release.Namespace $secretName) -}} +{{ if $secret -}} +data: + signing.key: {{ (b64dec (index $secret.data "signing.key")) | b64enc }} +{{ end -}} +{{- end }} diff --git a/values.yaml b/values.yaml index af66f24..729fc46 100644 --- a/values.yaml +++ b/values.yaml @@ -339,6 +339,46 @@ signing: # -----END PGP PRIVATE KEY BLOCK----- existingSecret: "" +# Configure Gitea Actions +# - must enable persistence +# - must define deployment.env.GITEA__ACTIONS__ENABLED and GITEA__SERVER__LOCAL_ROOT_URL +## @section GiteaActions +# +## @param actions.statefulset.enabled Create an act-runner StatefulSet. +## @param actions.job.enabled Create a job that will create and save the token in a Kubernetes Secret +## @param actions.job.tokenImage.repository The image that can create a token via `gitea actions generate-runner-token` +## @param actions.job.tokenImage.tag The token image tag that can create a token +## @param actions.job.tokenImage.pullPolicy The token image pullPolicy that can create a token +## @param actions.job.publishImage.repository The image that can create the secret via kubectl +## @param actions.job.publishImage.tag The publish image tag that can create the secret +## @param actions.job.publishImage.pullPolicy The publish image pullPolicy that can create the secret +## @param actions.existingSecret Secret that contains the token +## @param actions.existingSecretKey Secret key +actions: + statefulset: + enabled: false + + job: + enabled: false + + annotations: {} + resources: {} + + tokenImage: + repository: gitea/gitea + # tag: latest-rootless + pullPolicy: IfNotPresent + + publishImage: + repository: bitnami/kubectl + # tag: latest + pullPolicy: IfNotPresent + + ## Specify an existing token secret + ## + # existingSecret: secretName + # existingSecretKey: token + ## @section Gitea # gitea: