package k8s
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/sirupsen/logrus"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// ClientManager manages Kubernetes client connections
type ClientManager struct {
}
// NewClientManager creates a new Kubernetes client manager
func NewClientManager() *ClientManager {
return &ClientManager{}
}
// GetClient creates a Kubernetes client using the same approach as mcp-server-kubernetes
func (cm *ClientManager) GetClient() (*kubernetes.Clientset, error) {
logrus.Info("Attempting to create Kubernetes client...")
logrus.Debug("Starting Kubernetes client creation process")
config, err := cm.getKubernetesConfig()
if err != nil {
logrus.Errorf("Failed to get Kubernetes config: %v", err)
return nil, fmt.Errorf("failed to get Kubernetes config: %w", err)
}
logrus.Debug("Successfully obtained Kubernetes configuration")
return cm.createClientset(config)
}
// GetClientWithContext creates a Kubernetes client with specific context
func (cm *ClientManager) GetClientWithContext(contextName string) (*kubernetes.Clientset, error) {
logrus.Infof("Attempting to create Kubernetes client with context: %s", contextName)
config, err := cm.getKubernetesConfigWithContext(contextName)
if err != nil {
return nil, fmt.Errorf("failed to get Kubernetes config for context %s: %w", contextName, err)
}
return cm.createClientset(config)
}
// getKubernetesConfig gets Kubernetes configuration using priority order
func (cm *ClientManager) getKubernetesConfig() (*rest.Config, error) {
// Priority 1: Check for KUBECONFIG environment variable
if kubeconfigPath := os.Getenv("KUBECONFIG"); kubeconfigPath != "" {
logrus.Infof("Using KUBECONFIG environment variable: %s", kubeconfigPath)
// Handle colon-separated paths (multiple kubeconfig files)
paths := strings.Split(kubeconfigPath, ":")
var validPaths []string
for _, path := range paths {
path = strings.TrimSpace(path)
if path != "" {
// Check if file exists
if _, err := os.Stat(path); err == nil {
validPaths = append(validPaths, path)
logrus.Debugf("Found valid kubeconfig file: %s", path)
} else {
logrus.Warnf("Kubeconfig file not found, skipping: %s", path)
}
}
}
if len(validPaths) > 0 {
// Use the first valid path
config, err := clientcmd.BuildConfigFromFlags("", validPaths[0])
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig from KUBECONFIG: %w", err)
}
return config, nil
} else {
logrus.Warn("No valid kubeconfig files found in KUBECONFIG, falling back to default")
}
}
// Priority 2: Try in-cluster config
logrus.Info("Checking for in-cluster configuration...")
if config, err := rest.InClusterConfig(); err == nil {
logrus.Info("Using in-cluster Kubernetes configuration")
return config, nil
} else {
logrus.Debugf("In-cluster config not available: %v", err)
}
// Priority 3: Use default kubeconfig loading
logrus.Info("Using default kubeconfig loading strategy...")
config, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
if err != nil {
return nil, fmt.Errorf("failed to load default kubeconfig: %w", err)
}
// Apply context override if specified
if contextName := os.Getenv("K8S_CONTEXT"); contextName != "" {
logrus.Infof("Setting context to: %s", contextName)
config.CurrentContext = contextName
}
// Convert to rest.Config
restConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to create rest config: %w", err)
}
return restConfig, nil
}
// getKubernetesConfigWithContext gets Kubernetes configuration with specific context
func (cm *ClientManager) getKubernetesConfigWithContext(contextName string) (*rest.Config, error) {
// Try to use KUBECONFIG environment variable first if available
if kubeconfigPath := os.Getenv("KUBECONFIG"); kubeconfigPath != "" {
logrus.Infof("Using KUBECONFIG environment variable for context %s: %s", contextName, kubeconfigPath)
// Handle colon-separated paths (multiple kubeconfig files)
paths := strings.Split(kubeconfigPath, ":")
var validPaths []string
for _, path := range paths {
path = strings.TrimSpace(path)
if path != "" {
// Check if file exists
if _, err := os.Stat(path); err == nil {
validPaths = append(validPaths, path)
logrus.Debugf("Found valid kubeconfig file: %s", path)
} else {
logrus.Warnf("Kubeconfig file not found, skipping: %s", path)
}
}
}
if len(validPaths) > 0 {
// Use the first valid path
config, err := clientcmd.BuildConfigFromFlags("", validPaths[0])
if err != nil {
logrus.Warnf("Failed to load kubeconfig from KUBECONFIG, falling back to default: %v", err)
} else {
// Override context if specified
if contextName != "" {
// Create a new config with the specific context
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.ExplicitPath = validPaths[0]
configOverrides := &clientcmd.ConfigOverrides{
CurrentContext: contextName,
}
// Load the kubeconfig file and apply context override
rawConfig, err := loadingRules.Load()
if err != nil {
logrus.Warnf("Failed to load kubeconfig file %s, falling back to default: %v", validPaths[0], err)
} else {
clientConfig := clientcmd.NewDefaultClientConfig(*rawConfig, configOverrides)
restConfig, err := clientConfig.ClientConfig()
if err != nil {
logrus.Warnf("Failed to create rest config with context %s, falling back to default: %v", contextName, err)
} else {
logrus.Infof("Using context: %s", contextName)
return restConfig, nil
}
}
} else {
return config, nil
}
}
}
}
// Fallback to default kubeconfig loading
config, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
if err != nil {
return nil, fmt.Errorf("failed to load default kubeconfig: %w", err)
}
// Set the specific context
if contextName != "" {
config.CurrentContext = contextName
logrus.Infof("Using context: %s", contextName)
}
// Convert to rest.Config
restConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to create rest config for context %s: %w", contextName, err)
}
return restConfig, nil
}
// createClientset creates a Kubernetes clientset with proper configuration
func (cm *ClientManager) createClientset(config *rest.Config) (*kubernetes.Clientset, error) {
logrus.Debug("Configuring Kubernetes client settings")
// Set timeouts and rate limits
config.Timeout = 30 * time.Second
config.QPS = 50.0
config.Burst = 100
logrus.Debugf("Client config - Timeout: %v, QPS: %.1f, Burst: %d", config.Timeout, config.QPS, config.Burst)
// Create the clientset
logrus.Info("Creating Kubernetes clientset...")
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
logrus.Errorf("Failed to create Kubernetes client: %v", err)
return nil, fmt.Errorf("failed to create Kubernetes client: %w", err)
}
logrus.Info("Successfully created Kubernetes client")
return clientset, nil
}
// TestConnection tests the connection to the Kubernetes cluster
func (cm *ClientManager) TestConnection(ctx context.Context) error {
client, err := cm.GetClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
// Test connection by getting server version
_, err = client.Discovery().ServerVersion()
if err != nil {
return fmt.Errorf("failed to get server version: %w", err)
}
return nil
}