Skip to content

Commit ed06fa4

Browse files
haasonsaasclaude
andcommitted
Add Helm chart for Kubernetes deployment
Helm chart (charts/diffscope/) with: - Deployment with health probes (/api/status), resource limits, security context - Service (ClusterIP) + optional Ingress with TLS support - ConfigMap for model/adapter/base URL config, Secret for API keys - PVC for review data persistence (~/.local/share/diffscope/) - ServiceAccount, HPA for autoscaling - Optional Ollama deployment (separate Deployment+Service or sidecar mode) with init container model pull and GPU support - Git repo support via init container clone or existing PVC mount - Helm test pod for connectivity verification - Supports existingSecret for external secret managers Also adds --host flag to `diffscope serve` (defaults to 127.0.0.1, set to 0.0.0.0 in the chart) so the server is reachable inside containers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 98feb73 commit ed06fa4

File tree

16 files changed

+775
-4
lines changed

16 files changed

+775
-4
lines changed

charts/diffscope/Chart.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: v2
2+
name: diffscope
3+
description: AI-powered code review engine with smart analysis and professional reporting
4+
type: application
5+
version: 0.1.0
6+
appVersion: "0.5.3"
7+
home: https://github.com/haasonsaas/diffscope
8+
sources:
9+
- https://github.com/haasonsaas/diffscope
10+
maintainers:
11+
- name: Jonathan Haas
12+
keywords:
13+
- code-review
14+
- diff
15+
- analysis
16+
- llm
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{{/*
2+
Expand the name of the chart.
3+
*/}}
4+
{{- define "diffscope.name" -}}
5+
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6+
{{- end }}
7+
8+
{{/*
9+
Create a default fully qualified app name.
10+
*/}}
11+
{{- define "diffscope.fullname" -}}
12+
{{- if .Values.fullnameOverride }}
13+
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
14+
{{- else }}
15+
{{- $name := default .Chart.Name .Values.nameOverride }}
16+
{{- if contains $name .Release.Name }}
17+
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
18+
{{- else }}
19+
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
20+
{{- end }}
21+
{{- end }}
22+
{{- end }}
23+
24+
{{/*
25+
Create chart name and version as used by the chart label.
26+
*/}}
27+
{{- define "diffscope.chart" -}}
28+
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
29+
{{- end }}
30+
31+
{{/*
32+
Common labels
33+
*/}}
34+
{{- define "diffscope.labels" -}}
35+
helm.sh/chart: {{ include "diffscope.chart" . }}
36+
{{ include "diffscope.selectorLabels" . }}
37+
{{- if .Chart.AppVersion }}
38+
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
39+
{{- end }}
40+
app.kubernetes.io/managed-by: {{ .Release.Service }}
41+
{{- end }}
42+
43+
{{/*
44+
Selector labels
45+
*/}}
46+
{{- define "diffscope.selectorLabels" -}}
47+
app.kubernetes.io/name: {{ include "diffscope.name" . }}
48+
app.kubernetes.io/instance: {{ .Release.Name }}
49+
{{- end }}
50+
51+
{{/*
52+
Create the name of the service account to use
53+
*/}}
54+
{{- define "diffscope.serviceAccountName" -}}
55+
{{- if .Values.serviceAccount.create }}
56+
{{- default (include "diffscope.fullname" .) .Values.serviceAccount.name }}
57+
{{- else }}
58+
{{- default "default" .Values.serviceAccount.name }}
59+
{{- end }}
60+
{{- end }}
61+
62+
{{/*
63+
Ollama fully qualified name
64+
*/}}
65+
{{- define "diffscope.ollamaFullname" -}}
66+
{{- printf "%s-ollama" (include "diffscope.fullname" .) }}
67+
{{- end }}
68+
69+
{{/*
70+
Computed Ollama URL (used when ollama.enabled and no explicit baseUrl)
71+
*/}}
72+
{{- define "diffscope.ollamaUrl" -}}
73+
{{- printf "http://%s:%d" (include "diffscope.ollamaFullname" .) (int .Values.ollama.port) }}
74+
{{- end }}
75+
76+
{{/*
77+
Resolve the secret name (created or existing)
78+
*/}}
79+
{{- define "diffscope.secretName" -}}
80+
{{- if .Values.secrets.existingSecret }}
81+
{{- .Values.secrets.existingSecret }}
82+
{{- else }}
83+
{{- include "diffscope.fullname" . }}
84+
{{- end }}
85+
{{- end }}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: {{ include "diffscope.fullname" . }}
5+
labels:
6+
{{- include "diffscope.labels" . | nindent 4 }}
7+
data:
8+
DIFFSCOPE_MODEL: {{ .Values.diffscope.model | quote }}
9+
{{- if .Values.diffscope.baseUrl }}
10+
DIFFSCOPE_BASE_URL: {{ .Values.diffscope.baseUrl | quote }}
11+
{{- else if .Values.ollama.enabled }}
12+
DIFFSCOPE_BASE_URL: {{ include "diffscope.ollamaUrl" . | quote }}
13+
{{- end }}
14+
{{- range $key, $value := .Values.config.extraEnv }}
15+
{{ $key }}: {{ $value | quote }}
16+
{{- end }}
17+
{{- if .Values.config.configFile }}
18+
diffscope.yml: |
19+
{{ .Values.config.configFile | indent 4 }}
20+
{{- end }}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: {{ include "diffscope.fullname" . }}
5+
labels:
6+
{{- include "diffscope.labels" . | nindent 4 }}
7+
spec:
8+
{{- if not .Values.autoscaling.enabled }}
9+
replicas: {{ .Values.replicaCount }}
10+
{{- end }}
11+
selector:
12+
matchLabels:
13+
{{- include "diffscope.selectorLabels" . | nindent 6 }}
14+
template:
15+
metadata:
16+
annotations:
17+
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
18+
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
19+
{{- with .Values.podAnnotations }}
20+
{{- toYaml . | nindent 8 }}
21+
{{- end }}
22+
labels:
23+
{{- include "diffscope.selectorLabels" . | nindent 8 }}
24+
{{- with .Values.podLabels }}
25+
{{- toYaml . | nindent 8 }}
26+
{{- end }}
27+
spec:
28+
{{- with .Values.imagePullSecrets }}
29+
imagePullSecrets:
30+
{{- toYaml . | nindent 8 }}
31+
{{- end }}
32+
serviceAccountName: {{ include "diffscope.serviceAccountName" . }}
33+
securityContext:
34+
{{- toYaml .Values.podSecurityContext | nindent 8 }}
35+
{{- if or (and .Values.gitRepo.enabled .Values.gitRepo.url) (and .Values.ollama.enabled (eq .Values.ollama.mode "sidecar")) }}
36+
initContainers:
37+
{{- if and .Values.gitRepo.enabled .Values.gitRepo.url }}
38+
- name: git-clone
39+
image: alpine/git:latest
40+
command: ["sh", "-c"]
41+
args:
42+
- |
43+
if [ ! -d /workspace/.git ]; then
44+
git clone --branch {{ .Values.gitRepo.branch }} --depth 1 {{ .Values.gitRepo.url }} /workspace
45+
else
46+
cd /workspace && git fetch origin && git reset --hard origin/{{ .Values.gitRepo.branch }}
47+
fi
48+
volumeMounts:
49+
- name: workspace
50+
mountPath: /workspace
51+
{{- end }}
52+
{{- if and .Values.ollama.enabled (eq .Values.ollama.mode "sidecar") }}
53+
- name: ollama-pull
54+
image: "{{ .Values.ollama.image.repository }}:{{ .Values.ollama.image.tag }}"
55+
command: ["sh", "-c"]
56+
args:
57+
- |
58+
ollama serve &
59+
sleep 5
60+
ollama pull {{ .Values.ollama.model }}
61+
kill %1
62+
volumeMounts:
63+
- name: ollama-data
64+
mountPath: /root/.ollama
65+
{{- end }}
66+
{{- end }}
67+
containers:
68+
- name: {{ .Chart.Name }}
69+
securityContext:
70+
{{- toYaml .Values.securityContext | nindent 12 }}
71+
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
72+
imagePullPolicy: {{ .Values.image.pullPolicy }}
73+
args:
74+
- serve
75+
- --host
76+
- {{ .Values.diffscope.host | quote }}
77+
- --port
78+
- {{ .Values.diffscope.port | quote }}
79+
{{- if .Values.diffscope.model }}
80+
- --model
81+
- {{ .Values.diffscope.model | quote }}
82+
{{- end }}
83+
{{- if .Values.diffscope.adapter }}
84+
- --adapter
85+
- {{ .Values.diffscope.adapter | quote }}
86+
{{- end }}
87+
{{- range .Values.diffscope.extraArgs }}
88+
- {{ . | quote }}
89+
{{- end }}
90+
workingDir: {{ .Values.gitRepo.mountPath }}
91+
ports:
92+
- name: http
93+
containerPort: {{ .Values.diffscope.port }}
94+
protocol: TCP
95+
envFrom:
96+
- configMapRef:
97+
name: {{ include "diffscope.fullname" . }}
98+
env:
99+
- name: DIFFSCOPE_API_KEY
100+
valueFrom:
101+
secretKeyRef:
102+
name: {{ include "diffscope.secretName" . }}
103+
key: {{ .Values.secrets.existingSecretKeys.diffscopeApiKey }}
104+
optional: true
105+
- name: OPENAI_API_KEY
106+
valueFrom:
107+
secretKeyRef:
108+
name: {{ include "diffscope.secretName" . }}
109+
key: {{ .Values.secrets.existingSecretKeys.openaiApiKey }}
110+
optional: true
111+
- name: OPENROUTER_API_KEY
112+
valueFrom:
113+
secretKeyRef:
114+
name: {{ include "diffscope.secretName" . }}
115+
key: {{ .Values.secrets.existingSecretKeys.openrouterApiKey }}
116+
optional: true
117+
- name: ANTHROPIC_API_KEY
118+
valueFrom:
119+
secretKeyRef:
120+
name: {{ include "diffscope.secretName" . }}
121+
key: {{ .Values.secrets.existingSecretKeys.anthropicApiKey }}
122+
optional: true
123+
livenessProbe:
124+
httpGet:
125+
path: /api/status
126+
port: http
127+
initialDelaySeconds: 5
128+
periodSeconds: 15
129+
timeoutSeconds: 5
130+
failureThreshold: 3
131+
readinessProbe:
132+
httpGet:
133+
path: /api/status
134+
port: http
135+
initialDelaySeconds: 3
136+
periodSeconds: 10
137+
timeoutSeconds: 5
138+
failureThreshold: 3
139+
resources:
140+
{{- toYaml .Values.resources | nindent 12 }}
141+
volumeMounts:
142+
- name: reviews-data
143+
mountPath: /home/diffscope/.local/share/diffscope
144+
- name: workspace
145+
mountPath: {{ .Values.gitRepo.mountPath }}
146+
{{- if .Values.config.configFile }}
147+
- name: config-file
148+
mountPath: {{ .Values.gitRepo.mountPath }}/.diffscope.yml
149+
subPath: diffscope.yml
150+
{{- end }}
151+
{{- with .Values.config.extraVolumeMounts }}
152+
{{- toYaml . | nindent 12 }}
153+
{{- end }}
154+
{{- if and .Values.ollama.enabled (eq .Values.ollama.mode "sidecar") }}
155+
- name: ollama
156+
image: "{{ .Values.ollama.image.repository }}:{{ .Values.ollama.image.tag }}"
157+
imagePullPolicy: {{ .Values.ollama.image.pullPolicy }}
158+
ports:
159+
- name: ollama
160+
containerPort: {{ .Values.ollama.port }}
161+
protocol: TCP
162+
livenessProbe:
163+
httpGet:
164+
path: /api/tags
165+
port: ollama
166+
initialDelaySeconds: 30
167+
periodSeconds: 10
168+
readinessProbe:
169+
httpGet:
170+
path: /api/tags
171+
port: ollama
172+
initialDelaySeconds: 10
173+
periodSeconds: 10
174+
resources:
175+
{{- toYaml .Values.ollama.resources | nindent 12 }}
176+
volumeMounts:
177+
- name: ollama-data
178+
mountPath: /root/.ollama
179+
{{- end }}
180+
volumes:
181+
- name: reviews-data
182+
{{- if and .Values.persistence.enabled .Values.persistence.existingClaim }}
183+
persistentVolumeClaim:
184+
claimName: {{ .Values.persistence.existingClaim }}
185+
{{- else if .Values.persistence.enabled }}
186+
persistentVolumeClaim:
187+
claimName: {{ include "diffscope.fullname" . }}-data
188+
{{- else }}
189+
emptyDir: {}
190+
{{- end }}
191+
- name: workspace
192+
{{- if and .Values.gitRepo.enabled .Values.gitRepo.existingPvc }}
193+
persistentVolumeClaim:
194+
claimName: {{ .Values.gitRepo.existingPvc }}
195+
{{- else }}
196+
emptyDir: {}
197+
{{- end }}
198+
{{- if .Values.config.configFile }}
199+
- name: config-file
200+
configMap:
201+
name: {{ include "diffscope.fullname" . }}
202+
{{- end }}
203+
{{- if and .Values.ollama.enabled (eq .Values.ollama.mode "sidecar") }}
204+
- name: ollama-data
205+
{{- if and .Values.ollama.persistence.enabled .Values.ollama.persistence.existingClaim }}
206+
persistentVolumeClaim:
207+
claimName: {{ .Values.ollama.persistence.existingClaim }}
208+
{{- else if .Values.ollama.persistence.enabled }}
209+
persistentVolumeClaim:
210+
claimName: {{ include "diffscope.ollamaFullname" . }}-data
211+
{{- else }}
212+
emptyDir: {}
213+
{{- end }}
214+
{{- end }}
215+
{{- with .Values.config.extraVolumes }}
216+
{{- toYaml . | nindent 8 }}
217+
{{- end }}
218+
{{- with .Values.nodeSelector }}
219+
nodeSelector:
220+
{{- toYaml . | nindent 8 }}
221+
{{- end }}
222+
{{- with .Values.affinity }}
223+
affinity:
224+
{{- toYaml . | nindent 8 }}
225+
{{- end }}
226+
{{- with .Values.tolerations }}
227+
tolerations:
228+
{{- toYaml . | nindent 8 }}
229+
{{- end }}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{{- if .Values.autoscaling.enabled }}
2+
apiVersion: autoscaling/v2
3+
kind: HorizontalPodAutoscaler
4+
metadata:
5+
name: {{ include "diffscope.fullname" . }}
6+
labels:
7+
{{- include "diffscope.labels" . | nindent 4 }}
8+
spec:
9+
scaleTargetRef:
10+
apiVersion: apps/v1
11+
kind: Deployment
12+
name: {{ include "diffscope.fullname" . }}
13+
minReplicas: {{ .Values.autoscaling.minReplicas }}
14+
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
15+
metrics:
16+
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
17+
- type: Resource
18+
resource:
19+
name: cpu
20+
target:
21+
type: Utilization
22+
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
23+
{{- end }}
24+
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
25+
- type: Resource
26+
resource:
27+
name: memory
28+
target:
29+
type: Utilization
30+
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
31+
{{- end }}
32+
{{- end }}

0 commit comments

Comments
 (0)