feat(resourcepolicies): support PVC volume mode and access mode matching

Signed-off-by: chlins <chlins.zhang@gmail.com>
This commit is contained in:
chlins
2026-07-16 11:26:41 +08:00
parent 2190f87950
commit 97a0375a8d
8 changed files with 745 additions and 69 deletions
@@ -155,10 +155,35 @@ func unmarshalResourcePolicies(yamlData *string) (*ResourcePolicies, error) {
return nil, fmt.Errorf("pvcLabels must be a map of string to string, got %T", raw)
}
}
if raw, ok := vp.Conditions["pvcVolumeMode"]; ok {
if _, ok := raw.(string); !ok {
return nil, fmt.Errorf("pvcVolumeMode must be a string, got %T", raw)
}
}
if raw, ok := vp.Conditions["pvcAccessModes"]; ok {
if err := validateStringSliceCondition("pvcAccessModes", raw); err != nil {
return nil, err
}
}
}
return resPolicies, nil
}
func validateStringSliceCondition(name string, raw any) error {
switch values := raw.(type) {
case []any:
for _, value := range values {
if _, ok := value.(string); !ok {
return fmt.Errorf("%s must be a list of strings, got element %T", name, value)
}
}
case []string:
default:
return fmt.Errorf("%s must be a list of strings, got %T", name, raw)
}
return nil
}
func (p *Policies) BuildPolicy(resPolicies *ResourcePolicies) error {
for _, vp := range resPolicies.VolumePolicies {
con, err := unmarshalVolConditions(vp.Conditions)
@@ -182,6 +207,12 @@ func (p *Policies) BuildPolicy(resPolicies *ResourcePolicies) error {
if len(con.PVCPhase) > 0 {
volP.conditions = append(volP.conditions, &pvcPhaseCondition{phases: con.PVCPhase})
}
if con.PVCVolumeMode != "" {
volP.conditions = append(volP.conditions, &pvcVolumeModeCondition{volumeMode: con.PVCVolumeMode})
}
if len(con.PVCAccessModes) > 0 {
volP.conditions = append(volP.conditions, &pvcAccessModesCondition{accessModes: con.PVCAccessModes})
}
p.volumePolicies = append(p.volumePolicies, volP)
}
@@ -25,6 +25,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func pvcVolumeMode(mode corev1api.PersistentVolumeMode) *corev1api.PersistentVolumeMode {
return &mode
}
func TestLoadResourcePolicies(t *testing.T) {
testCases := []struct {
name string
@@ -158,6 +162,52 @@ volumePolicies:
`,
wantErr: false,
},
{
name: "supported format pvcVolumeMode",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
action:
type: skip
`,
wantErr: false,
},
{
name: "error format of pvcVolumeMode (not a string)",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode:
- Block
action:
type: skip
`,
wantErr: true,
},
{
name: "supported format pvcAccessModes",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes:
- ReadWriteOnce
action:
type: skip
`,
wantErr: false,
},
{
name: "error format of pvcAccessModes (not a list)",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ReadWriteOnce
action:
type: skip
`,
wantErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -1046,6 +1096,271 @@ volumePolicies:
},
skip: true,
},
{
name: "PVC volume mode matching - Block volume mode should skip",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-block",
},
Spec: corev1api.PersistentVolumeClaimSpec{
VolumeMode: pvcVolumeMode(corev1api.PersistentVolumeBlock),
},
},
skip: true,
},
{
name: "PVC volume mode matching - Filesystem volume mode should not skip",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-filesystem",
},
Spec: corev1api.PersistentVolumeClaimSpec{
VolumeMode: pvcVolumeMode(corev1api.PersistentVolumeFilesystem),
},
},
skip: false,
},
{
name: "PVC volume mode matching - nil volume mode should not match Filesystem",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Filesystem
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-without-volume-mode",
},
},
skip: false,
},
{
name: "PVC volume mode matching - unknown condition value should not match empty volume mode",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode: foo
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-without-volume-mode",
},
},
skip: false,
},
{
name: "PVC volume mode matching - omitted condition should not restrict volume mode",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ["ReadWriteOnce"]
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-block-rwo",
},
Spec: corev1api.PersistentVolumeClaimSpec{
VolumeMode: pvcVolumeMode(corev1api.PersistentVolumeBlock),
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteOnce},
},
},
skip: true,
},
{
name: "PVC volume mode matching - non-PVC volume should not match",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Filesystem
action:
type: skip`,
vol: nil,
podVol: &corev1api.Volume{
Name: "empty-dir-volume",
VolumeSource: corev1api.VolumeSource{
EmptyDir: &corev1api.EmptyDirVolumeSource{},
},
},
pvc: nil,
skip: false,
},
{
name: "PVC access modes matching - non-PVC volume should not match",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ["ReadWriteOnce"]
action:
type: skip`,
vol: nil,
podVol: &corev1api.Volume{
Name: "configmap-volume",
VolumeSource: corev1api.VolumeSource{
ConfigMap: &corev1api.ConfigMapVolumeSource{},
},
},
pvc: nil,
skip: false,
},
{
name: "PVC access modes matching - ReadWriteOnce should skip",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ["ReadWriteOnce"]
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-rwo",
},
Spec: corev1api.PersistentVolumeClaimSpec{
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteOnce},
},
},
skip: true,
},
{
name: "PVC access modes matching - extra PVC access mode should not skip",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ["ReadWriteOnce"]
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-rwo-rom",
},
Spec: corev1api.PersistentVolumeClaimSpec{
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteOnce, corev1api.ReadOnlyMany},
},
},
skip: false,
},
{
name: "PVC access modes matching - ReadWriteMany should not skip",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ["ReadWriteOnce"]
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-rwx",
},
Spec: corev1api.PersistentVolumeClaimSpec{
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteMany},
},
},
skip: false,
},
{
name: "PVC access modes matching - exact access mode set should match regardless of order",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ["ReadWriteMany", "ReadOnlyMany"]
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-rom-rwx",
},
Spec: corev1api.PersistentVolumeClaimSpec{
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadOnlyMany, corev1api.ReadWriteMany},
},
},
skip: true,
},
{
name: "PVC access modes matching - missing one configured access mode should not skip",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcAccessModes: ["ReadOnlyMany", "ReadWriteMany"]
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-rwx",
},
Spec: corev1api.PersistentVolumeClaimSpec{
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteMany},
},
},
skip: false,
},
{
name: "PVC access modes matching - Combined with volume mode",
yamlData: `version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
pvcAccessModes: ["ReadWriteOnce"]
action:
type: skip`,
vol: nil,
podVol: nil,
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pvc-block-rwo",
},
Spec: corev1api.PersistentVolumeClaimSpec{
VolumeMode: pvcVolumeMode(corev1api.PersistentVolumeBlock),
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteOnce},
},
},
skip: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -1119,28 +1434,36 @@ func TestGetMatchAction_Errors(t *testing.T) {
func TestParsePVC(t *testing.T) {
tests := []struct {
name string
pvc *corev1api.PersistentVolumeClaim
expectedLabels map[string]string
expectedPhase string
expectErr bool
name string
pvc *corev1api.PersistentVolumeClaim
expectedLabels map[string]string
expectedPhase string
expectedVolumeMode string
expectedAccessModes []string
expectErr bool
}{
{
name: "valid PVC with labels and Pending phase",
name: "valid PVC with labels, Pending phase, Block volume mode, and access modes",
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"env": "prod"},
},
Spec: corev1api.PersistentVolumeClaimSpec{
VolumeMode: pvcVolumeMode(corev1api.PersistentVolumeBlock),
AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteOnce, corev1api.ReadOnlyMany},
},
Status: corev1api.PersistentVolumeClaimStatus{
Phase: corev1api.ClaimPending,
},
},
expectedLabels: map[string]string{"env": "prod"},
expectedPhase: "Pending",
expectErr: false,
expectedLabels: map[string]string{"env": "prod"},
expectedPhase: "Pending",
expectedVolumeMode: "Block",
expectedAccessModes: []string{"ReadWriteOnce", "ReadOnlyMany"},
expectErr: false,
},
{
name: "valid PVC with Bound phase",
name: "valid PVC with Bound phase and nil volume mode",
pvc: &corev1api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{},
@@ -1149,27 +1472,52 @@ func TestParsePVC(t *testing.T) {
Phase: corev1api.ClaimBound,
},
},
expectedLabels: nil,
expectedPhase: "Bound",
expectErr: false,
expectedLabels: nil,
expectedPhase: "Bound",
expectedVolumeMode: "",
expectedAccessModes: nil,
expectErr: false,
},
{
name: "valid PVC with Lost phase",
name: "valid PVC with Lost phase and Filesystem volume mode",
pvc: &corev1api.PersistentVolumeClaim{
Spec: corev1api.PersistentVolumeClaimSpec{
VolumeMode: pvcVolumeMode(corev1api.PersistentVolumeFilesystem),
},
Status: corev1api.PersistentVolumeClaimStatus{
Phase: corev1api.ClaimLost,
},
},
expectedLabels: nil,
expectedPhase: "Lost",
expectErr: false,
expectedLabels: nil,
expectedPhase: "Lost",
expectedVolumeMode: "Filesystem",
expectedAccessModes: nil,
expectErr: false,
},
{
name: "nil PVC pointer",
pvc: (*corev1api.PersistentVolumeClaim)(nil),
expectedLabels: nil,
expectedPhase: "",
expectErr: false,
name: "valid PVC with unknown non-nil volume mode",
pvc: &corev1api.PersistentVolumeClaim{
Spec: corev1api.PersistentVolumeClaimSpec{
VolumeMode: pvcVolumeMode(corev1api.PersistentVolumeMode("foo")),
},
Status: corev1api.PersistentVolumeClaimStatus{
Phase: corev1api.ClaimBound,
},
},
expectedLabels: nil,
expectedPhase: "Bound",
expectedVolumeMode: "foo",
expectedAccessModes: nil,
expectErr: false,
},
{
name: "nil PVC pointer",
pvc: (*corev1api.PersistentVolumeClaim)(nil),
expectedLabels: nil,
expectedPhase: "",
expectedVolumeMode: "",
expectedAccessModes: nil,
expectErr: false,
},
}
@@ -1180,6 +1528,8 @@ func TestParsePVC(t *testing.T) {
assert.Equal(t, tc.expectedLabels, s.pvcLabels)
assert.Equal(t, tc.expectedPhase, s.pvcPhase)
assert.Equal(t, tc.expectedVolumeMode, s.pvcVolumeMode)
assert.Equal(t, tc.expectedAccessModes, s.pvcAccessModes)
})
}
}
@@ -1509,7 +1859,7 @@ namespacedFilterPolicies:
- namespaces: ["team-frontend-*", "specific-ns"]
resourceFilters:
- kinds: ["Pod", "ConfigMap", "Secret"]
- namespaces: ["team-*", "another-pattern"]
- namespaces: ["team-*", "another-pattern"]
resourceFilters:
- kinds: ["Deployment", "Service"]`
@@ -1680,3 +2030,145 @@ clusterScopedFilterPolicy:
})
}
}
func TestPVCVolumeModeMatch(t *testing.T) {
tests := []struct {
name string
condition *pvcVolumeModeCondition
volume *structuredVolume
expectedMatch bool
}{
{
name: "match Block volume mode",
condition: &pvcVolumeModeCondition{volumeMode: "Block"},
volume: &structuredVolume{pvcVolumeMode: "Block"},
expectedMatch: true,
},
{
name: "match Filesystem volume mode",
condition: &pvcVolumeModeCondition{volumeMode: "Filesystem"},
volume: &structuredVolume{pvcVolumeMode: "Filesystem"},
expectedMatch: true,
},
{
name: "no match for different volume mode",
condition: &pvcVolumeModeCondition{volumeMode: "Block"},
volume: &structuredVolume{pvcVolumeMode: "Filesystem"},
expectedMatch: false,
},
{
name: "case-sensitive no match for lowercase volume mode",
condition: &pvcVolumeModeCondition{volumeMode: "block"},
volume: &structuredVolume{pvcVolumeMode: "Block"},
expectedMatch: false,
},
{
name: "no match for unknown condition value against Filesystem",
condition: &pvcVolumeModeCondition{volumeMode: "foo"},
volume: &structuredVolume{pvcVolumeMode: "Filesystem"},
expectedMatch: false,
},
{
name: "match unknown condition value only when volume has same value",
condition: &pvcVolumeModeCondition{volumeMode: "foo"},
volume: &structuredVolume{pvcVolumeMode: "foo"},
expectedMatch: true,
},
{
name: "no match for empty volume mode",
condition: &pvcVolumeModeCondition{volumeMode: "Block"},
volume: &structuredVolume{pvcVolumeMode: ""},
expectedMatch: false,
},
{
name: "match with empty volume mode condition (always match)",
condition: &pvcVolumeModeCondition{volumeMode: ""},
volume: &structuredVolume{pvcVolumeMode: "Block"},
expectedMatch: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := tc.condition.match(tc.volume)
assert.Equal(t, tc.expectedMatch, result)
})
}
}
func TestPVCAccessModesMatch(t *testing.T) {
tests := []struct {
name string
condition *pvcAccessModesCondition
volume *structuredVolume
expectedMatch bool
}{
{
name: "match ReadWriteOnce access mode",
condition: &pvcAccessModesCondition{accessModes: []string{"ReadWriteOnce"}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteOnce"}},
expectedMatch: true,
},
{
name: "match exact multiple access modes",
condition: &pvcAccessModesCondition{accessModes: []string{"ReadWriteOnce", "ReadOnlyMany"}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteOnce", "ReadOnlyMany"}},
expectedMatch: true,
},
{
name: "match exact multiple access modes regardless of order",
condition: &pvcAccessModesCondition{accessModes: []string{"ReadOnlyMany", "ReadWriteOnce"}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteOnce", "ReadOnlyMany"}},
expectedMatch: true,
},
{
name: "no match when one of multiple access modes is missing",
condition: &pvcAccessModesCondition{accessModes: []string{"ReadWriteOnce", "ReadOnlyMany"}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadOnlyMany"}},
expectedMatch: false,
},
{
name: "no match when PVC has extra access modes",
condition: &pvcAccessModesCondition{accessModes: []string{"ReadWriteMany"}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteOnce", "ReadWriteMany"}},
expectedMatch: false,
},
{
name: "no match for different access mode",
condition: &pvcAccessModesCondition{accessModes: []string{"ReadWriteOnce"}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteMany"}},
expectedMatch: false,
},
{
name: "case-sensitive no match for lowercase access mode",
condition: &pvcAccessModesCondition{accessModes: []string{"readwriteonce"}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteOnce"}},
expectedMatch: false,
},
{
name: "no match for empty PVC access modes",
condition: &pvcAccessModesCondition{accessModes: []string{"ReadWriteOnce"}},
volume: &structuredVolume{pvcAccessModes: []string{}},
expectedMatch: false,
},
{
name: "match with empty access modes list (always match)",
condition: &pvcAccessModesCondition{accessModes: []string{}},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteOnce"}},
expectedMatch: true,
},
{
name: "match with nil access modes list (always match)",
condition: &pvcAccessModesCondition{accessModes: nil},
volume: &structuredVolume{pvcAccessModes: []string{"ReadWriteOnce"}},
expectedMatch: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := tc.condition.match(tc.volume)
assert.Equal(t, tc.expectedMatch, result)
})
}
}
+63 -13
View File
@@ -18,9 +18,11 @@ package resourcepolicies
import (
"bytes"
"fmt"
"slices"
"strings"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/cockroachdb/errors"
"go.yaml.in/yaml/v3"
@@ -45,13 +47,15 @@ type capacity struct {
}
type structuredVolume struct {
capacity resource.Quantity
storageClass string
nfs *nFSVolumeSource
csi *csiVolumeSource
volumeType SupportedVolume
pvcLabels map[string]string
pvcPhase string
capacity resource.Quantity
storageClass string
nfs *nFSVolumeSource
csi *csiVolumeSource
volumeType SupportedVolume
pvcLabels map[string]string
pvcPhase string
pvcVolumeMode string
pvcAccessModes []string
}
func (s *structuredVolume) parsePV(pv *corev1api.PersistentVolume) {
@@ -76,6 +80,15 @@ func (s *structuredVolume) parsePVC(pvc *corev1api.PersistentVolumeClaim) {
s.pvcLabels = pvc.Labels
}
s.pvcPhase = string(pvc.Status.Phase)
if pvc.Spec.VolumeMode != nil {
s.pvcVolumeMode = string(*pvc.Spec.VolumeMode)
}
if len(pvc.Spec.AccessModes) > 0 {
s.pvcAccessModes = make([]string, 0, len(pvc.Spec.AccessModes))
for _, accessMode := range pvc.Spec.AccessModes {
s.pvcAccessModes = append(s.pvcAccessModes, string(accessMode))
}
}
}
}
@@ -127,18 +140,55 @@ func (c *pvcPhaseCondition) match(v *structuredVolume) bool {
if v.pvcPhase == "" {
return false
}
for _, phase := range c.phases {
if v.pvcPhase == phase {
return true
}
}
return false
return slices.Contains(c.phases, v.pvcPhase)
}
func (c *pvcPhaseCondition) validate() error {
return nil
}
// pvcVolumeModeCondition defines a condition that matches if the PVC's volume mode matches the provided volume mode.
type pvcVolumeModeCondition struct {
volumeMode string
}
func (c *pvcVolumeModeCondition) match(v *structuredVolume) bool {
// No volume mode specified: always match.
if c.volumeMode == "" {
return true
}
// Here allows unknown strings for forward compatibility. If Kubernetes adds another volume mode later,
// Velero would not reject the policy just because the string is unfamiliar.
return v.pvcVolumeMode == c.volumeMode
}
func (c *pvcVolumeModeCondition) validate() error {
return nil
}
// pvcAccessModesCondition defines a condition that matches if the PVC has exactly the provided access modes.
type pvcAccessModesCondition struct {
accessModes []string
}
func (c *pvcAccessModesCondition) match(v *structuredVolume) bool {
// No access modes specified: always match.
if len(c.accessModes) == 0 {
return true
}
if len(v.pvcAccessModes) != len(c.accessModes) {
return false
}
return sets.New(c.accessModes...).Equal(sets.New(v.pvcAccessModes...))
}
func (c *pvcAccessModesCondition) validate() error {
return nil
}
type capacityCondition struct {
capacity capacity
}
@@ -430,6 +430,38 @@ func TestUnmarshalVolumeConditions(t *testing.T) {
},
expectedError: "!!str `production` into map[string]string",
},
{
name: "Valid pvcVolumeMode input",
input: map[string]any{
"capacity": "1Gi,10Gi",
"pvcVolumeMode": "Block",
},
expectedError: "",
},
{
name: "Invalid pvcVolumeMode input: not a string",
input: map[string]any{
"capacity": "1Gi,10Gi",
"pvcVolumeMode": []string{"Filesystem", "Block"},
},
expectedError: "cannot unmarshal !!seq",
},
{
name: "Valid pvcAccessModes input",
input: map[string]any{
"capacity": "1Gi,10Gi",
"pvcAccessModes": []string{"ReadWriteOnce", "ReadWriteMany"},
},
expectedError: "",
},
{
name: "Invalid pvcAccessModes input: not a list",
input: map[string]any{
"capacity": "1Gi,10Gi",
"pvcAccessModes": "ReadWriteOnce",
},
expectedError: "cannot unmarshal !!str",
},
}
for _, tc := range testCases {
@@ -40,13 +40,15 @@ type nFSVolumeSource struct {
// volumeConditions defined the current format of conditions we parsed
type volumeConditions struct {
Capacity string `yaml:"capacity,omitempty"`
StorageClass []string `yaml:"storageClass,omitempty"`
NFS *nFSVolumeSource `yaml:"nfs,omitempty"`
CSI *csiVolumeSource `yaml:"csi,omitempty"`
VolumeTypes []SupportedVolume `yaml:"volumeTypes,omitempty"`
PVCLabels map[string]string `yaml:"pvcLabels,omitempty"`
PVCPhase []string `yaml:"pvcPhase,omitempty"`
Capacity string `yaml:"capacity,omitempty"`
StorageClass []string `yaml:"storageClass,omitempty"`
NFS *nFSVolumeSource `yaml:"nfs,omitempty"`
CSI *csiVolumeSource `yaml:"csi,omitempty"`
VolumeTypes []SupportedVolume `yaml:"volumeTypes,omitempty"`
PVCLabels map[string]string `yaml:"pvcLabels,omitempty"`
PVCPhase []string `yaml:"pvcPhase,omitempty"`
PVCVolumeMode string `yaml:"pvcVolumeMode,omitempty"`
PVCAccessModes []string `yaml:"pvcAccessModes,omitempty"`
}
func (c *capacityCondition) validate() error {