22
33from configparser import ConfigParser
44from io import StringIO
5- from pathlib import Path
6- from typing import Any , Optional , Self
5+ from pathlib import PurePosixPath
6+ from typing import Any , Final , Optional , Self
77
8+ from kubernetes import client
89from marshmallow import EXCLUDE , Schema , ValidationError , fields , validates_schema
910
1011from renku_data_services .base_models import APIUser
1112from renku_data_services .notebooks .api .classes .cloud_storage import ICloudStorageRequest
1213from renku_data_services .notebooks .config import _NotebooksConfig
1314
15+ _sanitize_for_serialization = client .ApiClient ().sanitize_for_serialization
16+
1417
1518class RCloneStorageRequest (Schema ):
1619 """Request for RClone based storage."""
@@ -36,6 +39,8 @@ def validate_storage(self, data: dict, **kwargs: dict) -> None:
3639class RCloneStorage (ICloudStorageRequest ):
3740 """RClone based storage."""
3841
42+ pvc_secret_annotation_name : Final [str ] = "csi-rclone.dev/secretName"
43+
3944 def __init__ (
4045 self ,
4146 source_path : str ,
@@ -60,7 +65,7 @@ async def storage_from_schema(
6065 user : APIUser ,
6166 internal_gitlab_user : APIUser ,
6267 project_id : int ,
63- work_dir : Path ,
68+ work_dir : PurePosixPath ,
6469 config : _NotebooksConfig ,
6570 ) -> Self :
6671 """Create storage object from request."""
@@ -92,8 +97,73 @@ async def storage_from_schema(
9297 await config .storage_validator .validate_storage_configuration (configuration , source_path )
9398 return cls (source_path , configuration , readonly , mount_folder , name , config )
9499
100+ def pvc (
101+ self ,
102+ base_name : str ,
103+ namespace : str ,
104+ labels : dict [str , str ] | None = None ,
105+ annotations : dict [str , str ] | None = None ,
106+ ) -> client .V1PersistentVolumeClaim :
107+ """The PVC for mounting cloud storage."""
108+ return client .V1PersistentVolumeClaim (
109+ metadata = client .V1ObjectMeta (
110+ name = base_name ,
111+ namespace = namespace ,
112+ annotations = {self .pvc_secret_annotation_name : base_name } | (annotations or {}),
113+ labels = {"name" : base_name } | (labels or {}),
114+ ),
115+ spec = client .V1PersistentVolumeClaimSpec (
116+ access_modes = ["ReadOnlyMany" if self .readonly else "ReadWriteMany" ],
117+ resources = client .V1VolumeResourceRequirements (requests = {"storage" : "10Gi" }),
118+ storage_class_name = self .config .cloud_storage .storage_class ,
119+ ),
120+ )
121+
122+ def volume_mount (self , base_name : str ) -> client .V1VolumeMount :
123+ """The volume mount for cloud storage."""
124+ return client .V1VolumeMount (
125+ mount_path = self .mount_folder ,
126+ name = base_name ,
127+ read_only = self .readonly ,
128+ )
129+
130+ def volume (self , base_name : str ) -> client .V1Volume :
131+ """The volume entry for the statefulset specification."""
132+ return client .V1Volume (
133+ name = base_name ,
134+ persistent_volume_claim = client .V1PersistentVolumeClaimVolumeSource (
135+ claim_name = base_name , read_only = self .readonly
136+ ),
137+ )
138+
139+ def secret (
140+ self ,
141+ base_name : str ,
142+ namespace : str ,
143+ labels : dict [str , str ] | None = None ,
144+ annotations : dict [str , str ] | None = None ,
145+ ) -> client .V1Secret :
146+ """The secret containing the configuration for the rclone csi driver."""
147+ return client .V1Secret (
148+ metadata = client .V1ObjectMeta (
149+ name = base_name ,
150+ namespace = namespace ,
151+ annotations = annotations ,
152+ labels = {"name" : base_name } | (labels or {}),
153+ ),
154+ string_data = {
155+ "remote" : self .name or base_name ,
156+ "remotePath" : self .source_path ,
157+ "configData" : self .config_string (self .name or base_name ),
158+ },
159+ )
160+
95161 def get_manifest_patch (
96- self , base_name : str , namespace : str , labels : dict = {}, annotations : dict = {}
162+ self ,
163+ base_name : str ,
164+ namespace : str ,
165+ labels : dict [str , str ] | None = None ,
166+ annotations : dict [str , str ] | None = None ,
97167 ) -> list [dict [str , Any ]]:
98168 """Get server manifest patch."""
99169 patches = []
@@ -104,57 +174,22 @@ def get_manifest_patch(
104174 {
105175 "op" : "add" ,
106176 "path" : f"/{ base_name } -pv" ,
107- "value" : {
108- "apiVersion" : "v1" ,
109- "kind" : "PersistentVolumeClaim" ,
110- "metadata" : {
111- "name" : base_name ,
112- "labels" : {"name" : base_name },
113- },
114- "spec" : {
115- "accessModes" : ["ReadOnlyMany" if self .readonly else "ReadWriteMany" ],
116- "resources" : {"requests" : {"storage" : "10Gi" }},
117- "storageClassName" : self .config .cloud_storage .storage_class ,
118- },
119- },
177+ "value" : _sanitize_for_serialization (self .pvc (base_name , namespace , labels , annotations )),
120178 },
121179 {
122180 "op" : "add" ,
123181 "path" : f"/{ base_name } -secret" ,
124- "value" : {
125- "apiVersion" : "v1" ,
126- "kind" : "Secret" ,
127- "metadata" : {
128- "name" : base_name ,
129- "labels" : {"name" : base_name },
130- },
131- "type" : "Opaque" ,
132- "stringData" : {
133- "remote" : self .name or base_name ,
134- "remotePath" : self .source_path ,
135- "configData" : self .config_string (self .name or base_name ),
136- },
137- },
182+ "value" : _sanitize_for_serialization (self .secret (base_name , namespace , labels , annotations )),
138183 },
139184 {
140185 "op" : "add" ,
141186 "path" : "/statefulset/spec/template/spec/containers/0/volumeMounts/-" ,
142- "value" : {
143- "mountPath" : self .mount_folder ,
144- "name" : base_name ,
145- "readOnly" : self .readonly ,
146- },
187+ "value" : _sanitize_for_serialization (self .volume_mount (base_name )),
147188 },
148189 {
149190 "op" : "add" ,
150191 "path" : "/statefulset/spec/template/spec/volumes/-" ,
151- "value" : {
152- "name" : base_name ,
153- "persistentVolumeClaim" : {
154- "claimName" : base_name ,
155- "readOnly" : self .readonly ,
156- },
157- },
192+ "value" : _sanitize_for_serialization (self .volume (base_name )),
158193 },
159194 ],
160195 }
0 commit comments