Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support to specify file permissions for pvc hostpaths #157

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
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
48 changes: 40 additions & 8 deletions cmd/provisioner-localpv/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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]}
Expand Down
37 changes: 37 additions & 0 deletions cmd/provisioner-localpv/app/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion cmd/provisioner-localpv/app/provisioner_hostpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 9 additions & 5 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/).

Expand All @@ -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
```

<details>
<summary>Click here for configuration options.</summary>

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)'`
Expand All @@ -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 \
Expand Down Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions docs/tutorials/hostpath/filepermissions.md
Original file line number Diff line number Diff line change
@@ -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
```