Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
75 changes: 72 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,84 @@ jobs:
go mod vendor
git diff --exit-code -- go.mod go.sum vendor/

- name: Setup envtest assets
env:
GOFLAGS: -mod=vendor
run: |
KUBEBUILDER_ASSETS_PATH="$(go run ./vendor/sigs.k8s.io/controller-runtime/tools/setup-envtest use 1.35.x --bin-dir "${{ github.workspace }}/bin/envtest" -p path)"
echo "KUBEBUILDER_ASSETS=${KUBEBUILDER_ASSETS_PATH}" >> "$GITHUB_ENV"

- name: Run tests
env:
GOFLAGS: -mod=vendor
run: go test ./...
KUBEBUILDER_ASSETS: ${{ env.KUBEBUILDER_ASSETS }}
run: go test ./... -count=1

- name: Build
env:
GOFLAGS: -mod=vendor
run: go build ./...

e2e-kind:
name: E2E Smoke (Kind)
needs: [changes, test]
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false

- name: Setup Go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod
cache: true

- name: Create Kind cluster
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
with:
cluster_name: e2e

- name: Build binary
env:
GOFLAGS: -mod=vendor
CGO_ENABLED: "0"
GOOS: linux
GOARCH: amd64
run: go build -o coder-k8s ./

- name: Build and load image
run: |
docker build -f Dockerfile.goreleaser -t ghcr.io/coder/coder-k8s:e2e .
kind load docker-image ghcr.io/coder/coder-k8s:e2e --name e2e

- name: Apply CRDs and RBAC
run: |
kubectl apply -f config/crd/bases/
kubectl apply -f config/rbac/

- name: Deploy controller
run: |
kubectl apply -f config/e2e/namespace.yaml
kubectl apply -f config/e2e/

- name: Wait for controller
run: kubectl wait --for=condition=Available deploy/coder-k8s -n coder-system --timeout=120s

- name: Apply sample CR
run: kubectl apply -f config/samples/coder_v1alpha1_codercontrolplane.yaml

- name: Verify CR exists
run: |
kubectl get codercontrolplanes -A
COUNT=$(kubectl get codercontrolplanes -A -o json | jq '.items | length')
if [ "$COUNT" -lt 1 ]; then
echo "assertion failed: expected at least 1 CoderControlPlane resource" >&2
exit 1
fi

lint-actions:
name: Lint GitHub Actions
needs: changes
Expand Down Expand Up @@ -167,15 +235,16 @@ jobs:

publish-main:
name: Publish GHCR :main
needs: [changes, test, lint, lint-actions]
needs: [changes, test, lint, lint-actions, e2e-kind]
if: |
always() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
needs.changes.outputs.publish == 'true' &&
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
(needs.lint.result == 'success' || needs.lint.result == 'skipped') &&
(needs.lint-actions.result == 'success' || needs.lint-actions.result == 'skipped')
(needs.lint-actions.result == 'success' || needs.lint-actions.result == 'skipped') &&
(needs.e2e-kind.result == 'success' || needs.e2e-kind.result == 'skipped')
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
17 changes: 15 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
GOFLAGS ?= -mod=vendor
VENDOR_STAMP := vendor/.modules.stamp
MODULE_FILES := go.mod $(wildcard go.sum)
ENVTEST_K8S_VERSION ?= 1.35.x
ENVTEST_ASSETS_DIR := $(shell pwd)/bin/envtest

.PHONY: vendor test build lint vuln verify-vendor codegen
.PHONY: vendor test test-integration setup-envtest build lint vuln verify-vendor codegen manifests

$(VENDOR_STAMP): $(MODULE_FILES)
go mod tidy
Expand All @@ -12,9 +14,17 @@ $(VENDOR_STAMP): $(MODULE_FILES)

vendor: $(VENDOR_STAMP)

test: $(VENDOR_STAMP)
setup-envtest:
GOFLAGS=-mod=vendor go run ./vendor/sigs.k8s.io/controller-runtime/tools/setup-envtest use $(ENVTEST_K8S_VERSION) --bin-dir $(ENVTEST_ASSETS_DIR) -p path > /dev/null

test: $(VENDOR_STAMP) setup-envtest
KUBEBUILDER_ASSETS="$$(GOFLAGS=-mod=vendor go run ./vendor/sigs.k8s.io/controller-runtime/tools/setup-envtest use $(ENVTEST_K8S_VERSION) --bin-dir $(ENVTEST_ASSETS_DIR) -p path)" \
GOFLAGS=$(GOFLAGS) go test ./...

test-integration: $(VENDOR_STAMP) setup-envtest
KUBEBUILDER_ASSETS="$$(GOFLAGS=-mod=vendor go run ./vendor/sigs.k8s.io/controller-runtime/tools/setup-envtest use $(ENVTEST_K8S_VERSION) --bin-dir $(ENVTEST_ASSETS_DIR) -p path)" \
GOFLAGS=$(GOFLAGS) go test ./internal/controller/... -count=1 -v

build: $(VENDOR_STAMP)
GOFLAGS=$(GOFLAGS) go build ./...

Expand All @@ -32,5 +42,8 @@ verify-vendor:
go mod vendor
git diff --exit-code -- go.mod go.sum vendor/

manifests: $(VENDOR_STAMP)
bash ./hack/update-manifests.sh

codegen: $(VENDOR_STAMP)
bash ./hack/update-codegen.sh
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,57 @@
# coder-k8s

## Project description

`coder-k8s` is a Go-based Kubernetes operator for managing `CoderControlPlane` resources (`coder.com/v1alpha1`). It is built with `sigs.k8s.io/controller-runtime`.

## Prerequisites

- Go 1.25+ (`go.mod` currently declares Go 1.25.7)
- A Kubernetes cluster (OrbStack is recommended for local development; any cluster works)
- `kubectl` configured to point at your cluster context

## Quick start / Local development (OrbStack)

```bash
# Generate CRD and RBAC manifests
make manifests

# Apply CRDs to your cluster
kubectl apply -f config/crd/bases/

# Run the controller locally (uses your kubeconfig context)
GOFLAGS=-mod=vendor go run .

# In another terminal: apply the sample CR
kubectl apply -f config/samples/coder_v1alpha1_codercontrolplane.yaml

# Verify
kubectl get codercontrolplanes -A
```

## Essential commands

| Command | Description |
| --- | --- |
| `make build` | Build the project |
| `make test` | Run tests |
| `make manifests` | Generate CRD/RBAC manifests |
| `make codegen` | Run deepcopy code generation |
| `make verify-vendor` | Verify vendor consistency |
| `make lint` | Run linter (requires `golangci-lint`) |
| `make vuln` | Run vulnerability check (requires `govulncheck`) |

## Testing strategy

- **Unit tests**: `make test` runs all tests, including unit tests in `main_test.go`.
- **Integration tests**: Use `envtest` to exercise reconciliation against a lightweight API server (no real cluster needed). Run them via `make test` (included in the full suite) or `make test-integration` (focused on controller tests only).
- **E2E smoke tests**: Recommended CI smoke coverage uses a Kind-based flow that deploys the controller image and verifies pod health.

## Project structure

- `api/v1alpha1/` — CRD types and generated deepcopy code
- `internal/controller/` — Reconciliation logic
- `config/crd/bases/` — Generated CRD manifests
- `config/rbac/` — Generated RBAC manifests
- `config/samples/` — Sample custom resources
- `hack/` — Code generation and maintenance scripts
59 changes: 59 additions & 0 deletions config/crd/bases/coder.com_codercontrolplanes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.20.0
name: codercontrolplanes.coder.com
spec:
group: coder.com
names:
kind: CoderControlPlane
listKind: CoderControlPlaneList
plural: codercontrolplanes
singular: codercontrolplane
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: CoderControlPlane is the schema for Coder control plane resources.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: CoderControlPlaneSpec defines the desired state of a CoderControlPlane.
properties:
image:
description: Image is the placeholder container image for the control
plane deployment.
type: string
type: object
status:
description: CoderControlPlaneStatus defines the observed state of a CoderControlPlane.
properties:
phase:
description: Phase is a placeholder status field for future reconciliation
stages.
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}
12 changes: 12 additions & 0 deletions config/e2e/clusterrole-binding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: coder-k8s
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: manager-role
subjects:
- kind: ServiceAccount
name: coder-k8s
namespace: coder-system
37 changes: 37 additions & 0 deletions config/e2e/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: coder-k8s
namespace: coder-system
labels:
app: coder-k8s
spec:
replicas: 1
selector:
matchLabels:
app: coder-k8s
template:
metadata:
labels:
app: coder-k8s
spec:
serviceAccountName: coder-k8s
containers:
- name: manager
image: ghcr.io/coder/coder-k8s:e2e
imagePullPolicy: Never
ports:
- containerPort: 8081
name: health
livenessProbe:
httpGet:
path: /healthz
port: health
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: health
initialDelaySeconds: 5
periodSeconds: 10
4 changes: 4 additions & 0 deletions config/e2e/namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: coder-system
5 changes: 5 additions & 0 deletions config/e2e/serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: coder-k8s
namespace: coder-system
32 changes: 32 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: manager-role
rules:
- apiGroups:
- coder.com
resources:
- codercontrolplanes
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- coder.com
resources:
- codercontrolplanes/finalizers
verbs:
- update
- apiGroups:
- coder.com
resources:
- codercontrolplanes/status
verbs:
- get
- patch
- update
7 changes: 7 additions & 0 deletions config/samples/coder_v1alpha1_codercontrolplane.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: coder.com/v1alpha1
kind: CoderControlPlane
metadata:
name: codercontrolplane-sample
namespace: default
spec:
image: "ghcr.io/coder/coder-k8s:main"
Loading
Loading