v1.PersistentVolume.ObjectMeta: v1.ObjectMeta.Annotations: ReadString: expects \" or n, but found {

The problem was a mess inside those dictionaries, access_mode got twice into the spec and caused bad things. I had to ditch dictionaries and use dot notation, also I’ve added check if pv+pvc already exists. I use spawner.api to call request functions and I somehow like better than spawner._make_create_pvc_request.

I’m including whole pre-spawn hook for anyone who might need it (or just as an inspiration). For me, it was hard finding any example on how to do it :sweat_smile:

extraConfig:                                                                  
    pre-spawn-hook: |                                                           
      from kubernetes.client.models import V1PersistentVolumeClaim              
      from kubernetes.client.models import V1PersistentVolumeClaimSpec          
      from kubernetes.client.models import V1ResourceRequirements               
      from kubernetes.client.models import V1LabelSelector                      
      from kubernetes.client.models import V1PersistentVolume                   
      from kubernetes.client.models import V1ObjectMeta                         
      from kubernetes.client.models import V1CSIPersistentVolumeSource          
      from kubernetes.client.models import V1PersistentVolumeSpec               
      from kubernetes.client.api import CoreV1Api                               
                                                                                
      def create_homes(spawner):                                           
            username = spawner.user.name                                        
            home_pvc_name = username + "-claim"                                 
            home_pv_name = username + "-data-sshfs"                             
            share_path = "/nfs4/home/" + username                               
            pk_secret = "jupyterhub-prod-ns/" + username + "-secret"            
            mount_path = "/home/meta/" + username                               
                                                                                
            v1 = client.CoreV1Api()                                             
            pvs = v1.list_persistent_volume(watch=False)                        
            pv = None                                                           
            for volume in pvs.items:                                            
              if volume.metadata.name == home_pv_name:                          
                pv = volume                                                     
            if not pv:                                                          
              pv = V1PersistentVolume()                                         
              pv.api_version = "v1"                                             
              pv.kind = "PersistentVolume"                                      
              pv.metadata = V1ObjectMeta()                                      
              pv.metadata.name = home_pv_name                                   
              pv.metadata.labels = {"name":home_pv_name}                        
              pv.spec = V1PersistentVolumeSpec()                                
              pv.spec.access_modes = ['ReadWriteMany']                          
              pv.spec.capacity = {"storage":"100Gi"}                            
              pv.spec.storage_class_name = "sshfs"                              
                                                                                
              volume_attr = {"server":"server.cz","port":"22",        
                             "share":share_path,"privateKey":pk_secret,         
                             "user":username,"sshOpts":"uid=1000,gid=100,allow_other"}
              pv.spec.csi = V1CSIPersistentVolumeSource(driver="csi-sshfs",     
                                                        volume_attributes=volume_attr,
                                                        volume_handle="data-id")
              spawner.api.create_persistent_volume(pv)                          

                                                                                
            pvcs = v1.list_persistent_volume_claim_for_all_namespaces(watch=False)
            pvc = None                                                          
            for claim in pvcs.items:                                            
              if claim.metadata.name == home_pvc_name:                          
                pvc = claim                                                     
            if not pvc:                                                         
              pvc = V1PersistentVolumeClaim()                                   
              pvc.api_version = "v1"                                            
              pvc.kind = "PersistentVolumeClaim"                                
              pvc.metadata = V1ObjectMeta()                                     
              pvc.metadata.name = home_pvc_name                                 
              pvc.spec = V1PersistentVolumeClaimSpec()                          
              pvc.spec.access_modes = ['ReadWriteMany']                         
              pvc.spec.resources = V1ResourceRequirements()                     
              pvc.spec.resources.requests = {"storage":"100Gi"}                 
              pvc.spec.storage_class_name = "sshfs"                             
              pvc.spec.selector = V1LabelSelector()                             
              pvc.spec.selector.match_labels = {"name":home_pv_name}            
              spawner.api.create_namespaced_persistent_volume_claim("jupyterhub-prod-ns", pvc)
                                                                                
      c.KubeSpawner.pre_spawn_hook = create_homes