Ctrl K
ComunidadGuías…

internal/inspect/events.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}