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 }