diff --git a/.gitignore b/.gitignore
index f2ae5d0d..1648f2e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,4 +29,4 @@ config.yaml
*.DS_Store
# debug
-__debug_bin
\ No newline at end of file
+__debug_bin*
\ No newline at end of file
diff --git a/README.md b/README.md
index 4a3167dd..ead3a994 100644
--- a/README.md
+++ b/README.md
@@ -28,11 +28,6 @@
**kwatch** helps you monitor all changes in your Kubernetes(K8s) cluster, detects crashes in your running apps in realtime, and publishes notifications to your channels (Slack, Discord, etc.) instantly
-## ⭐️ Join Waitlist
-
-We're working on SAAS version of kwatch that provides User interface, optimized notifications, more details about crashes, and more. You can join the waitlist to get early access!
-
-
## ⚡️ Getting Started
### Install
diff --git a/config/config.go b/config/config.go
index 174e76ac..02521b70 100644
--- a/config/config.go
+++ b/config/config.go
@@ -46,12 +46,12 @@ type Config struct {
Alert map[string]map[string]interface{} `yaml:"alert"`
// AllowedNamespaces, ForbiddenNamespaces are calculated internally
- // after loading Namespaces configuration
+ // after populating Namespaces configuration
AllowedNamespaces []string
ForbiddenNamespaces []string
- // AllowedReasons, ForbiddenReasons are calculated internally after loading
- // Reasons configuration
+ // AllowedReasons, ForbiddenReasons are calculated internally after
+ // populating Reasons configuration
AllowedReasons []string
ForbiddenReasons []string
diff --git a/config/defaultConfig.go b/config/defaultConfig.go
index 07de6012..0957a3dd 100644
--- a/config/defaultConfig.go
+++ b/config/defaultConfig.go
@@ -2,6 +2,7 @@ package config
func DefaultConfig() *Config {
return &Config{
+ IgnoreFailedGracefulShutdown: true,
PvcMonitor: PvcMonitor{
Enabled: true,
Interval: 5,
diff --git a/constant/constant.go b/constant/constant.go
index 9f7c8717..df1a2036 100644
--- a/constant/constant.go
+++ b/constant/constant.go
@@ -9,12 +9,6 @@ const KwatchUpdateMsg = ":tada: A newer version " +
" of Kwatch " +
"is available! Please update to the latest version."
-// NumRequeues indicates number of retries when worker fails to handle item
-const NumRequeues = 5
-
-// NumWorkers is the number concurrent workers that consume items for the queue
-const NumWorkers = 4
-
const (
Footer = ""
DefaultTitle = ":red_circle: kwatch detected a crash in pod"
diff --git a/controller/controller.go b/controller/controller.go
deleted file mode 100644
index 8fbc0b17..00000000
--- a/controller/controller.go
+++ /dev/null
@@ -1,269 +0,0 @@
-package controller
-
-import (
- "errors"
- "time"
-
- "github.com/abahmed/kwatch/alertmanager"
- "github.com/abahmed/kwatch/config"
- "github.com/abahmed/kwatch/constant"
- "github.com/abahmed/kwatch/event"
- "github.com/abahmed/kwatch/storage"
- "github.com/abahmed/kwatch/util"
- "github.com/sirupsen/logrus"
- v1 "k8s.io/api/core/v1"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- "k8s.io/apimachinery/pkg/util/wait"
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/tools/cache"
- "k8s.io/client-go/util/workqueue"
- "k8s.io/utils/strings/slices"
-)
-
-// Controller holds necessary
-type Controller struct {
- name string
- informer cache.Controller
- indexer cache.Indexer
- kclient kubernetes.Interface
- queue workqueue.RateLimitingInterface
- alertManager *alertmanager.AlertManager
- store storage.Storage
- config *config.Config
-}
-
-// run starts the controller
-func (c *Controller) run(workers int, stopCh chan struct{}) {
- defer utilruntime.HandleCrash()
- defer c.queue.ShutDown()
-
- logrus.Infof("starting %s controller", c.name)
-
- go c.informer.Run(stopCh)
-
- if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
- utilruntime.HandleError(
- errors.New("timed out waiting for caches to sync"))
- return
- }
-
- logrus.Infof("%s controller synced and ready", c.name)
-
- // start workers
- for i := 0; i < workers; i++ {
- go wait.Until(c.runWorker, time.Second, stopCh)
- }
-
- <-stopCh
-}
-
-func (c *Controller) runWorker() {
- for c.processNextItem() {
- // continue looping
- }
-}
-
-func (c *Controller) processNextItem() bool {
- newEvent, quit := c.queue.Get()
-
- if quit {
- return false
- }
-
- defer c.queue.Done(newEvent)
-
- err := c.processItem(newEvent.(string))
- if err == nil {
- // No error, reset the ratelimit counters
- c.queue.Forget(newEvent)
- } else if c.queue.NumRequeues(newEvent) < constant.NumRequeues {
- logrus.Errorf("failed to process %v (will retry): %v", newEvent, err)
- c.queue.AddRateLimited(newEvent)
- } else {
- // err != nil and too many retries
- logrus.Errorf("failed to process %v (giving up): %v", newEvent, err)
- c.queue.Forget(newEvent)
- utilruntime.HandleError(err)
- }
-
- return true
-}
-
-func (c *Controller) processItem(key string) error {
- obj, exists, err := c.indexer.GetByKey(key)
- if err != nil {
- logrus.Errorf(
- "failed to fetch object %s from store: %s",
- key,
- err.Error())
- return err
- }
-
- if !exists {
- // Below we will warm up our cache with a Pod, so that we will see a
- // delete for one pod
- logrus.Infof("pod %s does not exist anymore\n", key)
-
- c.store.DelPod(key)
-
- // Clean up intervals if possible
- return nil
- }
-
- pod, ok := obj.(*v1.Pod)
- if !ok {
- logrus.Warnf("failed to cast to pod object: %v", obj)
-
- // to avoid re-queuing it
- return nil
- }
-
- // filter by namespaces in config if specified
- if len(c.config.AllowedNamespaces) > 0 &&
- !slices.Contains(c.config.AllowedNamespaces, pod.Namespace) {
- logrus.Infof(
- "skip namespace %s as not in namespace allow list",
- pod.Namespace)
- return nil
- }
- if len(c.config.ForbiddenNamespaces) > 0 &&
- slices.Contains(c.config.ForbiddenNamespaces, pod.Namespace) {
- logrus.Infof(
- "skip namespace %s as in namespace forbid list",
- pod.Namespace)
- return nil
- }
-
- c.processPod(key, pod)
-
- return nil
-}
-
-// processPod checks status of pod and notify in abnormal cases
-func (c *Controller) processPod(key string, pod *v1.Pod) {
- for _, container := range pod.Status.ContainerStatuses {
- // filter running containers
- if container.Ready ||
- (container.State.Waiting == nil &&
- container.State.Terminated == nil) {
- c.store.DelPodContainer(key, container.Name)
- continue
- }
-
- if container.State.Waiting != nil {
- switch {
- case container.State.Waiting.Reason == "ContainerCreating":
- continue
- case container.State.Waiting.Reason == "PodInitializing":
- continue
- }
- } else if container.State.Terminated != nil {
- switch {
- case container.State.Terminated.Reason == "Completed":
- continue
- case container.State.Terminated.ExitCode == 143:
- // 143 is the exit code for graceful termination
- continue
- case container.State.Terminated.ExitCode == 0:
- // 0 is the exit code for purpose stop
- continue
- }
- }
-
- // if reported, continue
- if c.store.HasPodContainer(key, container.Name) {
- continue
- }
-
- if c.config.IgnoreFailedGracefulShutdown &&
- util.ContainsKillingStoppingContainerEvents(
- c.kclient,
- pod.Name,
- pod.Namespace) {
- // Graceful shutdown did not work and container was killed during
- // shutdown.
- // Not really an error
- continue
- }
-
- logrus.Debugf(
- "processing container %s in pod %s@%s",
- container.Name,
- pod.Name,
- pod.Namespace)
-
- // get reason according to state
- reason := "Unknown"
- if container.State.Waiting != nil {
- reason = container.State.Waiting.Reason
- } else if container.State.Terminated != nil {
- reason = container.State.Terminated.Reason
- }
-
- if len(c.config.AllowedReasons) > 0 &&
- !slices.Contains(c.config.AllowedReasons, reason) {
- logrus.Infof("skip reason %s as not in reason allow list", reason)
- return
- }
- if len(c.config.ForbiddenReasons) > 0 &&
- slices.Contains(c.config.ForbiddenReasons, reason) {
- logrus.Infof("skip reason %s as in reason forbid list", reason)
- return
- }
-
- if len(c.config.IgnoreContainerNames) > 0 &&
- slices.Contains(c.config.IgnoreContainerNames, container.Name) {
- logrus.Infof(
- "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" {
- previous = false
- } else if container.RestartCount > 0 {
- previous = true
- }
-
- logs := util.GetPodContainerLogs(
- c.kclient,
- pod.Name,
- container.Name,
- pod.Namespace,
- previous,
- c.config.MaxRecentLogLines)
-
- // get events for this pod
- eventsString :=
- util.GetPodEventsStr(c.kclient, pod.Name, pod.Namespace)
-
- evnt := event.Event{
- Name: pod.Name,
- Container: container.Name,
- Namespace: pod.Namespace,
- Reason: reason,
- Logs: logs,
- Events: eventsString,
- Labels: pod.Labels,
- }
-
- // save container as it's reported to avoid duplication
- c.store.AddPodContainer(key, container.Name)
-
- // send event to providers
- c.alertManager.NotifyEvent(evnt)
- }
-}
diff --git a/controller/start.go b/controller/start.go
deleted file mode 100644
index c8b22733..00000000
--- a/controller/start.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package controller
-
-import (
- "context"
-
- "github.com/abahmed/kwatch/alertmanager"
- "github.com/abahmed/kwatch/config"
- "github.com/abahmed/kwatch/constant"
- memory "github.com/abahmed/kwatch/storage/memory"
- "github.com/sirupsen/logrus"
- v1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/watch"
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/tools/cache"
- "k8s.io/client-go/util/workqueue"
-)
-
-// Start creates an instance of controller after initialization and runs it
-func Start(
- client kubernetes.Interface,
- alertManager *alertmanager.AlertManager,
- config *config.Config) {
- // create rate limiting queue
- queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
-
- // Namespace to watch, if all is selected it will watch all namespaces
- // in a cluster scope, if not then it will watch only in the namespace
- var namespaceToWatch = v1.NamespaceAll
-
- // if there is exactly 1 namespace listen only to that namespace for events
- if len(config.AllowedNamespaces) == 1 {
- namespaceToWatch = config.AllowedNamespaces[0]
- }
-
- indexer, informer := cache.NewIndexerInformer(
- &cache.ListWatch{
- ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
- return client.CoreV1().
- Pods(namespaceToWatch).
- List(context.TODO(), opts)
- },
- WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
- return client.CoreV1().
- Pods(namespaceToWatch).
- Watch(context.TODO(), opts)
- },
- },
- &v1.Pod{},
- 0,
- cache.ResourceEventHandlerFuncs{
- AddFunc: func(obj interface{}) {
- key, err := cache.MetaNamespaceKeyFunc(obj)
- if err == nil {
- logrus.Debugf("received create for Pod %s\n", key)
- queue.Add(key)
- }
- },
- UpdateFunc: func(_, new interface{}) {
- key, err := cache.MetaNamespaceKeyFunc(new)
- if err == nil {
- logrus.Debugf("received update for Pod %s\n", key)
- queue.Add(key)
- }
- },
- DeleteFunc: func(obj interface{}) {
- // IndexerInformer uses a delta queue, therefore for deletes
- // we have to use this key function.
- key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
- if err == nil {
- logrus.Debugf("received delete for Pod %s\n", key)
- queue.Add(key)
- }
- },
- }, cache.Indexers{})
-
- controller := Controller{
- name: "pod-crash",
- informer: informer,
- indexer: indexer,
- queue: queue,
- kclient: client,
- alertManager: alertManager,
- store: memory.NewMemory(),
- config: config,
- }
-
- stopCh := make(chan struct{})
- defer close(stopCh)
-
- controller.run(constant.NumWorkers, stopCh)
-}
diff --git a/filter/containerKillingFilter.go b/filter/containerKillingFilter.go
new file mode 100644
index 00000000..13ff9c9b
--- /dev/null
+++ b/filter/containerKillingFilter.go
@@ -0,0 +1,32 @@
+package filter
+
+import (
+ "strings"
+)
+
+type ContainerKillingFilter struct{}
+
+func (f ContainerKillingFilter) Execute(ctx *Context) bool {
+ if !ctx.Config.IgnoreFailedGracefulShutdown || ctx.Events == nil {
+ return false
+ }
+ container := ctx.Container.Container
+
+ isOk := false
+ if container.State.Waiting != nil {
+ return isOk
+ }
+
+ for _, ev := range *ctx.Events {
+ // Graceful shutdown did not work and container was killed during
+ // shutdown. Not really an error
+ if ev.Reason == "Killing" &&
+ strings.Contains(
+ ev.Message,
+ "Stopping container "+container.Name) {
+ isOk = true
+ }
+ }
+
+ return isOk
+}
diff --git a/filter/containerLogsFilter.go b/filter/containerLogsFilter.go
new file mode 100644
index 00000000..420db3a6
--- /dev/null
+++ b/filter/containerLogsFilter.go
@@ -0,0 +1,31 @@
+package filter
+
+import (
+ "github.com/abahmed/kwatch/util"
+)
+
+type ContainerLogsFilter struct{}
+
+func (f ContainerLogsFilter) Execute(ctx *Context) bool {
+ container := ctx.Container.Container
+
+ if container.RestartCount == 0 && container.State.Waiting != nil {
+ return false
+ }
+
+ previousLogs := false
+ if ctx.Container.HasRestarts && container.State.Running != nil {
+ previousLogs = true
+ }
+
+ logs := util.GetPodContainerLogs(
+ ctx.Client,
+ ctx.Pod.Name,
+ container.Name,
+ ctx.Pod.Namespace,
+ previousLogs,
+ ctx.Config.MaxRecentLogLines)
+
+ ctx.Container.Logs = logs
+ return false
+}
diff --git a/filter/containerNameFilter.go b/filter/containerNameFilter.go
new file mode 100644
index 00000000..0046da54
--- /dev/null
+++ b/filter/containerNameFilter.go
@@ -0,0 +1,21 @@
+package filter
+
+import (
+ "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slices"
+)
+
+type ContainerNameFilter struct{}
+
+func (f ContainerNameFilter) Execute(ctx *Context) bool {
+ container := ctx.Container.Container
+ if len(ctx.Config.IgnoreContainerNames) > 0 &&
+ slices.Contains(ctx.Config.IgnoreContainerNames, container.Name) {
+ logrus.Infof(
+ "skip pod %s as in container ignore list",
+ container.Name)
+ return true
+ }
+
+ return false
+}
diff --git a/filter/containerReasonsFilter.go b/filter/containerReasonsFilter.go
new file mode 100644
index 00000000..b6b377a6
--- /dev/null
+++ b/filter/containerReasonsFilter.go
@@ -0,0 +1,68 @@
+package filter
+
+import (
+ "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slices"
+)
+
+type ContainerReasonsFilter struct{}
+
+func (f ContainerReasonsFilter) Execute(ctx *Context) bool {
+ container := ctx.Container.Container
+
+ if container.State.Waiting != nil {
+ ctx.Container.Reason = container.State.Waiting.Reason
+ ctx.Container.Msg = container.State.Waiting.Message
+ } else if container.State.Terminated != nil {
+ ctx.Container.Reason = container.State.Terminated.Reason
+ ctx.Container.Msg = container.State.Terminated.Message
+ ctx.Container.ExitCode = container.State.Terminated.ExitCode
+ ctx.Container.LastTerminatedOn = container.State.Terminated.StartedAt.Time
+ }
+
+ if len(ctx.Config.AllowedReasons) > 0 &&
+ !slices.Contains(ctx.Config.AllowedReasons, ctx.Container.Reason) {
+ logrus.Infof(
+ "skip reason %s as not in reason allow list",
+ ctx.Container.Reason)
+ return true
+ }
+
+ if len(ctx.Config.ForbiddenReasons) > 0 &&
+ slices.Contains(ctx.Config.ForbiddenReasons, ctx.Container.Reason) {
+ logrus.Infof(
+ "skip reason %s as in reason forbid list",
+ ctx.Container.Reason)
+ return true
+ }
+ if (ctx.Container.Reason == "CrashLoopBackOff" ||
+ ctx.Container.HasRestarts) &&
+ container.LastTerminationState.Terminated != nil {
+ ctx.Container.Reason =
+ container.LastTerminationState.Terminated.Reason
+ ctx.Container.Msg =
+ container.LastTerminationState.Terminated.Message
+ ctx.Container.ExitCode =
+ container.LastTerminationState.Terminated.ExitCode
+ ctx.Container.LastTerminatedOn =
+ container.LastTerminationState.Terminated.StartedAt.Time
+ }
+
+ lastState := ctx.Memory.GetPodContainer(ctx.Pod.Namespace,
+ ctx.Pod.Name,
+ container.Name)
+
+ if lastState != nil {
+ if lastState.LastTerminatedOn == ctx.Container.LastTerminatedOn {
+ return true
+ }
+
+ if lastState.Reason == ctx.Container.Reason &&
+ lastState.Msg == ctx.Container.Msg &&
+ lastState.ExitCode == ctx.Container.ExitCode {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/filter/containerRestartsFilter.go b/filter/containerRestartsFilter.go
new file mode 100644
index 00000000..95f76454
--- /dev/null
+++ b/filter/containerRestartsFilter.go
@@ -0,0 +1,22 @@
+package filter
+
+type ContainerRestartsFilter struct{}
+
+func (f ContainerRestartsFilter) Execute(ctx *Context) bool {
+ container := ctx.Container.Container
+
+ lastState := ctx.Memory.GetPodContainer(ctx.Pod.Namespace,
+ ctx.Pod.Name,
+ container.Name)
+
+ ctx.Container.HasRestarts = false
+ if lastState == nil {
+ return false
+ }
+
+ if container.RestartCount > lastState.RestartCount {
+ ctx.Container.HasRestarts = true
+ }
+
+ return false
+}
diff --git a/filter/containerStateFilter.go b/filter/containerStateFilter.go
new file mode 100644
index 00000000..92b89609
--- /dev/null
+++ b/filter/containerStateFilter.go
@@ -0,0 +1,36 @@
+package filter
+
+type ContainerStateFilter struct{}
+
+func (f ContainerStateFilter) Execute(ctx *Context) bool {
+ container := ctx.Container.Container
+
+ if container.State.Running != nil {
+ ctx.Container.Status = "running"
+ } else if container.State.Waiting != nil {
+ ctx.Container.Status = "waiting"
+ } else if container.State.Terminated != nil {
+ ctx.Container.Status = "terminated"
+ }
+
+ if !ctx.Container.HasRestarts && container.State.Running != nil {
+ return true
+ }
+
+ if container.State.Waiting != nil &&
+ (container.State.Waiting.Reason == "ContainerCreating" ||
+ container.State.Waiting.Reason == "PodInitializing") {
+ return true
+ }
+
+ if container.State.Terminated != nil &&
+ (container.State.Terminated.Reason == "Completed" ||
+ // 143 is the exit code for graceful termination
+ container.State.Terminated.ExitCode == 143 ||
+ // 0 is the exit code for purpose stop
+ container.State.Terminated.ExitCode == 0) {
+ return true
+ }
+
+ return false
+}
diff --git a/filter/eventFilter.go b/filter/eventFilter.go
new file mode 100644
index 00000000..08e853b5
--- /dev/null
+++ b/filter/eventFilter.go
@@ -0,0 +1,12 @@
+package filter
+
+type EventFilter struct{}
+
+func (f EventFilter) Execute(ctx *Context) bool {
+ if ctx.EvType == "DELETED" {
+ ctx.Memory.DelPod(ctx.Pod.Namespace, ctx.Pod.Name)
+ return true
+ }
+
+ return false
+}
diff --git a/filter/filter.go b/filter/filter.go
new file mode 100644
index 00000000..3c93d138
--- /dev/null
+++ b/filter/filter.go
@@ -0,0 +1,51 @@
+package filter
+
+import (
+ "time"
+
+ "github.com/abahmed/kwatch/config"
+ "github.com/abahmed/kwatch/storage"
+ corev1 "k8s.io/api/core/v1"
+ apiv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+type Filter interface {
+ Execute(ctx *Context) (ShouldStop bool)
+}
+
+type FilterResult struct {
+ ShouldStop bool
+}
+
+type Context struct {
+ Client kubernetes.Interface
+ Config *config.Config
+ Memory storage.Storage
+
+ Pod *corev1.Pod
+ EvType string
+
+ Owner *apiv1.OwnerReference
+ Events *[]corev1.Event
+
+ PodHasIssues bool
+ ContainersHasIssues bool
+ PodReason string
+ PodMsg string
+
+ // Container
+ Container *ContainerContext
+}
+
+type ContainerContext struct {
+ Container *corev1.ContainerStatus
+ Reason string
+ Msg string
+ ExitCode int32
+ Logs string
+ HasRestarts bool
+ LastTerminatedOn time.Time
+ State string
+ Status string
+}
diff --git a/filter/nsFilter.go b/filter/nsFilter.go
new file mode 100644
index 00000000..dcbc609f
--- /dev/null
+++ b/filter/nsFilter.go
@@ -0,0 +1,29 @@
+package filter
+
+import (
+ "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slices"
+)
+
+type NsFilter struct{}
+
+func (f NsFilter) Execute(ctx *Context) bool {
+ // filter by namespaces in config if specified
+ if len(ctx.Config.AllowedNamespaces) > 0 &&
+ !slices.Contains(ctx.Config.AllowedNamespaces, ctx.Pod.Namespace) {
+ logrus.Infof(
+ "skip namespace %s as not in namespace allow list",
+ ctx.Pod.Namespace)
+ return true
+ }
+
+ if len(ctx.Config.ForbiddenNamespaces) > 0 &&
+ slices.Contains(ctx.Config.ForbiddenNamespaces, ctx.Pod.Namespace) {
+ logrus.Infof(
+ "skip namespace %s as in namespace forbid list",
+ ctx.Pod.Namespace)
+ return true
+ }
+
+ return false
+}
diff --git a/filter/podEventsFilter.go b/filter/podEventsFilter.go
new file mode 100644
index 00000000..d7cf7f75
--- /dev/null
+++ b/filter/podEventsFilter.go
@@ -0,0 +1,42 @@
+package filter
+
+import (
+ "strings"
+
+ "github.com/abahmed/kwatch/util"
+ corev1 "k8s.io/api/core/v1"
+)
+
+type PodEventsFilter struct{}
+
+func (f PodEventsFilter) Execute(ctx *Context) bool {
+ if !ctx.PodHasIssues {
+ return false
+ }
+ events, _ := util.GetPodEvents(ctx.Client, ctx.Pod.Name, ctx.Pod.Namespace)
+ ctx.Events = &events.Items
+
+ if ctx.Events == nil {
+ return false
+ }
+
+ for _, ev := range *ctx.Events {
+ if ev.Type == corev1.EventTypeWarning {
+ if strings.Contains(ev.Message, "deleting pod") {
+ ctx.PodHasIssues = false
+ ctx.ContainersHasIssues = false
+ return true
+ }
+
+ /*
+ if ev.Reason == "FailedScheduling" ||
+ ev.Reason == "NetworkNotReady" ||
+ ev.Reason == "FailedMount" {
+ ctx.PodHasIssues = true
+ ctx.ContainersHasIssues = false
+ return false
+ }*/
+ }
+ }
+ return false
+}
diff --git a/filter/podOwnersFilter.go b/filter/podOwnersFilter.go
new file mode 100644
index 00000000..0170f4e7
--- /dev/null
+++ b/filter/podOwnersFilter.go
@@ -0,0 +1,54 @@
+package filter
+
+import (
+ "context"
+
+ apiv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type PodOwnersFilter struct{}
+
+func (f PodOwnersFilter) Execute(ctx *Context) bool {
+ if ctx.Owner != nil {
+ return false
+ }
+
+ if len(ctx.Pod.OwnerReferences) == 0 {
+ return false
+ }
+
+ owner := ctx.Pod.OwnerReferences[0]
+ if owner.Kind == "ReplicaSet" {
+ rs, _ :=
+ ctx.Client.AppsV1().ReplicaSets(ctx.Pod.Namespace).Get(
+ context.TODO(),
+ owner.Name,
+ apiv1.GetOptions{})
+
+ if rs != nil && len(rs.ObjectMeta.OwnerReferences) > 0 {
+ owner = rs.ObjectMeta.OwnerReferences[0]
+ }
+ } else if owner.Kind == "DaemonSet" {
+ ds, _ :=
+ ctx.Client.AppsV1().DaemonSets(ctx.Pod.Namespace).Get(
+ context.TODO(),
+ owner.Name,
+ apiv1.GetOptions{})
+ if ds != nil && len(ds.ObjectMeta.OwnerReferences) > 0 {
+ owner = ds.ObjectMeta.OwnerReferences[0]
+ }
+ } else if owner.Kind == "StatefulSet" {
+ ss, _ :=
+ ctx.Client.AppsV1().StatefulSets(ctx.Pod.Namespace).Get(
+ context.TODO(),
+ owner.Name,
+ apiv1.GetOptions{})
+ if ss != nil && len(ss.ObjectMeta.OwnerReferences) > 0 {
+ owner = ss.ObjectMeta.OwnerReferences[0]
+ }
+ }
+
+ ctx.Owner = &owner
+
+ return false
+}
diff --git a/filter/podStatusFilter.go b/filter/podStatusFilter.go
new file mode 100644
index 00000000..39ad2f4d
--- /dev/null
+++ b/filter/podStatusFilter.go
@@ -0,0 +1,60 @@
+package filter
+
+import (
+ corev1 "k8s.io/api/core/v1"
+)
+
+type PodStatusFilter struct{}
+
+func (f PodStatusFilter) Execute(ctx *Context) bool {
+ if ctx.Pod.Status.Phase == corev1.PodSucceeded {
+ ctx.PodHasIssues = false
+ ctx.ContainersHasIssues = false
+ return true
+ }
+
+ if ctx.EvType == "Added" && len(ctx.Pod.Status.Conditions) == 0 {
+ ctx.PodHasIssues = false
+ ctx.ContainersHasIssues = false
+ return true
+ }
+
+ issueInContainers := true
+ issueInPod := true
+ for _, c := range ctx.Pod.Status.Conditions {
+ if c.Type == corev1.PodReady {
+ if c.Status == corev1.ConditionFalse && c.Reason == "PodCompleted" {
+ ctx.PodHasIssues = false
+ ctx.ContainersHasIssues = false
+ return true
+ }
+
+ issueInPod = false
+ issueInContainers = false
+ if c.Status != corev1.ConditionTrue {
+ issueInContainers = true
+ }
+ } else if c.Type == corev1.PodScheduled && c.Status == corev1.ConditionFalse {
+ issueInPod = true
+ issueInContainers = false
+ ctx.PodReason = c.Reason
+ ctx.PodMsg = c.Message
+ } else if c.Type == corev1.ContainersReady && c.Status == corev1.ConditionFalse {
+ issueInContainers = true
+ issueInPod = false
+ }
+ }
+
+ ctx.PodHasIssues = issueInPod
+ ctx.ContainersHasIssues = issueInContainers
+
+ lastState := ctx.Memory.GetPodContainer(ctx.Pod.Namespace,
+ ctx.Pod.Name,
+ ".")
+
+ if ctx.PodHasIssues && lastState != nil {
+ return true
+ }
+
+ return false
+}
diff --git a/go.mod b/go.mod
index 90af1362..a9c17527 100644
--- a/go.mod
+++ b/go.mod
@@ -1,36 +1,36 @@
module github.com/abahmed/kwatch
-go 1.22
+go 1.22.3
require (
github.com/bwmarrin/discordgo v0.28.1
github.com/google/go-github/v41 v41.0.1-0.20211227215900-a899e0fadbec
github.com/sirupsen/logrus v1.9.3
- github.com/slack-go/slack v0.12.5
+ github.com/slack-go/slack v0.13.0
github.com/stretchr/testify v1.9.0
gopkg.in/mail.v2 v2.3.1
gopkg.in/yaml.v3 v3.0.1
- k8s.io/api v0.29.4
- k8s.io/apimachinery v0.29.4
- k8s.io/client-go v0.29.4
+ k8s.io/api v0.30.1
+ k8s.io/apimachinery v0.30.1
+ k8s.io/client-go v0.30.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/emicklei/go-restful/v3 v3.11.0 // indirect
- github.com/evanphx/json-patch v5.6.0+incompatible // indirect
- github.com/go-logr/logr v1.3.0 // indirect
- github.com/go-openapi/jsonpointer v0.19.6 // indirect
- github.com/go-openapi/jsonreference v0.20.2 // indirect
- github.com/go-openapi/swag v0.22.3 // indirect
+ github.com/emicklei/go-restful/v3 v3.12.0 // indirect
+ github.com/evanphx/json-patch v5.9.0+incompatible // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-openapi/jsonpointer v0.21.0 // indirect
+ github.com/go-openapi/jsonreference v0.21.0 // indirect
+ github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
- github.com/google/uuid v1.3.0 // indirect
- github.com/gorilla/websocket v1.5.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/gorilla/websocket v1.5.1 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@@ -41,22 +41,22 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
- golang.org/x/crypto v0.21.0 // indirect
- golang.org/x/net v0.23.0 // indirect
- golang.org/x/oauth2 v0.10.0 // indirect
- golang.org/x/sys v0.18.0 // indirect
- golang.org/x/term v0.18.0 // indirect
- golang.org/x/text v0.14.0 // indirect
- golang.org/x/time v0.3.0 // indirect
- google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/protobuf v1.33.0 // indirect
+ golang.org/x/crypto v0.23.0 // indirect
+ golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d
+ golang.org/x/net v0.25.0 // indirect
+ golang.org/x/oauth2 v0.20.0 // indirect
+ golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/term v0.20.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ golang.org/x/time v0.5.0 // indirect
+ google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- k8s.io/klog/v2 v2.110.1 // indirect
- k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
- k8s.io/utils v0.0.0-20230726121419-3b25d923346b
+ k8s.io/klog/v2 v2.120.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect
+ k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
- sigs.k8s.io/yaml v1.3.0 // indirect
+ sigs.k8s.io/yaml v1.4.0 // indirect
)
diff --git a/go.sum b/go.sum
index 8d979b78..1121ea78 100644
--- a/go.sum
+++ b/go.sum
@@ -1,28 +1,27 @@
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
-github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
-github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
-github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
-github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
-github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
-github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
-github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
-github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
+github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
+github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
@@ -39,13 +38,13 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
-github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
-github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -54,11 +53,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@@ -70,31 +66,26 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
-github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
-github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
-github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
+github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
+github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
+github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
+github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
-github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/slack-go/slack v0.12.5 h1:ddZ6uz6XVaB+3MTDhoW04gG+Vc/M/X1ctC+wssy2cqs=
-github.com/slack-go/slack v0.12.5/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
+github.com/slack-go/slack v0.13.0 h1:7my/pR2ubZJ9912p9FtvALYpbt0cQPAqkRy2jaSI1PQ=
+github.com/slack-go/slack v0.13.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -103,20 +94,23 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
+golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
+golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
-golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
+golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -125,32 +119,29 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
-golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
+golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
-google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -167,21 +158,21 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.29.4 h1:WEnF/XdxuCxdG3ayHNRR8yH3cI1B/llkWBma6bq4R3w=
-k8s.io/api v0.29.4/go.mod h1:DetSv0t4FBTcEpfA84NJV3g9a7+rSzlUHk5ADAYHUv0=
-k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
-k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y=
-k8s.io/client-go v0.29.4 h1:79ytIedxVfyXV8rpH3jCBW0u+un0fxHDwX5F9K8dPR8=
-k8s.io/client-go v0.29.4/go.mod h1:kC1thZQ4zQWYwldsfI088BbK6RkxK+aF5ebV8y9Q4tk=
-k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
-k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
-k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
-k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
-k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
-k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY=
+k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM=
+k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U=
+k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
+k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q=
+k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc=
+k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
+k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a h1:zD1uj3Jf+mD4zmA7W+goE5TxDkI7OGJjBNBzq5fJtLA=
+k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
-sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
-sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/handler/executeContainersFilters.go b/handler/executeContainersFilters.go
new file mode 100644
index 00000000..e9d08c0e
--- /dev/null
+++ b/handler/executeContainersFilters.go
@@ -0,0 +1,56 @@
+package handler
+
+import (
+ "time"
+
+ "github.com/abahmed/kwatch/filter"
+ "github.com/abahmed/kwatch/storage"
+ "github.com/sirupsen/logrus"
+)
+
+func (h *handler) executeContainersFilters(ctx *filter.Context) {
+ for cIdx := range ctx.Pod.Status.ContainerStatuses {
+ ctx.Container = &filter.ContainerContext{
+ Container: &ctx.Pod.Status.ContainerStatuses[cIdx],
+ HasRestarts: false,
+ LastTerminatedOn: time.Time{},
+ }
+
+ isContainerOk := false
+ for i := range h.containerFilters {
+ if shouldStop := h.containerFilters[i].Execute(ctx); shouldStop {
+ isContainerOk = true
+ break
+ }
+ }
+
+ ctx.Memory.AddPodContainer(
+ ctx.Pod.Namespace,
+ ctx.Pod.Name,
+ ctx.Container.Container.Name,
+ &storage.ContainerState{
+ RestartCount: ctx.Container.Container.RestartCount,
+ LastTerminatedOn: ctx.Container.LastTerminatedOn,
+ Reason: ctx.Container.Reason,
+ Msg: ctx.Container.Msg,
+ ExitCode: ctx.Container.ExitCode,
+ Status: ctx.Container.Status,
+ })
+
+ if !isContainerOk {
+ ownerName := ""
+ if ctx.Owner != nil {
+ ownerName = ctx.Owner.Name
+ }
+
+ logrus.Printf(
+ "container only issue %s %s %s %s %s %d",
+ ctx.Container.Container.Name,
+ ctx.Pod.Name,
+ ownerName,
+ ctx.Container.Reason,
+ ctx.Container.Msg,
+ ctx.Container.ExitCode)
+ }
+ }
+}
diff --git a/handler/executePodFilters.go b/handler/executePodFilters.go
new file mode 100644
index 00000000..9d0afadc
--- /dev/null
+++ b/handler/executePodFilters.go
@@ -0,0 +1,41 @@
+package handler
+
+import (
+ "github.com/abahmed/kwatch/filter"
+ "github.com/abahmed/kwatch/storage"
+ "github.com/sirupsen/logrus"
+)
+
+func (h *handler) executePodFilters(ctx *filter.Context) {
+ isPodOk := false
+ for i := range h.podFilters {
+ if shouldStop := h.podFilters[i].Execute(ctx); shouldStop {
+ isPodOk = true
+ break
+ }
+ }
+
+ if isPodOk ||
+ ctx.ContainersHasIssues ||
+ !ctx.PodHasIssues {
+ return
+ }
+
+ ownerName := ""
+ if ctx.Owner != nil {
+ ownerName = ctx.Owner.Name
+ }
+
+ ctx.Memory.AddPodContainer(
+ ctx.Pod.Namespace,
+ ctx.Pod.Name,
+ ".",
+ &storage.ContainerState{
+ Reason: ctx.PodReason,
+ Msg: ctx.PodMsg,
+ Status: "",
+ },
+ )
+
+ logrus.Printf("pod only issue %s %s %s %s", ctx.Pod.Name, ownerName, ctx.PodReason, ctx.PodMsg)
+}
diff --git a/handler/handler.go b/handler/handler.go
new file mode 100644
index 00000000..c02b6ae9
--- /dev/null
+++ b/handler/handler.go
@@ -0,0 +1,56 @@
+package handler
+
+import (
+ "github.com/abahmed/kwatch/alertmanager"
+ "github.com/abahmed/kwatch/config"
+ "github.com/abahmed/kwatch/filter"
+ "github.com/abahmed/kwatch/storage"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+type Handler interface {
+ ProcessPod(evType string, pod *corev1.Pod)
+}
+
+type handler struct {
+ kclient kubernetes.Interface
+ config *config.Config
+ memory storage.Storage
+ podFilters []filter.Filter
+ containerFilters []filter.Filter
+ alertManager *alertmanager.AlertManager
+}
+
+func NewHandler(
+ cli kubernetes.Interface,
+ cfg *config.Config,
+ mem storage.Storage,
+ alertManager *alertmanager.AlertManager) Handler {
+ // Order is important
+ podFilters := []filter.Filter{
+ filter.NsFilter{},
+ filter.PodStatusFilter{},
+ filter.PodEventsFilter{},
+ //filter.PodOwnersFilter{},
+ }
+
+ containersFilters := []filter.Filter{
+ filter.ContainerNameFilter{},
+ filter.ContainerRestartsFilter{},
+ filter.ContainerStateFilter{},
+ filter.ContainerKillingFilter{},
+ filter.ContainerReasonsFilter{},
+ filter.ContainerLogsFilter{},
+ filter.PodOwnersFilter{},
+ }
+
+ return &handler{
+ kclient: cli,
+ config: cfg,
+ podFilters: podFilters,
+ containerFilters: containersFilters,
+ memory: mem,
+ alertManager: alertManager,
+ }
+}
diff --git a/handler/processPod.go b/handler/processPod.go
new file mode 100644
index 00000000..487b3e95
--- /dev/null
+++ b/handler/processPod.go
@@ -0,0 +1,28 @@
+package handler
+
+import (
+ "github.com/abahmed/kwatch/filter"
+ corev1 "k8s.io/api/core/v1"
+)
+
+func (h *handler) ProcessPod(eventType string, pod *corev1.Pod) {
+ if pod == nil {
+ return
+ }
+
+ if eventType == "DELETED" {
+ h.memory.DelPod(pod.Namespace, pod.Name)
+ return
+ }
+
+ ctx := filter.Context{
+ Client: h.kclient,
+ Config: h.config,
+ Memory: h.memory,
+ Pod: pod,
+ EvType: eventType,
+ }
+
+ h.executePodFilters(&ctx)
+ h.executeContainersFilters(&ctx)
+}
diff --git a/main.go b/main.go
index c962b506..15dc82b5 100644
--- a/main.go
+++ b/main.go
@@ -7,11 +7,14 @@ import (
"github.com/abahmed/kwatch/client"
"github.com/abahmed/kwatch/config"
"github.com/abahmed/kwatch/constant"
- "github.com/abahmed/kwatch/controller"
+ "github.com/abahmed/kwatch/handler"
"github.com/abahmed/kwatch/pvcmonitor"
+ "github.com/abahmed/kwatch/storage/memory"
"github.com/abahmed/kwatch/upgrader"
"github.com/abahmed/kwatch/version"
+ "github.com/abahmed/kwatch/watcher"
"github.com/sirupsen/logrus"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func main() {
@@ -42,10 +45,19 @@ func main() {
pvcmonitor.NewPvcMonitor(client, &config.PvcMonitor, &alertManager)
go pvcMonitor.Start()
- // start controller
- controller.Start(
+ // Create handler
+ h := handler.NewHandler(
client,
- &alertManager,
config,
+ memory.NewMemory(),
+ &alertManager,
)
+
+ namespace := metav1.NamespaceAll
+ if len(config.AllowedNamespaces) == 1 {
+ namespace = config.AllowedNamespaces[0]
+ }
+
+ // start watcher
+ watcher.Start(client, namespace, h.ProcessPod)
}
diff --git a/storage/memory/memory.go b/storage/memory/memory.go
index e4189b3a..a0bef40d 100644
--- a/storage/memory/memory.go
+++ b/storage/memory/memory.go
@@ -18,46 +18,72 @@ func NewMemory() storage.Storage {
}
// AddPodContainer attaches container to pod to mark it has an error
-func (m *memory) AddPodContainer(podKey, containerKey string) {
- if v, ok := m.smap.Load(podKey); ok {
- containers := v.(map[string]bool)
- containers[containerKey] = true
+func (m *memory) AddPodContainer(namespace, podKey, containerKey string, state *storage.ContainerState) {
+ key := m.getKey(namespace, podKey)
+ if v, ok := m.smap.Load(key); ok {
+ containers := v.(map[string]*storage.ContainerState)
+ containers[containerKey] = state
- m.smap.Store(podKey, containers)
+ m.smap.Store(key, containers)
return
}
- m.smap.Store(podKey, map[string]bool{containerKey: true})
+ m.smap.Store(key, map[string]*storage.ContainerState{containerKey: state})
}
// Delete deletes pod with all its containers
-func (m *memory) DelPod(key string) {
+func (m *memory) DelPod(namespace, podKey string) {
+ key := m.getKey(namespace, podKey)
m.smap.Delete(key)
}
// DelPodContainer detaches container from pod to mark error is resolved
-func (m *memory) DelPodContainer(podKey, containerKey string) {
- v, ok := m.smap.Load(podKey)
+func (m *memory) DelPodContainer(namespace, podKey, containerKey string) {
+ key := m.getKey(namespace, podKey)
+
+ v, ok := m.smap.Load(key)
if !ok {
return
}
- containers := v.(map[string]bool)
+ containers := v.(map[string]*storage.ContainerState)
delete(containers, containerKey)
- m.smap.Store(podKey, containers)
+ m.smap.Store(key, containers)
}
// HasPodContainer checks if container is attached to given pod or not
-func (m *memory) HasPodContainer(podKey, containerKey string) bool {
- v, ok := m.smap.Load(podKey)
+func (m *memory) HasPodContainer(namespace, podKey, containerKey string) bool {
+ key := m.getKey(namespace, podKey)
+
+ v, ok := m.smap.Load(key)
if !ok {
return false
}
- containers := v.(map[string]bool)
+ containers := v.(map[string]*storage.ContainerState)
if _, ok := containers[containerKey]; ok {
return true
}
return false
}
+
+func (m *memory) GetPodContainer(namespace, podKey, containerKey string) *storage.ContainerState {
+ key := m.getKey(namespace, podKey)
+
+ v, ok := m.smap.Load(key)
+ if !ok {
+ return nil
+ }
+
+ containers := v.(map[string]*storage.ContainerState)
+ if val, ok := containers[containerKey]; ok {
+ return val
+ }
+
+ return nil
+}
+
+func (*memory) getKey(namespace, pod string) string {
+ return namespace + "/" + pod
+}
diff --git a/storage/memory/memory_test.go b/storage/memory/memory_test.go
index 49221ab1..3db9652f 100644
--- a/storage/memory/memory_test.go
+++ b/storage/memory/memory_test.go
@@ -20,19 +20,19 @@ func TestAddPodContainer(t *testing.T) {
smap: sync.Map{},
}
- mem.AddPodContainer("test", "test")
- mem.AddPodContainer("test", "test2")
+ mem.AddPodContainer("default", "test", "container1", &storage.ContainerState{})
+ mem.AddPodContainer("default", "test", "container2", &storage.ContainerState{})
- if v, ok := mem.smap.Load("test"); !ok {
+ if v, ok := mem.smap.Load(mem.getKey("default", "test")); !ok {
t.Errorf("expected to find value in pod test")
} else {
- containers := v.(map[string]bool)
- if _, ok = containers["test"]; !ok {
- t.Errorf("expected to find container test in pod test")
+ containers := v.(map[string]*storage.ContainerState)
+ if _, ok = containers["container1"]; !ok {
+ t.Errorf("expected to find container container1 in pod test")
}
- if _, ok = containers["test2"]; !ok {
- t.Errorf("expected to find container test2 in pod test")
+ if _, ok = containers["container2"]; !ok {
+ t.Errorf("expected to find container container2 in pod test")
}
}
}
@@ -42,25 +42,25 @@ func TestHasPodContainer(t *testing.T) {
smap: sync.Map{},
}
- mem.AddPodContainer("test", "test")
- mem.AddPodContainer("test", "test2")
+ mem.AddPodContainer("default", "test", "test", &storage.ContainerState{})
+ mem.AddPodContainer("default", "test", "test2", &storage.ContainerState{})
- mem.DelPodContainer("test", "test")
- mem.DelPodContainer("test3", "test")
+ mem.DelPodContainer("default", "test", "test")
+ mem.DelPodContainer("default", "test3", "test")
- if !mem.HasPodContainer("test", "test2") {
+ if !mem.HasPodContainer("default", "test", "test2") {
t.Errorf("expected to find container test2 in pod test")
}
- if mem.HasPodContainer("test", "test") {
+ if mem.HasPodContainer("default", "test", "test") {
t.Errorf("expected not to find container test in pod test")
}
- if mem.HasPodContainer("test", "test6") {
+ if mem.HasPodContainer("default", "test", "test6") {
t.Errorf("expected not to find container test6 in pod test")
}
- if mem.HasPodContainer("test4", "test") {
+ if mem.HasPodContainer("default", "test4", "test") {
t.Errorf("expected to not find container test in pod test4")
}
}
@@ -70,34 +70,58 @@ func TestDelPodContainer(t *testing.T) {
smap: sync.Map{},
}
- mem.AddPodContainer("test", "test")
- mem.AddPodContainer("test", "test2")
+ mem.AddPodContainer("default", "test", "test", &storage.ContainerState{})
+ mem.AddPodContainer("default", "test", "test2", &storage.ContainerState{})
- mem.DelPodContainer("test", "test")
- mem.DelPodContainer("test3", "test")
+ mem.DelPodContainer("default", "test", "test")
+ mem.DelPodContainer("default", "test3", "test")
- if v, ok := mem.smap.Load("test"); !ok {
+ if v, ok := mem.smap.Load(mem.getKey("default", "test")); !ok {
t.Errorf("expected to find value in pod test")
} else {
- containers := v.(map[string]bool)
+ containers := v.(map[string]*storage.ContainerState)
if _, ok = containers["test"]; ok {
t.Errorf("expected not to find container test in pod test")
}
}
}
+func TestGetPodContainer(t *testing.T) {
+ mem := &memory{
+ smap: sync.Map{},
+ }
+
+ mem.AddPodContainer("default", "test", "test1", &storage.ContainerState{})
+ mem.AddPodContainer("default", "test", "test2", &storage.ContainerState{})
+
+ state := mem.GetPodContainer("default", "test", "test1")
+ if state == nil {
+ t.Errorf("expected to find value in pod test")
+ }
+
+ state2 := mem.GetPodContainer("default", "test", "test3")
+ if state2 != nil {
+ t.Errorf("expected to be nil as container does not exist")
+ }
+
+ state3 := mem.GetPodContainer("default", "test3", "test1")
+ if state3 != nil {
+ t.Errorf("expected to be nil as pod does not exist")
+ }
+}
+
func TestDelPod(t *testing.T) {
mem := &memory{
smap: sync.Map{},
}
- mem.AddPodContainer("test", "test")
- mem.AddPodContainer("test", "test2")
+ mem.AddPodContainer("default", "test", "test1", &storage.ContainerState{})
+ mem.AddPodContainer("default", "test", "test2", &storage.ContainerState{})
- mem.DelPod("test")
- mem.DelPod("test3")
+ mem.DelPod("default", "test")
+ mem.DelPod("default", "test3")
- if _, ok := mem.smap.Load("test"); ok {
+ if _, ok := mem.smap.Load(mem.getKey("default", "test")); ok {
t.Errorf("expected not to find pod test")
}
}
diff --git a/storage/storage.go b/storage/storage.go
index 82697a1e..c09db911 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -1,9 +1,22 @@
package storage
+import "time"
+
+type ContainerState struct {
+ RestartCount int32
+ LastTerminatedOn time.Time
+ Reason string
+ Msg string
+ ExitCode int32
+ Status string
+ Reported bool
+}
+
// Storage interface
type Storage interface {
- AddPodContainer(podKey, containerKey string)
- DelPodContainer(podKey, containerKey string)
- DelPod(podKey string)
- HasPodContainer(podKey, containerKey string) bool
+ AddPodContainer(namespace, podKey, containerKey string, state *ContainerState)
+ DelPodContainer(namespace, podKey, containerKey string)
+ DelPod(namespace, podKey string)
+ HasPodContainer(namespace, podKey, containerKey string) bool
+ GetPodContainer(namespace, podKey, containerKey string) *ContainerState
}
diff --git a/upgrader/upgrader.go b/upgrader/upgrader.go
index a9bbc793..36f0fb88 100644
--- a/upgrader/upgrader.go
+++ b/upgrader/upgrader.go
@@ -29,7 +29,8 @@ func NewUpgrader(config *config.Upgrader,
// CheckUpdates checks every 24 hours if a newer version of Kwatch is available
func (u *Upgrader) CheckUpdates() {
- if u.config.DisableUpdateCheck {
+ if u.config.DisableUpdateCheck ||
+ version.Short() == "dev" {
return
}
diff --git a/util/util.go b/util/util.go
index fc3a19ba..2355c86c 100644
--- a/util/util.go
+++ b/util/util.go
@@ -18,7 +18,7 @@ import (
// GetPodEventsStr returns formatted events as a string for specified pod
func GetPodEventsStr(c kubernetes.Interface, name, namespace string) string {
- events, err := getPodEvents(c, name, namespace)
+ events, err := GetPodEvents(c, name, namespace)
eventsString := ""
if err != nil {
@@ -49,7 +49,7 @@ func ContainsKillingStoppingContainerEvents(
c kubernetes.Interface,
name,
namespace string) bool {
- events, err := getPodEvents(c, name, namespace)
+ events, err := GetPodEvents(c, name, namespace)
if err != nil {
return false
}
@@ -120,7 +120,7 @@ func getContainerLogs(
DoRaw(context.TODO())
}
-func getPodEvents(
+func GetPodEvents(
c kubernetes.Interface,
name,
namespace string) (*v1.EventList, error) {
@@ -180,9 +180,9 @@ func RandomString(n int) string {
"NOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
- rand.Seed(time.Now().UnixNano())
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := range b {
- b[i] = availableCharacterBytes[rand.Intn(len(availableCharacterBytes))]
+ b[i] = availableCharacterBytes[r.Intn(len(availableCharacterBytes))]
}
return string(b)
diff --git a/watcher/start.go b/watcher/start.go
new file mode 100644
index 00000000..147cb77d
--- /dev/null
+++ b/watcher/start.go
@@ -0,0 +1,45 @@
+package watcher
+
+import (
+ "context"
+
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/cache"
+ toolsWatch "k8s.io/client-go/tools/watch"
+ "k8s.io/client-go/util/workqueue"
+)
+
+// Start creates an instance of watcher after initialization and runs it
+func Start(
+ client kubernetes.Interface,
+ namespace string,
+ handleFunc func(string, *corev1.Pod)) {
+ watchFunc :=
+ func(options metav1.ListOptions) (watch.Interface, error) {
+ return client.CoreV1().Pods(namespace).Watch(
+ context.Background(),
+ metav1.ListOptions{},
+ )
+ }
+
+ watcher, _ :=
+ toolsWatch.NewRetryWatcher(
+ "1",
+ &cache.ListWatch{WatchFunc: watchFunc},
+ )
+
+ w := &Watcher{
+ watcher: watcher,
+ queue: workqueue.New(),
+ handlerFunc: handleFunc,
+ }
+
+ stopCh := make(chan struct{})
+ defer close(stopCh)
+
+ w.run(stopCh)
+}
diff --git a/watcher/watcher.go b/watcher/watcher.go
new file mode 100644
index 00000000..6e72c997
--- /dev/null
+++ b/watcher/watcher.go
@@ -0,0 +1,76 @@
+package watcher
+
+import (
+ "time"
+
+ "github.com/sirupsen/logrus"
+ corev1 "k8s.io/api/core/v1"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apimachinery/pkg/util/wait"
+ toolsWatch "k8s.io/client-go/tools/watch"
+ "k8s.io/client-go/util/workqueue"
+)
+
+type watcherEvent struct {
+ eventType string
+ pod *corev1.Pod
+}
+
+type Watcher struct {
+ watcher *toolsWatch.RetryWatcher
+ queue *workqueue.Type
+ handlerFunc func(string, *corev1.Pod)
+}
+
+// run starts the watcher
+func (w *Watcher) run(stopCh chan struct{}) {
+ defer utilruntime.HandleCrash()
+ defer w.queue.ShutDown()
+
+ logrus.Info("starting pod watcher")
+
+ go wait.Until(w.processEvents, time.Second, stopCh)
+ go wait.Until(w.runWorker, time.Second, stopCh)
+
+ <-stopCh
+}
+
+func (w *Watcher) processEvents() {
+ if w.watcher == nil {
+ return
+ }
+
+ for event := range w.watcher.ResultChan() {
+ pod := event.Object.(*corev1.Pod)
+ w.queue.Add(watcherEvent{
+ eventType: string(event.Type),
+ pod: pod.DeepCopy(),
+ })
+ }
+}
+
+func (w *Watcher) runWorker() {
+ for w.processNextItem() {
+ // continue looping
+ }
+}
+
+func (w *Watcher) processNextItem() bool {
+ newEvent, quit := w.queue.Get()
+
+ if quit {
+ return false
+ }
+
+ defer w.queue.Done(newEvent)
+
+ ev, ok := newEvent.(watcherEvent)
+ if !ok {
+ logrus.Errorf("failed to cast watcher event: %v", ev)
+ return true
+ }
+
+ w.handlerFunc(ev.eventType, ev.pod)
+
+ return true
+}