mirror of
https://github.com/imjasonh/git-k8s
synced 2026-07-06 22:12:25 +00:00
Add README with installation, auth, and architecture docs
Covers ko-based deployment, Kubernetes Secret setup for Git authentication, controller responsibilities, and resource lifecycle using mermaid diagrams. https://claude.ai/code/session_01Dg28Ex9qLvZWRHtYwBwnVs
This commit is contained in:
parent
2798083edc
commit
eb8d13d41c
1 changed files with 275 additions and 0 deletions
275
README.md
Normal file
275
README.md
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
# git-k8s
|
||||
|
||||
Kubernetes-native controllers for declarative Git repository management. Define
|
||||
Git repositories, track branches, execute atomic pushes, and keep repositories
|
||||
in sync — all through Custom Resources.
|
||||
|
||||
## Architecture
|
||||
|
||||
git-k8s runs four controllers that communicate through Kubernetes resources
|
||||
rather than direct APIs. Each controller is a separate deployment that watches
|
||||
specific CRDs and takes action when their state changes.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Custom Resources"
|
||||
GR[GitRepository]
|
||||
GB[GitBranch]
|
||||
GPT[GitPushTransaction]
|
||||
GRS[GitRepoSync]
|
||||
end
|
||||
|
||||
subgraph "Controllers"
|
||||
RW[Repo Watcher]
|
||||
SC[Sync Controller]
|
||||
RC[Resolver Controller]
|
||||
PC[Push Controller]
|
||||
end
|
||||
|
||||
subgraph "External"
|
||||
Remote["Git Remote(s)"]
|
||||
Secret[K8s Secret]
|
||||
end
|
||||
|
||||
RW -- "watches" --> GR
|
||||
RW -- "creates / updates / deletes" --> GB
|
||||
RW -- "polls refs via ls-remote" --> Remote
|
||||
|
||||
SC -- "watches" --> GRS
|
||||
SC -- "reads" --> GB
|
||||
SC -- "creates" --> GPT
|
||||
|
||||
RC -- "watches (Conflicted)" --> GRS
|
||||
RC -- "creates" --> GPT
|
||||
|
||||
PC -- "watches" --> GPT
|
||||
PC -- "pushes commits" --> Remote
|
||||
PC -- "updates" --> GB
|
||||
|
||||
PC -. "reads credentials" .-> Secret
|
||||
RW -. "reads credentials" .-> Secret
|
||||
```
|
||||
|
||||
### Controller responsibilities
|
||||
|
||||
| Controller | Watches | Creates / Mutates | Purpose |
|
||||
|---|---|---|---|
|
||||
| **Repo Watcher** | `GitRepository` | `GitBranch` | Polls remotes on a configurable interval, mirrors branch state into `GitBranch` resources |
|
||||
| **Sync** | `GitRepoSync`, `GitBranch` | `GitPushTransaction` | Compares HEAD commits between two repos, calculates merge bases, creates push transactions to keep branches aligned |
|
||||
| **Resolver** | `GitRepoSync` (Conflicted) | `GitPushTransaction` | Performs automated 3-way merge, falls back to `RequiresManualIntervention` on file-level conflicts |
|
||||
| **Push** | `GitPushTransaction` | `GitBranch` | Clones into memory, executes atomic pushes with optional compare-and-swap, updates branch status |
|
||||
|
||||
### Resource lifecycle
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "GitPushTransaction" as push {
|
||||
[*] --> Pending
|
||||
Pending --> InProgress
|
||||
InProgress --> Succeeded
|
||||
InProgress --> Failed
|
||||
}
|
||||
|
||||
state "GitRepoSync" as sync {
|
||||
[*] --> InSync
|
||||
InSync --> Syncing : commits diverge
|
||||
Syncing --> InSync : push succeeds
|
||||
Syncing --> Conflicted : both sides changed
|
||||
Conflicted --> InSync : merge succeeds
|
||||
Conflicted --> RequiresManualIntervention : merge fails
|
||||
}
|
||||
```
|
||||
|
||||
### End-to-end flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Remote as Git Remote
|
||||
participant RW as Repo Watcher
|
||||
participant GB as GitBranch
|
||||
participant Sync as Sync Controller
|
||||
participant GPT as GitPushTransaction
|
||||
participant Push as Push Controller
|
||||
|
||||
RW->>Remote: git ls-remote
|
||||
Remote-->>RW: refs + SHAs
|
||||
RW->>GB: create / update branch
|
||||
|
||||
GB-->>Sync: branch change triggers reconcile
|
||||
Sync->>Sync: compare commits, find merge base
|
||||
Sync->>GPT: create push transaction
|
||||
|
||||
GPT-->>Push: new transaction triggers reconcile
|
||||
Push->>Remote: git push (in-memory clone)
|
||||
Push->>GB: update headCommit
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- A Kubernetes cluster (KinD works for local development)
|
||||
- [`ko`](https://ko.build) v0.15+
|
||||
- `kubectl`
|
||||
- Go 1.24.7+ (for building from source)
|
||||
|
||||
### Deploy with ko
|
||||
|
||||
Set `KO_DOCKER_REPO` to a registry your cluster can pull from, then apply
|
||||
everything in order:
|
||||
|
||||
```bash
|
||||
export KO_DOCKER_REPO=<your-registry> # e.g. ghcr.io/you, kind.local
|
||||
|
||||
# Create namespace
|
||||
kubectl create namespace git-system
|
||||
|
||||
# Install CRDs
|
||||
kubectl apply -f config/crds/
|
||||
|
||||
# Install RBAC (ServiceAccount, ClusterRole, ClusterRoleBinding)
|
||||
kubectl apply -f config/rbac/
|
||||
|
||||
# Build images and deploy all four controllers
|
||||
ko apply -f config/deployments/
|
||||
```
|
||||
|
||||
For local development with KinD, use the built-in local registry:
|
||||
|
||||
```bash
|
||||
export KO_DOCKER_REPO=kind.local
|
||||
ko apply -f config/deployments/
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
kubectl -n git-system get pods
|
||||
```
|
||||
|
||||
You should see four controller pods running:
|
||||
|
||||
```
|
||||
push-controller-... 1/1 Running
|
||||
sync-controller-... 1/1 Running
|
||||
resolver-controller-... 1/1 Running
|
||||
repo-watcher-... 1/1 Running
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Controllers authenticate to Git remotes using Kubernetes Secrets referenced from
|
||||
`GitRepository` resources. The Secret must exist in the same namespace as the
|
||||
`GitRepository`.
|
||||
|
||||
### Create a Secret
|
||||
|
||||
For HTTPS repositories using a username and personal access token:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic my-git-creds \
|
||||
--namespace=default \
|
||||
--from-literal=username=<git-username> \
|
||||
--from-literal=password=<personal-access-token>
|
||||
```
|
||||
|
||||
### Reference it from a GitRepository
|
||||
|
||||
```yaml
|
||||
apiVersion: git-k8s.imjasonh.com/v1alpha1
|
||||
kind: GitRepository
|
||||
metadata:
|
||||
name: my-repo
|
||||
namespace: default
|
||||
spec:
|
||||
url: https://github.com/example/repo.git
|
||||
defaultBranch: main
|
||||
pollInterval: 30s
|
||||
auth:
|
||||
secretRef:
|
||||
name: my-git-creds
|
||||
```
|
||||
|
||||
The `auth` field is optional — omit it for public repositories. When present,
|
||||
the Push Controller and Repo Watcher Controller both resolve the Secret to
|
||||
authenticate clone, push, and ls-remote operations.
|
||||
|
||||
### RBAC
|
||||
|
||||
The controllers' ClusterRole already includes read access to Secrets:
|
||||
|
||||
```yaml
|
||||
- apiGroups: [""]
|
||||
resources: [secrets]
|
||||
verbs: [get, list, watch]
|
||||
```
|
||||
|
||||
No additional RBAC configuration is needed.
|
||||
|
||||
## Usage
|
||||
|
||||
### Track a repository
|
||||
|
||||
```yaml
|
||||
apiVersion: git-k8s.imjasonh.com/v1alpha1
|
||||
kind: GitRepository
|
||||
metadata:
|
||||
name: upstream
|
||||
spec:
|
||||
url: https://github.com/example/repo.git
|
||||
defaultBranch: main
|
||||
pollInterval: 1m
|
||||
```
|
||||
|
||||
The Repo Watcher will poll the remote and create a `GitBranch` resource for each
|
||||
branch it discovers.
|
||||
|
||||
### Push to a repository
|
||||
|
||||
```yaml
|
||||
apiVersion: git-k8s.imjasonh.com/v1alpha1
|
||||
kind: GitPushTransaction
|
||||
metadata:
|
||||
name: push-feature
|
||||
spec:
|
||||
repositoryRef: upstream
|
||||
atomic: true
|
||||
refSpecs:
|
||||
- source: abc123def
|
||||
destination: refs/heads/main
|
||||
expectedOldCommit: 789fed456 # optional CAS check
|
||||
```
|
||||
|
||||
### Sync two repositories
|
||||
|
||||
```yaml
|
||||
apiVersion: git-k8s.imjasonh.com/v1alpha1
|
||||
kind: GitRepoSync
|
||||
metadata:
|
||||
name: keep-in-sync
|
||||
spec:
|
||||
repoA:
|
||||
name: upstream
|
||||
repoB:
|
||||
name: fork
|
||||
branchName: main
|
||||
```
|
||||
|
||||
The Sync Controller will detect when the branch diverges between the two repos
|
||||
and create `GitPushTransaction` resources to bring them back in sync. If both
|
||||
sides have diverged, the Resolver Controller attempts an automated 3-way merge.
|
||||
|
||||
## Design decisions
|
||||
|
||||
- **Stateless controllers** — All Git operations use in-memory storage
|
||||
(`go-git` with `memory.NewStorage()`). No persistent volumes required.
|
||||
- **Separate binaries** — Each controller is its own deployment. They
|
||||
communicate exclusively through Kubernetes resources.
|
||||
- **Atomic pushes** — Push transactions support compare-and-swap via
|
||||
`expectedOldCommit` to prevent race conditions.
|
||||
- **No finalizers** — Resource relationships use labels and owner references
|
||||
only.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
Loading…
Add table
Add a link
Reference in a new issue