Configurable mode and ownership via PVC annotations and provider wide defaults

This commit is contained in:
Floodpants 2025-06-13 18:49:14 +10:00
parent 59a3ca2cd1
commit c147d7263e
4 changed files with 119 additions and 9 deletions

View File

@ -293,6 +293,18 @@ spec:
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
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.

View File

@ -57,6 +57,12 @@ spec:
value: {{ .Values.nfs.server }}
- name: 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 }}
- name: ENABLE_LEADER_ELECTION
value: "false"

View File

@ -12,6 +12,9 @@ nfs:
path: /nfs-storage
mountOptions:
volumeName: nfs-subdir-external-provisioner-root
defaultMode: "777"
defaultUid: "0"
defaultGid: "0"
# Reclaim policy for the main nfs volume
reclaimPolicy: Retain

View File

@ -44,9 +44,12 @@ const (
)
type nfsProvisioner struct {
client kubernetes.Interface
server string
path string
client kubernetes.Interface
server string
path string
defaultMode os.FileMode
defaultUid int
defaultGid int
}
type pvcMetadata struct {
@ -74,7 +77,8 @@ func (meta *pvcMetadata) stringParser(str string) string {
}
const (
mountPath = "/persistentvolumes"
mountPath = "/persistentvolumes"
annotationPrefix = "k8s-sigs.io"
)
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)
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())
}
err := os.Chmod(fullPath, 0o777)
err := os.Chmod(fullPath, mode)
if err != nil {
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{
ObjectMeta: metav1.ObjectMeta{
Name: options.PVName,
@ -205,6 +248,36 @@ func (p *nfsProvisioner) getClassForVolume(ctx context.Context, pv *v1.Persisten
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() {
flag.Parse()
flag.Set("logtostderr", "true")
@ -221,6 +294,19 @@ func main() {
if provisionerName == "" {
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")
var config *rest.Config
if kubeconfig != "" {
@ -262,9 +348,12 @@ func main() {
}
clientNFSProvisioner := &nfsProvisioner{
client: clientset,
server: server,
path: path,
client: clientset,
server: server,
path: path,
defaultMode: mode,
defaultUid: uid,
defaultGid: gid,
}
// Start the provision controller which will dynamically provision efs NFS
// PVs