// Package match provides composable K8s object matchers. package match import ( "strings" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" ) // Matcher is an object evaluation that can be composed into sets for // implementing "Do X if Any/All/None matchers apply" style logic, such as batch // operations against objects that need to implement configurable behavior for // subsets of objects that meet specific requirements. type Matcher interface { Match(o client.Object) bool } // Object executes the [Matcher] against the K8s object. If the matcher or the // object is nil, false is returned. This allows optional matchers to be // evaluated cleanly, e.g.: // // type Options struct { // Exclude match.Matcher // } // // func DoThing(obj client.Object, opts Options) error { // // This will return false if the Exclude option is unset, which would be // // the expected behavior (i.e., include all objects) // if match.Object(obj, opts.Exclude) { // // Ignore this object, do nothing // return nil // } // // [...] // } func Object(o client.Object, m Matcher) bool { if m == nil || o == nil { return false } return m.Match(o) } // Everything matches on everything type Everything struct{} func (e Everything) Match(_ client.Object) bool { return true } // All returns true if all matchers return true for the object. Nil matchers are // ignored. Empty list of matchers returns false. type All []Matcher func (a All) Match(o client.Object) bool { if len(a) == 0 { return false } for _, m := range a { if m != nil && !m.Match(o) { return false } } return true } // Any returns true if any matcher returns true for the object. Nil matchers are // ignored. Empty list of matchers returns false. type Any []Matcher func (a Any) Match(o client.Object) bool { for _, m := range a { if m != nil && m.Match(o) { return true } } return false } // AnyInMetadata returns true if the object contains any individual record in // either `metadata.labels` or `metadata.annotations` type AnyInMetadata map[string]string func (a AnyInMetadata) Match(o client.Object) bool { labels, annos := o.GetLabels(), o.GetAnnotations() for k, v := range a { if strings.EqualFold(labels[k], v) || strings.EqualFold(annos[k], v) { return true } } return false } // Labels returns true if all key value pairs are present on the object's // `metadata.labels` field. type Labels map[string]string func (l Labels) Match(o client.Object) bool { if len(l) == 0 { return false } labels := o.GetLabels() for k, v := range l { if !strings.EqualFold(labels[k], v) { return false } } return true } // Annotations returns true if all key value pairs are present on the object's // `metadata.annotations` field. type Annotations map[string]string func (a Annotations) Match(o client.Object) bool { if len(a) == 0 { return false } annos := o.GetAnnotations() for k, v := range a { if !strings.EqualFold(annos[k], v) { return false } } return true } // Kind returns true if the object has a matching kind. type Kind struct{ Kind string } func (k Kind) Match(o client.Object) bool { return o.GetObjectKind().GroupVersionKind().Kind == k.Kind } // GroupVersion returns true if the object has a matching GroupVersion. type GroupVersion schema.GroupVersion func (gv GroupVersion) Match(o client.Object) bool { actual := o.GetObjectKind().GroupVersionKind().GroupVersion() return gv.Group == actual.Group && gv.Version == actual.Version } // Name returns true if the object has a matching Name. type Name struct{ Name string } func (n Name) Match(o client.Object) bool { return o.GetName() == n.Name } // Namespace returns true if the object has a matching Namespace. type Namespace struct{ Namespace string } func (n Namespace) Match(o client.Object) bool { return o.GetNamespace() == n.Namespace }