internal/inspect/events.go
Rama main · réplica de lectura del repositorio original.
package inspect
import (
"context"
"errors"
"fmt"
"regexp"
"slices"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"github.com/sophotechlabs/spinoza/internal/api"
"github.com/sophotechlabs/spinoza/internal/unstr"
)
var eventGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "events"}
var objectUID = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
var ErrInvalidUID = errors.New("uid must be a kubernetes object uid")
var listTimeout = 15 * time.Second
func Events(ctx context.Context, dyn dynamic.Interface, namespace, uid string) ([]api.Event, error) {
if uid == "" {
return []api.Event{}, nil
}
if !objectUID.MatchString(uid) {
return nil, fmt.Errorf("%w, got %q", ErrInvalidUID, uid)
}
opts := metav1.ListOptions{FieldSelector: "involvedObject.uid=" + uid}
bounded, cancel := context.WithTimeout(ctx, listTimeout)
defer cancel()
list, err := eventsFor(dyn, namespace).List(bounded, opts)
if err != nil {
return nil, fmt.Errorf("listing events: %w", err)
}
out := make([]api.Event, 0, len(list.Items))
for i := range list.Items {
out = append(out, EventOf(&list.Items[i]))
}
sortEvents(out)
return out, nil
}
func eventsFor(dyn dynamic.Interface, namespace string) dynamic.ResourceInterface {
if namespace == "" {
return dyn.Resource(eventGVR)
}
return dyn.Resource(eventGVR).Namespace(namespace)
}
func sortEvents(events []api.Event) {
slices.SortStableFunc(events, func(left, right api.Event) int {
if when := seenAt(right.LastSeen).Compare(seenAt(left.LastSeen)); when != 0 {
return when
}
return strings.Compare(eventKey(left), eventKey(right))
})
}
func seenAt(stamp string) time.Time {
t, err := time.Parse(time.RFC3339, stamp)
if err != nil {
return time.Time{}
}
return t
}
func EventOf(obj *unstructured.Unstructured) api.Event {
return api.Event{
Type: unstr.String(obj, "type"),
Reason: unstr.String(obj, "reason"),
Message: unstr.String(obj, "message"),
Source: sourceOf(obj),
Count: countOf(obj),
FirstSeen: unstr.String(obj, "firstTimestamp"),
LastSeen: lastSeenOf(obj),
}
}
func sourceOf(u *unstructured.Unstructured) string {
component := unstr.String(u, "source", "component")
if component != "" {
return component
}
return unstr.String(u, "reportingComponent")
}
func countOf(u *unstructured.Unstructured) int64 {
count := unstr.Int(u, "count")
if count > 0 {
return count
}
series := unstr.Int(u, "series", "count")
if series > 0 {
return series
}
return 1
}
func lastSeenOf(obj *unstructured.Unstructured) string {
paths := [][]string{
{"lastTimestamp"},
{"series", "lastObservedTime"},
{"eventTime"},
{"firstTimestamp"},
}
for _, p := range paths {
v := unstr.String(obj, p...)
if v != "" {
return v
}
}
return ""
}
func eventKey(one api.Event) string {
return one.Reason + "\x00" + one.Source + "\x00" + one.Message
}