@@ -49,6 +49,7 @@ import (
4949 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5050 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
5151 "k8s.io/apimachinery/pkg/fields"
52+ "k8s.io/apimachinery/pkg/labels"
5253 "k8s.io/apimachinery/pkg/runtime"
5354 "k8s.io/apimachinery/pkg/runtime/schema"
5455 "k8s.io/client-go/dynamic"
@@ -77,6 +78,8 @@ type ConfigDynamic struct {
7778 IncludeNamespaces []string `yaml:"include-namespaces"`
7879 // FieldSelectors is a list of field selectors to use when listing this resource
7980 FieldSelectors []string `yaml:"field-selectors"`
81+ // LabelSelectors is a list of label selectors to use when listing this resource
82+ LabelSelectors []string `yaml:"label-selectors"`
8083}
8184
8285// UnmarshalYAML unmarshals the ConfigDynamic resolving GroupVersionResource.
@@ -91,6 +94,7 @@ func (c *ConfigDynamic) UnmarshalYAML(unmarshal func(any) error) error {
9194 ExcludeNamespaces []string `yaml:"exclude-namespaces"`
9295 IncludeNamespaces []string `yaml:"include-namespaces"`
9396 FieldSelectors []string `yaml:"field-selectors"`
97+ LabelSelectors []string `yaml:"label-selectors"`
9498 }{}
9599 err := unmarshal (& aux )
96100 if err != nil {
@@ -104,6 +108,7 @@ func (c *ConfigDynamic) UnmarshalYAML(unmarshal func(any) error) error {
104108 c .ExcludeNamespaces = aux .ExcludeNamespaces
105109 c .IncludeNamespaces = aux .IncludeNamespaces
106110 c .FieldSelectors = aux .FieldSelectors
111+ c .LabelSelectors = aux .LabelSelectors
107112
108113 return nil
109114}
@@ -119,16 +124,26 @@ func (c *ConfigDynamic) validate() error {
119124 errs = append (errs , "invalid configuration: GroupVersionResource.Resource cannot be empty" )
120125 }
121126
122- for i , selectorString := range c .FieldSelectors {
123- if selectorString == "" {
127+ for i , fieldSelectorString := range c .FieldSelectors {
128+ if fieldSelectorString == "" {
124129 errs = append (errs , fmt .Sprintf ("invalid field selector %d: must not be empty" , i ))
125130 }
126- _ , err := fields .ParseSelector (selectorString )
131+ _ , err := fields .ParseSelector (fieldSelectorString )
127132 if err != nil {
128133 errs = append (errs , fmt .Sprintf ("invalid field selector %d: %s" , i , err ))
129134 }
130135 }
131136
137+ for i , labelSelectorString := range c .LabelSelectors {
138+ if labelSelectorString == "" {
139+ errs = append (errs , fmt .Sprintf ("invalid label selector %d: must not be empty" , i ))
140+ }
141+ _ , err := labels .Parse (labelSelectorString )
142+ if err != nil {
143+ errs = append (errs , fmt .Sprintf ("invalid label selector %d: %s" , i , err ))
144+ }
145+ }
146+
132147 if len (errs ) > 0 {
133148 return errors .New (strings .Join (errs , ", " ))
134149 }
@@ -207,8 +222,22 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
207222 // Add any custom field selectors to the excluded namespaces selector
208223 // The selectors have already been validated, so it is safe to use
209224 // ParseSelectorOrDie here.
210- for _ , selectorString := range c .FieldSelectors {
211- fieldSelector = fields .AndSelectors (fieldSelector , fields .ParseSelectorOrDie (selectorString ))
225+ for _ , fieldSelectorString := range c .FieldSelectors {
226+ fieldSelector = fields .AndSelectors (fieldSelector , fields .ParseSelectorOrDie (fieldSelectorString ))
227+ }
228+
229+ // Add any custom label selectors
230+ // The selectors have already been validated, so Parse is expected to
231+ // succeed; any parse error is treated as a programming error.
232+ labelSelector := labels .Everything ()
233+ for _ , labelSelectorString := range c .LabelSelectors {
234+ selector , err := labels .Parse (labelSelectorString )
235+ if err != nil {
236+ panic (fmt .Sprintf ("PROGRAMMING ERROR: should have been caught in validation: " +
237+ "failed to parse validated label selector %q: %v" , labelSelectorString , err ))
238+ }
239+ reqs , _ := selector .Requirements ()
240+ labelSelector = labelSelector .Add (reqs ... )
212241 }
213242
214243 // init cache to store gathered resources
@@ -217,6 +246,7 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
217246 newDataGatherer := & DataGathererDynamic {
218247 groupVersionResource : c .GroupVersionResource ,
219248 fieldSelector : fieldSelector .String (),
249+ labelSelector : labelSelector .String (),
220250 namespaces : c .IncludeNamespaces ,
221251 cache : dgCache ,
222252 }
@@ -237,6 +267,7 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
237267 informers .WithNamespace (metav1 .NamespaceAll ),
238268 informers .WithTweakListOptions (func (options * metav1.ListOptions ) {
239269 options .FieldSelector = fieldSelector .String ()
270+ options .LabelSelector = labelSelector .String ()
240271 }),
241272 )
242273 newDataGatherer .informer = informerFunc (factory )
@@ -249,6 +280,7 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami
249280 metav1 .NamespaceAll ,
250281 func (options * metav1.ListOptions ) {
251282 options .FieldSelector = fieldSelector .String ()
283+ options .LabelSelector = labelSelector .String ()
252284 },
253285 )
254286 newDataGatherer .informer = factory .ForResource (c .GroupVersionResource ).Informer ()
@@ -293,6 +325,9 @@ type DataGathererDynamic struct {
293325 // returned by the Kubernetes API.
294326 // https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/
295327 fieldSelector string
328+ // labelSelector is a label selector string used to filter resources
329+ // returned by the Kubernetes API.
330+ labelSelector string
296331 // cache holds all resources watched by the data gatherer, default object expiry time 5 minutes
297332 // 30 seconds purge time https://pkg.go.dev/github.com/patrickmn/go-cache
298333 cache * cache.Cache
0 commit comments