diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index eabb62b3b..4a3b81f76 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -1186,6 +1186,36 @@ spec: type: string selector: properties: + matchExpressions: + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array matchLabel: additionalProperties: type: string diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml index 27eef2914..7b9f23bf9 100644 --- a/config/crds/troubleshoot.sh_preflights.yaml +++ b/config/crds/troubleshoot.sh_preflights.yaml @@ -1186,6 +1186,36 @@ spec: type: string selector: properties: + matchExpressions: + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array matchLabel: additionalProperties: type: string diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index e0dc56819..bca8003a0 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -1217,6 +1217,36 @@ spec: type: string selector: properties: + matchExpressions: + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array matchLabel: additionalProperties: type: string diff --git a/pkg/analyze/node_resources.go b/pkg/analyze/node_resources.go index 7698593df..1f6e62c6f 100644 --- a/pkg/analyze/node_resources.go +++ b/pkg/analyze/node_resources.go @@ -8,11 +8,14 @@ import ( "strings" "github.com/pkg/errors" - util "github.com/replicatedhq/troubleshoot/internal/util" - troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" - "github.com/replicatedhq/troubleshoot/pkg/constants" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + + "github.com/replicatedhq/troubleshoot/internal/util" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/replicatedhq/troubleshoot/pkg/constants" ) type AnalyzeNodeResources struct { @@ -381,13 +384,19 @@ func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta2.NodeResou // all filters must pass for this to pass if filters.Selector != nil { - for k, v := range filters.Selector.MatchLabel { - l, found := node.Labels[k] - if !found { - return false, nil - } else if l != v { - return false, nil - } + selector, err := metav1.LabelSelectorAsSelector( + &metav1.LabelSelector{ + MatchLabels: filters.Selector.MatchLabel, + MatchExpressions: filters.Selector.MatchExpressions, + }, + ) + if err != nil { + return false, errors.Wrap(err, "failed to create label selector") + } + + found := selector.Matches(labels.Set(node.Labels)) + if !found { + return false, nil } } diff --git a/pkg/analyze/node_resources_test.go b/pkg/analyze/node_resources_test.go index 87e37327e..204840843 100644 --- a/pkg/analyze/node_resources_test.go +++ b/pkg/analyze/node_resources_test.go @@ -3,12 +3,13 @@ package analyzer import ( "testing" - troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" ) func Test_compareNodeResourceConditionalToActual(t *testing.T) { @@ -501,6 +502,130 @@ func Test_nodeMatchesFilters(t *testing.T) { }, expectResult: true, }, + { + name: "true when the label expression matches with operator In", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value"}, + }, + }, + }, + }, + expectResult: true, + }, + { + name: "false when the label expression does not match with operator In", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value2"}, + }, + }, + }, + }, + expectResult: false, + }, + { + name: "true when the label expression matches with operator NotIn", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value2"}, + }, + }, + }, + }, + expectResult: true, + }, + { + name: "false when the label expression does not match with operator NotIn", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"value"}, + }, + }, + }, + }, + expectResult: false, + }, + { + name: "true when the label expression matches with operator Exists", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label", + Operator: metav1.LabelSelectorOpExists, + }, + }, + }, + }, + expectResult: true, + }, + { + name: "false when the label expression matches with operator Exists", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label2", + Operator: metav1.LabelSelectorOpExists, + }, + }, + }, + }, + expectResult: false, + }, + { + name: "true when the label expression matches with operator DoesNotExist", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label2", + Operator: metav1.LabelSelectorOpDoesNotExist, + }, + }, + }, + }, + expectResult: true, + }, + { + name: "false when the label expression does not match with operator DoesNotExist", + node: node, + filters: &troubleshootv1beta2.NodeResourceFilters{ + Selector: &troubleshootv1beta2.NodeResourceSelectors{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label", + Operator: metav1.LabelSelectorOpDoesNotExist, + }, + }, + }, + }, + expectResult: false, + }, } for _, test := range tests { diff --git a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go index 297a4163e..0ce3e8f24 100644 --- a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go @@ -1,6 +1,8 @@ package v1beta2 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/replicatedhq/troubleshoot/pkg/multitype" ) @@ -133,7 +135,8 @@ type NodeResourceFilters struct { } type NodeResourceSelectors struct { - MatchLabel map[string]string `json:"matchLabel,omitempty" yaml:"matchLabel,omitempty"` + MatchLabel map[string]string `json:"matchLabel,omitempty" yaml:"matchLabel,omitempty"` + MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty" yaml:"matchExpressions,omitempty"` } type TextAnalyze struct { diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 7f40bc835..e19fb0b3b 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1beta2 import ( "github.com/replicatedhq/troubleshoot/pkg/multitype" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -3408,6 +3409,13 @@ func (in *NodeResourceSelectors) DeepCopyInto(out *NodeResourceSelectors) { (*out)[key] = val } } + if in.MatchExpressions != nil { + in, out := &in.MatchExpressions, &out.MatchExpressions + *out = make([]v1.LabelSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeResourceSelectors. diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index 31e556ffe..29d46b5ea 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -1785,6 +1785,35 @@ "selector": { "type": "object", "properties": { + "matchExpressions": { + "type": "array", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values.", + "type": "object", + "required": [ + "key", + "operator" + ], + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch.", + "type": "array", + "items": { + "type": "string" + }, + "x-kubernetes-list-type": "atomic" + } + } + } + }, "matchLabel": { "type": "object", "additionalProperties": { diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json index 02e7687e8..8e1e2af55 100644 --- a/schemas/preflight-troubleshoot-v1beta2.json +++ b/schemas/preflight-troubleshoot-v1beta2.json @@ -1785,6 +1785,35 @@ "selector": { "type": "object", "properties": { + "matchExpressions": { + "type": "array", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values.", + "type": "object", + "required": [ + "key", + "operator" + ], + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch.", + "type": "array", + "items": { + "type": "string" + }, + "x-kubernetes-list-type": "atomic" + } + } + } + }, "matchLabel": { "type": "object", "additionalProperties": { diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index 387e23727..d7c25e705 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -1831,6 +1831,35 @@ "selector": { "type": "object", "properties": { + "matchExpressions": { + "type": "array", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values.", + "type": "object", + "required": [ + "key", + "operator" + ], + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch.", + "type": "array", + "items": { + "type": "string" + }, + "x-kubernetes-list-type": "atomic" + } + } + } + }, "matchLabel": { "type": "object", "additionalProperties": {