Compare commits

...
Sign in to create a new pull request.

6 commits

Author SHA1 Message Date
Andrei Kvapil
a05fead357
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 <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-02-12 18:13:52 +01:00
Andrei Kvapil
b7df10de6c
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 <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-02-12 18:13:46 +01:00
Andrei Kvapil
19f9deca9e
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 <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-02-12 18:13:40 +01:00
Andrei Kvapil
fe6d8bd8d1
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 <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-02-12 18:13:32 +01:00
Andrei Kvapil
053481f248
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 <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-02-12 15:55:24 +01:00
Andrei Kvapil
0421841178
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 <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-02-12 15:48:51 +01:00
34 changed files with 853 additions and 4 deletions

View file

@ -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 - <<EOF
apiVersion: apps.cozystack.io/v1alpha1
kind: NFS
metadata:
name: "${nfs_name}"
namespace: tenant-test
spec:
size: 1Gi
storageClass: replicated
EOF
# Wait for NFS HelmRelease to be ready
kubectl -n tenant-test wait hr nfs-${nfs_name} --timeout=120s --for=condition=ready
# Wait for NFS PVC to be bound
kubectl -n tenant-test wait pvc nfs-${nfs_name} --timeout=120s --for=jsonpath='{.status.phase}'=Bound
kubectl apply -f - <<EOF
apiVersion: apps.cozystack.io/v1alpha1
@ -59,6 +78,9 @@ spec:
minReplicas: 0
roles:
- ingress-nginx
nfs:
instances:
- name: "${nfs_name}"
storageClass: replicated
version: "${k8s_version}"
EOF
@ -214,14 +236,73 @@ fi
exit 1
fi
# Cleanup
# Cleanup LoadBalancer test
kubectl delete deployment --kubeconfig tenantkubeconfig-${test_name} "${test_name}-backend" -n tenant-test
kubectl delete service --kubeconfig tenantkubeconfig-${test_name} "${test_name}-backend" -n tenant-test
# Test NFS mount in tenant cluster
# Wait for NFS StorageClass to appear in tenant cluster
timeout 120 sh -ec 'until kubectl --kubeconfig tenantkubeconfig-'"${test_name}"' get sc nfs-'"${nfs_name}"' 2>/dev/null; do sleep 5; done'
# Create PVC with NFS StorageClass
kubectl --kubeconfig tenantkubeconfig-${test_name} apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-test-pvc
namespace: tenant-test
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-${nfs_name}
resources:
requests:
storage: 1Gi
EOF
# Wait for PVC to be bound
kubectl --kubeconfig tenantkubeconfig-${test_name} wait pvc nfs-test-pvc -n tenant-test --timeout=2m --for=jsonpath='{.status.phase}'=Bound
# Create Pod that writes and reads data from NFS volume
kubectl --kubeconfig tenantkubeconfig-${test_name} apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nfs-test-pod
namespace: tenant-test
spec:
containers:
- name: test
image: busybox
command: ["sh", "-c", "echo 'nfs-mount-ok' > /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}
}

View file

@ -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

View file

@ -108,6 +108,15 @@ See the reference for components utilized in this service:
| `host` | External hostname for Kubernetes cluster. Defaults to `<cluster-name>.<tenant-host>` 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-<name>` 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

View file

@ -0,0 +1,62 @@
{{- if .Values.nfs.instances }}
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
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:
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 }}
namespace: {{ .Release.Namespace }}
{{- end }}
- name: {{ .Release.Name }}-cilium
namespace: {{ .Release.Namespace }}
{{- end }}

View file

@ -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"

View file

@ -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",

View file

@ -62,6 +62,24 @@ version: "v1.33"
## @param {string} host - External hostname for Kubernetes cluster. Defaults to `<cluster-name>.<tenant-host>` 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-<name>` 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

View file

@ -0,0 +1,2 @@
logos/
hack/

View file

@ -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

View file

@ -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

View file

@ -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: <https://github.com/nfs-ganesha/nfs-ganesha/wiki>
- GitHub: <https://github.com/nfs-ganesha/nfs-ganesha>
## Parameters
### Common parameters
| Name | Description | Type | Value |
| -------------- | ------------------------------------ | ---------- | ------------ |
| `storageClass` | StorageClass used to store the data. | `string` | `replicated` |
| `size` | Volume size for NFS storage. | `quantity` | `10Gi` |

View file

@ -0,0 +1 @@
../../../library/cozy-lib

View file

@ -0,0 +1,13 @@
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="144" height="144" rx="24" fill="url(#paint0_linear)"/>
<path d="M36 58V104C36 107.314 38.6863 110 42 110H102C105.314 110 108 107.314 108 104V52C108 48.6863 105.314 46 102 46H70L62 34H42C38.6863 34 36 36.6863 36 40V58Z" fill="#1565C0"/>
<path d="M30 62C30 58.6863 32.6863 56 36 56H108C111.314 56 114 58.6863 114 62V104C114 107.314 111.314 110 108 110H36C32.6863 110 30 107.314 30 104V62Z" fill="#42A5F5"/>
<path d="M63 76V94M63 76L54 85M63 76L72 85" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M81 94V76M81 94L90 85M81 94L72 85" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear" x1="0" y1="0" x2="144" y2="144" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3F2FD"/>
<stop offset="1" stop-color="#64B5F6"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 976 B

View file

@ -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 }}

View file

@ -0,0 +1,30 @@
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"]
- apiGroups:
- ""
resources:
- secrets
resourceNames:
- {{ .Release.Name }}-credentials
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

View file

@ -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 <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: ${PVC_NAME}-credentials
namespace: ${NAMESPACE}
ownerReferences:
- apiVersion: v1
kind: PersistentVolumeClaim
name: ${PVC_NAME}
uid: ${PVC_UID}
type: Opaque
stringData:
host: "${HOST}"
port: "${PORT}"
path: "${PATH_EXPORT}"
endpoint: "nfs://${HOST}:${PORT}${PATH_EXPORT}"
EOF
# Apply CiliumNetworkPolicy with owner reference to PVC
cat <<EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-nfs-${PVC_NAME}
namespace: ${NAMESPACE}
ownerReferences:
- apiVersion: v1
kind: PersistentVolumeClaim
name: ${PVC_NAME}
uid: ${PVC_UID}
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
EOF
volumeMounts:
- name: data
mountPath: /mnt/data
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ .Release.Name }}

View file

@ -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 }}

View file

@ -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 }}

View file

@ -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"
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -0,0 +1,27 @@
---
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
path: apps/nfs
libraries: ["cozy-lib"]
- name: nfs-rd
path: system/nfs-rd
install:
namespace: cozy-system
releaseName: nfs-rd

View file

@ -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" $) }}

File diff suppressed because one or more lines are too long

View file

@ -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

View file

@ -0,0 +1,17 @@
{{- range $name, $config := .Values.extraStorageClasses }}
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: {{ $name }}
provisioner: nfs.csi.k8s.io
parameters:
server: {{ $config.server | quote }}
share: {{ $config.share | quote }}
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
mountOptions:
- nfsvers=4.2
- port={{ $config.port }}
{{- end }}

View file

@ -0,0 +1,127 @@
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:
nfs-data:
server: "10.96.1.1"
share: "/"
port: "2049"
nfs-backup:
server: "10.96.2.2"
share: "/"
port: "2050"
asserts:
- hasDocuments:
count: 2
- it: sets correct provisioner
set:
extraStorageClasses:
nfs-test:
server: "10.96.1.1"
share: "/"
port: "2049"
asserts:
- equal:
path: provisioner
value: nfs.csi.k8s.io
- it: sets server and share parameters
set:
extraStorageClasses:
nfs-mydata:
server: "10.96.50.100"
share: "/exports"
port: "2049"
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 with port
set:
extraStorageClasses:
nfs-test:
server: "10.96.1.1"
share: "/"
port: "2049"
asserts:
- equal:
path: mountOptions[0]
value: nfsvers=4.2
- equal:
path: mountOptions[1]
value: port=2049
- it: allows volume expansion
set:
extraStorageClasses:
nfs-test:
server: "10.96.1.1"
share: "/"
port: "2049"
asserts:
- equal:
path: allowVolumeExpansion
value: true
- it: uses Delete reclaim policy
set:
extraStorageClasses:
nfs-test:
server: "10.96.1.1"
share: "/"
port: "2049"
asserts:
- equal:
path: reclaimPolicy
value: Delete
- it: creates distinct StorageClasses for multiple shares
set:
extraStorageClasses:
nfs-alpha:
server: "10.96.10.1"
share: "/"
port: "2049"
nfs-beta:
server: "10.96.20.2"
share: "/data"
port: "2050"
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

View file

@ -1 +1,3 @@
csi-driver-nfs: {}
extraStorageClasses: {}

View file

@ -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

View file

@ -0,0 +1,4 @@
export NAME=nfs-rd
export NAMESPACE=cozy-system
include ../../../hack/package.mk

View file

@ -0,0 +1,29 @@
apiVersion: cozystack.io/v1alpha1
kind: ApplicationDefinition
metadata:
name: nfs
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"}}}
release:
prefix: nfs-
chartRef:
kind: ExternalArtifact
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==
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "storageClass"], ["spec", "size"]]
secrets:
exclude: []
include:
- resourceNames:
- nfs-{{ .name }}-credentials

View file

@ -0,0 +1,4 @@
{{- range $path, $_ := .Files.Glob "cozyrds/*" }}
---
{{ $.Files.Get $path }}
{{- end }}

View file

@ -0,0 +1 @@
{}