diff --git a/cmd/provisioner-localpv/app/config.go b/cmd/provisioner-localpv/app/config.go index 40012452..ebf2ee9d 100644 --- a/cmd/provisioner-localpv/app/config.go +++ b/cmd/provisioner-localpv/app/config.go @@ -130,6 +130,19 @@ const ( KeyQuotaSoftLimit = "softLimitGrace" KeyQuotaHardLimit = "hardLimitGrace" + + // FilePermissions allows to define the default directory mode + // Exemple StorageClass snippet: + // - name: FilePermissions + // data: + // UID: 1000 + // GID: 1000 + // mode: g+s + // This is the cas-template key for all file permission 'data' keys + KeyFilePermissions = "FilePermissions" + + // FSMode defines the file permission mode of the shared directory + KeyFsMode = "mode" ) const ( @@ -170,15 +183,21 @@ func (p *Provisioner) GetVolumeConfig(ctx context.Context, pvName string, pvc *c } //TODO : extract and merge the cas volume config from pvc - //This block can be added once validation checks are added + // This block can be added once validation checks are added // as to the type of config that can be passed via PVC - //pvcCASConfigStr := pvc.ObjectMeta.Annotations[string(mconfig.CASConfigKey)] - //if len(strings.TrimSpace(pvcCASConfigStr)) != 0 { - // pvcCASConfig, err := cast.UnMarshallToConfig(pvcCASConfigStr) - // if err == nil { - // pvConfig = cast.MergeConfig(pvcCASConfig, pvConfig) - // } - //} + pvcCASConfigStr := pvc.ObjectMeta.Annotations[string(mconfig.CASConfigKey)] + klog.V(4).Infof("PVC %v has config:%v", pvc.Name, pvcCASConfigStr) + if len(strings.TrimSpace(pvcCASConfigStr)) != 0 { + pvcCASConfig, err := cast.UnMarshallToConfig(pvcCASConfigStr) + if err == nil { + pvConfig = cast.MergeConfig(pvConfig, pvcCASConfig) + } else { + return nil, errors.Wrapf(err, "failed to get config: invalid config {%v}"+ + " in pvc {%v} in namespace {%v}", + pvcCASConfigStr, pvc.Name, pvc.Namespace, + ) + } + } pvConfigMap, err := cast.ConfigToMap(pvConfig) if err != nil { @@ -342,6 +361,19 @@ func (c *VolumeConfig) IsExt4QuotaEnabled() bool { return enableExt4QuotaBool } +// GetFsMode fetches the file mode from PVC +// or StorageClass annotation, if specified +func (c *VolumeConfig) GetFsMode() string { + configData := c.getData(KeyFilePermissions) + if configData != nil { + if val, p := configData[KeyFsMode]; p { + return strings.TrimSpace(val) + } + } + //Keep the original default mode + return "" +} + // getValue is a utility function to extract the value // of the `key` from the ConfigMap object - which is // map[string]interface{map[string][string]} diff --git a/cmd/provisioner-localpv/app/config_test.go b/cmd/provisioner-localpv/app/config_test.go index 477a3fcd..64ae37e1 100644 --- a/cmd/provisioner-localpv/app/config_test.go +++ b/cmd/provisioner-localpv/app/config_test.go @@ -105,6 +105,43 @@ func TestDataConfigToMap(t *testing.T) { } } +func TestPermissionConfigToMap(t *testing.T) { + hostpathConfig := mconfig.Config{Name: "StorageType", Value: "hostpath"} + permissionConfig := mconfig.Config{Name: "FilePermissions", + Data: map[string]string{ + "mode": "0750", + }, + } + + testCases := map[string]struct { + config []mconfig.Config + expectedValue map[string]interface{} + }{ + "nil 'Data' map": { + config: []mconfig.Config{hostpathConfig, permissionConfig}, + expectedValue: map[string]interface{}{ + "FilePermissions": map[string]string{ + "mode": "0750", + }, + }, + }, + } + + for k, v := range testCases { + v := v + k := k + t.Run(k, func(t *testing.T) { + actualValue, err := dataConfigToMap(v.config) + if err != nil { + t.Errorf("expected error to be nil, but got %v", err) + } + if !reflect.DeepEqual(actualValue, v.expectedValue) { + t.Errorf("expected %v, but got %v", v.expectedValue, actualValue) + } + }) + } +} + func Test_listConfigToMap(t *testing.T) { tests := map[string]struct { pvConfig []mconfig.Config diff --git a/cmd/provisioner-localpv/app/provisioner_hostpath.go b/cmd/provisioner-localpv/app/provisioner_hostpath.go index c1b88af4..a21d16bd 100644 --- a/cmd/provisioner-localpv/app/provisioner_hostpath.go +++ b/cmd/provisioner-localpv/app/provisioner_hostpath.go @@ -77,7 +77,12 @@ func (p *Provisioner) ProvisionHostPath(ctx context.Context, opts pvController.P klog.Infof("Creating volume %v at node with labels {%v}, path:%v,ImagePullSecrets:%v", name, nodeAffinityLabels, path, imagePullSecrets) //Before using the path for local PV, make sure it is created. - initCmdsForPath := []string{"mkdir", "-m", "0777", "-p"} + fsMode := volumeConfig.GetFsMode() + // Set default value if FilePermissions mode is not specified. + if len(fsMode) == 0 { + fsMode = "0777" + } + initCmdsForPath := []string{"mkdir", "-m", fsMode, "-p"} podOpts := &HelperPodOptions{ cmdsForPath: initCmdsForPath, name: name, diff --git a/docs/quickstart.md b/docs/quickstart.md index e0a4157b..1ecb5ee5 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -2,7 +2,7 @@ ## Prerequisites -A Kubernetes cluster with Kubernetes v1.16 or above. +A Kubernetes cluster with Kubernetes v1.16 or above. For more platform-specific installation instructions, [click here](./installation/platforms/). @@ -13,12 +13,12 @@ Install OpenEBS Dynamic LocalPV Provisioner using the openebs helm chart. Sample #helm repo update helm install openebs openebs/openebs -n openebs --create-namespace ``` - +
Click here for configuration options. - 1. Install OpenEBS Dynamic LocalPV Provisioner without NDM. - + 1. Install OpenEBS Dynamic LocalPV Provisioner without NDM. + You may choose to exclude the NDM subchart from installation if... - you want to only use OpenEBS LocalPV Hostpath - you already have NDM installed. Check if NDM pods exist with the command `kubectl get pods -n openebs -l 'openebs.io/component-name in (ndm, ndm-operator)'` @@ -35,7 +35,7 @@ helm install openebs openebs/openebs -n openebs --create-namespace \ --set ndmOperator.enabled=false \ --set localprovisioner.deviceClass.enabled=false ``` - 3. Install OpenEBS Dynamic LocalPV Provisioner with a custom hostpath directory. + 3. Install OpenEBS Dynamic LocalPV Provisioner with a custom hostpath directory. This will change the `BasePath` value for the 'openebs-hostpath' StorageClass. ```console helm install openebs openebs/openebs -n openebs --create-namespace \ @@ -92,6 +92,10 @@ You can provision LocalPV hostpath StorageType volumes dynamically using the def # hostpath directory #- name: BasePath # value: "/var/openebs/local" + #Use this to set a specific mode for directory creation + #- name: FilePermissions + # data: + # mode: "0770" provisioner: openebs.io/local reclaimPolicy: Delete #It is necessary to have volumeBindingMode as WaitForFirstConsumer diff --git a/docs/tutorials/hostpath/filepermissions.md b/docs/tutorials/hostpath/filepermissions.md new file mode 100644 index 00000000..c9e38aa7 --- /dev/null +++ b/docs/tutorials/hostpath/filepermissions.md @@ -0,0 +1,52 @@ +# File permission tuning + +Hostpath LocalPV will by default create folder with the following rights: `0777`. In some usecases, these rights are too wide and should be reduced. +As an important point, when using hostpath the underlying PV will be a localpath whichs allows kubelet to chown the folder based on the [fsGroup](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#configure-volume-permission-and-ownership-change-policy-for-pods)) + +We allow to set file permissions using: + +```yaml + #This is a custom StorageClass template + apiVersion: storage.k8s.io/v1 + kind: StorageClass + metadata: + name: custom-hostpath + annotations: + openebs.io/cas-type: local + cas.openebs.io/config: | + - name: StorageType + value: "hostpath" + - name: BasePath + value: "/var/openebs/local" + - name: FilePermissions + data: + mode: "0770" + provisioner: openebs.io/local + reclaimPolicy: Delete + #It is necessary to have volumeBindingMode as WaitForFirstConsumer + volumeBindingMode: WaitForFirstConsumer +``` + +With such configuration the folder will be crated with `0770` rights for all the PVC using this storage class. + +The same configuration is available at PVC level to have a more fined grained configuration capability (the Storage class configuration will always win against PVC one): + +```yaml +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: localpv-vol + annotations: + cas.openebs.io/config: | + - name: FilePermissions + data: + mode: "0770" +spec: + #Change this name if you are using a custom StorageClass + storageClassName: openebs-hostpath + accessModes: ["ReadWriteOnce"] + resources: + requests: + #Set capacity here + storage: 5Gi +```