Ctrl K
ComunidadGuías…

internal/argocd/dashboard.go

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

Archivo original ↗Preguntar a KiBot
package argocd

import (
	"context"
	"slices"
	"strings"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"github.com/sophotechlabs/spinoza/internal/api"
	"github.com/sophotechlabs/spinoza/internal/listerr"
	"github.com/sophotechlabs/spinoza/internal/unstr"
)

const Group = "argoproj.io"

const applications = "applications"

const applicationSets = "applicationsets"

const appProjects = "appprojects"

const trackingLabel = "app.kubernetes.io/instance"

const trackingAnnotation = "argocd.argoproj.io/tracking-id"

type Lister interface {
	List(ctx context.Context, desc api.ResourceDescriptor) ([]*unstructured.Unstructured, error)
	Warm(ctx context.Context, descs []api.ResourceDescriptor)
}

func Installed(descs map[string]api.ResourceDescriptor) bool {
	for _, desc := range descs {
		if desc.Group == Group && desc.Resource == applications {
			return true
		}
	}
	return false
}

func wanted(descs map[string]api.ResourceDescriptor) []api.ResourceDescriptor {
	out := []api.ResourceDescriptor{}
	for _, desc := range descs {
		if desc.Group != Group {
			continue
		}
		if !listed(desc.Resource) {
			continue
		}
		out = append(out, desc)
	}
	return out
}

func listed(resource string) bool {
	switch resource {
	case applications, applicationSets, appProjects:
		return true
	}
	return false
}

func Build(ctx context.Context, lister Lister, descs map[string]api.ResourceDescriptor) api.ArgoDashboard {
	types := wanted(descs)
	if len(types) == 0 {
		return api.ArgoDashboard{}
	}
	lister.Warm(ctx, types)
	failures := listerr.New()
	apps := []api.ArgoApp{}
	sets := []api.ArgoApp{}
	projects := []api.ArgoApp{}
	for _, desc := range types {
		gvr := schema.GroupVersionResource{Group: desc.Group, Version: desc.Version, Resource: desc.Resource}
		found, err := lister.List(ctx, desc)
		failures.Record(gvr.GroupResource().String(), err)
		if err != nil {
			continue
		}
		for _, item := range found {
			switch desc.Resource {
			case applications:
				apps = append(apps, appOf(item, desc))
			case applicationSets:
				sets = append(sets, appOf(item, desc))
			default:
				projects = append(projects, appOf(item, desc))
			}
		}
	}
	slices.SortFunc(apps, byName)
	slices.SortFunc(sets, byName)
	slices.SortFunc(projects, byName)
	return api.ArgoDashboard{
		Apps:            withParents(apps, sets),
		ApplicationSets: sets,
		Projects:        projects,
		Error:           failures.Message(),
	}
}

func byName(left, right api.ArgoApp) int {
	if left.Namespace != right.Namespace {
		return strings.Compare(left.Namespace, right.Namespace)
	}
	return strings.Compare(left.Name, right.Name)
}

func appOf(item *unstructured.Unstructured, desc api.ResourceDescriptor) api.ArgoApp {
	return api.ArgoApp{
		Kind:        desc.Kind,
		Automation:  automationOf(item, desc),
		Group:       desc.Group,
		Version:     desc.Version,
		Resource:    desc.Resource,
		Name:        item.GetName(),
		Namespace:   item.GetNamespace(),
		Project:     unstr.String(item, "spec", "project"),
		Sync:        unstr.String(item, "status", "sync", "status"),
		Health:      unstr.String(item, "status", "health", "status"),
		Revision:    revisionOf(item),
		Repo:        sourceOf(item, "repoURL"),
		Path:        pathOf(item),
		Destination: destinationOf(item),
		Message:     messageOf(item),
		Owner:       ownerOf(item),
		CreatedAt:   item.GetCreationTimestamp().UTC().Format("2006-01-02T15:04:05Z"),
	}
}

func automationOf(item *unstructured.Unstructured, desc api.ResourceDescriptor) string {
	if desc.Resource != applications {
		return ""
	}
	mode := syncModeOf(item)
	policy := policyOf(item)
	if policy == "" {
		return mode
	}
	return mode + " · " + policy
}

func revisionOf(item *unstructured.Unstructured) string {
	revision := unstr.String(item, "status", "sync", "revision")
	if revision != "" {
		return revision
	}
	return sourceOf(item, "targetRevision")
}

func sourceOf(item *unstructured.Unstructured, field string) string {
	single := unstr.String(item, "spec", "source", field)
	if single != "" {
		return single
	}
	sources := unstr.Slice(item, "spec", "sources")
	for _, raw := range sources {
		entry, ok := raw.(map[string]any)
		if !ok {
			continue
		}
		value, ok := entry[field].(string)
		if ok && value != "" {
			return value
		}
	}
	return ""
}

func pathOf(item *unstructured.Unstructured) string {
	path := sourceOf(item, "path")
	if path != "" {
		return path
	}
	return sourceOf(item, "chart")
}

func destinationOf(item *unstructured.Unstructured) string {
	namespace := unstr.String(item, "spec", "destination", "namespace")
	server := unstr.String(item, "spec", "destination", "name")
	if server == "" {
		server = unstr.String(item, "spec", "destination", "server")
	}
	if namespace == "" {
		return server
	}
	if server == "" {
		return namespace
	}
	return server + " " + namespace
}

func messageOf(item *unstructured.Unstructured) string {
	message := unstr.String(item, "status", "health", "message")
	if message != "" {
		return message
	}
	return unstr.String(item, "status", "operationState", "message")
}

func ownerOf(item *unstructured.Unstructured) string {
	for _, owner := range item.GetOwnerReferences() {
		if owner.Kind == "ApplicationSet" {
			return owner.Name
		}
	}
	labels := item.GetLabels()
	tracked, ok := labels[trackingLabel]
	if ok && tracked != "" && tracked != item.GetName() {
		return tracked
	}
	return fromTrackingID(item.GetAnnotations()[trackingAnnotation], item.GetName())
}

func fromTrackingID(id, self string) string {
	if id == "" {
		return ""
	}
	owner, _, found := strings.Cut(id, ":")
	if !found || owner == self {
		return ""
	}
	return owner
}

func withParents(apps, sets []api.ArgoApp) []api.ArgoApp {
	known := map[string]bool{}
	for _, app := range apps {
		known[app.Name] = true
	}
	for _, set := range sets {
		known[set.Name] = true
	}
	out := make([]api.ArgoApp, 0, len(apps))
	for _, app := range apps {
		if !known[app.Owner] {
			app.Owner = ""
		}
		out = append(out, app)
	}
	return out
}