Repository: abahmed/kwatch (go)

Issue:
Ignore pods by label/name pattern
**Is your feature request related to a problem? Please describe.**
I would like to ignore alerts from pods that are part of helm charts that I do not have control over. Sometimes these pods include containers with very generic names such as `main` or `app`, which make it difficult to use `ignoreContainerNames` for targeting these specific pods while continuing to receive alerts for other pods for our own apps with containers having those same names. 

**Describe the solution you'd like**
I think something similar to this would be useful:

```yaml
ignorePodNames:
- some-pod-*
```
or maybe regex:
```yaml
ignorePodNamePatterns:
- some-pod-.*
```
Perhaps something involving pod labels:
```yaml
ignorePodLabels:
- label: something
  value: a value
- label: another-selector
  value: pattern-*
```

I feel like the name/name pattern based option would be much more straightforward, while the label-based option would be very helpful for a lot of different scenarios. I could see the label-based option getting complicated though (must a pod match _all_ labels before being ignored? must one pod match multiple labels but we still need to add other labels to ignore separate pods?).

Pull request description:
Ignore pods by pattern
Add a config option to ignore events by pods matching a regex pattern

Fixes #240 

Changes proposed in this pull request:
- Add a new config option `ignorePodNames`. This takes a list of regex patterns which can be used to ignore events from pods matching one of the patterns.


Patch:
diff --git a/README.md b/README.md
index caa5642..3f5e2cb 100644
--- a/README.md
+++ b/README.md
@@ -98,6 +98,7 @@ kubectl apply -f https://raw.githubusercontent.com/abahmed/kwatch/v0.8.4/deploy/
 | `reasons`                      | Optional comma separated list of reasons that you want to watch or forbid, if it's not provided it will watch all reasons. If you want to forbid a reason, configure it with `!<reason>`. You can either set forbidden reasons or allowed, not both.                     |
 | `ignoreFailedGracefulShutdown` | If set to true, containers which are forcefully killed during shutdown (as their graceful shutdown failed) are not reported as error     |
 | `ignoreContainerNames`         | Optional comma separated list of container names to ignore    |
+| `ignorePodNames`               | Optional list of pod name regexp patterns to ignore    |
 
 ### App
 
diff --git a/config/config.go b/config/config.go
index 97b6972..174e76a 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,5 +1,9 @@
 package config
 
+import (
+	"regexp"
+)
+
 type Config struct {
 	// App general configuration
 	App App `yaml:"app"`
@@ -34,6 +38,9 @@ type Config struct {
 	// IgnoreContainerNames optional list of container names to ignore
 	IgnoreContainerNames []string `yaml:"ignoreContainerNames"`
 
+	// IgnorePodNames optional list of pod name regexp patterns to ignore
+	IgnorePodNames []string `yaml:"ignorePodNames"`
+
 	// Alert is a map contains a map of each provider configuration
 	// e.g. {"slack": {"webhook": "URL"}}
 	Alert map[string]map[string]interface{} `yaml:"alert"`
@@ -47,6 +54,9 @@ type Config struct {
 	// Reasons configuration
 	AllowedReasons   []string
 	ForbiddenReasons []string
+
+	// Patterns are compiled from IgnorePodNames after loading
+	IgnorePodNamePatterns []*regexp.Regexp
 }
 
 // App confing struct
diff --git a/config/loadConfig.go b/config/loadConfig.go
index 4133ae7..94bfc8d 100644
--- a/config/loadConfig.go
+++ b/config/loadConfig.go
@@ -1,7 +1,9 @@
 package config
 
 import (
+	"fmt"
 	"os"
+	"regexp"
 	"strings"
 
 	"github.com/sirupsen/logrus"
@@ -46,6 +48,13 @@ func LoadConfig() (*Config, error) {
 			"Can't set both")
 	}
 
+	// Prepare ignored pod name patters
+	config.IgnorePodNamePatterns, err =
+		getCompiledIgnorePodNamePatterns(config.IgnorePodNames)
+	if err != nil {
+		logrus.Errorf("Failed to compile pod name pattern: %s", err.Error())
+	}
+
 	// Parse proxy config
 	if len(config.App.ProxyURL) > 0 {
 		os.Setenv("HTTPS_PROXY", config.App.ProxyURL)
@@ -67,3 +76,19 @@ func getAllowForbidSlices(items []string) (allow []string, forbid []string) {
 	}
 	return allow, forbid
 }
+
+func getCompiledIgnorePodNamePatterns(patterns []string) (compiledPatterns []*regexp.Regexp, err error) {
+	compiledPatterns = make([]*regexp.Regexp, 0)
+
+	for _, pattern := range patterns {
+		compiledPattern, err := regexp.Compile(pattern)
+
+		if err != nil {
+			return nil, fmt.Errorf("failed to compile pattern '%s'", pattern)
+		}
+
+		compiledPatterns = append(compiledPatterns, compiledPattern)
+	}
+
+	return compiledPatterns, nil
+}
diff --git a/controller/controller.go b/controller/controller.go
index b7f87f2..8fbc0b1 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -214,11 +214,22 @@ func (c *Controller) processPod(key string, pod *v1.Pod) {
 		if len(c.config.IgnoreContainerNames) > 0 &&
 			slices.Contains(c.config.IgnoreContainerNames, container.Name) {
 			logrus.Infof(
-				"skip pod %s as in container ignore list",
+				"skip container %s as in container ignore list",
 				container.Name)
 			return
 		}
 
+		if len(c.config.IgnorePodNames) > 0 {
+			for _, pattern := range c.config.IgnorePodNamePatterns {
+				if pattern.MatchString(pod.Name) {
+					logrus.Infof(
+						"skip pod %s as in pod name patterns ignore list",
+						container.Name)
+					return
+				}
+			}
+		}
+
 		// get logs for this container
 		previous := true
 		if reason == "Error" {


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
index 65a81e3..2aa6b03 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -97,3 +97,24 @@ func TestConfigFromFile(t *testing.T) {
 	_, err := LoadConfig()
 	assert.NotNil(err)
 }
+
+func TestGetCompiledIgnorePodNamePatterns(t *testing.T) {
+	assert := assert.New(t)
+
+	validPatterns := []string{
+		"my-fancy-pod-[0-9]",
+	}
+
+	compiledPatterns, err := getCompiledIgnorePodNamePatterns(validPatterns)
+
+	assert.Nil(err)
+	assert.True(compiledPatterns[0].MatchString("my-fancy-pod-8"))
+
+	invalidPatterns := []string{
+		"my-fancy-pod-[.*",
+	}
+
+	compiledPatterns, err = getCompiledIgnorePodNamePatterns(invalidPatterns)
+
+	assert.NotNil(err)
+}