1
0
Fork 0
mirror of https://github.com/imjasonh/git-k8s synced 2026-07-18 23:15:47 +00:00

Implement Git-as-K8s control plane with Knative controllers

Build a complete Kubernetes control plane for Git operations using Knative's
pkg framework, go-git for in-memory Git operations, and ko for builds.

Architecture:
- 4 CRDs: GitRepository, GitBranch, GitPushTransaction, GitRepoSync
- Push controller: atomic Git push via go-git with CAS (compare-and-swap)
- Sync controller: two-way sync with in-memory merge base calculation
- Resolver controller: automated 3-way merge for conflicted syncs
- In-cluster Gitea server for backing storage
- Full RBAC, ko-based deployments, and code-gen scaffolding

Note: go.sum needs generation via `go mod tidy` in an environment with
full internet access (knative.dev/pkg v0.0.0-20250520014526-44579e9ce5ed).

https://claude.ai/code/session_01QfFzqKQUsxxBsiZUhuJ3pG
This commit is contained in:
Claude 2026-02-27 23:58:44 +00:00
commit 8e65104201
No known key found for this signature in database
27 changed files with 3198 additions and 0 deletions

113
config/core/git-server.yaml Normal file
View file

@ -0,0 +1,113 @@
## In-cluster Git server using Gitea.
## Provides a lightweight Git hosting service that controllers push to and fetch from.
## This is the backing store for all GitRepository resources.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: git-server-data
namespace: git-system
labels:
app.kubernetes.io/name: git-server
app.kubernetes.io/component: storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: git-server
namespace: git-system
labels:
app.kubernetes.io/name: git-server
app.kubernetes.io/component: server
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: git-server
template:
metadata:
labels:
app.kubernetes.io/name: git-server
app.kubernetes.io/component: server
spec:
containers:
- name: gitea
image: gitea/gitea:1.21-rootless
ports:
- name: http
containerPort: 3000
protocol: TCP
- name: ssh
containerPort: 2222
protocol: TCP
env:
- name: GITEA__database__DB_TYPE
value: sqlite3
- name: GITEA__server__DISABLE_SSH
value: "false"
- name: GITEA__server__SSH_PORT
value: "2222"
- name: GITEA__server__START_SSH_SERVER
value: "true"
volumeMounts:
- name: data
mountPath: /var/lib/gitea
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /api/v1/version
port: http
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /api/v1/version
port: http
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: data
persistentVolumeClaim:
claimName: git-server-data
---
apiVersion: v1
kind: Service
metadata:
name: git-server
namespace: git-system
labels:
app.kubernetes.io/name: git-server
app.kubernetes.io/component: server
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: git-server
ports:
- name: http
port: 3000
targetPort: http
protocol: TCP
- name: ssh
port: 2222
targetPort: ssh
protocol: TCP
---
apiVersion: v1
kind: Namespace
metadata:
name: git-system
labels:
app.kubernetes.io/name: git-k8s
app.kubernetes.io/part-of: git-k8s

View file

@ -0,0 +1,77 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: gitbranches.git.k8s.io
spec:
group: git.k8s.io
names:
kind: GitBranch
listKind: GitBranchList
plural: gitbranches
singular: gitbranch
shortNames:
- gitbr
categories:
- git
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- repositoryRef
- branchName
properties:
repositoryRef:
type: string
description: Name of the parent GitRepository resource.
branchName:
type: string
description: Name of the branch (e.g., "main", "feature/foo").
status:
type: object
properties:
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
format: date-time
reason:
type: string
message:
type: string
headCommit:
type: string
description: SHA of the current HEAD commit.
lastUpdated:
type: string
format: date-time
additionalPrinterColumns:
- name: Repository
type: string
jsonPath: .spec.repositoryRef
- name: Branch
type: string
jsonPath: .spec.branchName
- name: HEAD
type: string
jsonPath: .status.headCommit
priority: 1
- name: Age
type: date
jsonPath: .metadata.creationTimestamp

View file

@ -0,0 +1,103 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: gitpushtransactions.git.k8s.io
spec:
group: git.k8s.io
names:
kind: GitPushTransaction
listKind: GitPushTransactionList
plural: gitpushtransactions
singular: gitpushtransaction
shortNames:
- gitpush
categories:
- git
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- repositoryRef
- refSpecs
properties:
repositoryRef:
type: string
description: Name of the target GitRepository resource.
atomic:
type: boolean
description: Whether the push should be atomic.
default: true
refSpecs:
type: array
minItems: 1
items:
type: object
required:
- source
- destination
properties:
source:
type: string
description: Local ref (commit SHA or branch name).
destination:
type: string
description: Remote ref to update.
expectedOldCommit:
type: string
description: Expected current commit SHA for CAS.
status:
type: object
properties:
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
format: date-time
reason:
type: string
message:
type: string
phase:
type: string
enum:
- Pending
- InProgress
- Succeeded
- Failed
resultCommit:
type: string
startTime:
type: string
format: date-time
completionTime:
type: string
format: date-time
message:
type: string
additionalPrinterColumns:
- name: Repository
type: string
jsonPath: .spec.repositoryRef
- name: Phase
type: string
jsonPath: .status.phase
- name: Age
type: date
jsonPath: .metadata.creationTimestamp

View file

@ -0,0 +1,80 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: gitrepositories.git.k8s.io
spec:
group: git.k8s.io
names:
kind: GitRepository
listKind: GitRepositoryList
plural: gitrepositories
singular: gitrepository
shortNames:
- gitrepo
categories:
- git
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- url
properties:
url:
type: string
description: Clone URL of the Git repository.
defaultBranch:
type: string
description: Default branch name (e.g., "main").
default: main
auth:
type: object
properties:
secretRef:
type: object
properties:
name:
type: string
required:
- name
status:
type: object
properties:
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
format: date-time
reason:
type: string
message:
type: string
lastFetchTime:
type: string
format: date-time
additionalPrinterColumns:
- name: URL
type: string
jsonPath: .spec.url
- name: Branch
type: string
jsonPath: .spec.defaultBranch
- name: Age
type: date
jsonPath: .metadata.creationTimestamp

View file

@ -0,0 +1,105 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: gitreposyncs.git.k8s.io
spec:
group: git.k8s.io
names:
kind: GitRepoSync
listKind: GitRepoSyncList
plural: gitreposyncs
singular: gitreposync
shortNames:
- gitsync
categories:
- git
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- repoA
- repoB
- branchName
properties:
repoA:
type: object
required:
- name
properties:
name:
type: string
description: Name of the first GitRepository resource.
repoB:
type: object
required:
- name
properties:
name:
type: string
description: Name of the second GitRepository resource.
branchName:
type: string
description: Branch to keep in sync between the two repos.
status:
type: object
properties:
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
format: date-time
reason:
type: string
message:
type: string
phase:
type: string
enum:
- InSync
- Syncing
- Conflicted
- RequiresManualIntervention
lastSyncTime:
type: string
format: date-time
repoACommit:
type: string
repoBCommit:
type: string
mergeBase:
type: string
message:
type: string
additionalPrinterColumns:
- name: Repo A
type: string
jsonPath: .spec.repoA.name
- name: Repo B
type: string
jsonPath: .spec.repoB.name
- name: Branch
type: string
jsonPath: .spec.branchName
- name: Phase
type: string
jsonPath: .status.phase
- name: Age
type: date
jsonPath: .metadata.creationTimestamp

View file

@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: push-controller
namespace: git-system
labels:
app.kubernetes.io/name: git-k8s
app.kubernetes.io/component: push-controller
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: push-controller
template:
metadata:
labels:
app.kubernetes.io/name: git-k8s
app.kubernetes.io/component: push-controller
spec:
serviceAccountName: git-k8s-controller
containers:
- name: controller
# ko will replace this with the actual registry image SHA.
image: ko://github.com/imjasonh/git-k8s/cmd/push-controller
env:
- name: SYSTEM_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CONFIG_LOGGING_NAME
value: config-logging
- name: CONFIG_OBSERVABILITY_NAME
value: config-observability
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL

View file

@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: resolver-controller
namespace: git-system
labels:
app.kubernetes.io/name: git-k8s
app.kubernetes.io/component: resolver-controller
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: resolver-controller
template:
metadata:
labels:
app.kubernetes.io/name: git-k8s
app.kubernetes.io/component: resolver-controller
spec:
serviceAccountName: git-k8s-controller
containers:
- name: controller
# ko will replace this with the actual registry image SHA.
image: ko://github.com/imjasonh/git-k8s/cmd/resolver-controller
env:
- name: SYSTEM_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CONFIG_LOGGING_NAME
value: config-logging
- name: CONFIG_OBSERVABILITY_NAME
value: config-observability
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL

View file

@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sync-controller
namespace: git-system
labels:
app.kubernetes.io/name: git-k8s
app.kubernetes.io/component: sync-controller
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: sync-controller
template:
metadata:
labels:
app.kubernetes.io/name: git-k8s
app.kubernetes.io/component: sync-controller
spec:
serviceAccountName: git-k8s-controller
containers:
- name: controller
# ko will replace this with the actual registry image SHA.
image: ko://github.com/imjasonh/git-k8s/cmd/sync-controller
env:
- name: SYSTEM_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CONFIG_LOGGING_NAME
value: config-logging
- name: CONFIG_OBSERVABILITY_NAME
value: config-observability
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL

101
config/rbac/role.yaml Normal file
View file

@ -0,0 +1,101 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: git-k8s-controller
labels:
app.kubernetes.io/name: git-k8s
rules:
# Full access to all git.k8s.io custom resources.
- apiGroups:
- git.k8s.io
resources:
- gitrepositories
- gitbranches
- gitpushtransactions
- gitreposyncs
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
# Status subresource access.
- apiGroups:
- git.k8s.io
resources:
- gitrepositories/status
- gitbranches/status
- gitpushtransactions/status
- gitreposyncs/status
verbs:
- get
- update
- patch
# Read secrets for Git authentication.
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
# Events for controller status reporting.
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
# ConfigMaps for leader election and config.
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
# Leases for leader election.
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: git-k8s-controller
labels:
app.kubernetes.io/name: git-k8s
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: git-k8s-controller
subjects:
- kind: ServiceAccount
name: git-k8s-controller
namespace: git-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: git-k8s-controller
namespace: git-system
labels:
app.kubernetes.io/name: git-k8s