package metrics import ( "context" "fmt" "strings" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "admin-console/internal/model" ) // K8sClient — обёртка над kubernetes.Clientset для нужд admin-console. type K8sClient struct { cs *kubernetes.Clientset } // NewK8sClient создаёт клиента из in-cluster конфига, с fallback на ~/.kube/config. func NewK8sClient() (*K8sClient, error) { cfg, err := rest.InClusterConfig() if err != nil { cfg, err = clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile) if err != nil { return nil, fmt.Errorf("no k8s config: %w", err) } } cs, err := kubernetes.NewForConfig(cfg) if err != nil { return nil, err } return &K8sClient{cs: cs}, nil } // ListClientNamespaces возвращает namespace-ы с лейблом managed-by=fission-console. // Для каждого считает количество объектов Fission CRD через Dynamic клиент // (пока упрощённо — только через secrets и labels). func (k *K8sClient) ListClientNamespaces(ctx context.Context) ([]model.NamespaceInfo, error) { nsList, err := k.cs.CoreV1().Namespaces().List(ctx, metav1.ListOptions{ LabelSelector: "managed-by=fission-console", }) if err != nil { return nil, err } result := make([]model.NamespaceInfo, 0, len(nsList.Items)) for _, ns := range nsList.Items { info := model.NamespaceInfo{ Name: ns.Name, UserEmail: ns.Labels["user"], CreatedAt: ns.CreationTimestamp.Format(time.RFC3339), } // TODO: добавить подсчёт Fission CRD через dynamic client result = append(result, info) } return result, nil } // CollectUsage возвращает агрегированную статистику по всем клиентским namespace-ам. func (k *K8sClient) CollectUsage(ctx context.Context) ([]model.UsageEntry, error) { nsList, err := k.cs.CoreV1().Namespaces().List(ctx, metav1.ListOptions{ LabelSelector: "managed-by=fission-console", }) if err != nil { return nil, err } now := time.Now().UTC() startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC) result := make([]model.UsageEntry, 0, len(nsList.Items)) for _, ns := range nsList.Items { entry := model.UsageEntry{ Namespace: ns.Name, UserEmail: ns.Labels["user"], PeriodStart: startOfMonth.Format("2006-01-02"), PeriodEnd: now.Format("2006-01-02"), } // TODO: заполнить реальными данными из Prometheus / metrics-server / S3 result = append(result, entry) } return result, nil } // ListUsers собирает пользователей из secret fission-console-users в каждом // клиентском namespace. func (k *K8sClient) ListUsers(ctx context.Context) ([]model.UserInfo, error) { nsList, err := k.cs.CoreV1().Namespaces().List(ctx, metav1.ListOptions{ LabelSelector: "managed-by=fission-console", }) if err != nil { return nil, err } var users []model.UserInfo for _, ns := range nsList.Items { // label user содержит email владельца namespace email := ns.Labels["user"] if email == "" { // fallback: попробовать прочитать из secret secret, err := k.cs.CoreV1().Secrets(ns.Name).Get(ctx, "fission-console-users", metav1.GetOptions{}) if err == nil { for key := range secret.Data { if strings.Contains(key, "@") { email = key break } } } } users = append(users, model.UserInfo{ Email: email, Namespace: ns.Name, CreatedAt: ns.CreationTimestamp.Format(time.RFC3339), }) } return users, nil }