From 04218411784894dc88627a8630586d277d6805bd Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 00:33:02 +0100 Subject: [PATCH 1/6] feat(nfs): add managed NFS application with kubernetes tenant integration Add managed NFS application and integrate it with tenant Kubernetes clusters: - New `nfs` app deploying NFS server as a HelmRelease with standalone RWX PVC storage - `nfs-rd` resource definition package for the NFS app - `system/nfs` package with Cilium egress policy for NFS traffic - `extraStorageClasses` support in `nfs-driver` system chart - NFS addon in `kubernetes` app with PVC-based volume lookup and top-level `nfs.instances` configuration - `nfs-application` PackageSource for platform artifact delivery - E2E test with NFS mount verification - Helm-unittest tests for new templates Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/e2e-apps/run-kubernetes.sh | 88 +++++++++++- packages/apps/kubernetes/Makefile | 3 + packages/apps/kubernetes/README.md | 11 ++ .../templates/helmreleases/nfs-driver.yaml | 67 +++++++++ .../kubernetes/tests/nfs-driver_test.yaml | 45 ++++++ packages/apps/kubernetes/values.schema.json | 41 ++++++ packages/apps/kubernetes/values.yaml | 24 ++++ packages/apps/nfs/.helmignore | 2 + packages/apps/nfs/Chart.yaml | 6 + packages/apps/nfs/Makefile | 6 + packages/apps/nfs/README.md | 20 +++ packages/apps/nfs/charts/cozy-lib | 1 + packages/apps/nfs/logos/nfs.svg | 13 ++ packages/apps/nfs/templates/_helpers.tpl | 49 +++++++ .../nfs/templates/dashboard-resourcemap.yaml | 23 +++ packages/apps/nfs/templates/helmrelease.yaml | 22 +++ packages/apps/nfs/templates/pvc.yaml | 15 ++ .../apps/nfs/templates/workloadmonitor.yaml | 12 ++ packages/apps/nfs/values.schema.json | 25 ++++ packages/apps/nfs/values.yaml | 9 ++ .../platform/images/migrations/migrations/25 | 3 + .../sources/kubernetes-application.yaml | 2 + .../platform/sources/nfs-application.yaml | 29 ++++ .../kubernetes-rd/cozyrds/kubernetes.yaml | 4 +- packages/system/nfs-driver/Makefile | 3 + .../templates/extra-storageclasses.yaml | 18 +++ .../tests/extra-storageclasses_test.yaml | 133 ++++++++++++++++++ packages/system/nfs-driver/values.yaml | 2 + packages/system/nfs-rd/Chart.yaml | 3 + packages/system/nfs-rd/Makefile | 4 + packages/system/nfs-rd/cozyrds/nfs.yaml | 21 +++ packages/system/nfs-rd/templates/cozyrd.yaml | 4 + packages/system/nfs-rd/values.yaml | 1 + packages/system/nfs/.helmignore | 2 + packages/system/nfs/Chart.yaml | 3 + packages/system/nfs/Makefile | 3 + .../system/nfs/templates/cilium-policy.yaml | 20 +++ packages/system/nfs/values.yaml | 1 + 38 files changed, 734 insertions(+), 4 deletions(-) create mode 100644 packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml create mode 100644 packages/apps/kubernetes/tests/nfs-driver_test.yaml create mode 100644 packages/apps/nfs/.helmignore create mode 100644 packages/apps/nfs/Chart.yaml create mode 100644 packages/apps/nfs/Makefile create mode 100644 packages/apps/nfs/README.md create mode 120000 packages/apps/nfs/charts/cozy-lib create mode 100644 packages/apps/nfs/logos/nfs.svg create mode 100644 packages/apps/nfs/templates/_helpers.tpl create mode 100644 packages/apps/nfs/templates/dashboard-resourcemap.yaml create mode 100644 packages/apps/nfs/templates/helmrelease.yaml create mode 100644 packages/apps/nfs/templates/pvc.yaml create mode 100644 packages/apps/nfs/templates/workloadmonitor.yaml create mode 100644 packages/apps/nfs/values.schema.json create mode 100644 packages/apps/nfs/values.yaml create mode 100644 packages/core/platform/sources/nfs-application.yaml create mode 100644 packages/system/nfs-driver/templates/extra-storageclasses.yaml create mode 100644 packages/system/nfs-driver/tests/extra-storageclasses_test.yaml create mode 100644 packages/system/nfs-rd/Chart.yaml create mode 100644 packages/system/nfs-rd/Makefile create mode 100644 packages/system/nfs-rd/cozyrds/nfs.yaml create mode 100644 packages/system/nfs-rd/templates/cozyrd.yaml create mode 100644 packages/system/nfs-rd/values.yaml create mode 100644 packages/system/nfs/.helmignore create mode 100644 packages/system/nfs/Chart.yaml create mode 100644 packages/system/nfs/Makefile create mode 100644 packages/system/nfs/templates/cilium-policy.yaml create mode 100644 packages/system/nfs/values.yaml diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index 3e2e20f9..51dc837c 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -3,6 +3,25 @@ run_kubernetes_test() { local test_name="$2" local port="$3" local k8s_version=$(yq "$version_expr" packages/apps/kubernetes/files/versions.yaml) + local nfs_name="${test_name}" + + # Create NFS volume for testing NFS mount + kubectl apply -f - </dev/null; do sleep 5; done' + + # Create PVC with NFS StorageClass + kubectl --kubeconfig tenantkubeconfig-${test_name} apply -f - < /data/test.txt && cat /data/test.txt"] + volumeMounts: + - name: nfs-vol + mountPath: /data + volumes: + - name: nfs-vol + persistentVolumeClaim: + claimName: nfs-test-pvc + restartPolicy: Never +EOF + + # Wait for Pod to complete successfully + kubectl --kubeconfig tenantkubeconfig-${test_name} wait pod nfs-test-pod -n tenant-test --timeout=2m --for=jsonpath='{.status.phase}'=Succeeded + + # Verify NFS data integrity + nfs_result=$(kubectl --kubeconfig tenantkubeconfig-${test_name} logs nfs-test-pod -n tenant-test) + if [ "$nfs_result" != "nfs-mount-ok" ]; then + echo "NFS mount test failed: expected 'nfs-mount-ok', got '$nfs_result'" >&2 + exit 1 + fi + + # Cleanup NFS test resources in tenant cluster + kubectl --kubeconfig tenantkubeconfig-${test_name} delete pod nfs-test-pod -n tenant-test + kubectl --kubeconfig tenantkubeconfig-${test_name} delete pvc nfs-test-pvc -n tenant-test + # Wait for all machine deployment replicas to be ready (timeout after 10 minutes) kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=10m --for=jsonpath='{.status.v1beta2.readyReplicas}'=2 - for component in cilium coredns csi vsnap-crd; do + for component in cilium coredns csi nfs-driver vsnap-crd; do kubectl wait hr kubernetes-${test_name}-${component} -n tenant-test --timeout=1m --for=condition=ready done kubectl wait hr kubernetes-${test_name}-ingress-nginx -n tenant-test --timeout=5m --for=condition=ready @@ -229,4 +310,7 @@ fi # Clean up by deleting the Kubernetes resource kubectl -n tenant-test delete kuberneteses.apps.cozystack.io $test_name + # Clean up NFS volume + kubectl -n tenant-test delete nfs.apps.cozystack.io ${nfs_name} + } diff --git a/packages/apps/kubernetes/Makefile b/packages/apps/kubernetes/Makefile index e6ed7c6f..9b4dadf0 100644 --- a/packages/apps/kubernetes/Makefile +++ b/packages/apps/kubernetes/Makefile @@ -4,6 +4,9 @@ KUBERNETES_PKG_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml) include ../../../hack/common-envs.mk include ../../../hack/package.mk +test: + helm unittest . + generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md ../../../hack/update-crd.sh diff --git a/packages/apps/kubernetes/README.md b/packages/apps/kubernetes/README.md index b2fe102f..66e72a6d 100644 --- a/packages/apps/kubernetes/README.md +++ b/packages/apps/kubernetes/README.md @@ -108,6 +108,15 @@ See the reference for components utilized in this service: | `host` | External hostname for Kubernetes cluster. Defaults to `.` if empty. | `string` | `""` | +### NFS + +| Name | Description | Type | Value | +| ----------------------- | -------------------------------------------------------------------------------------------------------------- | ---------- | ----- | +| `nfs` | NFS configuration. | `object` | `{}` | +| `nfs.instances` | List of NFS volume instances to connect. Each creates a StorageClass named `nfs-` in the tenant cluster. | `[]object` | `[]` | +| `nfs.instances[i].name` | Name of the NFS volume instance in the same namespace. | `string` | `""` | + + ### Cluster Addons | Name | Description | Type | Value | @@ -141,6 +150,8 @@ See the reference for components utilized in this service: | `addons.velero.valuesOverride` | Custom Helm values overrides. | `object` | `{}` | | `addons.coredns` | CoreDNS addon. | `object` | `{}` | | `addons.coredns.valuesOverride` | Custom Helm values overrides. | `object` | `{}` | +| `addons.nfs` | NFS CSI driver addon. | `object` | `{}` | +| `addons.nfs.valuesOverride` | Custom Helm values overrides. | `object` | `{}` | ### Kubernetes Control Plane Configuration diff --git a/packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml b/packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml new file mode 100644 index 00000000..c00ea763 --- /dev/null +++ b/packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml @@ -0,0 +1,67 @@ +{{- if .Values.nfs.instances }} +{{- $extraStorageClasses := list }} +{{- range .Values.nfs.instances }} +{{- $pvcName := printf "nfs-%s" .name }} +{{- $pvc := lookup "v1" "PersistentVolumeClaim" $.Release.Namespace $pvcName }} +{{- if not $pvc }} +{{- fail (printf "NFS PVC not found in namespace %s: %s" $.Release.Namespace $pvcName) }} +{{- end }} +{{- if not $pvc.spec.volumeName }} +{{- fail (printf "NFS PVC %s is not yet bound" $pvcName) }} +{{- end }} +{{- $pv := lookup "v1" "PersistentVolume" "" $pvc.spec.volumeName }} +{{- if not $pv }} +{{- fail (printf "PersistentVolume %s not found for PVC %s" $pvc.spec.volumeName $pvcName) }} +{{- end }} +{{- $nfsExport := index $pv.spec.csi.volumeAttributes "linstor.csi.linbit.com/nfs-export" }} +{{- if not $nfsExport }} +{{- fail (printf "PV %s has no linstor.csi.linbit.com/nfs-export attribute" $pv.metadata.name) }} +{{- end }} +{{- $parsed := urlParse $nfsExport }} +{{- $hostParts := splitList ":" $parsed.host }} +{{- $server := first $hostParts }} +{{- $port := last $hostParts }} +{{- $sc := dict "name" (printf "nfs-%s" .name) "server" $server "share" $parsed.path "mountOptions" (list (printf "nfsvers=4.2") (printf "port=%s" $port)) }} +{{- $extraStorageClasses = append $extraStorageClasses $sc }} +{{- end }} +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: {{ .Release.Name }}-nfs-driver + labels: + cozystack.io/repository: system + cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants +spec: + releaseName: nfs-driver + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-nfs-driver + namespace: cozy-system + kubeConfig: + secretRef: + name: {{ .Release.Name }}-admin-kubeconfig + key: super-admin.svc + targetNamespace: cozy-nfs-driver + storageNamespace: cozy-nfs-driver + interval: 5m + timeout: 10m + install: + createNamespace: true + remediation: + retries: -1 + upgrade: + force: true + remediation: + retries: -1 + values: + {{- $defaultValues := dict "csi-driver-nfs" (dict "storageClass" (dict "create" false)) "extraStorageClasses" $extraStorageClasses }} + {{- toYaml (deepCopy .Values.addons.nfs.valuesOverride | mergeOverwrite $defaultValues) | nindent 4 }} + dependsOn: + {{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }} + - name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + {{- end }} + - name: {{ .Release.Name }}-cilium + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/packages/apps/kubernetes/tests/nfs-driver_test.yaml b/packages/apps/kubernetes/tests/nfs-driver_test.yaml new file mode 100644 index 00000000..902d3e69 --- /dev/null +++ b/packages/apps/kubernetes/tests/nfs-driver_test.yaml @@ -0,0 +1,45 @@ +suite: NFS driver HelmRelease tests + +templates: + - templates/helmreleases/nfs-driver.yaml + +tests: + - it: renders nothing when instances is empty + release: + name: kubernetes-test + namespace: tenant-root + asserts: + - hasDocuments: + count: 0 + + - it: renders nothing when instances is not set + release: + name: kubernetes-test + namespace: tenant-root + set: + nfs.instances: [] + asserts: + - hasDocuments: + count: 0 + + - it: fails when NFS PVC is not found + release: + name: kubernetes-test + namespace: tenant-root + set: + nfs.instances: + - name: my-data + asserts: + - failedTemplate: + errorMessage: "NFS PVC not found in namespace tenant-root: nfs-my-data" + + - it: constructs correct PVC name for lookup + release: + name: kubernetes-test + namespace: custom-ns + set: + nfs.instances: + - name: backup + asserts: + - failedTemplate: + errorMessage: "NFS PVC not found in namespace custom-ns: nfs-backup" diff --git a/packages/apps/kubernetes/values.schema.json b/packages/apps/kubernetes/values.schema.json index 1f6c9361..caf741b5 100644 --- a/packages/apps/kubernetes/values.schema.json +++ b/packages/apps/kubernetes/values.schema.json @@ -15,6 +15,7 @@ "gpuOperator", "ingressNginx", "monitoringAgents", + "nfs", "velero", "verticalPodAutoscaler" ], @@ -194,6 +195,22 @@ } } }, + "nfs": { + "description": "NFS CSI driver addon.", + "type": "object", + "default": {}, + "required": [ + "valuesOverride" + ], + "properties": { + "valuesOverride": { + "description": "Custom Helm values overrides.", + "type": "object", + "default": {}, + "x-kubernetes-preserve-unknown-fields": true + } + } + }, "velero": { "description": "Velero backup/restore addon.", "type": "object", @@ -500,6 +517,30 @@ "type": "string", "default": "" }, + "nfs": { + "description": "NFS configuration.", + "type": "object", + "default": {}, + "properties": { + "instances": { + "description": "List of NFS volume instances to connect. Each creates a StorageClass named `nfs-\u003cname\u003e` in the tenant cluster.", + "type": "array", + "default": [], + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "Name of the NFS volume instance in the same namespace.", + "type": "string" + } + } + } + } + } + }, "nodeGroups": { "description": "Worker nodes configuration map.", "type": "object", diff --git a/packages/apps/kubernetes/values.yaml b/packages/apps/kubernetes/values.yaml index 7f15752f..d4e2a1c2 100644 --- a/packages/apps/kubernetes/values.yaml +++ b/packages/apps/kubernetes/values.yaml @@ -62,6 +62,24 @@ version: "v1.33" ## @param {string} host - External hostname for Kubernetes cluster. Defaults to `.` if empty. host: "" +## +## @section NFS +## + +## @typedef {struct} NFSInstance - Reference to an NFS volume instance. +## @field {string} name - Name of the NFS volume instance in the same namespace. + +## @typedef {struct} NFS - NFS configuration. +## @field {[]NFSInstance} instances - List of NFS volume instances to connect. Each creates a StorageClass named `nfs-` in the tenant cluster. + +## @param {NFS} nfs - NFS configuration. +nfs: + instances: [] + ## Example: + ## instances: + ## - name: my-data + ## - name: my-backup + ## ## @section Cluster Addons ## @@ -108,6 +126,9 @@ host: "" ## @typedef {struct} CoreDNSAddon - CoreDNS addon. ## @field {object} valuesOverride - Custom Helm values overrides. +## @typedef {struct} NFSAddon - NFS CSI driver addon. +## @field {object} valuesOverride - Custom Helm values overrides. + ## @typedef {struct} Addons - Cluster addons configuration. ## @field {CertManagerAddon} certManager - Cert-manager addon. ## @field {CiliumAddon} cilium - Cilium CNI plugin. @@ -119,6 +140,7 @@ host: "" ## @field {VerticalPodAutoscalerAddon} verticalPodAutoscaler - Vertical Pod Autoscaler. ## @field {VeleroAddon} velero - Velero backup/restore addon. ## @field {CoreDNSAddon} coredns - CoreDNS addon. +## @field {NFSAddon} nfs - NFS CSI driver addon. ## @param {Addons} addons - Cluster addons configuration. addons: @@ -150,6 +172,8 @@ addons: valuesOverride: {} coredns: valuesOverride: {} + nfs: + valuesOverride: {} ## ## @section Kubernetes Control Plane Configuration diff --git a/packages/apps/nfs/.helmignore b/packages/apps/nfs/.helmignore new file mode 100644 index 00000000..481ff39c --- /dev/null +++ b/packages/apps/nfs/.helmignore @@ -0,0 +1,2 @@ +logos/ +hack/ diff --git a/packages/apps/nfs/Chart.yaml b/packages/apps/nfs/Chart.yaml new file mode 100644 index 00000000..3426d019 --- /dev/null +++ b/packages/apps/nfs/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: nfs +description: Managed NFS storage +icon: /logos/nfs.svg +type: application +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/apps/nfs/Makefile b/packages/apps/nfs/Makefile new file mode 100644 index 00000000..3df774a0 --- /dev/null +++ b/packages/apps/nfs/Makefile @@ -0,0 +1,6 @@ +include ../../../hack/common-envs.mk +include ../../../hack/package.mk + +generate: + cozyvalues-gen -v values.yaml -s values.schema.json -r README.md + ../../../hack/update-crd.sh diff --git a/packages/apps/nfs/README.md b/packages/apps/nfs/README.md new file mode 100644 index 00000000..07e46408 --- /dev/null +++ b/packages/apps/nfs/README.md @@ -0,0 +1,20 @@ +# Managed File Share Service + +NFS Ganesha based managed file sharing service that provides NFSv4 network storage accessible from multiple clients simultaneously. + +## Deployment Details + +This service deploys NFS Ganesha as a StatefulSet with persistent volume storage, exposing NFSv4 on port 2049. + +- Docs: +- GitHub: + +## Parameters + +### Common parameters + +| Name | Description | Type | Value | +| -------------- | ------------------------------------ | ---------- | ------------ | +| `storageClass` | StorageClass used to store the data. | `string` | `replicated` | +| `size` | Volume size for NFS storage. | `quantity` | `10Gi` | + diff --git a/packages/apps/nfs/charts/cozy-lib b/packages/apps/nfs/charts/cozy-lib new file mode 120000 index 00000000..e1813509 --- /dev/null +++ b/packages/apps/nfs/charts/cozy-lib @@ -0,0 +1 @@ +../../../library/cozy-lib \ No newline at end of file diff --git a/packages/apps/nfs/logos/nfs.svg b/packages/apps/nfs/logos/nfs.svg new file mode 100644 index 00000000..3d57d868 --- /dev/null +++ b/packages/apps/nfs/logos/nfs.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/apps/nfs/templates/_helpers.tpl b/packages/apps/nfs/templates/_helpers.tpl new file mode 100644 index 00000000..63d66461 --- /dev/null +++ b/packages/apps/nfs/templates/_helpers.tpl @@ -0,0 +1,49 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "nfs.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "nfs.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nfs.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nfs.labels" -}} +helm.sh/chart: {{ include "nfs.chart" . }} +{{ include "nfs.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nfs.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nfs.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/packages/apps/nfs/templates/dashboard-resourcemap.yaml b/packages/apps/nfs/templates/dashboard-resourcemap.yaml new file mode 100644 index 00000000..106af486 --- /dev/null +++ b/packages/apps/nfs/templates/dashboard-resourcemap.yaml @@ -0,0 +1,23 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-dashboard-resources +rules: +- apiGroups: + - "" + resources: + - persistentvolumeclaims + resourceNames: + - {{ .Release.Name }} + verbs: ["get", "list", "watch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Release.Name }}-dashboard-resources +subjects: +{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" .Release.Namespace) }} +roleRef: + kind: Role + name: {{ .Release.Name }}-dashboard-resources + apiGroup: rbac.authorization.k8s.io diff --git a/packages/apps/nfs/templates/helmrelease.yaml b/packages/apps/nfs/templates/helmrelease.yaml new file mode 100644 index 00000000..598f065f --- /dev/null +++ b/packages/apps/nfs/templates/helmrelease.yaml @@ -0,0 +1,22 @@ +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: {{ .Release.Name }}-system + labels: + sharding.fluxcd.io/key: tenants +spec: + chartRef: + kind: ExternalArtifact + name: cozystack-nfs-application-default-nfs-system + namespace: cozy-system + interval: 5m + timeout: 10m + install: + remediation: + retries: -1 + upgrade: + force: true + remediation: + retries: -1 + values: + pvcName: {{ .Release.Name }} diff --git a/packages/apps/nfs/templates/pvc.yaml b/packages/apps/nfs/templates/pvc.yaml new file mode 100644 index 00000000..edfa64dc --- /dev/null +++ b/packages/apps/nfs/templates/pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }} + labels: + {{- include "nfs.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteMany + {{- with .Values.storageClass }} + storageClassName: "{{ . }}" + {{- end }} + resources: + requests: + storage: {{ .Values.size }} diff --git a/packages/apps/nfs/templates/workloadmonitor.yaml b/packages/apps/nfs/templates/workloadmonitor.yaml new file mode 100644 index 00000000..2125a1b3 --- /dev/null +++ b/packages/apps/nfs/templates/workloadmonitor.yaml @@ -0,0 +1,12 @@ +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ $.Release.Name }} +spec: + replicas: 1 + minReplicas: 1 + kind: nfs + type: nfs + selector: + app.kubernetes.io/instance: {{ .Release.Name }} + version: {{ $.Chart.Version }} diff --git a/packages/apps/nfs/values.schema.json b/packages/apps/nfs/values.schema.json new file mode 100644 index 00000000..012171ef --- /dev/null +++ b/packages/apps/nfs/values.schema.json @@ -0,0 +1,25 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": { + "size": { + "description": "Volume size for NFS storage.", + "default": "10Gi", + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "storageClass": { + "description": "StorageClass used to store the data.", + "type": "string", + "default": "replicated" + } + } +} \ No newline at end of file diff --git a/packages/apps/nfs/values.yaml b/packages/apps/nfs/values.yaml new file mode 100644 index 00000000..d08d9331 --- /dev/null +++ b/packages/apps/nfs/values.yaml @@ -0,0 +1,9 @@ +## +## @section Common parameters +## + +## @param {string} storageClass - StorageClass used to store the data. +storageClass: "replicated" + +## @param {quantity} size - Volume size for NFS storage. +size: 10Gi diff --git a/packages/core/platform/images/migrations/migrations/25 b/packages/core/platform/images/migrations/migrations/25 index 655cce23..5085699f 100755 --- a/packages/core/platform/images/migrations/migrations/25 +++ b/packages/core/platform/images/migrations/migrations/25 @@ -12,6 +12,9 @@ kubectl delete deploy cozystack -n cozy-system --ignore-not-found # Delete old cozystack-assets statefulset kubectl delete sts cozystack-assets -n cozy-system --ignore-not-found +kubectl delete helmrepositories.source.toolkit.fluxcd.io -n cozy-public cozystack-apps cozystack-extra --ignore-not-found --wait=false +kubectl delete helmrepositories.source.toolkit.fluxcd.io -n cozy-system cozystack-system --ignore-not-found --wait=false + # Delete old bootbox HelmReleases (will be recreated by new platform chart if enabled) kubectl delete hr bootbox -n cozy-system --ignore-not-found kubectl delete hr bootbox-rd -n cozy-system --ignore-not-found diff --git a/packages/core/platform/sources/kubernetes-application.yaml b/packages/core/platform/sources/kubernetes-application.yaml index 9787cb19..a329f002 100644 --- a/packages/core/platform/sources/kubernetes-application.yaml +++ b/packages/core/platform/sources/kubernetes-application.yaml @@ -58,6 +58,8 @@ spec: path: system/prometheus-operator-crds - name: kubernetes-metrics-server path: system/metrics-server + - name: kubernetes-nfs-driver + path: system/nfs-driver - name: kubernetes path: apps/kubernetes libraries: ["cozy-lib"] diff --git a/packages/core/platform/sources/nfs-application.yaml b/packages/core/platform/sources/nfs-application.yaml new file mode 100644 index 00000000..c9dc8575 --- /dev/null +++ b/packages/core/platform/sources/nfs-application.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.nfs-application +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + libraries: + - name: cozy-lib + path: library/cozy-lib + components: + - name: nfs-system + path: system/nfs + - name: nfs + path: apps/nfs + libraries: ["cozy-lib"] + - name: nfs-rd + path: system/nfs-rd + install: + namespace: cozy-system + releaseName: nfs-rd diff --git a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml index 1b4c7361..7ac884ea 100644 --- a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml +++ b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml @@ -8,7 +8,7 @@ spec: singular: kubernetes plural: kuberneteses openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","ingressNginx","monitoringAgents","velero","verticalPodAutoscaler"],"properties":{"certManager":{"description":"Cert-manager addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable cert-manager.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"cilium":{"description":"Cilium CNI plugin.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"coredns":{"description":"CoreDNS addon.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"fluxcd":{"description":"FluxCD GitOps operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable FluxCD.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"gatewayAPI":{"description":"Gateway API addon.","type":"object","default":{},"required":["enabled"],"properties":{"enabled":{"description":"Enable Gateway API.","type":"boolean","default":false}}},"gpuOperator":{"description":"NVIDIA GPU Operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable GPU Operator.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"ingressNginx":{"description":"Ingress-NGINX controller.","type":"object","default":{},"required":["enabled","exposeMethod","valuesOverride"],"properties":{"enabled":{"description":"Enable the controller (requires nodes labeled `ingress-nginx`).","type":"boolean","default":false},"exposeMethod":{"description":"Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.","type":"string","default":"Proxied","enum":["Proxied","LoadBalancer"]},"hosts":{"description":"Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.","type":"array","default":[],"items":{"type":"string"}},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"monitoringAgents":{"description":"Monitoring agents.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable monitoring agents.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"velero":{"description":"Velero backup/restore addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable Velero.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"verticalPodAutoscaler":{"description":"Vertical Pod Autoscaler.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}}}},"controlPlane":{"description":"Kubernetes control-plane configuration.","type":"object","default":{},"required":["apiServer","controllerManager","konnectivity","replicas","scheduler"],"properties":{"apiServer":{"description":"API Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for API Server.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"large","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"controllerManager":{"description":"Controller Manager configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Controller Manager.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"konnectivity":{"description":"Konnectivity configuration.","type":"object","default":{},"required":["server"],"properties":{"server":{"description":"Konnectivity Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Konnectivity.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"replicas":{"description":"Number of control-plane replicas.","type":"integer","default":2},"scheduler":{"description":"Scheduler configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Scheduler.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"host":{"description":"External hostname for Kubernetes cluster. Defaults to `.` if empty.","type":"string","default":""},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","default":"20Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of GPU, such as \"nvidia.com/AD102GL_L40S\".","type":"string"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","type":"object","properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"roles":{"description":"List of node roles.","type":"array","items":{"type":"string"}}}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"version":{"description":"Kubernetes major.minor version to deploy","type":"string","default":"v1.33","enum":["v1.33","v1.32","v1.31","v1.30","v1.29","v1.28"]}}} + {"title":"Chart Values","type":"object","properties":{"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","ingressNginx","monitoringAgents","nfs","velero","verticalPodAutoscaler"],"properties":{"certManager":{"description":"Cert-manager addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable cert-manager.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"cilium":{"description":"Cilium CNI plugin.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"coredns":{"description":"CoreDNS addon.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"fluxcd":{"description":"FluxCD GitOps operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable FluxCD.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"gatewayAPI":{"description":"Gateway API addon.","type":"object","default":{},"required":["enabled"],"properties":{"enabled":{"description":"Enable Gateway API.","type":"boolean","default":false}}},"gpuOperator":{"description":"NVIDIA GPU Operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable GPU Operator.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"ingressNginx":{"description":"Ingress-NGINX controller.","type":"object","default":{},"required":["enabled","exposeMethod","valuesOverride"],"properties":{"enabled":{"description":"Enable the controller (requires nodes labeled `ingress-nginx`).","type":"boolean","default":false},"exposeMethod":{"description":"Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.","type":"string","default":"Proxied","enum":["Proxied","LoadBalancer"]},"hosts":{"description":"Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.","type":"array","default":[],"items":{"type":"string"}},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"monitoringAgents":{"description":"Monitoring agents.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable monitoring agents.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"nfs":{"description":"NFS CSI driver addon.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"velero":{"description":"Velero backup/restore addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable Velero.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"verticalPodAutoscaler":{"description":"Vertical Pod Autoscaler.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}}}},"controlPlane":{"description":"Kubernetes control-plane configuration.","type":"object","default":{},"required":["apiServer","controllerManager","konnectivity","replicas","scheduler"],"properties":{"apiServer":{"description":"API Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for API Server.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"large","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"controllerManager":{"description":"Controller Manager configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Controller Manager.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"konnectivity":{"description":"Konnectivity configuration.","type":"object","default":{},"required":["server"],"properties":{"server":{"description":"Konnectivity Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Konnectivity.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"replicas":{"description":"Number of control-plane replicas.","type":"integer","default":2},"scheduler":{"description":"Scheduler configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Scheduler.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"host":{"description":"External hostname for Kubernetes cluster. Defaults to `.` if empty.","type":"string","default":""},"nfs":{"description":"NFS configuration.","type":"object","default":{},"properties":{"instances":{"description":"List of NFS volume instances to connect. Each creates a StorageClass named `nfs-` in the tenant cluster.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of the NFS volume instance in the same namespace.","type":"string"}}}}}},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","default":"20Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of GPU, such as \"nvidia.com/AD102GL_L40S\".","type":"string"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","type":"object","properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"roles":{"description":"List of node roles.","type":"array","items":{"type":"string"}}}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"version":{"description":"Kubernetes major.minor version to deploy","type":"string","default":"v1.33","enum":["v1.33","v1.32","v1.31","v1.30","v1.29","v1.28"]}}} release: prefix: kubernetes- labels: @@ -26,7 +26,7 @@ spec: tags: - compute icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODFfMjg0NSkiLz4KPHBhdGggZD0iTTcxLjk5NjggMTlDNzAuMzAzOSAxOS4wMDAyIDY4LjkzMTIgMjAuNTMyMiA2OC45MzE0IDIyLjQyMjFDNjguOTMxNCAyMi40NTExIDY4LjkzNzMgMjIuNDc4OCA2OC45Mzc5IDIyLjUwNzZDNjguOTM1NCAyMi43NjQ0IDY4LjkyMzEgMjMuMDczNyA2OC45MzE0IDIzLjI5NzNDNjguOTcxNyAyNC4zODczIDY5LjIwODIgMjUuMjIxNiA2OS4zNTA2IDI2LjIyNThDNjkuNjA4NCAyOC4zNzUyIDY5LjgyNDUgMzAuMTU2OSA2OS42OTEyIDMxLjgxM0M2OS41NjE1IDMyLjQzNzUgNjkuMTAzNyAzMy4wMDg2IDY4LjY5NTYgMzMuNDA1Nkw2OC42MjM1IDM0LjcwODZDNjYuNzgzOSAzNC44NjE3IDY0LjkzMTkgMzUuMTQyMSA2My4wODIxIDM1LjU2NDFDNTUuMTIyNiAzNy4zNzk4IDQ4LjI2OTUgNDEuNDk5MSA0My4wNTIgNDcuMDYwOUM0Mi43MTM0IDQ2LjgyODggNDIuMTIxMSA0Ni40MDE5IDQxLjk0NSA0Ni4yNzEyQzQxLjM5NzcgNDYuMzQ1NCA0MC44NDQ1IDQ2LjUxNTEgNDAuMTI0MSA0Ni4wOTM1QzM4Ljc1MjIgNDUuMTY1NyAzNy41MDI4IDQzLjg4NTEgMzUuOTkxIDQyLjM0MjRDMzUuMjk4MiA0MS42MDQ0IDM0Ljc5NjYgNDAuOTAxOCAzMy45NzM1IDQwLjE5MDRDMzMuNzg2NiA0MC4wMjg5IDMzLjUwMTQgMzkuODEwNCAzMy4yOTIzIDM5LjY0NDJDMzIuNjQ4OSAzOS4xMjg4IDMxLjg5IDM4Ljg2IDMxLjE1NyAzOC44MzQ4QzMwLjIxNDcgMzguODAyNCAyOS4zMDc1IDM5LjE3MjUgMjguNzEzOCAzOS45MjA2QzI3LjY1ODQgNDEuMjUwNiAyNy45OTYzIDQzLjI4MzMgMjkuNDY3MSA0NC40NjE0QzI5LjQ4MiA0NC40NzM0IDI5LjQ5NzkgNDQuNDgyNyAyOS41MTI5IDQ0LjQ5NDNDMjkuNzE1IDQ0LjY1ODkgMjkuOTYyNSA0NC44Njk4IDMwLjE0ODMgNDUuMDA3NkMzMS4wMjE3IDQ1LjY1NTUgMzEuODE5NSA0NS45ODcyIDMyLjY4OTcgNDYuNTAxNUMzNC41MjMxIDQ3LjYzOTEgMzYuMDQzIDQ4LjU4MjMgMzcuMjQ4NiA0OS43MTk2QzM3LjcxOTQgNTAuMjIzNyAzNy44MDE2IDUxLjExMjIgMzcuODY0MyA1MS40OTY0TDM4Ljg0NjggNTIuMzc4MkMzMy41ODcyIDYwLjMzMDggMzEuMTUzIDcwLjE1MzkgMzIuNTkxNSA4MC4xNjI3TDMxLjMwNzcgODAuNTM3OEMzMC45NjkzIDgwLjk3NjggMzAuNDkxMiA4MS42Njc2IDI5Ljk5MTEgODEuODczOEMyOC40MTM4IDgyLjM3MjkgMjYuNjM4NyA4Mi41NTYyIDI0LjQ5NTYgODIuNzgxOUMyMy40ODk0IDgyLjg2NiAyMi42MjEzIDgyLjgxNTggMjEuNTU0NiA4My4wMTg4QzIxLjMxOTggODMuMDYzNSAyMC45OTI3IDgzLjE0OTEgMjAuNzM1OCA4My4yMDk3QzIwLjcyNjkgODMuMjExNiAyMC43MTg2IDgzLjIxNDIgMjAuNzA5NiA4My4yMTYyQzIwLjY5NTYgODMuMjE5NSAyMC42NzcyIDgzLjIyNjMgMjAuNjYzOCA4My4yMjk0QzE4Ljg1NyA4My42NjggMTcuNjk2MyA4NS4zMzY1IDE4LjA2OTkgODYuOTgwNUMxOC40NDM3IDg4LjYyNDggMjAuMjA4NiA4OS42MjQ4IDIyLjAyNjIgODkuMjMxMkMyMi4wMzkzIDg5LjIyODIgMjIuMDU4NCA4OS4yMjc3IDIyLjA3MiA4OS4yMjQ2QzIyLjA5MjYgODkuMjE5OSAyMi4xMTA2IDg5LjIwOTkgMjIuMTMxIDg5LjIwNDlDMjIuMzg0NCA4OS4xNDkgMjIuNzAxOSA4OS4wODY4IDIyLjkyMzYgODkuMDI3MkMyMy45NzIzIDg4Ljc0NTEgMjQuNzMxOCA4OC4zMzA2IDI1LjY3NDYgODcuOTY3N0MyNy43MDI5IDg3LjIzNjggMjkuMzgyOCA4Ni42MjYyIDMxLjAxOTUgODYuMzg4M0MzMS43MDMgODYuMzM0NSAzMi40MjMyIDg2LjgxMiAzMi43ODE0IDg3LjAxMzRMMzQuMTE3NyA4Ni43ODMxQzM3LjE5MjYgOTYuMzYxMyA0My42MzY2IDEwNC4xMDMgNTEuNzk2MyAxMDguOTYxTDUxLjIzOTYgMTEwLjMwM0M1MS40NDAzIDExMC44MjQgNTEuNjYxNiAxMTEuNTMgNTEuNTEyMSAxMTIuMDQ1QzUwLjkxNzEgMTEzLjU5NSA0OS44OTggMTE1LjIzMSA0OC43Mzc0IDExNy4wNTVDNDguMTc1NSAxMTcuODk4IDQ3LjYwMDQgMTE4LjU1MiA0Ny4wOTM0IDExOS41MTZDNDYuOTcyIDExOS43NDcgNDYuODE3NSAxMjAuMTAyIDQ2LjcwMDQgMTIwLjM0NkM0NS45MTI1IDEyMi4wMzkgNDYuNDkwNCAxMjMuOTkgNDguMDAzOCAxMjQuNzIyQzQ5LjUyNjggMTI1LjQ1OSA1MS40MTcxIDEyNC42ODIgNTIuMjM1MiAxMjIuOTg1QzUyLjIzNjQgMTIyLjk4MiA1Mi4yNDA2IDEyMi45OCA1Mi4yNDE3IDEyMi45NzhDNTIuMjQyNiAxMjIuOTc2IDUyLjI0MDkgMTIyLjk3MyA1Mi4yNDE3IDEyMi45NzFDNTIuMzU4MiAxMjIuNzMxIDUyLjUyMzMgMTIyLjQxNSA1Mi42MjE2IDEyMi4xODhDNTMuMDU2IDEyMS4xODkgNTMuMjAwNSAxMjAuMzMyIDUzLjUwNTkgMTE5LjM2NUM1NC4zMTcgMTE3LjMxOCA1NC43NjI2IDExNS4xNyA1NS44NzkxIDExMy44MzJDNTYuMTg0OSAxMTMuNDY2IDU2LjY4MzMgMTEzLjMyNSA1Ny4yMDAxIDExMy4xODZMNTcuODk0NCAxMTEuOTIyQzY1LjAwOCAxMTQuNjY1IDcyLjk3MDUgMTE1LjQwMiA4MC45MjQ1IDExMy41ODdDODIuNzM5MSAxMTMuMTczIDg0LjQ5MDggMTEyLjYzNyA4Ni4xODQzIDExMS45OTRDODYuMzc5NCAxMTIuMzQyIDg2Ljc0MiAxMTMuMDExIDg2LjgzOTMgMTEzLjE3OUM4Ny4zNjQ0IDExMy4zNTEgODcuOTM3NyAxMTMuNDM5IDg4LjQwNDcgMTE0LjEzM0M4OS4yNDAxIDExNS41NjcgODkuODExNCAxMTcuMjYzIDkwLjUwNzMgMTE5LjMxMkM5MC44MTI4IDEyMC4yNzkgOTAuOTYzOCAxMjEuMTM2IDkxLjM5ODEgMTIyLjEzNkM5MS40OTcxIDEyMi4zNjMgOTEuNjYxNCAxMjIuNjg0IDkxLjc3OCAxMjIuOTI1QzkyLjU5NDQgMTI0LjYyOCA5NC40OTA3IDEyNS40MDcgOTYuMDE1OSAxMjQuNjY5Qzk3LjUyOTIgMTIzLjkzNyA5OC4xMDc3IDEyMS45ODYgOTcuMzE5NCAxMjAuMjkzQzk3LjIwMjMgMTIwLjA0OSA5Ny4wNDEyIDExOS42OTUgOTYuOTE5OCAxMTkuNDY0Qzk2LjQxMjcgMTE4LjQ5OSA5NS44Mzc3IDExNy44NTIgOTUuMjc1OCAxMTcuMDA5Qzk0LjExNTIgMTE1LjE4NSA5My4xNTI2IDExMy42NyA5Mi41NTc1IDExMi4xMkM5Mi4zMDg3IDExMS4zMiA5Mi41OTk1IDExMC44MjMgOTIuNzkzMyAxMTAuMzAzQzkyLjY3NzIgMTEwLjE3IDkyLjQyODggMTA5LjQxNCA5Mi4yODI0IDEwOS4wNTlDMTAwLjc2MiAxMDQuMDI5IDEwNy4wMTcgOTUuOTk4NSAxMDkuOTU1IDg2LjcyMzlDMTEwLjM1MSA4Ni43ODY1IDExMS4wNDEgODYuOTA5MSAxMTEuMjY1IDg2Ljk1NDJDMTExLjcyNiA4Ni42NDg3IDExMi4xNDkgODYuMjUwMSAxMTIuOTgxIDg2LjMxNTlDMTE0LjYxNyA4Ni41NTM3IDExNi4yOTcgODcuMTY0NSAxMTguMzI2IDg3Ljg5NTNDMTE5LjI2OCA4OC4yNTgxIDEyMC4wMjggODguNjc5MyAxMjEuMDc3IDg4Ljk2MTRDMTIxLjI5OCA4OS4wMjEgMTIxLjYxNiA4OS4wNzY2IDEyMS44NjkgODkuMTMyNUMxMjEuODg5IDg5LjEzNzUgMTIxLjkwOCA4OS4xNDc1IDEyMS45MjggODkuMTUyMkMxMjEuOTQyIDg5LjE1NTMgMTIxLjk2MSA4OS4xNTU4IDEyMS45NzQgODkuMTU4OEMxMjMuNzkyIDg5LjU1MiAxMjUuNTU3IDg4LjU1MjYgMTI1LjkzIDg2LjkwODFDMTI2LjMwMyA4NS4yNjQxIDEyNS4xNDMgODMuNTk1MiAxMjMuMzM2IDgzLjE1N0MxMjMuMDc0IDgzLjA5NyAxMjIuNzAxIDgyLjk5NSAxMjIuNDQ2IDgyLjk0NjVDMTIxLjM3OSA4Mi43NDM1IDEyMC41MTEgODIuNzkzNSAxMTkuNTA1IDgyLjcwOTVDMTE3LjM2MSA4Mi40ODM5IDExNS41ODYgODIuMzAwNCAxMTQuMDA5IDgxLjgwMTRDMTEzLjM2NiA4MS41NTA3IDExMi45MDggODAuNzgxOSAxMTIuNjg2IDgwLjQ2NTVMMTExLjQ0OCA4MC4xMDM1QzExMi4wOSA3NS40MzggMTExLjkxNyA3MC41ODI1IDExMC44MDYgNjUuNzI0M0MxMDkuNjg1IDYwLjgyMDggMTA3LjcwNCA1Ni4zMzYxIDEwNS4wNjIgNTIuMzg0OEMxMDUuMzc5IDUyLjA5NDggMTA1Ljk3OSA1MS41NjEyIDEwNi4xNDkgNTEuNDA0M0MxMDYuMTk5IDUwLjg1MTcgMTA2LjE1NiA1MC4yNzIyIDEwNi43MjUgNDkuNjYwM0MxMDcuOTMxIDQ4LjUyMyAxMDkuNDUxIDQ3LjU3OTkgMTExLjI4NCA0Ni40NDIzQzExMi4xNTQgNDUuOTI3OSAxMTIuOTU5IDQ1LjU5NjQgMTEzLjgzMiA0NC45NDg0QzExNC4wMyA0NC44MDE5IDExNC4yOTkgNDQuNTY5OSAxMTQuNTA3IDQ0LjQwMjJDMTE1Ljk3NyA0My4yMjM3IDExNi4zMTYgNDEuMTkxMSAxMTUuMjYgMzkuODYxNEMxMTQuMjA0IDM4LjUzMTcgMTEyLjE1OSAzOC40MDY1IDExMC42ODggMzkuNTg1QzExMC40NzkgMzkuNzUxNiAxMTAuMTk1IDM5Ljk2ODggMTEwLjAwNyA0MC4xMzEyQzEwOS4xODQgNDAuODQyNiAxMDguNjc2IDQxLjU0NTIgMTA3Ljk4MyA0Mi4yODMyQzEwNi40NzEgNDMuODI1OSAxMDUuMjIyIDQ1LjExMyAxMDMuODUgNDYuMDQwOUMxMDMuMjU1IDQ2LjM4ODUgMTAyLjM4NSA0Ni4yNjgyIDEwMS45OSA0Ni4yNDQ5TDEwMC44MjQgNDcuMDgwNkM5NC4xNzUzIDQwLjA3NjMgODUuMTIzNSAzNS41OTgyIDc1LjM3NjYgMzQuNzI4M0M3NS4zNDk0IDM0LjMxNzkgNzUuMzEzNyAzMy41NzYxIDc1LjMwNDYgMzMuMzUyOUM3NC45MDU2IDMyLjk2OTMgNzQuNDIzNSAzMi42NDE4IDc0LjMwMjQgMzEuODEzQzc0LjE2OTEgMzAuMTU2OSA3NC4zOTE3IDI4LjM3NTIgNzQuNjQ5NiAyNi4yMjU4Qzc0Ljc5MTkgMjUuMjIxNiA3NS4wMjg0IDI0LjM4NzMgNzUuMDY4OCAyMy4yOTczQzc1LjA3OCAyMy4wNDk1IDc1LjA2MzIgMjIuNjkgNzUuMDYyMiAyMi40MjIxQzc1LjA2MiAyMC41MzIyIDczLjY4OTggMTguOTk5OCA3MS45OTY4IDE5Wk02OC4xNTg1IDQyLjg4ODZMNjcuMjQ4IDU5LjA0NDdMNjcuMTgyNSA1OS4wNzc2QzY3LjEyMTQgNjAuNTIyOSA2NS45Mzc1IDYxLjY3NyA2NC40ODM5IDYxLjY3N0M2My44ODg0IDYxLjY3NyA2My4zMzg4IDYxLjQ4NDkgNjIuODkyMiA2MS4xNTcxTDYyLjg2NiA2MS4xNzAzTDQ5LjY4MDcgNTEuNzc5NEM1My43MzMxIDQ3Ljc3NTkgNTguOTE2NCA0NC44MTcyIDY0Ljg5IDQzLjQ1NDZDNjUuOTgxMiA0My4yMDU2IDY3LjA3MTkgNDMuMDIwOSA2OC4xNTg1IDQyLjg4ODZaTTc1Ljg0MTcgNDIuODg4NkM4Mi44MTU5IDQzLjc1MDQgODkuMjY1NyA0Ni45MjMyIDk0LjIwODEgNTEuNzg2TDgxLjEwOCA2MS4xMTc2TDgxLjA2MjEgNjEuMDk3OUM3OS44OTk0IDYxLjk1MTIgNzguMjYxMSA2MS43Mzk0IDc3LjM1NDggNjAuNTk3OEM3Ni45ODM1IDYwLjEzMDEgNzYuNzg4NyA1OS41ODAxIDc2Ljc2NTMgNTkuMDI0OUw3Ni43NTIyIDU5LjAxODRMNzUuODQxNyA0Mi44ODg2Wk00NC44OTkxIDU3LjgxNEw1Ni45MzgyIDY4LjYzM0w1Ni45MjUxIDY4LjY5ODhDNTguMDExNyA2OS42NDc5IDU4LjE3MiA3MS4yOTQ5IDU3LjI2NTcgNzIuNDM2OEM1Ni44OTQ0IDcyLjkwNDUgNTYuMzk3NSA3My4yMTgyIDU1Ljg2MzkgNzMuMzY0N0w1NS44NTA4IDczLjQxNzNMNDAuNDE4OCA3Ny44OTIzQzM5LjYzMzQgNzAuNjc2NSA0MS4zMjYxIDYzLjY2MjEgNDQuODk5MSA1Ny44MTRaTTk5LjAwOTQgNTcuODIwNkMxMDAuNzk4IDYwLjczMzYgMTAyLjE1MyA2My45ODcxIDEwMi45NTkgNjcuNTE0M0MxMDMuNzU2IDcwLjk5OTEgMTAzLjk1NiA3NC40Nzc4IDEwMy42MjcgNzcuODM5N0w4OC4xMTY2IDczLjM1MTVMODguMTAzNSA3My4yODU3Qzg2LjcxNDUgNzIuOTA0MyA4NS44NjA5IDcxLjQ4NDggODYuMTg0MyA3MC4wNjExQzg2LjMxNjggNjkuNDc3OCA4Ni42MjQ5IDY4Ljk4NDQgODcuMDQyMyA2OC42MTk4TDg3LjAzNTggNjguNTg2OUw5OS4wMDk0IDU3LjgyMDZaTTY5LjUyNzQgNjkuNDY4OEg3NC40NTk2TDc3LjUyNTEgNzMuMzE4Nkw3Ni40MjQ3IDc4LjEyMjZMNzEuOTk2OCA4MC4yNjE0TDY3LjU1NTggNzguMTE2MUw2Ni40NTU0IDczLjMxMkw2OS41Mjc0IDY5LjQ2ODhaTTg1LjMzOTMgODIuNjQzN0M4NS41NDg5IDgyLjYzMzEgODUuNzU3NiA4Mi42NTIgODUuOTYxNiA4Mi42ODk4TDg1Ljk4NzggODIuNjU2OUwxMDEuOTUgODUuMzY4MkM5OS42MTQyIDkxLjk2MjQgOTUuMTQ0IDk3LjY3NSA4OS4xNzExIDEwMS40OThMODIuOTc0NyA4Ni40NjA2TDgyLjk5NDQgODYuNDM0M0M4Mi40MjUyIDg1LjEwNTUgODIuOTk0OCA4My41NDcyIDg0LjMwNDQgODIuOTEzNUM4NC42Mzk3IDgyLjc1MTMgODQuOTkgODIuNjYxNCA4NS4zMzkzIDgyLjY0MzdaTTU4LjUyOTggODIuNzA5NUM1OS43NDggODIuNzI2NyA2MC44NDA2IDgzLjU3NjEgNjEuMTIzNyA4NC44MjJDNjEuMjU2MiA4NS40MDUyIDYxLjE5MTcgODUuOTgzMSA2MC45NzMgODYuNDkzNUw2MS4wMTg5IDg2LjU1MjdMNTQuODg4IDEwMS40MzlDNDkuMTU1OSA5Ny43NDMyIDQ0LjU5MDQgOTIuMjA5OSA0Mi4xNDgxIDg1LjQyMDhMNTcuOTczMSA4Mi43MjI3TDU3Ljk5OTMgODIuNzU1NkM1OC4xNzYzIDgyLjcyMjkgNTguMzU1OCA4Mi43MDcxIDU4LjUyOTggODIuNzA5NVpNNzEuODk4NiA4OS4yMzEyQzcyLjMyMjkgODkuMjE1NSA3Mi43NTM0IDg5LjMwMyA3My4xNjI3IDg5LjUwMUM3My42OTkyIDg5Ljc2MDYgNzQuMTEzNiA5MC4xNjkyIDc0LjM3NDUgOTAuNjU5Mkg3NC40MzM0TDgyLjIzNDYgMTA0LjgyMUM4MS4yMjIxIDEwNS4xNjIgODAuMTgxMyAxMDUuNDU0IDc5LjExNjcgMTA1LjY5N0M3My4xNTA1IDEwNy4wNTggNjcuMjAzMiAxMDYuNjQ1IDYxLjgxOCAxMDQuODAyTDY5LjU5OTUgOTAuNjY1OEg2OS42MTI2QzcwLjA3OTUgODkuNzg4OCA3MC45NjUgODkuMjY1NiA3MS44OTg2IDg5LjIzMTJaIiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIwLjI1Ii8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjgxXzI4NDUiIHgxPSIxMCIgeTE9IjE1LjUiIHgyPSIxNDQiIHkyPSIxMzEuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjNEQ4N0ZGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzA1NDdEMCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo= - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "nodeGroups"], ["spec", "nodeGroups", "md0"], ["spec", "nodeGroups", "md0", "minReplicas"], ["spec", "nodeGroups", "md0", "maxReplicas"], ["spec", "nodeGroups", "md0", "instanceType"], ["spec", "nodeGroups", "md0", "ephemeralStorage"], ["spec", "nodeGroups", "md0", "roles"], ["spec", "nodeGroups", "md0", "resources"], ["spec", "nodeGroups", "md0", "gpus"], ["spec", "version"], ["spec", "host"], ["spec", "addons"], ["spec", "addons", "certManager"], ["spec", "addons", "certManager", "enabled"], ["spec", "addons", "certManager", "valuesOverride"], ["spec", "addons", "cilium"], ["spec", "addons", "cilium", "valuesOverride"], ["spec", "addons", "gatewayAPI"], ["spec", "addons", "gatewayAPI", "enabled"], ["spec", "addons", "ingressNginx"], ["spec", "addons", "ingressNginx", "enabled"], ["spec", "addons", "ingressNginx", "exposeMethod"], ["spec", "addons", "ingressNginx", "hosts"], ["spec", "addons", "ingressNginx", "valuesOverride"], ["spec", "addons", "gpuOperator"], ["spec", "addons", "gpuOperator", "enabled"], ["spec", "addons", "gpuOperator", "valuesOverride"], ["spec", "addons", "fluxcd"], ["spec", "addons", "fluxcd", "enabled"], ["spec", "addons", "fluxcd", "valuesOverride"], ["spec", "addons", "monitoringAgents"], ["spec", "addons", "monitoringAgents", "enabled"], ["spec", "addons", "monitoringAgents", "valuesOverride"], ["spec", "addons", "verticalPodAutoscaler"], ["spec", "addons", "verticalPodAutoscaler", "valuesOverride"], ["spec", "addons", "velero"], ["spec", "addons", "velero", "enabled"], ["spec", "addons", "velero", "valuesOverride"], ["spec", "addons", "coredns"], ["spec", "addons", "coredns", "valuesOverride"], ["spec", "controlPlane"], ["spec", "controlPlane", "replicas"], ["spec", "controlPlane", "apiServer"], ["spec", "controlPlane", "apiServer", "resources"], ["spec", "controlPlane", "apiServer", "resourcesPreset"], ["spec", "controlPlane", "controllerManager"], ["spec", "controlPlane", "controllerManager", "resources"], ["spec", "controlPlane", "controllerManager", "resourcesPreset"], ["spec", "controlPlane", "scheduler"], ["spec", "controlPlane", "scheduler", "resources"], ["spec", "controlPlane", "scheduler", "resourcesPreset"], ["spec", "controlPlane", "konnectivity"], ["spec", "controlPlane", "konnectivity", "server"], ["spec", "controlPlane", "konnectivity", "server", "resources"], ["spec", "controlPlane", "konnectivity", "server", "resourcesPreset"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "nodeGroups"], ["spec", "nodeGroups", "md0"], ["spec", "nodeGroups", "md0", "minReplicas"], ["spec", "nodeGroups", "md0", "maxReplicas"], ["spec", "nodeGroups", "md0", "instanceType"], ["spec", "nodeGroups", "md0", "ephemeralStorage"], ["spec", "nodeGroups", "md0", "roles"], ["spec", "nodeGroups", "md0", "resources"], ["spec", "nodeGroups", "md0", "gpus"], ["spec", "version"], ["spec", "host"], ["spec", "nfs"], ["spec", "nfs", "instances"], ["spec", "addons"], ["spec", "addons", "certManager"], ["spec", "addons", "certManager", "enabled"], ["spec", "addons", "certManager", "valuesOverride"], ["spec", "addons", "cilium"], ["spec", "addons", "cilium", "valuesOverride"], ["spec", "addons", "gatewayAPI"], ["spec", "addons", "gatewayAPI", "enabled"], ["spec", "addons", "ingressNginx"], ["spec", "addons", "ingressNginx", "enabled"], ["spec", "addons", "ingressNginx", "exposeMethod"], ["spec", "addons", "ingressNginx", "hosts"], ["spec", "addons", "ingressNginx", "valuesOverride"], ["spec", "addons", "gpuOperator"], ["spec", "addons", "gpuOperator", "enabled"], ["spec", "addons", "gpuOperator", "valuesOverride"], ["spec", "addons", "fluxcd"], ["spec", "addons", "fluxcd", "enabled"], ["spec", "addons", "fluxcd", "valuesOverride"], ["spec", "addons", "monitoringAgents"], ["spec", "addons", "monitoringAgents", "enabled"], ["spec", "addons", "monitoringAgents", "valuesOverride"], ["spec", "addons", "verticalPodAutoscaler"], ["spec", "addons", "verticalPodAutoscaler", "valuesOverride"], ["spec", "addons", "velero"], ["spec", "addons", "velero", "enabled"], ["spec", "addons", "velero", "valuesOverride"], ["spec", "addons", "coredns"], ["spec", "addons", "coredns", "valuesOverride"], ["spec", "addons", "nfs"], ["spec", "addons", "nfs", "valuesOverride"], ["spec", "controlPlane"], ["spec", "controlPlane", "replicas"], ["spec", "controlPlane", "apiServer"], ["spec", "controlPlane", "apiServer", "resources"], ["spec", "controlPlane", "apiServer", "resourcesPreset"], ["spec", "controlPlane", "controllerManager"], ["spec", "controlPlane", "controllerManager", "resources"], ["spec", "controlPlane", "controllerManager", "resourcesPreset"], ["spec", "controlPlane", "scheduler"], ["spec", "controlPlane", "scheduler", "resources"], ["spec", "controlPlane", "scheduler", "resourcesPreset"], ["spec", "controlPlane", "konnectivity"], ["spec", "controlPlane", "konnectivity", "server"], ["spec", "controlPlane", "konnectivity", "server", "resources"], ["spec", "controlPlane", "konnectivity", "server", "resourcesPreset"]] secrets: exclude: [] include: diff --git a/packages/system/nfs-driver/Makefile b/packages/system/nfs-driver/Makefile index 2f14aa28..6cf799f5 100644 --- a/packages/system/nfs-driver/Makefile +++ b/packages/system/nfs-driver/Makefile @@ -4,6 +4,9 @@ export NAMESPACE=cozy-$(NAME) include ../../../hack/common-envs.mk include ../../../hack/package.mk +test: + helm unittest . + update: rm -rf charts helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts diff --git a/packages/system/nfs-driver/templates/extra-storageclasses.yaml b/packages/system/nfs-driver/templates/extra-storageclasses.yaml new file mode 100644 index 00000000..f627a474 --- /dev/null +++ b/packages/system/nfs-driver/templates/extra-storageclasses.yaml @@ -0,0 +1,18 @@ +{{- range .Values.extraStorageClasses }} +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: {{ .name }} +provisioner: nfs.csi.k8s.io +parameters: + server: {{ .server | quote }} + share: {{ .share | quote }} +reclaimPolicy: Delete +volumeBindingMode: Immediate +allowVolumeExpansion: true +{{- with .mountOptions }} +mountOptions: +{{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} diff --git a/packages/system/nfs-driver/tests/extra-storageclasses_test.yaml b/packages/system/nfs-driver/tests/extra-storageclasses_test.yaml new file mode 100644 index 00000000..6009c761 --- /dev/null +++ b/packages/system/nfs-driver/tests/extra-storageclasses_test.yaml @@ -0,0 +1,133 @@ +suite: Extra StorageClasses tests + +templates: + - templates/extra-storageclasses.yaml + +tests: + - it: renders nothing when extraStorageClasses is empty + asserts: + - hasDocuments: + count: 0 + + - it: creates one StorageClass per entry + set: + extraStorageClasses: + - name: nfs-data + server: "10.96.1.1" + share: "/" + mountOptions: + - nfsvers=4.1 + - name: nfs-backup + server: "10.96.2.2" + share: "/" + mountOptions: + - nfsvers=4.1 + asserts: + - hasDocuments: + count: 2 + + - it: sets correct provisioner + set: + extraStorageClasses: + - name: nfs-test + server: "10.96.1.1" + share: "/" + mountOptions: + - nfsvers=4.1 + asserts: + - equal: + path: provisioner + value: nfs.csi.k8s.io + + - it: sets server and share parameters + set: + extraStorageClasses: + - name: nfs-mydata + server: "10.96.50.100" + share: "/exports" + mountOptions: + - nfsvers=4.1 + asserts: + - equal: + path: metadata.name + value: nfs-mydata + - equal: + path: parameters.server + value: "10.96.50.100" + - equal: + path: parameters.share + value: "/exports" + + - it: sets mount options + set: + extraStorageClasses: + - name: nfs-test + server: "10.96.1.1" + share: "/" + mountOptions: + - nfsvers=4.1 + asserts: + - equal: + path: mountOptions[0] + value: nfsvers=4.1 + + - it: allows volume expansion + set: + extraStorageClasses: + - name: nfs-test + server: "10.96.1.1" + share: "/" + mountOptions: + - nfsvers=4.1 + asserts: + - equal: + path: allowVolumeExpansion + value: true + + - it: uses Delete reclaim policy + set: + extraStorageClasses: + - name: nfs-test + server: "10.96.1.1" + share: "/" + mountOptions: + - nfsvers=4.1 + asserts: + - equal: + path: reclaimPolicy + value: Delete + + - it: creates distinct StorageClasses for multiple shares + set: + extraStorageClasses: + - name: nfs-alpha + server: "10.96.10.1" + share: "/" + mountOptions: + - nfsvers=4.1 + - name: nfs-beta + server: "10.96.20.2" + share: "/data" + mountOptions: + - nfsvers=4.1 + asserts: + - equal: + path: metadata.name + value: nfs-alpha + documentIndex: 0 + - equal: + path: parameters.server + value: "10.96.10.1" + documentIndex: 0 + - equal: + path: metadata.name + value: nfs-beta + documentIndex: 1 + - equal: + path: parameters.server + value: "10.96.20.2" + documentIndex: 1 + - equal: + path: parameters.share + value: "/data" + documentIndex: 1 diff --git a/packages/system/nfs-driver/values.yaml b/packages/system/nfs-driver/values.yaml index 856e67be..eb1f99f0 100644 --- a/packages/system/nfs-driver/values.yaml +++ b/packages/system/nfs-driver/values.yaml @@ -1 +1,3 @@ csi-driver-nfs: {} + +extraStorageClasses: [] diff --git a/packages/system/nfs-rd/Chart.yaml b/packages/system/nfs-rd/Chart.yaml new file mode 100644 index 00000000..20c4e272 --- /dev/null +++ b/packages/system/nfs-rd/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: nfs-rd +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/nfs-rd/Makefile b/packages/system/nfs-rd/Makefile new file mode 100644 index 00000000..058bec71 --- /dev/null +++ b/packages/system/nfs-rd/Makefile @@ -0,0 +1,4 @@ +export NAME=nfs-rd +export NAMESPACE=cozy-system + +include ../../../hack/package.mk diff --git a/packages/system/nfs-rd/cozyrds/nfs.yaml b/packages/system/nfs-rd/cozyrds/nfs.yaml new file mode 100644 index 00000000..125401e6 --- /dev/null +++ b/packages/system/nfs-rd/cozyrds/nfs.yaml @@ -0,0 +1,21 @@ +apiVersion: cozystack.io/v1alpha1 +kind: ApplicationDefinition +metadata: + name: nfs +spec: + application: + kind: NFS + plural: nfs + openAPISchema: |- + {"title":"Chart Values","type":"object","properties":{"size":{"description":"Volume size for NFS storage.","default":"10Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}} + release: + prefix: nfs- + chartRef: + kind: ExternalArtifact + name: cozystack-nfs-application-default-nfs + namespace: cozy-system + dashboard: + description: Managed NFS storage + plural: NFS + icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcikiLz4KPHBhdGggZD0iTTM2IDU4VjEwNEMzNiAxMDcuMzE0IDM4LjY4NjMgMTEwIDQyIDExMEgxMDJDMTA1LjMxNCAxMTAgMTA4IDEwNy4zMTQgMTA4IDEwNFY1MkMxMDggNDguNjg2MyAxMDUuMzE0IDQ2IDEwMiA0Nkg3MEw2MiAzNEg0MkMzOC42ODYzIDM0IDM2IDM2LjY4NjMgMzYgNDBWNThaIiBmaWxsPSIjMTU2NUMwIi8+CjxwYXRoIGQ9Ik0zMCA2MkMzMCA1OC42ODYzIDMyLjY4NjMgNTYgMzYgNTZIMTA4QzExMS4zMTQgNTYgMTE0IDU4LjY4NjMgMTE0IDYyVjEwNEMxMTQgMTA3LjMxNCAxMTEuMzE0IDExMCAxMDggMTEwSDM2QzMyLjY4NjMgMTEwIDMwIDEwNy4zMTQgMzAgMTA0VjYyWiIgZmlsbD0iIzQyQTVGNSIvPgo8cGF0aCBkPSJNNjMgNzZWOTRNNjMgNzZMNTQgODVNNjMgNzZMNzIgODUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik04MSA5NFY3Nk04MSA5NEw5MCA4NU04MSA5NEw3MiA4NSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhciIgeDE9IjAiIHkxPSIwIiB4Mj0iMTQ0IiB5Mj0iMTQ0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFM0YyRkQiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNjRCNUY2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg== + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "size"]] diff --git a/packages/system/nfs-rd/templates/cozyrd.yaml b/packages/system/nfs-rd/templates/cozyrd.yaml new file mode 100644 index 00000000..e079ddcf --- /dev/null +++ b/packages/system/nfs-rd/templates/cozyrd.yaml @@ -0,0 +1,4 @@ +{{- range $path, $_ := .Files.Glob "cozyrds/*" }} +--- +{{ $.Files.Get $path }} +{{- end }} diff --git a/packages/system/nfs-rd/values.yaml b/packages/system/nfs-rd/values.yaml new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/packages/system/nfs-rd/values.yaml @@ -0,0 +1 @@ +{} diff --git a/packages/system/nfs/.helmignore b/packages/system/nfs/.helmignore new file mode 100644 index 00000000..481ff39c --- /dev/null +++ b/packages/system/nfs/.helmignore @@ -0,0 +1,2 @@ +logos/ +hack/ diff --git a/packages/system/nfs/Chart.yaml b/packages/system/nfs/Chart.yaml new file mode 100644 index 00000000..fed4e8ae --- /dev/null +++ b/packages/system/nfs/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-nfs +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/nfs/Makefile b/packages/system/nfs/Makefile new file mode 100644 index 00000000..194a6fa4 --- /dev/null +++ b/packages/system/nfs/Makefile @@ -0,0 +1,3 @@ +export NAME=nfs-system + +include ../../../hack/package.mk diff --git a/packages/system/nfs/templates/cilium-policy.yaml b/packages/system/nfs/templates/cilium-policy.yaml new file mode 100644 index 00000000..93008d07 --- /dev/null +++ b/packages/system/nfs/templates/cilium-policy.yaml @@ -0,0 +1,20 @@ +{{- $pvc := lookup "v1" "PersistentVolumeClaim" .Release.Namespace .Values.pvcName }} +{{- $pv := lookup "v1" "PersistentVolume" "" $pvc.spec.volumeName }} +{{- $nfsExport := index $pv.spec.csi.volumeAttributes "linstor.csi.linbit.com/nfs-export" }} +{{- $parsed := urlParse $nfsExport }} +{{- $port := last (splitList ":" $parsed.host) }} +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: allow-nfs-{{ .Values.pvcName }} +spec: + endpointSelector: {} + egress: + - toEndpoints: + - matchLabels: + k8s:app.kubernetes.io/component: linstor-csi-nfs-server + k8s:io.kubernetes.pod.namespace: cozy-linstor + toPorts: + - ports: + - port: "{{ $port }}" + protocol: TCP diff --git a/packages/system/nfs/values.yaml b/packages/system/nfs/values.yaml new file mode 100644 index 00000000..b405be18 --- /dev/null +++ b/packages/system/nfs/values.yaml @@ -0,0 +1 @@ +pvcName: "" From 053481f248b443e0791489558056a957581f1c1f Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 15:55:24 +0100 Subject: [PATCH 2/6] fix(nfs): add required fields to ApplicationDefinition and include in IaaS bundle Add missing required fields (singular, category) to NFS ApplicationDefinition and add nfs-application to the IaaS bundle. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/templates/bundles/iaas.yaml | 1 + packages/system/nfs-rd/cozyrds/nfs.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/core/platform/templates/bundles/iaas.yaml b/packages/core/platform/templates/bundles/iaas.yaml index 7c856b8c..8ee5d101 100644 --- a/packages/core/platform/templates/bundles/iaas.yaml +++ b/packages/core/platform/templates/bundles/iaas.yaml @@ -12,6 +12,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.capi-provider-cp-kamaji" $) }} {{include "cozystack.platform.package.default" (list "cozystack.capi-provider-infra-kubevirt" $) }} {{include "cozystack.platform.package.default" (list "cozystack.bucket-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.nfs-application" $) }} {{include "cozystack.platform.package" (list "cozystack.kubernetes-application" "kubevirt" $) }} {{include "cozystack.platform.package" (list "cozystack.virtual-machine-application" "kubevirt" $) }} {{include "cozystack.platform.package" (list "cozystack.virtualprivatecloud-application" "kubevirt" $) }} diff --git a/packages/system/nfs-rd/cozyrds/nfs.yaml b/packages/system/nfs-rd/cozyrds/nfs.yaml index 125401e6..9a51f6af 100644 --- a/packages/system/nfs-rd/cozyrds/nfs.yaml +++ b/packages/system/nfs-rd/cozyrds/nfs.yaml @@ -5,6 +5,7 @@ metadata: spec: application: kind: NFS + singular: nfs plural: nfs openAPISchema: |- {"title":"Chart Values","type":"object","properties":{"size":{"description":"Volume size for NFS storage.","default":"10Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}} @@ -15,6 +16,8 @@ spec: name: cozystack-nfs-application-default-nfs namespace: cozy-system dashboard: + category: IaaS + singular: NFS description: Managed NFS storage plural: NFS icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcikiLz4KPHBhdGggZD0iTTM2IDU4VjEwNEMzNiAxMDcuMzE0IDM4LjY4NjMgMTEwIDQyIDExMEgxMDJDMTA1LjMxNCAxMTAgMTA4IDEwNy4zMTQgMTA4IDEwNFY1MkMxMDggNDguNjg2MyAxMDUuMzE0IDQ2IDEwMiA0Nkg3MEw2MiAzNEg0MkMzOC42ODYzIDM0IDM2IDM2LjY4NjMgMzYgNDBWNThaIiBmaWxsPSIjMTU2NUMwIi8+CjxwYXRoIGQ9Ik0zMCA2MkMzMCA1OC42ODYzIDMyLjY4NjMgNTYgMzYgNTZIMTA4QzExMS4zMTQgNTYgMTE0IDU4LjY4NjMgMTE0IDYyVjEwNEMxMTQgMTA3LjMxNCAxMTEuMzE0IDExMCAxMDggMTEwSDM2QzMyLjY4NjMgMTEwIDMwIDEwNy4zMTQgMzAgMTA0VjYyWiIgZmlsbD0iIzQyQTVGNSIvPgo8cGF0aCBkPSJNNjMgNzZWOTRNNjMgNzZMNTQgODVNNjMgNzZMNzIgODUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik04MSA5NFY3Nk04MSA5NEw5MCA4NU04MSA5NEw3MiA4NSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhciIgeDE9IjAiIHkxPSIwIiB4Mj0iMTQ0IiB5Mj0iMTQ0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFM0YyRkQiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNjRCNUY2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg== From fe6d8bd8d173c38bb59606fedc9bb271692c08bd Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 18:13:32 +0100 Subject: [PATCH 3/6] refactor(nfs): replace system/nfs HelmRelease with setup Job Replace the system/nfs chart (which only created CiliumNetworkPolicy via Helm lookup) with a Helm hook Job in apps/nfs that: - Mounts the PVC to trigger WaitForFirstConsumer binding - Reads NFS export info from PV's CSI volume attributes - Creates CiliumNetworkPolicy for NFS server egress - Creates credentials Secret with host, port, path, endpoint - Sets ownerReferences to PVC for automatic cleanup Remove system/nfs chart entirely and its PackageSource component. Update dashboard-resourcemap to include credentials Secret. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../nfs/templates/dashboard-resourcemap.yaml | 7 + packages/apps/nfs/templates/helmrelease.yaml | 22 --- .../apps/nfs/templates/nfs-setup-hook.yaml | 168 ++++++++++++++++++ .../platform/sources/nfs-application.yaml | 2 - packages/system/nfs/.helmignore | 2 - packages/system/nfs/Chart.yaml | 3 - packages/system/nfs/Makefile | 3 - .../system/nfs/templates/cilium-policy.yaml | 20 --- packages/system/nfs/values.yaml | 1 - 9 files changed, 175 insertions(+), 53 deletions(-) delete mode 100644 packages/apps/nfs/templates/helmrelease.yaml create mode 100644 packages/apps/nfs/templates/nfs-setup-hook.yaml delete mode 100644 packages/system/nfs/.helmignore delete mode 100644 packages/system/nfs/Chart.yaml delete mode 100644 packages/system/nfs/Makefile delete mode 100644 packages/system/nfs/templates/cilium-policy.yaml delete mode 100644 packages/system/nfs/values.yaml diff --git a/packages/apps/nfs/templates/dashboard-resourcemap.yaml b/packages/apps/nfs/templates/dashboard-resourcemap.yaml index 106af486..e5f7f063 100644 --- a/packages/apps/nfs/templates/dashboard-resourcemap.yaml +++ b/packages/apps/nfs/templates/dashboard-resourcemap.yaml @@ -10,6 +10,13 @@ rules: resourceNames: - {{ .Release.Name }} verbs: ["get", "list", "watch"] +- apiGroups: + - "" + resources: + - secrets + resourceNames: + - {{ .Release.Name }}-credentials + verbs: ["get", "list", "watch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/packages/apps/nfs/templates/helmrelease.yaml b/packages/apps/nfs/templates/helmrelease.yaml deleted file mode 100644 index 598f065f..00000000 --- a/packages/apps/nfs/templates/helmrelease.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: {{ .Release.Name }}-system - labels: - sharding.fluxcd.io/key: tenants -spec: - chartRef: - kind: ExternalArtifact - name: cozystack-nfs-application-default-nfs-system - namespace: cozy-system - interval: 5m - timeout: 10m - install: - remediation: - retries: -1 - upgrade: - force: true - remediation: - retries: -1 - values: - pvcName: {{ .Release.Name }} diff --git a/packages/apps/nfs/templates/nfs-setup-hook.yaml b/packages/apps/nfs/templates/nfs-setup-hook.yaml new file mode 100644 index 00000000..759bfd21 --- /dev/null +++ b/packages/apps/nfs/templates/nfs-setup-hook.yaml @@ -0,0 +1,168 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Release.Name }}-nfs-setup + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-5" + helm.sh/hook-delete-policy: before-hook-creation +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-nfs-setup + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-5" + helm.sh/hook-delete-policy: before-hook-creation +rules: + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "patch", "update"] + - apiGroups: ["cilium.io"] + resources: ["ciliumnetworkpolicies"] + verbs: ["create", "get", "patch", "update"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-nfs-setup + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-5" + helm.sh/hook-delete-policy: before-hook-creation +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }}-nfs-setup +roleRef: + kind: Role + name: {{ .Release.Name }}-nfs-setup + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Namespace }}-{{ .Release.Name }}-nfs-setup + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-5" + helm.sh/hook-delete-policy: before-hook-creation +rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Namespace }}-{{ .Release.Name }}-nfs-setup + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-5" + helm.sh/hook-delete-policy: before-hook-creation +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }}-nfs-setup + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ .Release.Namespace }}-{{ .Release.Name }}-nfs-setup + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-nfs-setup + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation +spec: + backoffLimit: 3 + template: + metadata: + labels: + policy.cozystack.io/allow-to-apiserver: "true" + spec: + serviceAccountName: {{ .Release.Name }}-nfs-setup + restartPolicy: Never + containers: + - name: setup + image: docker.io/alpine/k8s:1.33.4 + command: ["sh", "-xec"] + args: + - | + PVC_NAME="{{ .Release.Name }}" + NAMESPACE="{{ .Release.Namespace }}" + + # Get PV name from PVC (PVC is bound since we mount it) + PV_NAME=$(kubectl get pvc "$PVC_NAME" -n "$NAMESPACE" -o jsonpath='{.spec.volumeName}') + + # Get NFS export URL from PV + NFS_EXPORT=$(kubectl get pv "$PV_NAME" -o 'jsonpath={.spec.csi.volumeAttributes.linstor\.csi\.linbit\.com/nfs-export}') + + # Parse port from URL (format: nfs://host:port/path) + PORT=$(echo "$NFS_EXPORT" | sed -E 's|.*://[^:]+:([0-9]+)/.*|\1|') + + # Get PVC UID for owner reference + PVC_UID=$(kubectl get pvc "$PVC_NAME" -n "$NAMESPACE" -o jsonpath='{.metadata.uid}') + + # Parse host and path from URL + HOST=$(echo "$NFS_EXPORT" | sed -E 's|.*://([^:]+):.*|\1|') + PATH_EXPORT=$(echo "$NFS_EXPORT" | sed -E 's|.*://[^/]+(/.*)|\1|') + + # Create credentials secret with NFS access info + cat < Date: Thu, 12 Feb 2026 18:13:40 +0100 Subject: [PATCH 4/6] refactor(nfs-driver): change extraStorageClasses from list to map Convert extraStorageClasses from list to map format to support Flux HelmRelease valuesFrom with targetPath deep-merge semantics. Update template to iterate over map entries and construct mountOptions from port parameter. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../templates/extra-storageclasses.yaml | 13 +++-- .../tests/extra-storageclasses_test.yaml | 52 ++++++++----------- packages/system/nfs-driver/values.yaml | 2 +- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/packages/system/nfs-driver/templates/extra-storageclasses.yaml b/packages/system/nfs-driver/templates/extra-storageclasses.yaml index f627a474..c86e1355 100644 --- a/packages/system/nfs-driver/templates/extra-storageclasses.yaml +++ b/packages/system/nfs-driver/templates/extra-storageclasses.yaml @@ -1,18 +1,17 @@ -{{- range .Values.extraStorageClasses }} +{{- range $name, $config := .Values.extraStorageClasses }} --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: - name: {{ .name }} + name: {{ $name }} provisioner: nfs.csi.k8s.io parameters: - server: {{ .server | quote }} - share: {{ .share | quote }} + server: {{ $config.server | quote }} + share: {{ $config.share | quote }} reclaimPolicy: Delete volumeBindingMode: Immediate allowVolumeExpansion: true -{{- with .mountOptions }} mountOptions: -{{- toYaml . | nindent 2 }} -{{- end }} + - nfsvers=4.2 + - port={{ $config.port }} {{- end }} diff --git a/packages/system/nfs-driver/tests/extra-storageclasses_test.yaml b/packages/system/nfs-driver/tests/extra-storageclasses_test.yaml index 6009c761..da3bd34d 100644 --- a/packages/system/nfs-driver/tests/extra-storageclasses_test.yaml +++ b/packages/system/nfs-driver/tests/extra-storageclasses_test.yaml @@ -12,16 +12,14 @@ tests: - it: creates one StorageClass per entry set: extraStorageClasses: - - name: nfs-data + nfs-data: server: "10.96.1.1" share: "/" - mountOptions: - - nfsvers=4.1 - - name: nfs-backup + port: "2049" + nfs-backup: server: "10.96.2.2" share: "/" - mountOptions: - - nfsvers=4.1 + port: "2050" asserts: - hasDocuments: count: 2 @@ -29,11 +27,10 @@ tests: - it: sets correct provisioner set: extraStorageClasses: - - name: nfs-test + nfs-test: server: "10.96.1.1" share: "/" - mountOptions: - - nfsvers=4.1 + port: "2049" asserts: - equal: path: provisioner @@ -42,11 +39,10 @@ tests: - it: sets server and share parameters set: extraStorageClasses: - - name: nfs-mydata + nfs-mydata: server: "10.96.50.100" share: "/exports" - mountOptions: - - nfsvers=4.1 + port: "2049" asserts: - equal: path: metadata.name @@ -58,27 +54,28 @@ tests: path: parameters.share value: "/exports" - - it: sets mount options + - it: sets mount options with port set: extraStorageClasses: - - name: nfs-test + nfs-test: server: "10.96.1.1" share: "/" - mountOptions: - - nfsvers=4.1 + port: "2049" asserts: - equal: path: mountOptions[0] - value: nfsvers=4.1 + value: nfsvers=4.2 + - equal: + path: mountOptions[1] + value: port=2049 - it: allows volume expansion set: extraStorageClasses: - - name: nfs-test + nfs-test: server: "10.96.1.1" share: "/" - mountOptions: - - nfsvers=4.1 + port: "2049" asserts: - equal: path: allowVolumeExpansion @@ -87,11 +84,10 @@ tests: - it: uses Delete reclaim policy set: extraStorageClasses: - - name: nfs-test + nfs-test: server: "10.96.1.1" share: "/" - mountOptions: - - nfsvers=4.1 + port: "2049" asserts: - equal: path: reclaimPolicy @@ -100,16 +96,14 @@ tests: - it: creates distinct StorageClasses for multiple shares set: extraStorageClasses: - - name: nfs-alpha + nfs-alpha: server: "10.96.10.1" share: "/" - mountOptions: - - nfsvers=4.1 - - name: nfs-beta + port: "2049" + nfs-beta: server: "10.96.20.2" share: "/data" - mountOptions: - - nfsvers=4.1 + port: "2050" asserts: - equal: path: metadata.name diff --git a/packages/system/nfs-driver/values.yaml b/packages/system/nfs-driver/values.yaml index eb1f99f0..a5676ee9 100644 --- a/packages/system/nfs-driver/values.yaml +++ b/packages/system/nfs-driver/values.yaml @@ -1,3 +1,3 @@ csi-driver-nfs: {} -extraStorageClasses: [] +extraStorageClasses: {} From b7df10de6c14bf674fb730200c0bfcca4685c396 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 18:13:46 +0100 Subject: [PATCH 5/6] refactor(kubernetes): use valuesFrom for NFS driver credentials Replace PVC/PV lookup logic with valuesFrom references to NFS credentials secrets, passing host, port, and path via targetPath to the nfs-driver extraStorageClasses map. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../templates/helmreleases/nfs-driver.yaml | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml b/packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml index c00ea763..590f3c4b 100644 --- a/packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/nfs-driver.yaml @@ -1,29 +1,4 @@ {{- if .Values.nfs.instances }} -{{- $extraStorageClasses := list }} -{{- range .Values.nfs.instances }} -{{- $pvcName := printf "nfs-%s" .name }} -{{- $pvc := lookup "v1" "PersistentVolumeClaim" $.Release.Namespace $pvcName }} -{{- if not $pvc }} -{{- fail (printf "NFS PVC not found in namespace %s: %s" $.Release.Namespace $pvcName) }} -{{- end }} -{{- if not $pvc.spec.volumeName }} -{{- fail (printf "NFS PVC %s is not yet bound" $pvcName) }} -{{- end }} -{{- $pv := lookup "v1" "PersistentVolume" "" $pvc.spec.volumeName }} -{{- if not $pv }} -{{- fail (printf "PersistentVolume %s not found for PVC %s" $pvc.spec.volumeName $pvcName) }} -{{- end }} -{{- $nfsExport := index $pv.spec.csi.volumeAttributes "linstor.csi.linbit.com/nfs-export" }} -{{- if not $nfsExport }} -{{- fail (printf "PV %s has no linstor.csi.linbit.com/nfs-export attribute" $pv.metadata.name) }} -{{- end }} -{{- $parsed := urlParse $nfsExport }} -{{- $hostParts := splitList ":" $parsed.host }} -{{- $server := first $hostParts }} -{{- $port := last $hostParts }} -{{- $sc := dict "name" (printf "nfs-%s" .name) "server" $server "share" $parsed.path "mountOptions" (list (printf "nfsvers=4.2") (printf "port=%s" $port)) }} -{{- $extraStorageClasses = append $extraStorageClasses $sc }} -{{- end }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -54,9 +29,29 @@ spec: force: true remediation: retries: -1 + valuesFrom: + {{- range .Values.nfs.instances }} + {{- $secretName := printf "nfs-%s-credentials" .name }} + - kind: Secret + name: {{ $secretName }} + valuesKey: host + targetPath: extraStorageClasses.nfs-{{ .name }}.server + - kind: Secret + name: {{ $secretName }} + valuesKey: port + targetPath: extraStorageClasses.nfs-{{ .name }}.port + - kind: Secret + name: {{ $secretName }} + valuesKey: path + targetPath: extraStorageClasses.nfs-{{ .name }}.share + {{- end }} values: - {{- $defaultValues := dict "csi-driver-nfs" (dict "storageClass" (dict "create" false)) "extraStorageClasses" $extraStorageClasses }} - {{- toYaml (deepCopy .Values.addons.nfs.valuesOverride | mergeOverwrite $defaultValues) | nindent 4 }} + csi-driver-nfs: + storageClass: + create: false + {{- with .Values.addons.nfs.valuesOverride }} + {{- toYaml . | nindent 4 }} + {{- end }} dependsOn: {{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }} - name: {{ .Release.Name }} From a05fead357c0f8b4b20f2c4b322ea45064f85073 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 18:13:52 +0100 Subject: [PATCH 6/6] fix(nfs): add secrets section to ApplicationDefinition Add secrets include/exclude configuration so the lineage webhook sets internal.cozystack.io/tenantresource label on NFS credentials secrets, making them visible via the tenantsecrets API. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/nfs-rd/cozyrds/nfs.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/system/nfs-rd/cozyrds/nfs.yaml b/packages/system/nfs-rd/cozyrds/nfs.yaml index 9a51f6af..9e720e1f 100644 --- a/packages/system/nfs-rd/cozyrds/nfs.yaml +++ b/packages/system/nfs-rd/cozyrds/nfs.yaml @@ -22,3 +22,8 @@ spec: plural: NFS icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcikiLz4KPHBhdGggZD0iTTM2IDU4VjEwNEMzNiAxMDcuMzE0IDM4LjY4NjMgMTEwIDQyIDExMEgxMDJDMTA1LjMxNCAxMTAgMTA4IDEwNy4zMTQgMTA4IDEwNFY1MkMxMDggNDguNjg2MyAxMDUuMzE0IDQ2IDEwMiA0Nkg3MEw2MiAzNEg0MkMzOC42ODYzIDM0IDM2IDM2LjY4NjMgMzYgNDBWNThaIiBmaWxsPSIjMTU2NUMwIi8+CjxwYXRoIGQ9Ik0zMCA2MkMzMCA1OC42ODYzIDMyLjY4NjMgNTYgMzYgNTZIMTA4QzExMS4zMTQgNTYgMTE0IDU4LjY4NjMgMTE0IDYyVjEwNEMxMTQgMTA3LjMxNCAxMTEuMzE0IDExMCAxMDggMTEwSDM2QzMyLjY4NjMgMTEwIDMwIDEwNy4zMTQgMzAgMTA0VjYyWiIgZmlsbD0iIzQyQTVGNSIvPgo8cGF0aCBkPSJNNjMgNzZWOTRNNjMgNzZMNTQgODVNNjMgNzZMNzIgODUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik04MSA5NFY3Nk04MSA5NEw5MCA4NU04MSA5NEw3MiA4NSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhciIgeDE9IjAiIHkxPSIwIiB4Mj0iMTQ0IiB5Mj0iMTQ0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFM0YyRkQiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNjRCNUY2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg== keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "size"]] + secrets: + exclude: [] + include: + - resourceNames: + - nfs-{{ .name }}-credentials