diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fce88e1..4baf91b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,9 @@ -name: Build and Push Docker Image +name: Build and Push on: push: branches: ["main", "master"] + tags: ["v*"] pull_request: branches: ["main", "master"] workflow_dispatch: @@ -12,7 +13,7 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: - build: + build-image: runs-on: ubuntu-latest permissions: contents: read @@ -22,6 +23,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -38,14 +42,63 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} type=ref,event=branch - type=sha + type=sha,prefix=sha- - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . - platforms: linux/amd64 + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + publish-chart: + needs: build-image + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Helm + uses: azure/setup-helm@v4 + + - name: Login to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ${{ env.REGISTRY }} --username ${{ github.actor }} --password-stdin + + - name: Get Version + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV + + - name: Update Chart Version and Image Tag + run: | + CHART_PATH="charts/gitea-runner-operator" + + # Update version (stripping v) + sed -i "s/^version:.*/version: ${{ env.VERSION }}/" $CHART_PATH/Chart.yaml + + # Update appVersion (keeping v, matches docker tag) + sed -i "s/^appVersion:.*/appVersion: \"v${{ env.VERSION }}\"/" $CHART_PATH/Chart.yaml + + # Update image tag in values.yaml to match the release + # Replaces 'tag: "latest"' with 'tag: "v1.2.3"' + sed -i 's/tag: "latest"/tag: "v${{ env.VERSION }}"/' $CHART_PATH/values.yaml + + - name: Package Helm Chart + run: | + helm package charts/gitea-runner-operator + + - name: Push Helm Chart + run: | + # Push to oci://ghcr.io//charts + # The chart will be accessible as oci://ghcr.io//charts/gitea-runner-operator + OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + helm push gitea-runner-operator-${{ env.VERSION }}.tgz oci://${{ env.REGISTRY }}/${OWNER}/charts diff --git a/README.md b/README.md index 0e6fc52..c0280e8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,14 @@ A Kubernetes Operator to manage ephemeral Gitea Act runners. This operator autom ## Installation (Helm Chart) -### Incoming +You can install the operator using the provided Helm chart. + +```bash +# Install the chart +helm upgrade --install gitea-runner-operator ./charts/gitea-runner-operator \ + --namespace gitea-runner-operator-system \ + --create-namespace +``` ## Installation (Manual) diff --git a/charts/gitea-runner-operator/Chart.yaml b/charts/gitea-runner-operator/Chart.yaml new file mode 100644 index 0000000..5828535 --- /dev/null +++ b/charts/gitea-runner-operator/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: gitea-runner-operator +description: A Helm chart for managing Gitea Runner Operator +type: application +version: 0.1.0 +appVersion: "0.0.1" +keywords: + - gitea + - runner + - operator + - ci + - cd +home: https://github.com/bapung/gitea-runner-operator +sources: + - https://github.com/bapung/gitea-runner-operator +maintainers: + - name: bapung diff --git a/charts/gitea-runner-operator/templates/NOTES.txt b/charts/gitea-runner-operator/templates/NOTES.txt new file mode 100644 index 0000000..e77d292 --- /dev/null +++ b/charts/gitea-runner-operator/templates/NOTES.txt @@ -0,0 +1,37 @@ +Gitea Runner Operator has been installed! + +1. Check if the controller is running: + + kubectl get pods -n {{ .Release.Namespace }} -l control-plane=controller-manager + +2. Create a Secret with your Gitea credentials: + + kubectl create secret generic gitea-runner-secret \ + --namespace {{ .Release.Namespace }} \ + --from-literal=registrationToken= \ + --from-literal=authToken= + +3. Create a RunnerGroup instance to spawn runners: + + apiVersion: gitea.bpg.pw/v1alpha1 + kind: RunnerGroup + metadata: + name: example-runner + namespace: {{ .Release.Namespace }} + spec: + scope: repo + org: your-org + repo: your-repo + giteaURL: https://gitea.example.com + maxActiveRunners: 2 + labels: ["ubuntu-latest"] + registrationToken: + secretRef: + name: gitea-runner-secret + key: registrationToken + authToken: + secretRef: + name: gitea-runner-secret + key: authToken + +For more details, please visit: https://github.com/bapung/gitea-runner-operator diff --git a/charts/gitea-runner-operator/templates/_helpers.tpl b/charts/gitea-runner-operator/templates/_helpers.tpl new file mode 100644 index 0000000..00c1769 --- /dev/null +++ b/charts/gitea-runner-operator/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "gitea-runner-operator.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 "gitea-runner-operator.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 "gitea-runner-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "gitea-runner-operator.labels" -}} +helm.sh/chart: {{ include "gitea-runner-operator.chart" . }} +{{ include "gitea-runner-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "gitea-runner-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "gitea-runner-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +control-plane: controller-manager +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "gitea-runner-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "gitea-runner-operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/gitea-runner-operator/templates/deployment.yaml b/charts/gitea-runner-operator/templates/deployment.yaml new file mode 100644 index 0000000..6fa300e --- /dev/null +++ b/charts/gitea-runner-operator/templates/deployment.yaml @@ -0,0 +1,67 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "gitea-runner-operator.fullname" . }} + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "gitea-runner-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "gitea-runner-operator.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "gitea-runner-operator.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: manager + command: + - /manager + args: + - --leader-elect={{ .Values.manager.leaderElect }} + - --health-probe-bind-address={{ .Values.manager.healthProbeBindAddress }} + - --metrics-bind-address={{ .Values.manager.metricsBindAddress }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + livenessProbe: + httpGet: + path: /healthz + port: {{ .Values.manager.healthProbeBindAddress | trimPrefix ":" }} + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: {{ .Values.manager.healthProbeBindAddress | trimPrefix ":" }} + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: 10 diff --git a/charts/gitea-runner-operator/templates/rbac.yaml b/charts/gitea-runner-operator/templates/rbac.yaml new file mode 100644 index 0000000..677a8c8 --- /dev/null +++ b/charts/gitea-runner-operator/templates/rbac.yaml @@ -0,0 +1,262 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gitea-runner-operator.serviceAccountName" . }} + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} + +{{- if .Values.rbac.create -}} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-leader-election-role + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-manager-role + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups/finalizers + verbs: + - update +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups/status + verbs: + - get + - patch + - update + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-metrics-reader + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +rules: +- nonResourceURLs: + - /metrics + verbs: + - get + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-metrics-auth-role + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-leader-election-rolebinding + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "gitea-runner-operator.fullname" . }}-leader-election-role +subjects: +- kind: ServiceAccount + name: {{ include "gitea-runner-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-manager-rolebinding + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "gitea-runner-operator.fullname" . }}-manager-role +subjects: +- kind: ServiceAccount + name: {{ include "gitea-runner-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-metrics-auth-rolebinding + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "gitea-runner-operator.fullname" . }}-metrics-auth-role +subjects: +- kind: ServiceAccount + name: {{ include "gitea-runner-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-runnergroup-admin-role + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups + verbs: + - '*' +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups/status + verbs: + - get + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-runnergroup-editor-role + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups/status + verbs: + - get + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-runnergroup-viewer-role + labels: + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups + verbs: + - get + - list + - watch +- apiGroups: + - gitea.bpg.pw + resources: + - runnergroups/status + verbs: + - get +{{- end }} diff --git a/charts/gitea-runner-operator/templates/service.yaml b/charts/gitea-runner-operator/templates/service.yaml new file mode 100644 index 0000000..9760ae3 --- /dev/null +++ b/charts/gitea-runner-operator/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "gitea-runner-operator.fullname" . }}-metrics-service + labels: + control-plane: controller-manager + {{- include "gitea-runner-operator.labels" . | nindent 4 }} +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: {{ .Values.manager.metricsBindAddress | trimPrefix ":" }} + selector: + {{- include "gitea-runner-operator.selectorLabels" . | nindent 4 }} diff --git a/charts/gitea-runner-operator/values.yaml b/charts/gitea-runner-operator/values.yaml new file mode 100644 index 0000000..1aa7c7d --- /dev/null +++ b/charts/gitea-runner-operator/values.yaml @@ -0,0 +1,62 @@ +# Default values for gitea-runner-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +image: + repository: ghcr.io/bapung/gitea-runner-operator + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: 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: "gitea-runner-operator-controller-manager" + +podAnnotations: {} + +podSecurityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Manager configuration +manager: + metricsBindAddress: ":8443" + healthProbeBindAddress: ":8081" + leaderElect: true + +# RBAC configuration +rbac: + create: true