kube_core/
labels.rs

1//! Type safe label selector logic
2use core::fmt;
3use k8s_openapi::apimachinery::pkg::apis::meta::v1::{LabelSelector, LabelSelectorRequirement};
4use serde::{Deserialize, Serialize};
5use std::{
6    cmp::PartialEq,
7    collections::{BTreeMap, BTreeSet},
8    fmt::Display,
9    iter::FromIterator,
10    option::IntoIter,
11};
12use thiserror::Error;
13
14mod private {
15    pub trait Sealed {}
16    impl Sealed for super::Expression {}
17    impl Sealed for super::Selector {}
18}
19
20#[derive(Debug, Error)]
21#[error("failed to parse value as expression: {0}")]
22/// Indicates failure of conversion to Expression
23pub struct ParseExpressionError(pub String);
24
25// local type aliases
26type Expressions = Vec<Expression>;
27
28/// Selector extension trait for querying selector-like objects
29pub trait SelectorExt: private::Sealed {
30    /// Collection type to compare with self
31    type Search;
32
33    /// Perform a match check on the arbitrary components like labels
34    ///
35    /// ```
36    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
37    /// use kube::core::{SelectorExt, Selector};
38    /// # use std::collections::BTreeMap;
39    ///
40    /// let selector: Selector = LabelSelector::default().try_into()?;
41    /// let search = BTreeMap::from([("app".to_string(), "myapp".to_string())]);
42    /// selector.matches(&search);
43    /// # Ok::<(), kube_core::ParseExpressionError>(())
44    /// ```
45    fn matches(&self, on: &Self::Search) -> bool;
46}
47
48/// A selector expression with existing operations
49#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
50pub enum Expression {
51    /// Key exists and in set:
52    ///
53    /// ```
54    /// # use kube_core::Expression;
55    /// let exp = Expression::In("foo".into(), ["bar".into(), "baz".into()].into());
56    /// assert_eq!(exp.to_string(), "foo in (bar,baz)");
57    /// let exp = Expression::In("foo".into(), ["bar".into(), "baz".into()].into_iter().collect());
58    /// assert_eq!(exp.to_string(), "foo in (bar,baz)");
59    /// ```
60    In(String, BTreeSet<String>),
61
62    /// Key does not exists or not in set:
63    ///
64    /// ```
65    /// # use kube_core::Expression;
66    /// let exp = Expression::NotIn("foo".into(), ["bar".into(), "baz".into()].into());
67    /// assert_eq!(exp.to_string(), "foo notin (bar,baz)");
68    /// let exp = Expression::NotIn("foo".into(), ["bar".into(), "baz".into()].into_iter().collect());
69    /// assert_eq!(exp.to_string(), "foo notin (bar,baz)");
70    /// ```
71    NotIn(String, BTreeSet<String>),
72
73    /// Key exists and is equal:
74    ///
75    /// ```
76    /// # use kube_core::Expression;
77    /// let exp = Expression::Equal("foo".into(), "bar".into());
78    /// assert_eq!(exp.to_string(), "foo=bar")
79    /// ```
80    Equal(String, String),
81
82    /// Key does not exists or is not equal:
83    ///
84    /// ```
85    /// # use kube_core::Expression;
86    /// let exp = Expression::NotEqual("foo".into(), "bar".into());
87    /// assert_eq!(exp.to_string(), "foo!=bar")
88    /// ```
89    NotEqual(String, String),
90
91    /// Key exists:
92    ///
93    /// ```
94    /// # use kube_core::Expression;
95    /// let exp = Expression::Exists("foo".into());
96    /// assert_eq!(exp.to_string(), "foo")
97    /// ```
98    Exists(String),
99
100    /// Key does not exist:
101    ///
102    /// ```
103    /// # use kube_core::Expression;
104    /// let exp = Expression::DoesNotExist("foo".into());
105    /// assert_eq!(exp.to_string(), "!foo")
106    /// ```
107    DoesNotExist(String),
108}
109
110/// Perform selection on a list of expressions
111///
112/// Can be injected into [`WatchParams`](crate::params::WatchParams::labels_from) or [`ListParams`](crate::params::ListParams::labels_from).
113#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Serialize)]
114pub struct Selector(Expressions);
115
116impl Selector {
117    /// Create a selector from a vector of expressions
118    fn from_expressions(exprs: Expressions) -> Self {
119        Self(exprs)
120    }
121
122    /// Create a selector from a map of key=value label matches
123    fn from_map(map: BTreeMap<String, String>) -> Self {
124        Self(map.into_iter().map(|(k, v)| Expression::Equal(k, v)).collect())
125    }
126
127    /// Indicates whether this label selector matches everything
128    pub fn selects_all(&self) -> bool {
129        self.0.is_empty()
130    }
131
132    /// Extend the list of expressions for the selector
133    ///
134    /// ```
135    /// use kube::core::{Selector, Expression};
136    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
137    ///
138    /// let mut selector = Selector::default();
139    ///
140    /// // Extend from expressions:
141    /// selector.extend(Expression::Equal("environment".into(), "production".into()));
142    /// selector.extend([Expression::Exists("bar".into()), Expression::Exists("foo".into())].into_iter());
143    ///
144    /// // Extend from native selectors:
145    /// let label_selector: Selector = LabelSelector::default().try_into()?;
146    /// selector.extend(label_selector);
147    /// # Ok::<(), kube_core::ParseExpressionError>(())
148    /// ```
149    pub fn extend(&mut self, exprs: impl IntoIterator<Item = Expression>) -> &mut Self {
150        self.0.extend(exprs);
151        self
152    }
153}
154
155impl SelectorExt for Selector {
156    type Search = BTreeMap<String, String>;
157
158    /// Perform a match check on the resource labels
159    fn matches(&self, labels: &BTreeMap<String, String>) -> bool {
160        for expr in self.0.iter() {
161            if !expr.matches(labels) {
162                return false;
163            }
164        }
165        true
166    }
167}
168
169impl SelectorExt for Expression {
170    type Search = BTreeMap<String, String>;
171
172    fn matches(&self, labels: &BTreeMap<String, String>) -> bool {
173        match self {
174            Expression::In(key, values) => match labels.get(key) {
175                Some(v) => values.contains(v),
176                None => false,
177            },
178            Expression::NotIn(key, values) => match labels.get(key) {
179                Some(v) => !values.contains(v),
180                None => true,
181            },
182            Expression::Exists(key) => labels.contains_key(key),
183            Expression::DoesNotExist(key) => !labels.contains_key(key),
184            Expression::Equal(key, value) => labels.get(key) == Some(value),
185            Expression::NotEqual(key, value) => labels.get(key) != Some(value),
186        }
187    }
188}
189
190impl Display for Expression {
191    /// Perform conversion to string
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        match self {
194            Expression::In(key, values) => {
195                write!(
196                    f,
197                    "{key} in ({})",
198                    values.iter().cloned().collect::<Vec<_>>().join(",")
199                )
200            }
201            Expression::NotIn(key, values) => {
202                write!(
203                    f,
204                    "{key} notin ({})",
205                    values.iter().cloned().collect::<Vec<_>>().join(",")
206                )
207            }
208            Expression::Equal(key, value) => write!(f, "{key}={value}"),
209            Expression::NotEqual(key, value) => write!(f, "{key}!={value}"),
210            Expression::Exists(key) => write!(f, "{key}"),
211            Expression::DoesNotExist(key) => write!(f, "!{key}"),
212        }
213    }
214}
215
216impl Display for Selector {
217    /// Convert a selector to a string for the API
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        let selectors: Vec<String> = self.0.iter().map(|e| e.to_string()).collect();
220        write!(f, "{}", selectors.join(","))
221    }
222}
223// convenience conversions for Selector and Expression
224
225impl IntoIterator for Expression {
226    type IntoIter = IntoIter<Self::Item>;
227    type Item = Self;
228
229    fn into_iter(self) -> Self::IntoIter {
230        Some(self).into_iter()
231    }
232}
233
234impl IntoIterator for Selector {
235    type IntoIter = std::vec::IntoIter<Self::Item>;
236    type Item = Expression;
237
238    fn into_iter(self) -> Self::IntoIter {
239        self.0.into_iter()
240    }
241}
242
243impl FromIterator<(String, String)> for Selector {
244    fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
245        Self::from_map(iter.into_iter().collect())
246    }
247}
248
249impl FromIterator<(&'static str, &'static str)> for Selector {
250    /// ```
251    /// use kube_core::{Selector, Expression};
252    ///
253    /// let sel: Selector = Some(("foo", "bar")).into_iter().collect();
254    /// let equal: Selector = Expression::Equal("foo".into(), "bar".into()).into();
255    /// assert_eq!(sel, equal)
256    /// ```
257    fn from_iter<T: IntoIterator<Item = (&'static str, &'static str)>>(iter: T) -> Self {
258        Self::from_map(
259            iter.into_iter()
260                .map(|(k, v)| (k.to_string(), v.to_string()))
261                .collect(),
262        )
263    }
264}
265
266impl FromIterator<Expression> for Selector {
267    fn from_iter<T: IntoIterator<Item = Expression>>(iter: T) -> Self {
268        Self::from_expressions(iter.into_iter().collect())
269    }
270}
271
272impl From<Expression> for Selector {
273    fn from(value: Expression) -> Self {
274        Self(vec![value])
275    }
276}
277
278impl TryFrom<LabelSelector> for Selector {
279    type Error = ParseExpressionError;
280
281    fn try_from(value: LabelSelector) -> Result<Self, Self::Error> {
282        let expressions = match value.match_expressions {
283            Some(requirements) => requirements.into_iter().map(TryInto::try_into).collect(),
284            None => Ok(vec![]),
285        }?;
286        let mut equality: Selector = value
287            .match_labels
288            .map(|labels| labels.into_iter().collect())
289            .unwrap_or_default();
290        equality.extend(expressions);
291        Ok(equality)
292    }
293}
294
295impl TryFrom<LabelSelectorRequirement> for Expression {
296    type Error = ParseExpressionError;
297
298    fn try_from(requirement: LabelSelectorRequirement) -> Result<Self, Self::Error> {
299        let key = requirement.key;
300        let values = requirement.values.map(|values| values.into_iter().collect());
301        match requirement.operator.as_str() {
302            "In" => match values {
303                Some(values) => Ok(Expression::In(key, values)),
304                None => Err(ParseExpressionError(
305                    "Expected values for In operator, got none".into(),
306                )),
307            },
308            "NotIn" => match values {
309                Some(values) => Ok(Expression::NotIn(key, values)),
310                None => Err(ParseExpressionError(
311                    "Expected values for In operator, got none".into(),
312                )),
313            },
314            "Exists" => Ok(Expression::Exists(key)),
315            "DoesNotExist" => Ok(Expression::DoesNotExist(key)),
316            _ => Err(ParseExpressionError("Invalid expression operator".into())),
317        }
318    }
319}
320
321impl From<Selector> for LabelSelector {
322    fn from(value: Selector) -> Self {
323        let mut equality = vec![];
324        let mut expressions = vec![];
325        for expr in value.0 {
326            match expr {
327                Expression::In(key, values) => expressions.push(LabelSelectorRequirement {
328                    key,
329                    operator: "In".into(),
330                    values: Some(values.into_iter().collect()),
331                }),
332                Expression::NotIn(key, values) => expressions.push(LabelSelectorRequirement {
333                    key,
334                    operator: "NotIn".into(),
335                    values: Some(values.into_iter().collect()),
336                }),
337                Expression::Equal(key, value) => equality.push((key, value)),
338                Expression::NotEqual(key, value) => expressions.push(LabelSelectorRequirement {
339                    key,
340                    operator: "NotIn".into(),
341                    values: Some(vec![value]),
342                }),
343                Expression::Exists(key) => expressions.push(LabelSelectorRequirement {
344                    key,
345                    operator: "Exists".into(),
346                    values: None,
347                }),
348                Expression::DoesNotExist(key) => expressions.push(LabelSelectorRequirement {
349                    key,
350                    operator: "DoesNotExist".into(),
351                    values: None,
352                }),
353            }
354        }
355
356        LabelSelector {
357            match_labels: (!equality.is_empty()).then_some(equality.into_iter().collect()),
358            match_expressions: (!expressions.is_empty()).then_some(expressions),
359        }
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366    use std::iter::FromIterator;
367
368    #[test]
369    fn test_raw_matches() {
370        for (selector, label_selector, labels, matches, msg) in &[
371            (
372                Selector::default(),
373                LabelSelector::default(),
374                Default::default(),
375                true,
376                "empty match",
377            ),
378            (
379                Selector::from_iter(Some(("foo", "bar"))),
380                LabelSelector {
381                    match_labels: Some([("foo".into(), "bar".into())].into()),
382                    match_expressions: Default::default(),
383                },
384                [("foo".to_string(), "bar".to_string())].into(),
385                true,
386                "exact label match",
387            ),
388            (
389                Selector::from_iter(Some(("foo", "bar"))),
390                LabelSelector {
391                    match_labels: Some([("foo".to_string(), "bar".to_string())].into()),
392                    match_expressions: None,
393                },
394                [
395                    ("foo".to_string(), "bar".to_string()),
396                    ("bah".to_string(), "baz".to_string()),
397                ]
398                .into(),
399                true,
400                "sufficient label match",
401            ),
402            (
403                Selector::from_iter(Some(Expression::In(
404                    "foo".into(),
405                    Some("bar".to_string()).into_iter().collect(),
406                ))),
407                LabelSelector {
408                    match_labels: None,
409                    match_expressions: Some(vec![LabelSelectorRequirement {
410                        key: "foo".into(),
411                        operator: "In".to_string(),
412                        values: Some(vec!["bar".into()]),
413                    }]),
414                },
415                [
416                    ("foo".to_string(), "bar".to_string()),
417                    ("bah".to_string(), "baz".to_string()),
418                ]
419                .into(),
420                true,
421                "In expression match",
422            ),
423            (
424                Selector::from_iter(Some(Expression::Equal(
425                    "foo".into(),
426                    Some("bar".to_string()).into_iter().collect(),
427                ))),
428                LabelSelector {
429                    match_labels: Some([("foo".into(), "bar".into())].into()),
430                    match_expressions: None,
431                },
432                [
433                    ("foo".to_string(), "bar".to_string()),
434                    ("bah".to_string(), "baz".to_string()),
435                ]
436                .into(),
437                true,
438                "Equal expression match",
439            ),
440            (
441                Selector::from_iter(Some(Expression::NotEqual(
442                    "foo".into(),
443                    Some("bar".to_string()).into_iter().collect(),
444                ))),
445                LabelSelector {
446                    match_labels: None,
447                    match_expressions: Some(vec![LabelSelectorRequirement {
448                        key: "foo".into(),
449                        operator: "NotIn".into(),
450                        values: Some(vec!["bar".into()]),
451                    }]),
452                },
453                [
454                    ("foo".to_string(), "bar".to_string()),
455                    ("bah".to_string(), "baz".to_string()),
456                ]
457                .into(),
458                false,
459                "NotEqual expression match",
460            ),
461            (
462                Selector::from_iter(Some(Expression::In(
463                    "foo".into(),
464                    Some("bar".to_string()).into_iter().collect(),
465                ))),
466                LabelSelector {
467                    match_labels: None,
468                    match_expressions: Some(vec![LabelSelectorRequirement {
469                        key: "foo".into(),
470                        operator: "In".into(),
471                        values: Some(vec!["bar".into()]),
472                    }]),
473                },
474                [
475                    ("foo".to_string(), "bar".to_string()),
476                    ("bah".to_string(), "baz".to_string()),
477                ]
478                .into(),
479                true,
480                "In expression match",
481            ),
482            (
483                Selector::from_iter(Some(Expression::NotIn(
484                    "foo".into(),
485                    Some("quux".to_string()).into_iter().collect(),
486                ))),
487                LabelSelector {
488                    match_labels: None,
489                    match_expressions: Some(vec![LabelSelectorRequirement {
490                        key: "foo".into(),
491                        operator: "NotIn".into(),
492                        values: Some(vec!["quux".into()]),
493                    }]),
494                },
495                [
496                    ("foo".to_string(), "bar".to_string()),
497                    ("bah".to_string(), "baz".to_string()),
498                ]
499                .into(),
500                true,
501                "NotIn expression match",
502            ),
503            (
504                Selector::from_iter(Some(Expression::NotIn(
505                    "foo".into(),
506                    Some("bar".to_string()).into_iter().collect(),
507                ))),
508                LabelSelector {
509                    match_labels: None,
510                    match_expressions: Some(vec![LabelSelectorRequirement {
511                        key: "foo".into(),
512                        operator: "NotIn".into(),
513                        values: Some(vec!["bar".into()]),
514                    }]),
515                },
516                [
517                    ("foo".to_string(), "bar".to_string()),
518                    ("bah".to_string(), "baz".to_string()),
519                ]
520                .into(),
521                false,
522                "NotIn expression non-match",
523            ),
524            (
525                Selector(vec![
526                    Expression::Equal("foo".to_string(), "bar".to_string()),
527                    Expression::In("bah".into(), Some("bar".to_string()).into_iter().collect()),
528                ]),
529                LabelSelector {
530                    match_labels: Some([("foo".into(), "bar".into())].into()),
531                    match_expressions: Some(vec![LabelSelectorRequirement {
532                        key: "bah".into(),
533                        operator: "In".into(),
534                        values: Some(vec!["bar".into()]),
535                    }]),
536                },
537                [
538                    ("foo".to_string(), "bar".to_string()),
539                    ("bah".to_string(), "baz".to_string()),
540                ]
541                .into(),
542                false,
543                "matches labels but not expressions",
544            ),
545            (
546                Selector(vec![
547                    Expression::Equal("foo".to_string(), "bar".to_string()),
548                    Expression::In("bah".into(), Some("bar".to_string()).into_iter().collect()),
549                ]),
550                LabelSelector {
551                    match_labels: Some([("foo".into(), "bar".into())].into()),
552                    match_expressions: Some(vec![LabelSelectorRequirement {
553                        key: "bah".into(),
554                        operator: "In".into(),
555                        values: Some(vec!["bar".into()]),
556                    }]),
557                },
558                [
559                    ("foo".to_string(), "bar".to_string()),
560                    ("bah".to_string(), "bar".to_string()),
561                ]
562                .into(),
563                true,
564                "matches both labels and expressions",
565            ),
566        ] {
567            assert_eq!(selector.matches(labels), *matches, "{}", msg);
568            let converted: LabelSelector = selector.clone().into();
569            assert_eq!(&converted, label_selector);
570            let converted_selector: Selector = converted.try_into().unwrap();
571            assert_eq!(
572                converted_selector.matches(labels),
573                *matches,
574                "After conversion: {}",
575                msg
576            );
577        }
578    }
579
580    #[test]
581    fn test_label_selector_matches() {
582        let selector: Selector = LabelSelector {
583            match_expressions: Some(vec![
584                LabelSelectorRequirement {
585                    key: "foo".into(),
586                    operator: "In".into(),
587                    values: Some(vec!["bar".into()]),
588                },
589                LabelSelectorRequirement {
590                    key: "foo".into(),
591                    operator: "NotIn".into(),
592                    values: Some(vec!["baz".into()]),
593                },
594                LabelSelectorRequirement {
595                    key: "foo".into(),
596                    operator: "Exists".into(),
597                    values: None,
598                },
599                LabelSelectorRequirement {
600                    key: "baz".into(),
601                    operator: "DoesNotExist".into(),
602                    values: None,
603                },
604            ]),
605            match_labels: Some([("foo".into(), "bar".into())].into()),
606        }
607        .try_into()
608        .unwrap();
609        assert!(selector.matches(&[("foo".into(), "bar".into())].into()));
610        assert!(!selector.matches(&Default::default()));
611    }
612
613    #[test]
614    fn test_to_string() {
615        let selector = Selector(vec![
616            Expression::In("foo".into(), ["bar".into(), "baz".into()].into()),
617            Expression::NotIn("foo".into(), ["bar".into(), "baz".into()].into()),
618            Expression::Equal("foo".into(), "bar".into()),
619            Expression::NotEqual("foo".into(), "bar".into()),
620            Expression::Exists("foo".into()),
621            Expression::DoesNotExist("foo".into()),
622        ])
623        .to_string();
624
625        assert_eq!(
626            selector,
627            "foo in (bar,baz),foo notin (bar,baz),foo=bar,foo!=bar,foo,!foo"
628        )
629    }
630}