Skip to content

Commit 2ccde60

Browse files
committed
feat!: convert storageClasses from map to list for multi-class support (#124)
BREAKING CHANGE: `storageClasses` is now a list instead of a map keyed by protocol. Each entry requires explicit `name` and `protocol` fields. This enables multiple StorageClasses of the same protocol (e.g., two NFS classes with different reclaim policies). Existing volumes and data are unaffected — only the Helm values format changed. See the "Migrating to v0.10.0" section in the chart README for upgrade instructions.
1 parent 2a93ed2 commit 2ccde60

File tree

18 files changed

+493
-421
lines changed

18 files changed

+493
-421
lines changed

.github/workflows/release.yml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,21 @@ jobs:
112112
--namespace kube-system \
113113
--set truenas.url="wss://TRUENAS_IP/api/current" \
114114
--set truenas.apiKey="REPLACE_WITH_API_KEY" \
115-
--set storageClasses.nfs.enabled=true \
116-
--set storageClasses.nfs.server="TRUENAS_IP" \
117-
--set storageClasses.nfs.pool="tank" \
118-
--set storageClasses.nvmeof.enabled=true \
119-
--set storageClasses.nvmeof.server="TRUENAS_IP" \
120-
--set storageClasses.nvmeof.pool="tank" \
121-
--set storageClasses.iscsi.enabled=true \
122-
--set storageClasses.iscsi.server="TRUENAS_IP" \
123-
--set storageClasses.iscsi.pool="tank" \
115+
--set storageClasses[0].name=tns-csi-nfs \
116+
--set storageClasses[0].enabled=true \
117+
--set storageClasses[0].protocol=nfs \
118+
--set storageClasses[0].server="TRUENAS_IP" \
119+
--set storageClasses[0].pool="tank" \
120+
--set storageClasses[1].name=tns-csi-nvmeof \
121+
--set storageClasses[1].enabled=true \
122+
--set storageClasses[1].protocol=nvmeof \
123+
--set storageClasses[1].server="TRUENAS_IP" \
124+
--set storageClasses[1].pool="tank" \
125+
--set storageClasses[2].name=tns-csi-iscsi \
126+
--set storageClasses[2].enabled=true \
127+
--set storageClasses[2].protocol=iscsi \
128+
--set storageClasses[2].server="TRUENAS_IP" \
129+
--set storageClasses[2].pool="tank" \
124130
--set snapshots.enabled=true \
125131
> .helm-releases/tns-csi-driver-${{ steps.version.outputs.version }}.yaml
126132

README.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,11 @@ helm install tns-csi oci://registry-1.docker.io/bfenski/tns-csi-driver \
149149
--create-namespace \
150150
--set truenas.url="wss://YOUR-TRUENAS-IP:443/api/current" \
151151
--set truenas.apiKey="YOUR-API-KEY" \
152-
--set storageClasses.nfs.enabled=true \
153-
--set storageClasses.nfs.pool="YOUR-POOL-NAME" \
154-
--set storageClasses.nfs.server="YOUR-TRUENAS-IP"
152+
--set storageClasses[0].name=tns-csi-nfs \
153+
--set storageClasses[0].enabled=true \
154+
--set storageClasses[0].protocol=nfs \
155+
--set storageClasses[0].pool="YOUR-POOL-NAME" \
156+
--set storageClasses[0].server="YOUR-TRUENAS-IP"
155157
```
156158

157159
**NVMe-oF Example:**
@@ -162,11 +164,13 @@ helm install tns-csi oci://registry-1.docker.io/bfenski/tns-csi-driver \
162164
--create-namespace \
163165
--set truenas.url="wss://YOUR-TRUENAS-IP:443/api/current" \
164166
--set truenas.apiKey="YOUR-API-KEY" \
165-
--set storageClasses.nvmeof.enabled=true \
166-
--set storageClasses.nvmeof.pool="YOUR-POOL-NAME" \
167-
--set storageClasses.nvmeof.server="YOUR-TRUENAS-IP" \
168-
--set storageClasses.nvmeof.transport=tcp \
169-
--set storageClasses.nvmeof.port=4420
167+
--set storageClasses[0].name=tns-csi-nvmeof \
168+
--set storageClasses[0].enabled=true \
169+
--set storageClasses[0].protocol=nvmeof \
170+
--set storageClasses[0].pool="YOUR-POOL-NAME" \
171+
--set storageClasses[0].server="YOUR-TRUENAS-IP" \
172+
--set storageClasses[0].transport=tcp \
173+
--set storageClasses[0].port=4420
170174
```
171175

172176
**Note:** NVMe-oF requires a TCP port to be pre-configured in TrueNAS (Shares > NVMe-oF Targets > Ports). Subsystems are automatically created per volume.
@@ -179,9 +183,11 @@ helm install tns-csi oci://registry-1.docker.io/bfenski/tns-csi-driver \
179183
--create-namespace \
180184
--set truenas.url="wss://YOUR-TRUENAS-IP:443/api/current" \
181185
--set truenas.apiKey="YOUR-API-KEY" \
182-
--set storageClasses.iscsi.enabled=true \
183-
--set storageClasses.iscsi.pool="YOUR-POOL-NAME" \
184-
--set storageClasses.iscsi.server="YOUR-TRUENAS-IP"
186+
--set storageClasses[0].name=tns-csi-iscsi \
187+
--set storageClasses[0].enabled=true \
188+
--set storageClasses[0].protocol=iscsi \
189+
--set storageClasses[0].pool="YOUR-POOL-NAME" \
190+
--set storageClasses[0].server="YOUR-TRUENAS-IP"
185191
```
186192

187193
**Note:** iSCSI requires the iSCSI service to be enabled in TrueNAS (System > Services). Targets and extents are automatically created per volume.

charts/tns-csi-driver/README.md

Lines changed: 161 additions & 136 deletions
Large diffs are not rendered by default.

charts/tns-csi-driver/templates/NOTES.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ To verify the installation:
1717
kubectl get storageclass
1818

1919
Storage Classes Created:
20-
{{- range $name, $sc := .Values.storageClasses }}
21-
{{- if $sc.enabled }}
22-
- {{ $sc.name }} ({{ $name }})
20+
{{- range .Values.storageClasses }}
21+
{{- if .enabled }}
22+
- {{ .name }} ({{ .protocol }})
2323
{{- end }}
2424
{{- end }}
2525

2626
To create a test PersistentVolumeClaim:
2727
{{- $firstSC := "" }}
28-
{{- range $name, $sc := .Values.storageClasses }}
29-
{{- if and $sc.enabled (eq $firstSC "") }}
30-
{{- $firstSC = $sc.name }}
28+
{{- range .Values.storageClasses }}
29+
{{- if and .enabled (eq $firstSC "") }}
30+
{{- $firstSC = .name }}
3131
{{- end }}
3232
{{- end }}
3333

charts/tns-csi-driver/templates/_helpers.tpl

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,18 @@ Validate required TrueNAS configuration
135135
{{- fail "\n\nCONFIGURATION ERROR: truenas.apiKey is required.\nCreate an API key in TrueNAS UI: Settings > API Keys\nExample: --set truenas.apiKey=\"1-xxxxxxxxxx\"" }}
136136
{{- end }}
137137
{{- end }}
138-
{{- if and .Values.storageClasses.nfs.enabled (not .Values.storageClasses.nfs.server) }}
139-
{{- fail "\n\nCONFIGURATION ERROR: storageClasses.nfs.server is required when NFS is enabled.\nExample: --set storageClasses.nfs.server=\"YOUR-TRUENAS-IP\"" }}
138+
{{- range .Values.storageClasses }}
139+
{{- if .enabled }}
140+
{{- if and (eq .protocol "nfs") (not .server) }}
141+
{{- fail (printf "\n\nCONFIGURATION ERROR: server is required for NFS storage class %q.\nExample: --set 'storageClasses[0].server=YOUR-TRUENAS-IP'" .name) }}
142+
{{- end }}
143+
{{- if and (eq .protocol "nvmeof") (not .server) }}
144+
{{- fail (printf "\n\nCONFIGURATION ERROR: server is required for NVMe-oF storage class %q.\nExample: --set 'storageClasses[1].server=YOUR-TRUENAS-IP'" .name) }}
145+
{{- end }}
146+
{{- if and (eq .protocol "iscsi") (not .server) }}
147+
{{- fail (printf "\n\nCONFIGURATION ERROR: server is required for iSCSI storage class %q." .name) }}
148+
{{- end }}
140149
{{- end }}
141-
{{- if and .Values.storageClasses.nvmeof.enabled (not .Values.storageClasses.nvmeof.server) }}
142-
{{- fail "\n\nCONFIGURATION ERROR: storageClasses.nvmeof.server is required when NVMe-oF is enabled.\nExample: --set storageClasses.nvmeof.server=\"YOUR-TRUENAS-IP\"" }}
143150
{{- end }}
144151
{{- end }}
145152

@@ -160,3 +167,89 @@ This allows users to either:
160167
{{- "latest" }}
161168
{{- end }}
162169
{{- end }}
170+
171+
{{/*
172+
Render a StorageClass resource.
173+
Accepts a dict with keys: protocol, sc (storage class config), root (root context).
174+
*/}}
175+
{{- define "tns-csi-driver.storageclass" -}}
176+
{{- $protocol := .protocol -}}
177+
{{- $sc := .sc -}}
178+
{{- $ := .root -}}
179+
---
180+
apiVersion: storage.k8s.io/v1
181+
kind: StorageClass
182+
metadata:
183+
name: {{ $sc.name }}
184+
labels:
185+
{{- include "tns-csi-driver.labels" $ | nindent 4 }}
186+
{{- if $sc.isDefault }}
187+
annotations:
188+
storageclass.kubernetes.io/is-default-class: "true"
189+
{{- end }}
190+
provisioner: {{ include "tns-csi-driver.driverName" $ }}
191+
parameters:
192+
protocol: {{ $protocol | quote }}
193+
pool: {{ $sc.pool | quote }}
194+
{{- if $sc.server }}
195+
server: {{ $sc.server | quote }}
196+
{{- end }}
197+
{{- if $sc.parentDataset }}
198+
parentDataset: {{ $sc.parentDataset | quote }}
199+
{{- end }}
200+
{{- if $sc.deleteStrategy }}
201+
deleteStrategy: {{ $sc.deleteStrategy | quote }}
202+
{{- end }}
203+
{{- if $sc.nameTemplate }}
204+
nameTemplate: {{ $sc.nameTemplate | quote }}
205+
{{- end }}
206+
{{- if $sc.namePrefix }}
207+
namePrefix: {{ $sc.namePrefix | quote }}
208+
{{- end }}
209+
{{- if $sc.nameSuffix }}
210+
nameSuffix: {{ $sc.nameSuffix | quote }}
211+
{{- end }}
212+
{{- if $sc.commentTemplate }}
213+
commentTemplate: {{ $sc.commentTemplate | quote }}
214+
{{- end }}
215+
{{- if $sc.markAdoptable }}
216+
markAdoptable: {{ $sc.markAdoptable | quote }}
217+
{{- end }}
218+
{{- if $sc.adoptExisting }}
219+
adoptExisting: {{ $sc.adoptExisting | quote }}
220+
{{- end }}
221+
{{- if $sc.encryption }}
222+
encryption: {{ $sc.encryption | quote }}
223+
{{- end }}
224+
{{- if $sc.encryptionAlgorithm }}
225+
encryptionAlgorithm: {{ $sc.encryptionAlgorithm | quote }}
226+
{{- end }}
227+
{{- if $sc.encryptionGenerateKey }}
228+
encryptionGenerateKey: {{ $sc.encryptionGenerateKey | quote }}
229+
{{- end }}
230+
{{- if eq $protocol "nvmeof" }}
231+
transport: {{ $sc.transport | default "tcp" | quote }}
232+
port: {{ $sc.port | default "4420" | quote }}
233+
{{- if $sc.fsType }}
234+
csi.storage.k8s.io/fstype: {{ $sc.fsType | quote }}
235+
{{- end }}
236+
{{- end }}
237+
{{- if eq $protocol "iscsi" }}
238+
port: {{ $sc.port | default "3260" | quote }}
239+
{{- if $sc.fsType }}
240+
csi.storage.k8s.io/fstype: {{ $sc.fsType | quote }}
241+
{{- end }}
242+
{{- end }}
243+
{{- if $sc.parameters }}
244+
{{- range $key, $value := $sc.parameters }}
245+
{{ $key }}: {{ $value | quote }}
246+
{{- end }}
247+
{{- end }}
248+
allowVolumeExpansion: {{ $sc.allowVolumeExpansion | default true }}
249+
reclaimPolicy: {{ $sc.reclaimPolicy | default "Delete" }}
250+
volumeBindingMode: {{ $sc.volumeBindingMode | default "Immediate" }}
251+
{{- if $sc.mountOptions }}
252+
mountOptions:
253+
{{- toYaml $sc.mountOptions | nindent 2 }}
254+
{{- end }}
255+
{{ end }}
Lines changed: 3 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,5 @@
1-
{{- range $name, $sc := .Values.storageClasses }}
2-
{{- if $sc.enabled }}
3-
---
4-
apiVersion: storage.k8s.io/v1
5-
kind: StorageClass
6-
metadata:
7-
name: {{ $sc.name }}
8-
labels:
9-
{{- include "tns-csi-driver.labels" $ | nindent 4 }}
10-
{{- if $sc.isDefault }}
11-
annotations:
12-
storageclass.kubernetes.io/is-default-class: "true"
13-
{{- end }}
14-
provisioner: {{ include "tns-csi-driver.driverName" $ }}
15-
parameters:
16-
protocol: {{ $name | quote }}
17-
pool: {{ $sc.pool | quote }}
18-
{{- if $sc.server }}
19-
server: {{ $sc.server | quote }}
20-
{{- end }}
21-
{{- if $sc.parentDataset }}
22-
parentDataset: {{ $sc.parentDataset | quote }}
23-
{{- end }}
24-
{{- if $sc.deleteStrategy }}
25-
deleteStrategy: {{ $sc.deleteStrategy | quote }}
26-
{{- end }}
27-
{{- if $sc.nameTemplate }}
28-
nameTemplate: {{ $sc.nameTemplate | quote }}
29-
{{- end }}
30-
{{- if $sc.namePrefix }}
31-
namePrefix: {{ $sc.namePrefix | quote }}
32-
{{- end }}
33-
{{- if $sc.nameSuffix }}
34-
nameSuffix: {{ $sc.nameSuffix | quote }}
35-
{{- end }}
36-
{{- if $sc.commentTemplate }}
37-
commentTemplate: {{ $sc.commentTemplate | quote }}
38-
{{- end }}
39-
{{- if $sc.markAdoptable }}
40-
markAdoptable: {{ $sc.markAdoptable | quote }}
41-
{{- end }}
42-
{{- if $sc.adoptExisting }}
43-
adoptExisting: {{ $sc.adoptExisting | quote }}
44-
{{- end }}
45-
{{- if $sc.encryption }}
46-
encryption: {{ $sc.encryption | quote }}
47-
{{- end }}
48-
{{- if $sc.encryptionAlgorithm }}
49-
encryptionAlgorithm: {{ $sc.encryptionAlgorithm | quote }}
50-
{{- end }}
51-
{{- if $sc.encryptionGenerateKey }}
52-
encryptionGenerateKey: {{ $sc.encryptionGenerateKey | quote }}
53-
{{- end }}
54-
{{- if eq $name "nvmeof" }}
55-
transport: {{ $sc.transport | default "tcp" | quote }}
56-
port: {{ $sc.port | default "4420" | quote }}
57-
{{- if $sc.fsType }}
58-
csi.storage.k8s.io/fstype: {{ $sc.fsType | quote }}
59-
{{- end }}
60-
{{- end }}
61-
{{- if eq $name "iscsi" }}
62-
port: {{ $sc.port | default "3260" | quote }}
63-
{{- if $sc.fsType }}
64-
csi.storage.k8s.io/fstype: {{ $sc.fsType | quote }}
65-
{{- end }}
66-
{{- end }}
67-
{{- if $sc.parameters }}
68-
{{- range $key, $value := $sc.parameters }}
69-
{{ $key }}: {{ $value | quote }}
70-
{{- end }}
71-
{{- end }}
72-
allowVolumeExpansion: {{ $sc.allowVolumeExpansion | default true }}
73-
reclaimPolicy: {{ $sc.reclaimPolicy | default "Delete" }}
74-
volumeBindingMode: {{ $sc.volumeBindingMode | default "Immediate" }}
75-
{{- if $sc.mountOptions }}
76-
mountOptions:
77-
{{- toYaml $sc.mountOptions | nindent 2 }}
78-
{{- end }}
1+
{{- range .Values.storageClasses }}
2+
{{- if .enabled }}
3+
{{- include "tns-csi-driver.storageclass" (dict "protocol" .protocol "sc" . "root" $) }}
794
{{- end }}
805
{{- end }}
Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,39 @@
11
{{- if and .Values.snapshots.enabled .Values.snapshots.volumeSnapshotClass.create }}
2-
{{- if .Values.storageClasses.nfs.enabled }}
2+
{{- range .Values.storageClasses }}
3+
{{- if .enabled }}
34
---
45
apiVersion: snapshot.storage.k8s.io/v1
56
kind: VolumeSnapshotClass
67
metadata:
7-
name: {{ .Values.storageClasses.nfs.name }}-snapshot
8+
name: {{ .name }}-snapshot
89
labels:
9-
{{- include "tns-csi-driver.labels" . | nindent 4 }}
10-
{{- with .Values.customAnnotations }}
10+
{{- include "tns-csi-driver.labels" $ | nindent 4 }}
11+
{{- with $.Values.customAnnotations }}
1112
annotations:
1213
{{- toYaml . | nindent 4 }}
1314
{{- end }}
14-
driver: {{ .Values.csiDriverName }}
15-
deletionPolicy: {{ .Values.snapshots.volumeSnapshotClass.deletionPolicy }}
16-
{{- end }}
17-
18-
{{- if .Values.storageClasses.nvmeof.enabled }}
15+
driver: {{ $.Values.csiDriverName }}
16+
deletionPolicy: {{ $.Values.snapshots.volumeSnapshotClass.deletionPolicy }}
17+
{{- if $.Values.snapshots.detached.enabled }}
1918
---
2019
apiVersion: snapshot.storage.k8s.io/v1
2120
kind: VolumeSnapshotClass
2221
metadata:
23-
name: {{ .Values.storageClasses.nvmeof.name }}-snapshot
22+
name: {{ .name }}-snapshot-detached
2423
labels:
25-
{{- include "tns-csi-driver.labels" . | nindent 4 }}
26-
{{- with .Values.customAnnotations }}
24+
{{- include "tns-csi-driver.labels" $ | nindent 4 }}
25+
{{- with $.Values.customAnnotations }}
2726
annotations:
2827
{{- toYaml . | nindent 4 }}
2928
{{- end }}
30-
driver: {{ .Values.csiDriverName }}
31-
deletionPolicy: {{ .Values.snapshots.volumeSnapshotClass.deletionPolicy }}
32-
{{- end }}
33-
34-
{{- /* Detached snapshot classes - create independent copies that survive source volume deletion */}}
35-
{{- if .Values.snapshots.detached.enabled }}
36-
{{- if .Values.storageClasses.nfs.enabled }}
37-
---
38-
apiVersion: snapshot.storage.k8s.io/v1
39-
kind: VolumeSnapshotClass
40-
metadata:
41-
name: {{ .Values.storageClasses.nfs.name }}-snapshot-detached
42-
labels:
43-
{{- include "tns-csi-driver.labels" . | nindent 4 }}
44-
{{- with .Values.customAnnotations }}
45-
annotations:
46-
{{- toYaml . | nindent 4 }}
47-
{{- end }}
48-
driver: {{ .Values.csiDriverName }}
49-
deletionPolicy: {{ .Values.snapshots.detached.deletionPolicy | default .Values.snapshots.volumeSnapshotClass.deletionPolicy }}
29+
driver: {{ $.Values.csiDriverName }}
30+
deletionPolicy: {{ $.Values.snapshots.detached.deletionPolicy | default $.Values.snapshots.volumeSnapshotClass.deletionPolicy }}
5031
parameters:
5132
detachedSnapshots: "true"
52-
{{- if .Values.snapshots.detached.parentDataset }}
53-
detachedSnapshotsParentDataset: {{ .Values.snapshots.detached.parentDataset | quote }}
33+
{{- if $.Values.snapshots.detached.parentDataset }}
34+
detachedSnapshotsParentDataset: {{ $.Values.snapshots.detached.parentDataset | quote }}
5435
{{- end }}
5536
{{- end }}
56-
57-
{{- if .Values.storageClasses.nvmeof.enabled }}
58-
---
59-
apiVersion: snapshot.storage.k8s.io/v1
60-
kind: VolumeSnapshotClass
61-
metadata:
62-
name: {{ .Values.storageClasses.nvmeof.name }}-snapshot-detached
63-
labels:
64-
{{- include "tns-csi-driver.labels" . | nindent 4 }}
65-
{{- with .Values.customAnnotations }}
66-
annotations:
67-
{{- toYaml . | nindent 4 }}
68-
{{- end }}
69-
driver: {{ .Values.csiDriverName }}
70-
deletionPolicy: {{ .Values.snapshots.detached.deletionPolicy | default .Values.snapshots.volumeSnapshotClass.deletionPolicy }}
71-
parameters:
72-
detachedSnapshots: "true"
73-
{{- if .Values.snapshots.detached.parentDataset }}
74-
detachedSnapshotsParentDataset: {{ .Values.snapshots.detached.parentDataset | quote }}
75-
{{- end }}
7637
{{- end }}
7738
{{- end }}
7839
{{- end }}

0 commit comments

Comments
 (0)