Configurable mode and ownership via PVC annotations and provider wide defaults
This commit is contained in:
		
							parent
							
								
									59a3ca2cd1
								
							
						
					
					
						commit
						0f7a89aab9
					
				
							
								
								
									
										12
									
								
								README.md
								
								
								
								
							
							
						
						
									
										12
									
								
								README.md
								
								
								
								
							|  | @ -293,6 +293,18 @@ spec: | ||||||
|       storage: 1Mi |       storage: 1Mi | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | **Step 8: Controlling the permissions and ownership of subdirs** | ||||||
|  | 
 | ||||||
|  | By default new directories will be created with `root:root` ownership, and `0777` permissions in most environments. If you have a need to control this, you can do so by providing the `NFS_DEFAULT_MODE`, `NFS_DEFAULT_UID` and `NFS_DEFAULT_GID` environment variables (or the appropriate configuration in the Helm chart values). The mode must be an octal representation of a file mode, for example `777`, `0755` etc. The uid and gid must be the numeric ids of your desired user and group, so `1000` not `my_user`.  | ||||||
|  | 
 | ||||||
|  | If your usecase requires per-PVC ownership and/or mode, this can be done via annotations on your PVC: | ||||||
|  | 
 | ||||||
|  | - `k8s-sigs.io/nfs-directory-mode` | ||||||
|  | - `k8s-sigs.io/nfs-directory-uid` | ||||||
|  | - `k8s-sigs.io/nfs-directory-gid` | ||||||
|  | 
 | ||||||
|  | The order of precedence is PVC annotations, ENV vars, then root:root 0777 if nothing else has been specified.  | ||||||
|  | 
 | ||||||
| # Build and publish your own container image | # Build and publish your own container image | ||||||
| 
 | 
 | ||||||
| To build your own custom container image from this repository, you will have to build and push the nfs-subdir-external-provisioner image using the following instructions. | To build your own custom container image from this repository, you will have to build and push the nfs-subdir-external-provisioner image using the following instructions. | ||||||
|  |  | ||||||
|  | @ -57,6 +57,12 @@ spec: | ||||||
|               value: {{ .Values.nfs.server }} |               value: {{ .Values.nfs.server }} | ||||||
|             - name: NFS_PATH |             - name: NFS_PATH | ||||||
|               value: {{ .Values.nfs.path }} |               value: {{ .Values.nfs.path }} | ||||||
|  |             - name: NFS_DEFAULT_MODE | ||||||
|  |               value: {{ .Values.nfs.defaultMode}} | ||||||
|  |             - name: NFS_DEFAULT_UID | ||||||
|  |               value: {{ .Values.nfs.defaultUid }} | ||||||
|  |             - name: NFS_DEFAULT_GID | ||||||
|  |               value: {{ .Values.nfs.defaultGid }} | ||||||
|             {{- if eq .Values.leaderElection.enabled false }} |             {{- if eq .Values.leaderElection.enabled false }} | ||||||
|             - name: ENABLE_LEADER_ELECTION |             - name: ENABLE_LEADER_ELECTION | ||||||
|               value: "false" |               value: "false" | ||||||
|  |  | ||||||
|  | @ -12,6 +12,9 @@ nfs: | ||||||
|   path: /nfs-storage |   path: /nfs-storage | ||||||
|   mountOptions: |   mountOptions: | ||||||
|   volumeName: nfs-subdir-external-provisioner-root |   volumeName: nfs-subdir-external-provisioner-root | ||||||
|  |   defaultMode: "777" | ||||||
|  |   defaultUid: "0" | ||||||
|  |   defaultGid: "0" | ||||||
|   # Reclaim policy for the main nfs volume |   # Reclaim policy for the main nfs volume | ||||||
|   reclaimPolicy: Retain |   reclaimPolicy: Retain | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -44,9 +44,12 @@ const ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type nfsProvisioner struct { | type nfsProvisioner struct { | ||||||
| 	client kubernetes.Interface | 	client      kubernetes.Interface | ||||||
| 	server string | 	server      string | ||||||
| 	path   string | 	path        string | ||||||
|  | 	defaultMode os.FileMode | ||||||
|  | 	defaultUid  int | ||||||
|  | 	defaultGid  int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type pvcMetadata struct { | type pvcMetadata struct { | ||||||
|  | @ -74,7 +77,8 @@ func (meta *pvcMetadata) stringParser(str string) string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	mountPath = "/persistentvolumes" | 	mountPath        = "/persistentvolumes" | ||||||
|  | 	annotationPrefix = "k8s-sigs.io" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var _ controller.Provisioner = &nfsProvisioner{} | var _ controller.Provisioner = &nfsProvisioner{} | ||||||
|  | @ -111,15 +115,54 @@ func (p *nfsProvisioner) Provision(ctx context.Context, options controller.Provi | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Check if the PVC has an annotation requesting a specific mode. Fallback to defaults if not.
 | ||||||
|  | 	mode := p.defaultMode | ||||||
|  | 	pvcMode := metadata.annotations[annotationPrefix+"/nfs-directory-mode"] | ||||||
|  | 	if pvcMode != "" { | ||||||
|  | 		var err error | ||||||
|  | 		mode, err = getModeFromString(pvcMode) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, controller.ProvisioningFinished, fmt.Errorf("invalid directoryMode %s: %v", pvcMode, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	glog.V(4).Infof("creating path %s", fullPath) | 	glog.V(4).Infof("creating path %s", fullPath) | ||||||
| 	if err := os.MkdirAll(fullPath, 0o777); err != nil { | 	if err := os.MkdirAll(fullPath, mode); err != nil { | ||||||
| 		return nil, controller.ProvisioningFinished, errors.New("unable to create directory to provision new pv: " + err.Error()) | 		return nil, controller.ProvisioningFinished, errors.New("unable to create directory to provision new pv: " + err.Error()) | ||||||
| 	} | 	} | ||||||
| 	err := os.Chmod(fullPath, 0o777) | 	err := os.Chmod(fullPath, mode) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, "", err | 		return nil, "", err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Check if the PVC has an annotation requesting a specific UID and GID. Again, fallback to defaults if not.
 | ||||||
|  | 	uid := p.defaultUid | ||||||
|  | 	pvcUid := metadata.annotations[annotationPrefix+"/nfs-directory-uid"] | ||||||
|  | 	if pvcUid != "" { | ||||||
|  | 		var err error | ||||||
|  | 		uid, err = getIdFromString(pvcUid) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// No real point in returning an error here as the dir will have already been created as root:root
 | ||||||
|  | 			// log the error and continue with the default uid
 | ||||||
|  | 			glog.Errorf("invalid directoryUid %s: %v", pvcUid, err) | ||||||
|  | 			uid = p.defaultUid | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	gid := p.defaultGid | ||||||
|  | 	pvcGid := metadata.annotations[annotationPrefix+"/nfs-directory-gid"] | ||||||
|  | 	if pvcGid != "" { | ||||||
|  | 		var err error | ||||||
|  | 		gid, err = getIdFromString(pvcGid) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// No real point in returning an error here as the dir will have already been created as root:root
 | ||||||
|  | 			// log the error and continue with the default gid
 | ||||||
|  | 			glog.Errorf("invalid directoryGid %s: %v", pvcGid, err) | ||||||
|  | 			gid = p.defaultGid | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	err = os.Chown(fullPath, uid, gid) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, "", err | ||||||
|  | 	} | ||||||
| 	pv := &v1.PersistentVolume{ | 	pv := &v1.PersistentVolume{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name: options.PVName, | 			Name: options.PVName, | ||||||
|  | @ -205,6 +248,36 @@ func (p *nfsProvisioner) getClassForVolume(ctx context.Context, pv *v1.Persisten | ||||||
| 	return class, nil | 	return class, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func getModeFromString(mode string) (os.FileMode, error) { | ||||||
|  | 	if mode == "" { | ||||||
|  | 		return os.FileMode(0o777), nil // Default to 0777, per current behavior
 | ||||||
|  | 	} | ||||||
|  | 	var modeInt int64 | ||||||
|  | 	var err error | ||||||
|  | 	modeInt, err = strconv.ParseInt(mode, 8, 64) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, fmt.Errorf("invalid mode %s: %v", mode, err) | ||||||
|  | 	} | ||||||
|  | 	if modeInt < 0 || modeInt > 0o777 { | ||||||
|  | 		return 0, fmt.Errorf("mode must be between 0 and 0777, got %s", mode) | ||||||
|  | 	} | ||||||
|  | 	return os.FileMode(modeInt), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getIdFromString(id string) (int, error) { | ||||||
|  | 	if id == "" { | ||||||
|  | 		return 0, nil // Default to 0 aka root, per current behavior
 | ||||||
|  | 	} | ||||||
|  | 	idInt, err := strconv.Atoi(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, fmt.Errorf("invalid id %s: %v", id, err) | ||||||
|  | 	} | ||||||
|  | 	if idInt < 0 || idInt > 65535 { | ||||||
|  | 		return 0, fmt.Errorf("id must be between 0 and 65535, got %s", id) | ||||||
|  | 	} | ||||||
|  | 	return idInt, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func main() { | func main() { | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
| 	flag.Set("logtostderr", "true") | 	flag.Set("logtostderr", "true") | ||||||
|  | @ -221,6 +294,19 @@ func main() { | ||||||
| 	if provisionerName == "" { | 	if provisionerName == "" { | ||||||
| 		glog.Fatalf("environment variable %s is not set! Please set it.", provisionerNameKey) | 		glog.Fatalf("environment variable %s is not set! Please set it.", provisionerNameKey) | ||||||
| 	} | 	} | ||||||
|  | 	// Get the default mode, uid, and gid from environment variables
 | ||||||
|  | 	mode, err := getModeFromString(os.Getenv("NFS_DEFAULT_MODE")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		glog.Fatalf("Failed to parse NFS_DEFAULT_MODE: %v", err) | ||||||
|  | 	} | ||||||
|  | 	uid, err := getIdFromString(os.Getenv("NFS_DEFAULT_UID")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		glog.Fatalf("Failed to parse NFS_DEFAULT_UID: %v", err) | ||||||
|  | 	} | ||||||
|  | 	gid, err := getIdFromString(os.Getenv("NFS_DEFAULT_GID")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		glog.Fatalf("Failed to parse NFS_DEFAULT_GID: %v", err) | ||||||
|  | 	} | ||||||
| 	kubeconfig := os.Getenv("KUBECONFIG") | 	kubeconfig := os.Getenv("KUBECONFIG") | ||||||
| 	var config *rest.Config | 	var config *rest.Config | ||||||
| 	if kubeconfig != "" { | 	if kubeconfig != "" { | ||||||
|  | @ -262,9 +348,12 @@ func main() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	clientNFSProvisioner := &nfsProvisioner{ | 	clientNFSProvisioner := &nfsProvisioner{ | ||||||
| 		client: clientset, | 		client:      clientset, | ||||||
| 		server: server, | 		server:      server, | ||||||
| 		path:   path, | 		path:        path, | ||||||
|  | 		defaultMode: mode, | ||||||
|  | 		defaultUid:  uid, | ||||||
|  | 		defaultGid:  gid, | ||||||
| 	} | 	} | ||||||
| 	// Start the provision controller which will dynamically provision efs NFS
 | 	// Start the provision controller which will dynamically provision efs NFS
 | ||||||
| 	// PVs
 | 	// PVs
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue