Skip to content

Commit 6df68be

Browse files
committed
feat: declarative GitOps config via Helm values + fsnotify watcher
Replace the CRD-based controller with a lightweight file-based approach that reads YAML config from a ConfigMap mounted at /etc/kite/config.d/. Key changes: - pkg/config/types.go: KiteConfig schema (OAuth, RBAC, GeneralSettings) - pkg/config/watcher.go: fsnotify watcher with conf.d merge, debounce, ConfigMap symlink support, and polling fallback - pkg/config/reconciler.go: full CRUD reconciliation to database with orphan cleanup and managed-resource tracking - main.go: replace CRD controller with file watcher startup - pkg/model/{oauth,rbac}.go: add ManagedBy field for tracking - charts/kite/: Helm templates for ConfigMap, volume mount, env var - deploy/examples/: Entra ID and conf.d usage examples Closes kite-org#226, kite-org#360, kite-org#233 Ref: maintainer feedback on PR kite-org#441 requesting Helm-only approach
1 parent adda7dd commit 6df68be

12 files changed

Lines changed: 1322 additions & 3 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{{- if .Values.kiteConfig.enabled }}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ include "kite.fullname" . }}-declarative-config
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "kite.labels" . | nindent 4 }}
9+
data:
10+
{{- if .Values.kiteConfig.oauth }}
11+
oauth.yaml: |
12+
oauth:
13+
providers:
14+
{{- range .Values.kiteConfig.oauth.providers }}
15+
- name: {{ .name | quote }}
16+
{{- if .issuerUrl }}
17+
issuerUrl: {{ .issuerUrl | quote }}
18+
{{- end }}
19+
{{- if .clientId }}
20+
clientId: {{ .clientId | quote }}
21+
{{- end }}
22+
{{- if .clientSecret }}
23+
clientSecret: {{ .clientSecret | quote }}
24+
{{- end }}
25+
{{- if .authUrl }}
26+
authUrl: {{ .authUrl | quote }}
27+
{{- end }}
28+
{{- if .tokenUrl }}
29+
tokenUrl: {{ .tokenUrl | quote }}
30+
{{- end }}
31+
{{- if .userInfoUrl }}
32+
userInfoUrl: {{ .userInfoUrl | quote }}
33+
{{- end }}
34+
{{- if .scopes }}
35+
scopes: {{ .scopes | quote }}
36+
{{- end }}
37+
{{- if not (kindIs "invalid" .enabled) }}
38+
enabled: {{ .enabled }}
39+
{{- end }}
40+
{{- end }}
41+
{{- end }}
42+
{{- if .Values.kiteConfig.roles }}
43+
roles.yaml: |
44+
roles:
45+
{{- range .Values.kiteConfig.roles }}
46+
- name: {{ .name | quote }}
47+
{{- if .description }}
48+
description: {{ .description | quote }}
49+
{{- end }}
50+
{{- if not (kindIs "invalid" .clusters) }}
51+
clusters:
52+
{{- toYaml .clusters | nindent 10 }}
53+
{{- end }}
54+
{{- if not (kindIs "invalid" .namespaces) }}
55+
namespaces:
56+
{{- toYaml .namespaces | nindent 10 }}
57+
{{- end }}
58+
{{- if not (kindIs "invalid" .resources) }}
59+
resources:
60+
{{- toYaml .resources | nindent 10 }}
61+
{{- end }}
62+
{{- if not (kindIs "invalid" .verbs) }}
63+
verbs:
64+
{{- toYaml .verbs | nindent 10 }}
65+
{{- end }}
66+
{{- if not (kindIs "invalid" .assignments) }}
67+
assignments:
68+
{{- range .assignments }}
69+
- subjectType: {{ .subjectType | quote }}
70+
subject: {{ .subject | quote }}
71+
{{- end }}
72+
{{- end }}
73+
{{- end }}
74+
{{- end }}
75+
{{- if .Values.kiteConfig.generalSettings }}
76+
settings.yaml: |
77+
generalSettings:
78+
{{- toYaml .Values.kiteConfig.generalSettings | nindent 6 }}
79+
{{- end }}
80+
{{- end }}

charts/kite/templates/deployment.yaml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ spec:
1616
{{- end }}
1717
template:
1818
metadata:
19-
{{- with .Values.podAnnotations }}
19+
{{- if or .Values.podAnnotations .Values.kiteConfig.enabled }}
2020
annotations:
21+
{{- with .Values.podAnnotations }}
2122
{{- toYaml . | nindent 8 }}
23+
{{- end }}
24+
{{- if .Values.kiteConfig.enabled }}
25+
checksum/declarative-config: {{ include (print $.Template.BasePath "/declarative-config.yaml") . | sha256sum }}
26+
{{- end }}
2227
{{- end }}
2328
labels:
2429
{{- include "kite.labels" . | nindent 8 }}
@@ -73,6 +78,10 @@ spec:
7378
- name: KITE_BASE
7479
value: {{ .Values.basePath }}
7580
{{- end }}
81+
{{- if .Values.kiteConfig.enabled }}
82+
- name: KITE_CONFIG_DIR
83+
value: /etc/kite/config.d
84+
{{- end }}
7685
{{- with .Values.extraEnvs }}
7786
{{- toYaml . | nindent 12 }}
7887
{{- end }}
@@ -96,6 +105,11 @@ spec:
96105
- name: {{ include "kite.fullname" . }}-storage
97106
mountPath: {{ .Values.db.sqlite.persistence.mountPath }}
98107
{{- end }}
108+
{{- if .Values.kiteConfig.enabled }}
109+
- name: declarative-config
110+
mountPath: /etc/kite/config.d
111+
readOnly: true
112+
{{- end }}
99113
volumes:
100114
{{- if eq .Values.db.type "sqlite"}}
101115
- name: {{ include "kite.fullname" . }}-storage
@@ -110,6 +124,11 @@ spec:
110124
emptyDir: {}
111125
{{- end }}
112126
{{- end }}
127+
{{- if .Values.kiteConfig.enabled }}
128+
- name: declarative-config
129+
configMap:
130+
name: {{ include "kite.fullname" . }}-declarative-config
131+
{{- end }}
113132
{{- with .Values.volumes }}
114133
{{- toYaml . | nindent 8 }}
115134
{{- end }}

charts/kite/values.yaml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,81 @@ nodeSelector: {}
305305
tolerations: []
306306

307307
affinity: {}
308+
309+
# ══════════════════════════════════════════════════════════════════════════════
310+
# Declarative Configuration (GitOps-ready)
311+
# ══════════════════════════════════════════════════════════════════════════════
312+
# When enabled, the chart creates a ConfigMap mounted at /etc/kite/config.d/
313+
# containing YAML config files. Kite watches this directory with fsnotify and
314+
# reconciles OAuth, RBAC, and settings to the database automatically.
315+
#
316+
# Features:
317+
# - Hot reload: changes apply within seconds (no pod restart needed)
318+
# - conf.d pattern: multiple files are merged alphabetically
319+
# - GitOps native: works with ArgoCD, Flux, or any Helm-based pipeline
320+
# - No CRDs required: configuration is a simple ConfigMap
321+
# - Managed resources are tracked: manually-created resources are never touched
322+
# - Orphan cleanup: if you remove a provider/role from config, it gets deleted
323+
#
324+
# For OAuth client secrets, do NOT put them in values.yaml. Instead:
325+
# - Use a Kubernetes Secret and reference it via extraEnvs, or
326+
# - Use External Secrets Operator / Sealed Secrets to inject them
327+
# - The clientSecret field supports env var interpolation at runtime
328+
#
329+
# Example with Azure Entra ID:
330+
# kiteConfig:
331+
# enabled: true
332+
# oauth:
333+
# providers:
334+
# - name: "microsoft-entra-id"
335+
# issuerUrl: "https://login.microsoftonline.com/YOUR_TENANT/v2.0"
336+
# clientId: "YOUR_CLIENT_ID"
337+
# clientSecret: "YOUR_CLIENT_SECRET"
338+
# authUrl: "https://login.microsoftonline.com/YOUR_TENANT/oauth2/v2.0/authorize"
339+
# tokenUrl: "https://login.microsoftonline.com/YOUR_TENANT/oauth2/v2.0/token"
340+
# userInfoUrl: "https://graph.microsoft.com/oidc/userinfo"
341+
# scopes: "openid profile email User.Read"
342+
# roles:
343+
# - name: admin
344+
# assignments:
345+
# - { subjectType: group, subject: "aad-group-id-for-admins" }
346+
# - name: viewer
347+
# assignments:
348+
# - { subjectType: group, subject: "aad-group-id-for-viewers" }
349+
# - name: project-alpha-dev
350+
# description: "Developer access for Project Alpha"
351+
# namespaces: ["alpha-dev", "alpha-pre"]
352+
# verbs: ["get", "log", "terminal"]
353+
# assignments:
354+
# - { subjectType: group, subject: "aad-group-id-alpha-devs" }
355+
# generalSettings:
356+
# kubectlEnabled: true
357+
# enableAnalytics: false
358+
# ══════════════════════════════════════════════════════════════════════════════
359+
kiteConfig:
360+
# Set to true to create a ConfigMap with declarative config
361+
enabled: false
362+
# OAuth/OIDC providers
363+
# oauth:
364+
# providers:
365+
# - name: "my-oidc-provider"
366+
# issuerUrl: ""
367+
# clientId: ""
368+
# clientSecret: ""
369+
# scopes: "openid profile email"
370+
# RBAC roles with assignments
371+
# roles:
372+
# - name: admin
373+
# assignments:
374+
# - { subjectType: group, subject: "admin-group-id" }
375+
# - name: custom-role
376+
# description: "Custom scoped role"
377+
# namespaces: ["ns1", "ns2"]
378+
# verbs: ["get"]
379+
# assignments:
380+
# - { subjectType: group, subject: "group-id" }
381+
# General settings
382+
# generalSettings:
383+
# kubectlEnabled: true
384+
# enableAnalytics: false
385+
# enableVersionCheck: true
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
## Example: Standalone declarative config files (conf.d pattern)
2+
##
3+
## These files can be placed in /etc/kite/config.d/ (or the path set in
4+
## KITE_CONFIG_DIR) either via ConfigMap mount, hostPath, or any volume type.
5+
##
6+
## Files are read alphabetically and merged:
7+
## - OAuth providers and roles are appended across files
8+
## - General settings use last-write-wins per field
9+
##
10+
## This example shows how to split config across multiple files:
11+
## /etc/kite/config.d/
12+
## 01-oauth.yaml ← OAuth providers
13+
## 02-roles-platform.yaml ← Platform RBAC
14+
## 03-roles-team-alpha.yaml ← Team-specific RBAC
15+
## 04-settings.yaml ← General settings
16+
##
17+
## Each file follows the same KiteConfig schema.
18+
19+
# ── 01-oauth.yaml ────────────────────────────────────────────────────────────
20+
# oauth:
21+
# providers:
22+
# - name: "my-oidc-provider"
23+
# issuerUrl: "https://accounts.google.com"
24+
# clientId: "xxx.apps.googleusercontent.com"
25+
# clientSecret: "GOCSPX-xxx"
26+
# scopes: "openid profile email"
27+
# enabled: true
28+
29+
# ── 02-roles-platform.yaml ──────────────────────────────────────────────────
30+
# roles:
31+
# - name: admin
32+
# assignments:
33+
# - { subjectType: group, subject: "platform-admins-group-id" }
34+
# - name: viewer
35+
# assignments:
36+
# - { subjectType: group, subject: "platform-viewers-group-id" }
37+
38+
# ── 03-roles-team-alpha.yaml ────────────────────────────────────────────────
39+
# roles:
40+
# - name: alpha-developer
41+
# description: "Alpha team developer access"
42+
# namespaces: ["alpha-*"]
43+
# verbs: ["get", "log", "terminal", "create", "update"]
44+
# assignments:
45+
# - { subjectType: group, subject: "alpha-devs-group-id" }
46+
47+
# ── 04-settings.yaml ────────────────────────────────────────────────────────
48+
# generalSettings:
49+
# kubectlEnabled: true
50+
# enableAnalytics: false
51+
# enableVersionCheck: true
52+
# aiAgentEnabled: true
53+
# aiProvider: "openai"
54+
# aiModel: "gpt-4o"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
## Example: Declarative config with Azure Entra ID OAuth + RBAC roles
2+
##
3+
## Usage with Helm:
4+
## helm upgrade --install kite kite/kite -f kite-values-entra-id.yaml
5+
##
6+
## Usage without Helm (standalone ConfigMap):
7+
## 1. Create the ConfigMap from this file:
8+
## kubectl create configmap kite-declarative-config \
9+
## --namespace kite \
10+
## --from-file=oauth.yaml=deploy/examples/entra-id-oauth.yaml \
11+
## --from-file=roles.yaml=deploy/examples/entra-id-roles.yaml
12+
## 2. Mount it at /etc/kite/config.d/ in the Kite Deployment
13+
## 3. Set env KITE_CONFIG_DIR=/etc/kite/config.d (optional, it's the default)
14+
##
15+
## For OAuth secrets, create a separate K8s Secret and inject via env vars:
16+
## kubectl create secret generic kite-oauth-credentials \
17+
## --namespace kite \
18+
## --from-literal=CLIENT_ID=your-client-id \
19+
## --from-literal=CLIENT_SECRET=your-client-secret
20+
##
21+
## Then reference in the Helm values extraEnvs or Deployment envFrom.
22+
23+
# ── Helm values.yaml example ─────────────────────────────────────────────────
24+
kiteConfig:
25+
enabled: true
26+
oauth:
27+
providers:
28+
- name: "microsoft-entra-id"
29+
issuerUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0"
30+
clientId: "YOUR_CLIENT_ID"
31+
clientSecret: "YOUR_CLIENT_SECRET"
32+
authUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/authorize"
33+
tokenUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token"
34+
userInfoUrl: "https://graph.microsoft.com/oidc/userinfo"
35+
scopes: "openid profile email User.Read"
36+
enabled: true
37+
roles:
38+
# Assign Entra ID groups to the built-in admin role
39+
- name: admin
40+
assignments:
41+
- subjectType: group
42+
subject: "00000000-0000-0000-0000-000000000001" # Platform Admins group ID
43+
# Assign Entra ID groups to the built-in viewer role
44+
- name: viewer
45+
assignments:
46+
- subjectType: group
47+
subject: "00000000-0000-0000-0000-000000000002" # Platform Viewers group ID
48+
# Custom project-scoped role
49+
- name: project-alpha-developer
50+
description: "Developer access for Project Alpha namespaces"
51+
clusters: ["*"]
52+
namespaces: ["alpha-dev", "alpha-pre", "alpha-pro"]
53+
resources: ["*"]
54+
verbs: ["get", "log", "terminal"]
55+
assignments:
56+
- subjectType: group
57+
subject: "00000000-0000-0000-0000-000000000003" # Alpha Dev Team group ID
58+
generalSettings:
59+
kubectlEnabled: true
60+
enableAnalytics: false
61+
enableVersionCheck: true

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/anthropics/anthropic-sdk-go v1.26.0
77
github.com/blang/semver/v4 v4.0.0
88
github.com/bytedance/mockey v1.4.5
9+
github.com/fsnotify/fsnotify v1.9.0
910
github.com/gin-contrib/gzip v1.2.5
1011
github.com/gin-gonic/gin v1.12.0
1112
github.com/glebarez/sqlite v1.11.0
@@ -47,7 +48,6 @@ require (
4748
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
4849
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
4950
github.com/fatih/camelcase v1.0.0 // indirect
50-
github.com/fsnotify/fsnotify v1.9.0 // indirect
5151
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
5252
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
5353
github.com/gin-contrib/sse v1.1.0 // indirect

main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/zxh326/kite/pkg/auth"
2424
"github.com/zxh326/kite/pkg/cluster"
2525
"github.com/zxh326/kite/pkg/common"
26+
"github.com/zxh326/kite/pkg/config"
2627
"github.com/zxh326/kite/pkg/handlers"
2728
"github.com/zxh326/kite/pkg/handlers/resources"
2829
"github.com/zxh326/kite/pkg/middleware"
@@ -254,6 +255,13 @@ func main() {
254255
log.Fatalf("Failed to create ClusterManager: %v", err)
255256
}
256257

258+
// Start declarative config watcher (reads /etc/kite/config.d/*.yaml)
259+
configCtx, configCancel := context.WithCancel(context.Background())
260+
defer configCancel()
261+
if w := config.NewWatcher(); w != nil {
262+
go w.Start(configCtx)
263+
}
264+
257265
base := r.Group(common.Base)
258266
// Setup router
259267
setupAPIRouter(base, cm)
@@ -277,6 +285,7 @@ func main() {
277285
<-quit
278286

279287
klog.Info("Shutting down server...")
288+
configCancel() // Stop declarative config watcher
280289
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
281290
defer cancel()
282291
if err := srv.Shutdown(ctx); err != nil {

0 commit comments

Comments
 (0)