Files
porthole/internal/collectors/k8s/client.go
OpenCode Test 1421b4659e feat: implement ControlTower TUI for cluster and host monitoring
Add complete TUI application for monitoring Kubernetes clusters and host
systems. Features include:

Core features:
- Collector framework with concurrent scheduling
- Host collectors: disk, memory, load, network
- Kubernetes collectors: pods, nodes, workloads, events with informers
- Issue deduplication, state management, and resolve-after logic
- Bubble Tea TUI with table view, details pane, and filtering
- JSON export functionality

UX improvements:
- Help overlay with keybindings
- Priority/category filters with visual indicators
- Direct priority jump (0/1/2/3)
- Bulk acknowledge (Shift+A)
- Clipboard copy (y)
- Theme toggle (T)
- Age format toggle (d)
- Wide title toggle (t)
- Vi-style navigation (j/k)
- Home/End jump (g/G)
- Rollup drill-down in details

Robustness:
- Grace period for unreachable clusters
- Rollups for high-volume issues
- Flap suppression
- RBAC error handling

Files: All core application code with tests for host collectors,
engine, store, model, and export packages.
2025-12-24 13:29:51 -08:00

89 lines
2.4 KiB
Go

package k8s
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// ClientFromCurrentContext creates a Kubernetes client-go Clientset using the
// user's kubeconfig current context.
//
// It is a pure helper (no global state) so it can be used by collectors and
// unit tests (with temporary kubeconfig files).
func ClientFromCurrentContext() (*kubernetes.Clientset, *rest.Config, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// Respect KUBECONFIG semantics (it may be a path list).
if p := os.Getenv("KUBECONFIG"); p != "" {
if list := filepath.SplitList(p); len(list) > 1 {
loadingRules.ExplicitPath = ""
loadingRules.Precedence = list
} else {
loadingRules.ExplicitPath = p
}
}
cfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
restCfg, err := cfg.ClientConfig()
if err != nil {
return nil, nil, err
}
// Ensure HTTP client timeouts are bounded. LIST fallback uses its own context
// timeouts, but this provides a safety net.
if restCfg.Timeout <= 0 {
restCfg.Timeout = 30 * time.Second
}
cs, err := kubernetes.NewForConfig(restCfg)
if err != nil {
return nil, nil, err
}
return cs, restCfg, nil
}
func defaultKubeconfigPath() string {
// This helper is used only for existence checks / UI messages. Client loading
// should use client-go's default loading rules.
if p := os.Getenv("KUBECONFIG"); p != "" {
// If KUBECONFIG is a list, return the first entry for display.
if list := filepath.SplitList(p); len(list) > 0 {
return list[0]
}
return p
}
h, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(h, ".kube", "config")
}
// Ping performs a lightweight API call to determine if the cluster is reachable
// and authentication works.
func Ping(ctx context.Context, cs kubernetes.Interface) error {
if cs == nil {
return errors.New("nil kubernetes client")
}
_, err := cs.Discovery().ServerVersion()
if err != nil {
// Treat authn/authz errors separately so callers can decide whether to
// surface "unreachable" vs "insufficient credentials".
if apierrors.IsForbidden(err) || apierrors.IsUnauthorized(err) {
return fmt.Errorf("discovery auth: %w", err)
}
return fmt.Errorf("discovery server version: %w", err)
}
return nil
}