diff --git a/pkg/exporter/rule.go b/pkg/exporter/rule.go index 329b7926..8ed9534c 100644 --- a/pkg/exporter/rule.go +++ b/pkg/exporter/rule.go @@ -15,18 +15,19 @@ func matchString(pattern, s string) bool { // Rule is for matching an event type Rule struct { - Labels map[string]string - Annotations map[string]string - Message string - APIVersion string - Kind string - Namespace string - Reason string - Type string - MinCount int32 - Component string - Host string - Receiver string + Labels map[string]string + Annotations map[string]string + NamespaceLabels map[string]string + Message string + APIVersion string + Kind string + Namespace string + Reason string + Type string + MinCount int32 + Component string + Host string + Receiver string } // MatchesEvent compares the rule to an event and returns a boolean value to indicate @@ -84,6 +85,20 @@ func (r *Rule) MatchesEvent(ev *kube.EnhancedEvent) bool { } } + // NamespaceLabels are also mutually exclusive, they all need to be present + if r.NamespaceLabels != nil && len(r.NamespaceLabels) > 0 { + for k, v := range r.NamespaceLabels { + if val, ok := ev.InvolvedObject.NamespaceLabels[k]; !ok { + return false + } else { + matches := matchString(v, val) + if !matches { + return false + } + } + } + } + // If minCount is not given via a config, it's already 0 and the count is already 1 and this passes. if ev.Count >= r.MinCount { return true diff --git a/pkg/kube/event.go b/pkg/kube/event.go index 8e564e5a..6ee02fd8 100644 --- a/pkg/kube/event.go +++ b/pkg/kube/event.go @@ -21,6 +21,7 @@ func (e EnhancedEvent) DeDot() EnhancedEvent { c.Annotations = dedotMap(e.Annotations) c.InvolvedObject.Labels = dedotMap(e.InvolvedObject.Labels) c.InvolvedObject.Annotations = dedotMap(e.InvolvedObject.Annotations) + c.InvolvedObject.NamespaceLabels = dedotMap(e.InvolvedObject.NamespaceLabels) return c } @@ -40,6 +41,7 @@ type EnhancedObjectReference struct { corev1.ObjectReference `json:",inline"` Labels map[string]string `json:"labels,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` + NamespaceLabels map[string]string `json:"namespaceLabels,omitempty"` } // ToJSON does not return an error because we are %99 confident it is JSON serializable. diff --git a/pkg/kube/namespace_labels.go b/pkg/kube/namespace_labels.go new file mode 100644 index 00000000..64fb8df9 --- /dev/null +++ b/pkg/kube/namespace_labels.go @@ -0,0 +1,52 @@ +package kube + +import ( + "context" + + lru "github.com/hashicorp/golang-lru" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +type NamespaceLabelCache struct { + dynClient dynamic.Interface + clientset *kubernetes.Clientset + + cache *lru.ARCCache +} + +func NewNamespaceLabelCache(kubeconfig *rest.Config) *NamespaceLabelCache { + cache, err := lru.NewARC(1024) + if err != nil { + panic("cannot init cache: " + err.Error()) + } + return &NamespaceLabelCache{ + dynClient: dynamic.NewForConfigOrDie(kubeconfig), + clientset: kubernetes.NewForConfigOrDie(kubeconfig), + cache: cache, + } +} + +func (n *NamespaceLabelCache) GetNamespaceLabelsWithCache(nsName string) (map[string]string, error) { + if val, ok := n.cache.Get(nsName); ok { + return val.(map[string]string), nil + } + + ns, err := n.clientset.CoreV1().Namespaces().Get(context.Background(), nsName, metav1.GetOptions{}) + if err == nil { + nsLabels := ns.GetLabels() + n.cache.Add(nsName, nsLabels) + return nsLabels, nil + } + + if errors.IsNotFound(err) { + var empty map[string]string + n.cache.Add(nsName, empty) + return nil, nil + } + + return nil, err +} diff --git a/pkg/kube/watcher.go b/pkg/kube/watcher.go index 7ef0fc04..6a791e6b 100644 --- a/pkg/kube/watcher.go +++ b/pkg/kube/watcher.go @@ -14,12 +14,13 @@ import ( type EventHandler func(event *EnhancedEvent) type EventWatcher struct { - informer cache.SharedInformer - stopper chan struct{} - labelCache *LabelCache - annotationCache *AnnotationCache - fn EventHandler - throttlePeriod time.Duration + informer cache.SharedInformer + stopper chan struct{} + labelCache *LabelCache + annotationCache *AnnotationCache + namespaceLabelCache *NamespaceLabelCache + fn EventHandler + throttlePeriod time.Duration } func NewEventWatcher(config *rest.Config, namespace string, throttlePeriod int64, fn EventHandler) *EventWatcher { @@ -28,12 +29,13 @@ func NewEventWatcher(config *rest.Config, namespace string, throttlePeriod int64 informer := factory.Core().V1().Events().Informer() watcher := &EventWatcher{ - informer: informer, - stopper: make(chan struct{}), - labelCache: NewLabelCache(config), - annotationCache: NewAnnotationCache(config), - fn: fn, - throttlePeriod: time.Second*time.Duration(throttlePeriod), + informer: informer, + stopper: make(chan struct{}), + labelCache: NewLabelCache(config), + annotationCache: NewAnnotationCache(config), + namespaceLabelCache: NewNamespaceLabelCache(config), + fn: fn, + throttlePeriod: time.Second * time.Duration(throttlePeriod), } informer.AddEventHandler(watcher) @@ -87,6 +89,13 @@ func (e *EventWatcher) onEvent(event *corev1.Event) { ev.InvolvedObject.ObjectReference = *event.InvolvedObject.DeepCopy() } + namespaceLabels, err := e.namespaceLabelCache.GetNamespaceLabelsWithCache(event.Namespace) + if err != nil { + log.Error().Err(err).Msg("Cannot list namespace labels of the object") + } else { + ev.InvolvedObject.NamespaceLabels = namespaceLabels + } + e.fn(ev) return }