Configurable mode and ownership via PVC annotations and provider wide defaults
This commit is contained in:
parent
59a3ca2cd1
commit
c147d7263e
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