diff --git a/sysdump/eventSummary.html b/sysdump/eventSummary.html new file mode 100644 index 0000000000..0fea2fba60 --- /dev/null +++ b/sysdump/eventSummary.html @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + Events + + + + + + + + + + + + + + + + + + + {{range . }} + + + + + + + + + + {{end}} + +
TimeNamespaceComponentHostRelatedObjectReasonMessage
{{formatTime .ObjectMeta.CreationTimestamp .FirstTimestamp .LastTimestamp .Count}}{{.Namespace}}{{.Source.Component}}{{.Source.Host}}{{.InvolvedObject.Name}}{{.Reason}}{{.Message}}
+ + + + \ No newline at end of file diff --git a/sysdump/sysdump.go b/sysdump/sysdump.go index 443066f6c5..9f281f5325 100644 --- a/sysdump/sysdump.go +++ b/sysdump/sysdump.go @@ -301,6 +301,9 @@ func (c *Collector) Run() error { if err := c.WriteYAML(kubernetesEventsFileName, v); err != nil { return fmt.Errorf("failed to collect Kubernetes events: %w", err) } + if err := c.WriteBytes("events.html", makeEventTable(v.Items)); err != nil { + return fmt.Errorf("failed to write event tble: %w", err) + } return nil }, }, diff --git a/sysdump/writers.go b/sysdump/writers.go index 16871391c9..d077ecd240 100644 --- a/sysdump/writers.go +++ b/sysdump/writers.go @@ -5,14 +5,24 @@ package sysdump import ( "bytes" + _ "embed" + "fmt" + "html/template" "os" + "sort" + "strings" + "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/kubernetes/scheme" ) +//go:embed eventSummary.html +var eventSummaryHTML string + func writeBytes(p string, b []byte) error { return os.WriteFile(p, b, fileMode) } @@ -44,3 +54,49 @@ func writeYaml(p string, o runtime.Object) error { } return writeString(p, b.String()) } + +// writeEventTable writes a html summary of cluster events. +func makeEventTable(events []corev1.Event) []byte { + // sort events by time + sort.Slice(events, func(i, j int) bool { + return events[i].LastTimestamp.Time.Before(events[j].LastTimestamp.Time) + }) + + t := template.Must(template.New("events").Funcs(template.FuncMap{ + "formatTime": func(created, firstSeen, lastSeen metav1.Time, count int32) template.HTML { + countMsg := "" + if count > 1 { + countMsg = fmt.Sprintf(" (x%d)", count) + } + if lastSeen.IsZero() { + lastSeen = created + } + firstSeenUTC := firstSeen.In(time.UTC) + lastSeenUTC := lastSeen.In(time.UTC) + //nolint:gosec + return template.HTML(fmt.Sprintf(`%s`, lastSeenUTC.String(), firstSeenUTC.Format("15:04:05Z"), lastSeenUTC.Format("15:04:05Z"), countMsg)) + }, + "reasonClass": func(r string) template.HTMLAttr { + cssClass := "text-muted" + switch { + case strings.Contains(strings.ToLower(r), "fail"), + strings.Contains(strings.ToLower(r), "error"), + strings.Contains(strings.ToLower(r), "kill"), + strings.Contains(strings.ToLower(r), "backoff"): + cssClass = "text-danger" + case strings.Contains(strings.ToLower(r), "notready"), + strings.Contains(strings.ToLower(r), "unhealthy"), + strings.Contains(strings.ToLower(r), "missing"): + cssClass = "text-warning" + } + //nolint:gosec + return template.HTMLAttr(fmt.Sprintf(`class="%s"`, cssClass)) + }, + }).Parse(eventSummaryHTML)) + + out := bytes.NewBuffer([]byte{}) + if err := t.Execute(out, events); err != nil { + panic(err) // unreachable + } + return out.Bytes() +}