diff --git a/changelogs/unreleased/8383-mayankagg9722 b/changelogs/unreleased/8383-mayankagg9722 new file mode 100644 index 0000000000..bd7581bee0 --- /dev/null +++ b/changelogs/unreleased/8383-mayankagg9722 @@ -0,0 +1 @@ +Adding support in velero Resource Policies for filtering PVs based on additional VolumeAttributes properties under CSI PVs \ No newline at end of file diff --git a/design/Implemented/supporting-volumeattributes-resource-policy.md b/design/Implemented/supporting-volumeattributes-resource-policy.md new file mode 100644 index 0000000000..a4f4d1bfc4 --- /dev/null +++ b/design/Implemented/supporting-volumeattributes-resource-policy.md @@ -0,0 +1,84 @@ +# Adding Support For VolumeAttributes in Resource Policy + +## Abstract +Currently [Velero Resource policies](https://velero.io/docs/main/resource-filtering/#creating-resource-policies) are only supporting "Driver" to be filtered for [CSI volume conditions](https://github.com/vmware-tanzu/velero/blob/8e23752a6ea83f101bd94a69dcf17f519a805388/internal/resourcepolicies/volume_resources_validator.go#L28) + +If user want to skip certain CSI volumes based on other volume attributes like protocol or SKU, etc, they can't do it with the current Velero resource policies. It would be convenient if Velero resource policies could be extended to filter on volume attributes along with existing driver filter in the resource policies `conditions` to handle the backup of volumes just by `some specific volumes attributes conditions`. + +## Background +As of Today, Velero resource policy already provides us the way to filter volumes based on the `driver` name. But it's not enough to handle the volumes based on other volume attributes like protocol, SKU, etc. + +## Example: + - Provision Azure NFS: Define the Storage class with `protocol: nfs` under storage class parameters to provision [CSI NFS Azure File Shares](https://learn.microsoft.com/en-us/azure/aks/azure-files-csi#nfs-file-shares). + - User wants to back up AFS (Azure file shares) but only want to backup `SMB` type of file share volumes and not `NFS` file share volumes. + +## Goals +- We are only bringing additional support in the resource policy to only handle volumes during backup. +- Introducing support for `VolumeAttributes` filter along with `driver` filter in CSI volume conditions to handle volumes. + +## Non-Goals +- Currently, only handles volumes, and does not support other resources. + +## Use-cases/Scenarios +### Skip backup volumes by some volume attributes: +Users want to skip PV with the requirements: +- option to skip specified PV on volume attributes type (like Protocol as NFS, SMB, etc) + +### Sample Storage Class Used to create such Volumes +``` +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: azurefile-csi-nfs +provisioner: file.csi.azure.com +allowVolumeExpansion: true +parameters: + protocol: nfs +``` + +## High-Level Design +Modifying the existing Resource Policies code for [csiVolumeSource](https://github.com/vmware-tanzu/velero/blob/8e23752a6ea83f101bd94a69dcf17f519a805388/internal/resourcepolicies/volume_resources_validator.go#L28C6-L28C22) to add the new `VolumeAttributes` filter for CSI volumes and adding validations in existing [csiCondition](https://github.com/vmware-tanzu/velero/blob/8e23752a6ea83f101bd94a69dcf17f519a805388/internal/resourcepolicies/volume_resources.go#L150) to match with volume attributes in the conditions from Resource Policy config map and original persistent volume. + +## Detailed Design +The volume resources policies should contain a list of policies which is the combination of conditions and related `action`, when target volumes meet the conditions, the related `action` will take effection. + +Below is the API Design for the user configuration: + +### API Design +```go +type csiVolumeSource struct { + Driver string `yaml:"driver,omitempty"` + // [NEW] CSI volume attributes + VolumeAttributes map[string]string `yaml:"volumeAttributes,omitempty"` +} +``` + +The policies YAML config file would look like this: +```yaml +version: v1 +volumePolicies: + - conditions: + csi: + driver: disk.csi.azure.com + action: + type: skip + - conditions: + csi: + driver: file.csi.azure.com + volumeAttributes: + protocol: nfs + action: + type: skip` +``` + +### New Supported Conditions +#### VolumeAttributes +Existing CSI Volume Condition can now add `volumeAttributes` which will be key and value pairs. + + Specify details for the related volume source (currently only csi driver is supported filter) + ```yaml + csi: // match volume using `file.csi.azure.com` and with volumeAttributes protocol as nfs + driver: file.csi.azure.com + volumeAttributes: + protocol: nfs + ``` \ No newline at end of file diff --git a/internal/resourcepolicies/resource_policies_test.go b/internal/resourcepolicies/resource_policies_test.go index e21458b2d1..53e79a1342 100644 --- a/internal/resourcepolicies/resource_policies_test.go +++ b/internal/resourcepolicies/resource_policies_test.go @@ -93,20 +93,32 @@ func TestLoadResourcePolicies(t *testing.T) { wantErr: true, }, { - name: "supported formart volume policies", + name: "supported format volume policies", yamlData: `version: v1 - volumePolicies: - - conditions: - capacity: "0,100Gi" - csi: - driver: aws.efs.csi.driver - nfs: {} - storageClass: - - gp2 - - ebs-sc - action: - type: skip`, - wantErr: true, +volumePolicies: + - conditions: + capacity: '0,100Gi' + csi: + driver: aws.efs.csi.driver + action: + type: skip +`, + wantErr: false, + }, + { + name: "supported format csi driver with volumeAttributes for volume policies", + yamlData: `version: v1 +volumePolicies: + - conditions: + capacity: '0,100Gi' + csi: + driver: aws.efs.csi.driver + volumeAttributes: + key1: value1 + action: + type: skip +`, + wantErr: false, }, } for _, tc := range testCases { @@ -135,6 +147,16 @@ func TestGetResourceMatchedAction(t *testing.T) { }), }, }, + { + Action: Action{Type: "skip"}, + Conditions: map[string]interface{}{ + "csi": interface{}( + map[string]interface{}{ + "driver": "files.csi.driver", + "volumeAttributes": map[string]string{"protocol": "nfs"}, + }), + }, + }, { Action: Action{Type: "snapshot"}, Conditions: map[string]interface{}{ @@ -172,6 +194,24 @@ func TestGetResourceMatchedAction(t *testing.T) { }, expectedAction: &Action{Type: "skip"}, }, + { + name: "match policy AFS NFS", + volume: &structuredVolume{ + capacity: *resource.NewQuantity(5<<30, resource.BinarySI), + storageClass: "afs-nfs", + csi: &csiVolumeSource{Driver: "files.csi.driver", VolumeAttributes: map[string]string{"protocol": "nfs"}}, + }, + expectedAction: &Action{Type: "skip"}, + }, + { + name: "match policy AFS SMB", + volume: &structuredVolume{ + capacity: *resource.NewQuantity(5<<30, resource.BinarySI), + storageClass: "afs-smb", + csi: &csiVolumeSource{Driver: "files.csi.driver"}, + }, + expectedAction: nil, + }, { name: "both matches return the first policy", volume: &structuredVolume{ @@ -226,7 +266,7 @@ func TestGetResourcePoliciesFromConfig(t *testing.T) { Namespace: "test-namespace", }, Data: map[string]string{ - "test-data": "version: v1\nvolumePolicies:\n- conditions:\n capacity: '0,10Gi'\n action:\n type: skip", + "test-data": "version: v1\nvolumePolicies:\n - conditions:\n capacity: '0,10Gi'\n csi:\n driver: disks.csi.driver\n action:\n type: skip\n - conditions:\n csi:\n driver: files.csi.driver\n volumeAttributes:\n protocol: nfs\n action:\n type: skip", }, } @@ -236,13 +276,27 @@ func TestGetResourcePoliciesFromConfig(t *testing.T) { // Check that the returned resourcePolicies object contains the expected data assert.Equal(t, "v1", resPolicies.version) - assert.Len(t, resPolicies.volumePolicies, 1) + assert.Len(t, resPolicies.volumePolicies, 2) policies := ResourcePolicies{ Version: "v1", VolumePolicies: []VolumePolicy{ { Conditions: map[string]interface{}{ "capacity": "0,10Gi", + "csi": map[string]interface{}{ + "driver": "disks.csi.driver", + }, + }, + Action: Action{ + Type: Skip, + }, + }, + { + Conditions: map[string]interface{}{ + "csi": map[string]interface{}{ + "driver": "files.csi.driver", + "volumeAttributes": map[string]string{"protocol": "nfs"}, + }, }, Action: Action{ Type: Skip, @@ -298,7 +352,173 @@ volumePolicies: skip: false, }, { - name: "csi not configured", + name: "Skip AFS CSI condition with Disk volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: files.csi.driver + action: + type: skip`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "disks.csi.driver"}, + }}, + }, + skip: false, + }, + { + name: "Skip AFS CSI condition with AFS volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: files.csi.driver + action: + type: skip`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "files.csi.driver"}, + }}, + }, + skip: true, + }, + { + name: "Skip AFS NFS CSI condition with Disk volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: files.csi.driver + volumeAttributes: + protocol: nfs + action: + type: skip +`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "disks.csi.driver"}, + }}, + }, + skip: false, + }, + { + name: "Skip AFS NFS CSI condition with AFS SMB volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: files.csi.driver + volumeAttributes: + protocol: nfs + action: + type: skip +`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "files.csi.driver", VolumeAttributes: map[string]string{"key1": "val1"}}, + }}, + }, + skip: false, + }, + { + name: "Skip AFS NFS CSI condition with AFS NFS volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: files.csi.driver + volumeAttributes: + protocol: nfs + action: + type: skip +`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "files.csi.driver", VolumeAttributes: map[string]string{"protocol": "nfs"}}, + }}, + }, + skip: true, + }, + { + name: "Skip Disk and AFS NFS CSI condition with Disk volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: disks.csi.driver + action: + type: skip + - conditions: + csi: + driver: files.csi.driver + volumeAttributes: + protocol: nfs + action: + type: skip`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "disks.csi.driver", VolumeAttributes: map[string]string{"key1": "val1"}}, + }}, + }, + skip: true, + }, + { + name: "Skip Disk and AFS NFS CSI condition with AFS SMB volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: disks.csi.driver + action: + type: skip + - conditions: + csi: + driver: files.csi.driver + volumeAttributes: + protocol: nfs + action: + type: skip`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "files.csi.driver", VolumeAttributes: map[string]string{"key1": "val1"}}, + }}, + }, + skip: false, + }, + { + name: "Skip Disk and AFS NFS CSI condition with AFS NFS volumes", + yamlData: `version: v1 +volumePolicies: + - conditions: + csi: + driver: disks.csi.driver + action: + type: skip + - conditions: + csi: + driver: files.csi.driver + volumeAttributes: + protocol: nfs + action: + type: skip`, + vol: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{Driver: "files.csi.driver", VolumeAttributes: map[string]string{"key1": "val1", "protocol": "nfs"}}, + }}, + }, + skip: true, + }, + { + name: "csi not configured and testing capacity condition", yamlData: `version: v1 volumePolicies: - conditions: diff --git a/internal/resourcepolicies/volume_resources.go b/internal/resourcepolicies/volume_resources.go index fd1b8182ac..6abdb0648e 100644 --- a/internal/resourcepolicies/volume_resources.go +++ b/internal/resourcepolicies/volume_resources.go @@ -60,7 +60,7 @@ func (s *structuredVolume) parsePV(pv *corev1api.PersistentVolume) { csi := pv.Spec.CSI if csi != nil { - s.csi = &csiVolumeSource{Driver: csi.Driver} + s.csi = &csiVolumeSource{Driver: csi.Driver, VolumeAttributes: csi.VolumeAttributes} } s.volumeType = getVolumeTypeFromPV(pv) @@ -74,7 +74,7 @@ func (s *structuredVolume) parsePodVolume(vol *corev1api.Volume) { csi := vol.CSI if csi != nil { - s.csi = &csiVolumeSource{Driver: csi.Driver} + s.csi = &csiVolumeSource{Driver: csi.Driver, VolumeAttributes: csi.VolumeAttributes} } s.volumeType = getVolumeTypeFromVolume(vol) @@ -160,7 +160,25 @@ func (c *csiCondition) match(v *structuredVolume) bool { return false } - return c.csi.Driver == v.csi.Driver + if c.csi.Driver != v.csi.Driver { + return false + } + + if len(c.csi.VolumeAttributes) == 0 { + return true + } + + if len(v.csi.VolumeAttributes) == 0 { + return false + } + + for key, value := range c.csi.VolumeAttributes { + if value != v.csi.VolumeAttributes[key] { + return false + } + } + + return true } // parseCapacity parse string into capacity format diff --git a/internal/resourcepolicies/volume_resources_test.go b/internal/resourcepolicies/volume_resources_test.go index 4d5d7a743a..9eca07ef75 100644 --- a/internal/resourcepolicies/volume_resources_test.go +++ b/internal/resourcepolicies/volume_resources_test.go @@ -201,23 +201,47 @@ func TestCSIConditionMatch(t *testing.T) { expectedMatch bool }{ { - name: "match csi condition", + name: "match csi driver condition", condition: &csiCondition{&csiVolumeSource{Driver: "test"}}, volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test"}), expectedMatch: true, }, { - name: "empty csi condition", + name: "empty csi driver condition", condition: &csiCondition{nil}, volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test"}), expectedMatch: true, }, { - name: "empty csi volume", + name: "empty csi driver volume", condition: &csiCondition{&csiVolumeSource{Driver: "test"}}, volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{}), expectedMatch: false, }, + { + name: "match csi volumeAttributes condition", + condition: &csiCondition{&csiVolumeSource{Driver: "test", VolumeAttributes: map[string]string{"protocol": "nfs"}}}, + volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test", VolumeAttributes: map[string]string{"protocol": "nfs"}}), + expectedMatch: true, + }, + { + name: "empty csi volumeAttributes condition", + condition: &csiCondition{&csiVolumeSource{Driver: "test"}}, + volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test", VolumeAttributes: map[string]string{"protocol": "nfs"}}), + expectedMatch: true, + }, + { + name: "empty csi volumeAttributes volume", + condition: &csiCondition{&csiVolumeSource{Driver: "test", VolumeAttributes: map[string]string{"protocol": "nfs"}}}, + volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test", VolumeAttributes: map[string]string{"protocol": ""}}), + expectedMatch: false, + }, + { + name: "empty csi volumeAttributes volume", + condition: &csiCondition{&csiVolumeSource{Driver: "test", VolumeAttributes: map[string]string{"protocol": "nfs"}}}, + volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", nil, &csiVolumeSource{Driver: "test"}), + expectedMatch: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -302,7 +326,8 @@ func TestParsePodVolume(t *testing.T) { } csiVolume := corev1api.Volume{} csiVolume.CSI = &corev1api.CSIVolumeSource{ - Driver: "csi.example.com", + Driver: "csi.example.com", + VolumeAttributes: map[string]string{"protocol": "nfs"}, } emptyVolume := corev1api.Volume{} @@ -321,7 +346,7 @@ func TestParsePodVolume(t *testing.T) { { name: "CSI volume", inputVolume: &csiVolume, - expectedCSI: &csiVolumeSource{Driver: "csi.example.com"}, + expectedCSI: &csiVolumeSource{Driver: "csi.example.com", VolumeAttributes: map[string]string{"protocol": "nfs"}}, }, { name: "Empty volume", @@ -348,9 +373,19 @@ func TestParsePodVolume(t *testing.T) { if tc.expectedCSI != nil { if structuredVolume.csi == nil { t.Errorf("Expected a non-nil CSI volume source") - } else if *tc.expectedCSI != *structuredVolume.csi { + } else if tc.expectedCSI.Driver != structuredVolume.csi.Driver { t.Errorf("CSI volume source does not match expected value") } + // Check volumeAttributes + if len(tc.expectedCSI.VolumeAttributes) != len(structuredVolume.csi.VolumeAttributes) { + t.Errorf("CSI volume attributes does not match expected value") + } else { + for k, v := range tc.expectedCSI.VolumeAttributes { + if structuredVolume.csi.VolumeAttributes[k] != v { + t.Errorf("CSI volume attributes does not match expected value") + } + } + } } }) } @@ -363,7 +398,7 @@ func TestParsePV(t *testing.T) { nfsVolume.Spec.NFS = &corev1api.NFSVolumeSource{Server: "nfs.example.com", Path: "/exports/data"} csiVolume := corev1api.PersistentVolume{} csiVolume.Spec.Capacity = corev1api.ResourceList{corev1api.ResourceStorage: resource.MustParse("2Gi")} - csiVolume.Spec.CSI = &corev1api.CSIPersistentVolumeSource{Driver: "csi.example.com"} + csiVolume.Spec.CSI = &corev1api.CSIPersistentVolumeSource{Driver: "csi.example.com", VolumeAttributes: map[string]string{"protocol": "nfs"}} emptyVolume := corev1api.PersistentVolume{} // Test cases @@ -383,7 +418,7 @@ func TestParsePV(t *testing.T) { name: "CSI volume", inputVolume: &csiVolume, expectedNFS: nil, - expectedCSI: &csiVolumeSource{Driver: "csi.example.com"}, + expectedCSI: &csiVolumeSource{Driver: "csi.example.com", VolumeAttributes: map[string]string{"protocol": "nfs"}}, }, { name: "Empty volume", @@ -415,9 +450,19 @@ func TestParsePV(t *testing.T) { if tc.expectedCSI != nil { if structuredVolume.csi == nil { t.Errorf("Expected a non-nil CSI volume source") - } else if *tc.expectedCSI != *structuredVolume.csi { + } else if tc.expectedCSI.Driver != structuredVolume.csi.Driver { t.Errorf("CSI volume source does not match expected value") } + // Check volumeAttributes + if len(tc.expectedCSI.VolumeAttributes) != len(structuredVolume.csi.VolumeAttributes) { + t.Errorf("CSI volume attributes does not match expected value") + } else { + for k, v := range tc.expectedCSI.VolumeAttributes { + if structuredVolume.csi.VolumeAttributes[k] != v { + t.Errorf("CSI volume attributes does not match expected value") + } + } + } } }) } diff --git a/internal/resourcepolicies/volume_resources_validator.go b/internal/resourcepolicies/volume_resources_validator.go index cf9a40c0fc..f2ca97a300 100644 --- a/internal/resourcepolicies/volume_resources_validator.go +++ b/internal/resourcepolicies/volume_resources_validator.go @@ -27,6 +27,8 @@ const currentSupportDataVersion = "v1" type csiVolumeSource struct { Driver string `yaml:"driver,omitempty"` + // CSI volume attributes + VolumeAttributes map[string]string `yaml:"volumeAttributes,omitempty"` } type nFSVolumeSource struct { @@ -68,7 +70,10 @@ func (c *nfsCondition) validate() error { } func (c *csiCondition) validate() error { - // validate by yamlv3 + if c != nil && c.csi != nil && c.csi.Driver == "" && c.csi.VolumeAttributes != nil { + return errors.New("csi driver should not be empty when filtering by volume attributes") + } + return nil } diff --git a/internal/resourcepolicies/volume_resources_validator_test.go b/internal/resourcepolicies/volume_resources_validator_test.go index 1cbc6d7325..a74bbc52fa 100644 --- a/internal/resourcepolicies/volume_resources_validator_test.go +++ b/internal/resourcepolicies/volume_resources_validator_test.go @@ -165,6 +165,47 @@ func TestValidate(t *testing.T) { }, wantErr: true, }, + { + name: "error format of csi driver", + res: &ResourcePolicies{ + Version: "v1", + VolumePolicies: []VolumePolicy{ + { + Action: Action{Type: "skip"}, + Conditions: map[string]interface{}{ + "capacity": "0,10Gi", + "storageClass": []string{"gp2", "ebs-sc"}, + "csi": interface{}( + map[string]interface{}{ + "driver": []string{"aws.efs.csi.driver"}, + }), + }, + }, + }, + }, + wantErr: true, + }, + { + name: "error format of csi driver volumeAttributes", + res: &ResourcePolicies{ + Version: "v1", + VolumePolicies: []VolumePolicy{ + { + Action: Action{Type: "skip"}, + Conditions: map[string]interface{}{ + "capacity": "0,10Gi", + "storageClass": []string{"gp2", "ebs-sc"}, + "csi": interface{}( + map[string]interface{}{ + "driver": "aws.efs.csi.driver", + "volumeAttributes": "test", + }), + }, + }, + }, + }, + wantErr: true, + }, { name: "unsupported version", res: &ResourcePolicies{ @@ -220,6 +261,65 @@ func TestValidate(t *testing.T) { }, wantErr: true, }, + { + name: "supported format volume policies only csi driver", + res: &ResourcePolicies{ + Version: "v1", + VolumePolicies: []VolumePolicy{ + { + Action: Action{Type: "skip"}, + Conditions: map[string]interface{}{ + "csi": interface{}( + map[string]interface{}{ + "driver": "aws.efs.csi.driver", + }), + }, + }, + }, + }, + wantErr: false, + }, + { + name: "unsupported format volume policies only csi volumeattributes", + res: &ResourcePolicies{ + Version: "v1", + VolumePolicies: []VolumePolicy{ + { + Action: Action{Type: "skip"}, + Conditions: map[string]interface{}{ + "csi": interface{}( + map[string]interface{}{ + "volumeAttributes": map[string]string{ + "key1": "value1", + }, + }), + }, + }, + }, + }, + wantErr: true, + }, + { + name: "supported format volume policies with csi driver and volumeattributes", + res: &ResourcePolicies{ + Version: "v1", + VolumePolicies: []VolumePolicy{ + { + Action: Action{Type: "skip"}, + Conditions: map[string]interface{}{ + "csi": interface{}( + map[string]interface{}{ + "driver": "aws.efs.csi.driver", + "volumeAttributes": map[string]string{ + "key1": "value1", + }, + }), + }, + }, + }, + }, + wantErr: false, + }, { name: "supported format volume policies", res: &ResourcePolicies{