Skip to content

Commit

Permalink
Add annotation for setting access mode on automounted configmap/secre…
Browse files Browse the repository at this point in the history
…t files (#6750)

* Support accessmode annotation

* Support accessmode annotation

* Add ddoc

* Add test for decimal value + more doc
  • Loading branch information
feloy authored Apr 22, 2023
1 parent 32f92d1 commit 61c38c4
Show file tree
Hide file tree
Showing 12 changed files with 428 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ Mounting resources can be additionally configured via annotations:

- `devfile.io/read-only`: for persistent volume claims, mount the resource as read-only

- `devfile.io/mount-access-mode`: for secret/configmap, can be used to configure file permissions on mounted files. The value can be in octal notation between `0000` and `0777` (for example `0400`), or in decimal notation between `0` and `511`. The default value is `0644` in octal (`420` in decimal)
2 changes: 2 additions & 0 deletions pkg/configAutomount/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type AutomountInfo struct {
ReadOnly bool
// Keys defines the list of keys to mount when MountAs is Subpath
Keys []string
// MountAccessMode indicates the access mode for configmap and secret mounted as files
MountAccessMode *int32
}

type Client interface {
Expand Down
65 changes: 50 additions & 15 deletions pkg/configAutomount/kubernetes.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package configAutomount

import (
"fmt"
"path/filepath"
"sort"
"strconv"

"github.com/redhat-developer/odo/pkg/kclient"
)
Expand All @@ -11,9 +13,10 @@ const (
labelMountName = "devfile.io/auto-mount"
labelMountValue = "true"

annotationMountPathName = "devfile.io/mount-path"
annotationMountAsName = "devfile.io/mount-as"
annotationReadOnlyName = "devfile.io/read-only"
annotationMountPathName = "devfile.io/mount-path"
annotationMountAsName = "devfile.io/mount-as"
annotationReadOnlyName = "devfile.io/read-only"
annotationMountAccessMode = "devfile.io/mount-access-mode"
)

type KubernetesClient struct {
Expand Down Expand Up @@ -96,13 +99,20 @@ func (o KubernetesClient) getAutomountingSecrets() ([]AutomountInfo, error) {
}
sort.Strings(keys)
}

mountAccessMode, err := getMountAccessModeFromAnnotation(secret.Annotations)
if err != nil {
return nil, err
}

result = append(result, AutomountInfo{
VolumeType: VolumeTypeSecret,
VolumeName: secret.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: secret.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
VolumeType: VolumeTypeSecret,
VolumeName: secret.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: secret.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
MountAccessMode: mountAccessMode,
})
}
return result, nil
Expand Down Expand Up @@ -131,13 +141,20 @@ func (o KubernetesClient) getAutomountingConfigmaps() ([]AutomountInfo, error) {
}
sort.Strings(keys)
}

mountAccessMode, err := getMountAccessModeFromAnnotation(cm.Annotations)
if err != nil {
return nil, err
}

result = append(result, AutomountInfo{
VolumeType: VolumeTypeConfigmap,
VolumeName: cm.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: cm.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
VolumeType: VolumeTypeConfigmap,
VolumeName: cm.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: cm.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
MountAccessMode: mountAccessMode,
})
}
return result, nil
Expand All @@ -158,3 +175,21 @@ func getMountAsFromAnnotation(annotations map[string]string) MountAs {
return MountAsFile
}
}

func getMountAccessModeFromAnnotation(annotations map[string]string) (*int32, error) {
accessModeStr, found := annotations[annotationMountAccessMode]
if !found {
return nil, nil
}
accessMode64, err := strconv.ParseInt(accessModeStr, 0, 32)
if err != nil {
return nil, err
}

if accessMode64 < 0 || accessMode64 > 0777 {
return nil, fmt.Errorf("invalid access mode annotation: value '%s' parsed to %o (octal). Must be in range 0000-0777", accessModeStr, accessMode64)
}

accessMode32 := int32(accessMode64)
return &accessMode32, nil
}
94 changes: 94 additions & 0 deletions pkg/configAutomount/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/redhat-developer/odo/pkg/kclient"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/pointer"
)

func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
Expand Down Expand Up @@ -93,6 +94,24 @@ func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
annotationReadOnlyName: "true",
})

secretMountAccessMode := corev1.Secret{}
secretMountAccessMode.SetName("secretMountAccessMode")
secretMountAccessMode.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
secretMountAccessMode.SetAnnotations(map[string]string{
annotationMountAccessMode: "0400",
})

secretMountAccessModeInvalid := corev1.Secret{}
secretMountAccessModeInvalid.SetName("secretMountAccessModeInvalid")
secretMountAccessModeInvalid.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
secretMountAccessModeInvalid.SetAnnotations(map[string]string{
annotationMountAccessMode: "01444",
})

defaultCM1 := corev1.ConfigMap{}
defaultCM1.SetName("defaultCM1")
defaultCM1.SetLabels(map[string]string{
Expand Down Expand Up @@ -145,6 +164,24 @@ func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
annotationReadOnlyName: "true",
})

cmMountAccessMode := corev1.ConfigMap{}
cmMountAccessMode.SetName("cmMountAccessMode")
cmMountAccessMode.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
cmMountAccessMode.SetAnnotations(map[string]string{
annotationMountAccessMode: "0444",
})

cmMountAccessModeInvalid := corev1.ConfigMap{}
cmMountAccessModeInvalid.SetName("cmMountAccessModeInvalid")
cmMountAccessModeInvalid.SetLabels(map[string]string{
labelMountName: labelMountValue,
})
cmMountAccessModeInvalid.SetAnnotations(map[string]string{
annotationMountAccessMode: "01444",
})

type fields struct {
kubeClient func(ctrl *gomock.Controller) kclient.ClientInterface
}
Expand Down Expand Up @@ -330,6 +367,63 @@ func TestKubernetesClient_GetAutomountingVolumes(t *testing.T) {
},
wantErr: false,
},
{
name: "Secret and ConfigMap with mount-access-mode annotations",
fields: fields{
kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
client := kclient.NewMockClientInterface(ctrl)
client.EXPECT().ListPVCs(gomock.Any()).Return([]corev1.PersistentVolumeClaim{}, nil).AnyTimes()
client.EXPECT().ListSecrets(gomock.Any()).Return([]corev1.Secret{secretMountAccessMode}, nil).AnyTimes()
client.EXPECT().ListConfigMaps(gomock.Any()).Return([]corev1.ConfigMap{cmMountAccessMode}, nil).AnyTimes()
return client
},
},
want: []AutomountInfo{
{
VolumeType: VolumeTypeSecret,
VolumeName: "secretMountAccessMode",
MountPath: "/etc/secret/secretMountAccessMode",
MountAs: MountAsFile,
MountAccessMode: pointer.Int32(0400),
},
{
VolumeType: VolumeTypeConfigmap,
VolumeName: "cmMountAccessMode",
MountPath: "/etc/config/cmMountAccessMode",
MountAs: MountAsFile,
MountAccessMode: pointer.Int32(0444),
},
},
wantErr: false,
},
{
name: "Secret with invalid mount-access-mode annotation",
fields: fields{
kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
client := kclient.NewMockClientInterface(ctrl)
client.EXPECT().ListPVCs(gomock.Any()).Return([]corev1.PersistentVolumeClaim{}, nil).AnyTimes()
client.EXPECT().ListSecrets(gomock.Any()).Return([]corev1.Secret{secretMountAccessModeInvalid}, nil).AnyTimes()
client.EXPECT().ListConfigMaps(gomock.Any()).Return([]corev1.ConfigMap{}, nil).AnyTimes()
return client
},
},
want: nil,
wantErr: true,
},
{
name: "Configmap with invalid mount-access-mode annotation",
fields: fields{
kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
client := kclient.NewMockClientInterface(ctrl)
client.EXPECT().ListPVCs(gomock.Any()).Return([]corev1.PersistentVolumeClaim{}, nil).AnyTimes()
client.EXPECT().ListSecrets(gomock.Any()).Return([]corev1.Secret{}, nil).AnyTimes()
client.EXPECT().ListConfigMaps(gomock.Any()).Return([]corev1.ConfigMap{cmMountAccessModeInvalid}, nil).AnyTimes()
return client
},
},
want: nil,
wantErr: true,
},
{
name: "PVC, Secret and ConfigMap read-only",
fields: fields{
Expand Down
8 changes: 6 additions & 2 deletions pkg/devfile/adapters/kubernetes/storage/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ func mountSecretAsFile(volumeInfo configAutomount.AutomountInfo, containers, ini
Name: volumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: volumeInfo.VolumeName,
SecretName: volumeInfo.VolumeName,
DefaultMode: volumeInfo.MountAccessMode,
},
},
})
Expand Down Expand Up @@ -352,7 +353,8 @@ func mountSecretAsSubpath(volumeInfo configAutomount.AutomountInfo, containers,
Name: volumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: volumeInfo.VolumeName,
SecretName: volumeInfo.VolumeName,
DefaultMode: volumeInfo.MountAccessMode,
},
},
})
Expand Down Expand Up @@ -386,6 +388,7 @@ func mountConfigMapAsFile(volumeInfo configAutomount.AutomountInfo, containers,
Name: volumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
DefaultMode: volumeInfo.MountAccessMode,
LocalObjectReference: corev1.LocalObjectReference{
Name: volumeInfo.VolumeName,
},
Expand Down Expand Up @@ -429,6 +432,7 @@ func mountConfigMapAsSubpath(volumeInfo configAutomount.AutomountInfo, container
LocalObjectReference: corev1.LocalObjectReference{
Name: volumeInfo.VolumeName,
},
DefaultMode: volumeInfo.MountAccessMode,
},
},
})
Expand Down
Loading

0 comments on commit 61c38c4

Please sign in to comment.