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 +}