2 Commits

Author SHA1 Message Date
b8fed8a62a simplify README.md 2026-01-13 21:22:52 +08:00
6a2a0f7683 add helm chart and workflow to publish 2026-01-13 21:17:14 +08:00
9 changed files with 610 additions and 71 deletions

View File

@@ -1,8 +1,9 @@
name: Build and Push Docker Image name: Build and Push
on: on:
push: push:
branches: ["main", "master"] branches: ["main", "master"]
tags: ["v*"]
pull_request: pull_request:
branches: ["main", "master"] branches: ["main", "master"]
workflow_dispatch: workflow_dispatch:
@@ -12,7 +13,7 @@ env:
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
build: build-image:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -22,6 +23,9 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -38,14 +42,63 @@ jobs:
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: | tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=ref,event=branch type=ref,event=branch
type=sha type=sha,prefix=sha-
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} 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/<owner>/charts
# The chart will be accessible as oci://ghcr.io/<owner>/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

View File

@@ -16,7 +16,14 @@ A Kubernetes Operator to manage ephemeral Gitea Act runners. This operator autom
## Installation (Helm Chart) ## 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) ## Installation (Manual)
@@ -63,27 +70,39 @@ kubectl apply -f secret.yaml
## Configuration ## Configuration
The core resource is the `RunnerGroup`. Below are examples for different scopes. The core resource is the `RunnerGroup`. Use the `scope` field to control which jobs the runner picks up.
### 1. Repository Scope
Spawns runners only for jobs in a specific repository.
```yaml ```yaml
apiVersion: gitea.bpg.pw/v1alpha1 apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup kind: RunnerGroup
metadata: metadata:
name: my-repo-runner name: example-runner
namespace: gitea-runner-operator-system namespace: gitea-runner-operator-system
spec: spec:
scope: repo # Base Gitea URL
org: myorg
repo: myrepo
giteaURL: https://gitea.example.com giteaURL: https://gitea.example.com
# Scope configuration:
# - global: Runs all jobs (requires instance admin token)
# - org: Runs jobs for a specific organization (requires `org` field)
# - user: Runs jobs for a specific user (requires `user` field)
# - repo: Runs jobs for a specific repository (requires `org` and `repo` fields)
scope: repo
# Scope-specific fields (uncomment as needed based on scope):
org: my-org # Required for 'org' or 'repo' scopes
repo: my-repo # Required for 'repo' scope
# user: my-user # Required for 'user' or 'repo' scope
# Maximum concurrent runners
maxActiveRunners: 5 maxActiveRunners: 5
# Labels supported by this runner group
labels: labels:
- "ubuntu-latest" - "ubuntu-latest"
- "custom-label" - "custom-label:docker://node:16"
# Credentials
registrationToken: registrationToken:
secretRef: secretRef:
name: gitea-runner-secret name: gitea-runner-secret
@@ -94,62 +113,6 @@ spec:
key: authToken key: authToken
``` ```
### 2. Organization Scope
Spawns runners for any repository within the organization.
```yaml
apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup
metadata:
name: my-org-runner
namespace: gitea-runner-operator-system
spec:
scope: org
org: myorg
# repo is omitted
giteaURL: https://gitea.example.com
maxActiveRunners: 10
# ... (tokens)
```
### 3. User Scope
Spawns runners for any repository owned by the specified user.
```yaml
apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup
metadata:
name: my-user-runner
namespace: gitea-runner-operator-system
spec:
scope: user
user: myusername
# org and repo are omitted
giteaURL: https://gitea.example.com
maxActiveRunners: 3
# ... (tokens)
```
### 4. Global Scope
Spawns runners for any job in the Gitea instance (Admin level).
```yaml
apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup
metadata:
name: global-runner
namespace: gitea-runner-operator-system
spec:
scope: global
# org, user, and repo are omitted
giteaURL: https://gitea.example.com
maxActiveRunners: 20
# ... (tokens)
```
## How it works ## How it works
1. The **Controller** polls the Gitea API (using the `authToken`) to check for queued jobs matching the scope and labels. 1. The **Controller** polls the Gitea API (using the `authToken`) to check for queued jobs matching the scope and labels.

View File

@@ -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

View File

@@ -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=<YOUR_TOKEN> \
--from-literal=authToken=<YOUR_API_TOKEN>
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

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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