Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions charts/nfs-subdir-external-provisioner/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
107 changes: 98 additions & 9 deletions cmd/nfs-subdir-external-provisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -74,7 +77,8 @@ func (meta *pvcMetadata) stringParser(str string) string {
}

const (
mountPath = "/persistentvolumes"
mountPath = "/persistentvolumes"
annotationPrefix = "k8s-sigs.io"
)

var _ controller.Provisioner = &nfsProvisioner{}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand All @@ -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 != "" {
Expand Down Expand Up @@ -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
Expand Down