From f6a167a69b8f02adbff82e027359b82cbc61a20b Mon Sep 17 00:00:00 2001 From: Justin Brooks Date: Mon, 21 Apr 2025 19:13:37 -0400 Subject: [PATCH 1/2] bump kubernetes --- cmd/ctrlc/root/sync/kubernetes/client.go | 68 ++++++++++++++++++++ cmd/ctrlc/root/sync/kubernetes/kubernetes.go | 68 ++++++++++++++++++++ cmd/ctrlc/root/sync/sync.go | 2 + go.mod | 6 +- 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 cmd/ctrlc/root/sync/kubernetes/client.go create mode 100644 cmd/ctrlc/root/sync/kubernetes/kubernetes.go diff --git a/cmd/ctrlc/root/sync/kubernetes/client.go b/cmd/ctrlc/root/sync/kubernetes/client.go new file mode 100644 index 0000000..7164d23 --- /dev/null +++ b/cmd/ctrlc/root/sync/kubernetes/client.go @@ -0,0 +1,68 @@ +package kubernetes + +import ( + "os" + "path/filepath" + + "github.com/charmbracelet/log" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +func getKubeConfig() (*rest.Config, string, error) { + // First, try the KUBECONFIG environment variable + kubeconfigPath := os.Getenv("KUBECONFIG") + if kubeconfigPath != "" { + log.Info("Loading kubeconfig from environment variable", "path", kubeconfigPath) + config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, "", err + } + context, err := getCurrentContextName(kubeconfigPath) + return config, context, err + } + + // Next, try the default location (~/.kube/config) + homeDir, err := os.UserHomeDir() + if err == nil { + kubeconfigPath = filepath.Join(homeDir, ".kube", "config") + if _, err := os.Stat(kubeconfigPath); err == nil { + log.Info("Loading kubeconfig from home directory", "path", kubeconfigPath) + config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, "", err + } + context, err := getCurrentContextName(kubeconfigPath) + return config, context, err + } + } + + // Finally, assume we're running in a cluster (inside pod) + log.Info("Loading in-cluster kubeconfig") + config, err := rest.InClusterConfig() + if err != nil { + return nil, "", err + } + + // When running in-cluster, we can get the cluster name from the namespace file + clusterName, err := getInClusterName() + return config, clusterName, err +} + +func getCurrentContextName(kubeconfigPath string) (string, error) { + kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath) + if err != nil { + return "", err + } + return kubeconfig.CurrentContext, nil +} + +func getInClusterName() (string, error) { + // When running in a pod, you can read the namespace from the service account + nsBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + return "unknown-cluster", nil // Return a default value if we can't determine the namespace + } + + return string(nsBytes), nil +} \ No newline at end of file diff --git a/cmd/ctrlc/root/sync/kubernetes/kubernetes.go b/cmd/ctrlc/root/sync/kubernetes/kubernetes.go new file mode 100644 index 0000000..094f755 --- /dev/null +++ b/cmd/ctrlc/root/sync/kubernetes/kubernetes.go @@ -0,0 +1,68 @@ +package kubernetes + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/MakeNowJust/heredoc/v2" + "github.com/charmbracelet/log" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +func NewSyncKubernetesCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "kubernetes", + Short: "Sync Kubernetes resources on a cluster", + Example: heredoc.Doc(` + $ ctrlc sync kubernetes --cluster 1234567890 + `), + RunE: func(cmd *cobra.Command, args []string) error { + log.Info("Syncing Kubernetes resources on a cluster") + config, clusterName, err := getKubeConfig() + if err != nil { + return err + } + + log.Info("Connected to cluster", "name", clusterName) + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return err + } + + + pods, err := clientset.CoreV1().Pods("default").List( + context.Background(), metav1.ListOptions{}) + if err != nil { + return err + } + + if len(pods.Items) > 0 { + log.Info("First two pods:") + for i := 0; i < min(2, len(pods.Items)); i++ { + pod := pods.Items[i] + podJson, err := json.Marshal(pod) + if err != nil { + log.Error("Failed to marshal pod to JSON", "error", err) + continue + } + fmt.Println(string(podJson)) + // log.Info("Pod", "json", string(podJson)) + } + } else { + log.Info("No pods found in default namespace") + } + + + return nil + }, + } + + cmd.Flags().String("cluster", "", "The cluster to sync") + + + return cmd +} diff --git a/cmd/ctrlc/root/sync/sync.go b/cmd/ctrlc/root/sync/sync.go index b776883..4a44a6d 100644 --- a/cmd/ctrlc/root/sync/sync.go +++ b/cmd/ctrlc/root/sync/sync.go @@ -4,6 +4,7 @@ import ( "github.com/MakeNowJust/heredoc/v2" "github.com/ctrlplanedev/cli/cmd/ctrlc/root/sync/aws/ec2" "github.com/ctrlplanedev/cli/cmd/ctrlc/root/sync/clickhouse" + "github.com/ctrlplanedev/cli/cmd/ctrlc/root/sync/kubernetes" "github.com/ctrlplanedev/cli/cmd/ctrlc/root/sync/tailscale" "github.com/ctrlplanedev/cli/cmd/ctrlc/root/sync/terraform" "github.com/ctrlplanedev/cli/internal/cliutil" @@ -29,6 +30,7 @@ func NewSyncCmd() *cobra.Command { cmd.AddCommand(cliutil.AddIntervalSupport(tailscale.NewSyncTailscaleCmd(), "")) cmd.AddCommand(cliutil.AddIntervalSupport(clickhouse.NewSyncClickhouseCmd(), "")) cmd.AddCommand(cliutil.AddIntervalSupport(ec2.NewSyncEC2Cmd(), "")) + cmd.AddCommand(kubernetes.NewSyncKubernetesCmd()) return cmd } diff --git a/go.mod b/go.mod index 288595f..0629b16 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,9 @@ go 1.24.2 require ( github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/avast/retry-go v3.0.0+incompatible + github.com/aws/aws-sdk-go-v2 v1.36.3 + github.com/aws/aws-sdk-go-v2/config v1.29.14 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.211.3 github.com/charmbracelet/log v0.4.0 github.com/creack/pty v1.1.24 github.com/google/uuid v1.6.0 @@ -25,14 +28,11 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect - github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ec2 v1.211.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect From 414548d1f438ca8bed38d9353c4362395e9c2f7c Mon Sep 17 00:00:00 2001 From: Justin Brooks Date: Thu, 24 Apr 2025 19:37:28 -0400 Subject: [PATCH 2/2] fix clickhouse sync --- .gitignore | 2 + cmd/ctrlc/root/sync/aws/ec2/ec2.go | 4 +- cmd/ctrlc/root/sync/clickhouse/clickhouse.go | 253 +++++++++++++++---- cmd/ctrlc/root/sync/kubernetes/client.go | 92 +++---- cmd/ctrlc/root/sync/kubernetes/kubernetes.go | 3 - 5 files changed, 248 insertions(+), 106 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f2be29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +test.bash \ No newline at end of file diff --git a/cmd/ctrlc/root/sync/aws/ec2/ec2.go b/cmd/ctrlc/root/sync/aws/ec2/ec2.go index d5c3221..d1743ee 100644 --- a/cmd/ctrlc/root/sync/aws/ec2/ec2.go +++ b/cmd/ctrlc/root/sync/aws/ec2/ec2.go @@ -121,8 +121,8 @@ func NewSyncEC2Cmd() *cobra.Command { } instanceData := EC2Instance{ - ID: *instance.InstanceId, - Name: name, + ID: *instance.InstanceId, + Name: name, ConnectionMethod: ConnectionMethod{ Type: "aws", Region: region, diff --git a/cmd/ctrlc/root/sync/clickhouse/clickhouse.go b/cmd/ctrlc/root/sync/clickhouse/clickhouse.go index 4e553f6..4c755dc 100644 --- a/cmd/ctrlc/root/sync/clickhouse/clickhouse.go +++ b/cmd/ctrlc/root/sync/clickhouse/clickhouse.go @@ -1,9 +1,11 @@ package clickhouse import ( + "bytes" "context" "encoding/json" "fmt" + "io" "net/http" "os" "strings" @@ -16,23 +18,85 @@ import ( "github.com/spf13/viper" ) -type ClickHouseConfig struct { - ID string `json:"id"` - Name string `json:"name"` - State string `json:"state"` - Region string `json:"region"` - CloudProvider string `json:"cloudProvider"` - Tier string `json:"tier"` - IdleScaling map[string]interface{} `json:"idleScaling"` - TotalDiskSize int `json:"totalDiskSize"` - TotalMemoryMB int `json:"totalMemoryMB"` - MinTotalMemory int `json:"minTotalMemory"` - MaxTotalMemory int `json:"maxTotalMemory"` - Created string `json:"created"` - Endpoints []map[string]interface{} `json:"endpoints"` +type ClickhouseEndpointResponse struct { + Protocol string `json:"protocol"` + Host string `json:"host"` + Port int `json:"port"` + Username string `json:"username"` } -func (c *ClickHouseConfig) Struct() map[string]interface{} { +type IPAccessListItem struct { + Source string `json:"source"` + Description string `json:"description"` +} + +type ClickHouseConfigResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Provider string `json:"provider"` + Region string `json:"region"` + State string `json:"state"` + Endpoints []ClickhouseEndpointResponse `json:"endpoints"` + Tier string `json:"tier"` + MinTotalMemoryGb int `json:"minTotalMemoryGb"` + MaxTotalMemoryGb int `json:"maxTotalMemoryGb"` + MinReplicaMemoryGb int `json:"minReplicaMemoryGb"` + MaxReplicaMemoryGb int `json:"maxReplicaMemoryGb"` + NumReplicas int `json:"numReplicas"` + IdleScaling bool `json:"idleScaling"` + IdleTimeoutMinutes int `json:"idleTimeoutMinutes"` + IPAccessList []IPAccessListItem `json:"ipAccessList"` + CreatedAt string `json:"createdAt"` + EncryptionKey string `json:"encryptionKey"` + EncryptionAssumedRoleIdentifier string `json:"encryptionAssumedRoleIdentifier"` + IamRole string `json:"iamRole"` + PrivateEndpointIds []string `json:"privateEndpointIds"` + AvailablePrivateEndpointIds []string `json:"availablePrivateEndpointIds"` + DataWarehouseId string `json:"dataWarehouseId"` + IsPrimary bool `json:"isPrimary"` + IsReadonly bool `json:"isReadonly"` + ReleaseChannel string `json:"releaseChannel"` + ByocId string `json:"byocId"` + HasTransparentDataEncryption bool `json:"hasTransparentDataEncryption"` + TransparentDataEncryptionKeyId string `json:"transparentDataEncryptionKeyId"` + EncryptionRoleId string `json:"encryptionRoleId"` +} + +type Connection struct { + Host string + Port int + Username string +} + +func (c *ClickHouseConfigResponse) GetConnection() Connection { + for _, endpoint := range c.Endpoints { + if endpoint.Protocol == "https" { + return Connection{ + Host: endpoint.Host, + Port: endpoint.Port, + Username: endpoint.Username, + } + } + } + for _, endpoint := range c.Endpoints { + if endpoint.Protocol == "native" { + return Connection{ + Host: endpoint.Host, + Port: endpoint.Port, + Username: endpoint.Username, + } + } + } + return Connection{} +} + +type ClickHouseListResponse struct { + Status int `json:"status"` + RequestId string `json:"requestId"` + Result []ClickHouseConfigResponse `json:"result"` +} + +func (c *ClickHouseConfigResponse) Struct() map[string]interface{} { b, _ := json.Marshal(c) var m map[string]interface{} json.Unmarshal(b, &m) @@ -42,14 +106,16 @@ func (c *ClickHouseConfig) Struct() map[string]interface{} { type ClickHouseClient struct { httpClient *http.Client apiUrl string + apiId string apiKey string organizationID string } -func NewClickHouseClient(apiUrl, apiKey, organizationID string) *ClickHouseClient { +func NewClickHouseClient(apiUrl, apiId, apiKey, organizationID string) *ClickHouseClient { return &ClickHouseClient{ httpClient: &http.Client{}, apiUrl: apiUrl, + apiId: apiId, apiKey: apiKey, organizationID: organizationID, } @@ -60,29 +126,30 @@ type ServiceList struct { } type Service struct { - ID string `json:"id"` - Name string `json:"name"` - State string `json:"state"` - Region string `json:"region"` - CloudProvider string `json:"cloudProvider"` - Tier string `json:"tier"` - IdleScaling map[string]interface{} `json:"idleScaling"` - TotalDiskSize int `json:"totalDiskSize"` - TotalMemoryMB int `json:"totalMemoryMB"` - MinTotalMemory int `json:"minTotalMemory"` - MaxTotalMemory int `json:"maxTotalMemory"` - Created string `json:"created"` - Endpoints []map[string]interface{} `json:"endpoints"` + ID string `json:"id"` + Name string `json:"name"` + State string `json:"state"` + Region string `json:"region"` + CloudProvider string `json:"cloudProvider"` + Tier string `json:"tier"` + IdleScaling map[string]any `json:"idleScaling"` + TotalDiskSize int `json:"totalDiskSize"` + TotalMemoryMB int `json:"totalMemoryMB"` + MinTotalMemory int `json:"minTotalMemory"` + MaxTotalMemory int `json:"maxTotalMemory"` + Created string `json:"created"` + Endpoints []map[string]any `json:"endpoints"` } -func (c *ClickHouseClient) GetServices(ctx context.Context) ([]Service, error) { +func (c *ClickHouseClient) GetServices(ctx context.Context) ([]ClickHouseConfigResponse, error) { + log.Info("Getting services for organization", "organizationID", c.organizationID) url := fmt.Sprintf("%s/v1/organizations/%s/services", c.apiUrl, c.organizationID) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) + req.SetBasicAuth(c.apiId, c.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -92,36 +159,67 @@ func (c *ClickHouseClient) GetServices(ctx context.Context) ([]Service, error) { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { + log.Error("Unexpected status code", "status", resp.StatusCode) + body, _ := io.ReadAll(resp.Body) + log.Error("Response body", "body", string(body)) return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } - var result ServiceList + // Print raw response for debugging + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + // Pretty print the JSON + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, body, "", " "); err != nil { + return nil, fmt.Errorf("failed to pretty print JSON: %w", err) + } + log.Info("Raw response:", "body", prettyJSON.String()) + + // Reset the response body for subsequent reading + resp.Body = io.NopCloser(bytes.NewBuffer(body)) + var result ClickHouseListResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } - return result.Services, nil + return result.Result, nil } func NewSyncClickhouseCmd() *cobra.Command { var providerName string var clickhouseApiUrl string - var clickhouseApiKey string + var clickhouseApiSecret string + var clickhouseApiId string var organizationID string cmd := &cobra.Command{ Use: "clickhouse", Short: "Sync ClickHouse instances into Ctrlplane", Example: heredoc.Doc(` - $ ctrlc sync clickhouse --workspace 2a7c5560-75c9-4dbe-be74-04ee33bf8188 + $ ctrlc sync clickhouse `), PreRunE: func(cmd *cobra.Command, args []string) error { - if clickhouseApiKey == "" { - return fmt.Errorf("clickhouse-key must be provided") + if clickhouseApiSecret == "" { + clickhouseApiSecret = os.Getenv("CLICKHOUSE_API_SECRET") + } + if clickhouseApiSecret == "" { + return fmt.Errorf("clickhouse-secret must be provided") + } + if organizationID == "" { + organizationID = os.Getenv("CLICKHOUSE_ORGANIZATION_ID") } if organizationID == "" { return fmt.Errorf("organization-id must be provided") } + if clickhouseApiId == "" { + clickhouseApiId = os.Getenv("CLICKHOUSE_API_ID") + } + if clickhouseApiId == "" { + return fmt.Errorf("clickhouse-api-id must be provided") + } return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -133,34 +231,80 @@ func NewSyncClickhouseCmd() *cobra.Command { if err != nil { return fmt.Errorf("failed to create API client: %w", err) } - chClient := NewClickHouseClient(clickhouseApiUrl, clickhouseApiKey, organizationID) + + chClient := NewClickHouseClient(clickhouseApiUrl, clickhouseApiId, clickhouseApiSecret, organizationID) ctx := context.Background() services, err := chClient.GetServices(ctx) if err != nil { return fmt.Errorf("failed to list ClickHouse services: %w", err) } + resources := []api.AgentResource{} for _, service := range services { - metadata := map[string]string{} - metadata["clickhouse/id"] = service.ID - metadata["clickhouse/name"] = service.Name - metadata["clickhouse/state"] = service.State - metadata["clickhouse/region"] = service.Region - metadata["clickhouse/cloud-provider"] = service.CloudProvider - metadata["clickhouse/tier"] = service.Tier - metadata["clickhouse/created"] = service.Created + var endpoints []string + for _, endpoint := range service.Endpoints { + endpointString := fmt.Sprintf("%s://%s:%d", endpoint.Protocol, endpoint.Host, endpoint.Port) + endpoints = append(endpoints, endpointString) + } + connection := service.GetConnection() + metadata := map[string]string{ + "database/id": service.ID, + "database/model": "relational", + "database/port": fmt.Sprintf("%d", connection.Port), + "database/host": connection.Host, - config := ClickHouseConfig(service) // Direct type conversion since fields match + "clickhouse/id": service.ID, + "clickhouse/name": service.Name, + "clickhouse/state": service.State, + "clickhouse/region": service.Region, + "clickhouse/tier": service.Tier, + "clickhouse/endpoints": strings.Join(endpoints, ","), + "clickhouse/data-warehouse-id": service.DataWarehouseId, + "clickhouse/is-primary": fmt.Sprintf("%t", service.IsPrimary), + "clickhouse/is-readonly": fmt.Sprintf("%t", service.IsReadonly), + "clickhouse/release-channel": service.ReleaseChannel, + "clickhouse/encryption-key": service.EncryptionKey, + "clickhouse/encryption-assumed-role-identifier": service.EncryptionAssumedRoleIdentifier, + "clickhouse/encryption-role-id": service.EncryptionRoleId, + "clickhouse/has-transparent-data-encryption": fmt.Sprintf("%t", service.HasTransparentDataEncryption), + "clickhouse/transparent-data-encryption-key-id": service.TransparentDataEncryptionKeyId, + "clickhouse/iam-role": service.IamRole, + "clickhouse/byoc-id": service.ByocId, + + "clickhouse/min-total-memory-gb": fmt.Sprintf("%d", service.MinTotalMemoryGb), + "clickhouse/max-total-memory-gb": fmt.Sprintf("%d", service.MaxTotalMemoryGb), + "clickhouse/min-replica-memory-gb": fmt.Sprintf("%d", service.MinReplicaMemoryGb), + "clickhouse/max-replica-memory-gb": fmt.Sprintf("%d", service.MaxReplicaMemoryGb), + "clickhouse/num-replicas": fmt.Sprintf("%d", service.NumReplicas), + "clickhouse/idle-scaling": fmt.Sprintf("%t", service.IdleScaling), + "clickhouse/idle-timeout-minutes": fmt.Sprintf("%d", service.IdleTimeoutMinutes), + } // Create a sanitized name name := strings.Split(service.Name, ".")[0] resources = append(resources, api.AgentResource{ - Version: "clickhouse/v1", - Kind: "Service", + Version: "https://schema.ctrlplane.dev/database/v1", + Kind: "ClickhouseCloud", Name: name, Identifier: fmt.Sprintf("%s/%s", organizationID, service.ID), - Config: config.Struct(), - Metadata: metadata, + Config: map[string]any{ + "host": connection.Host, + "port": connection.Port, + "username": connection.Username, + + "clickhouse": map[string]any{ + "id": service.ID, + "name": service.Name, + "state": service.State, + "provider": service.Provider, + "region": service.Region, + "endpoints": service.Endpoints, + "iamRole": service.IamRole, + "isPrimary": service.IsPrimary, + "isReadonly": service.IsReadonly, + }, + }, + Metadata: metadata, }) } log.Info("Upserting resources", "count", len(resources)) @@ -180,10 +324,9 @@ func NewSyncClickhouseCmd() *cobra.Command { cmd.Flags().StringVarP(&providerName, "provider", "p", "clickhouse", "The name of the provider to use") cmd.Flags().StringVarP(&clickhouseApiUrl, "clickhouse-url", "u", "https://api.clickhouse.cloud", "The URL of the ClickHouse API") - cmd.Flags().StringVarP(&clickhouseApiKey, "clickhouse-key", "k", os.Getenv("CLICKHOUSE_API_KEY"), "The API key to use") - cmd.Flags().StringVarP(&organizationID, "organization-id", "o", os.Getenv("CLICKHOUSE_ORGANIZATION_ID"), "The ClickHouse organization ID") - - cmd.MarkFlagRequired("organization-id") + cmd.Flags().StringVarP(&clickhouseApiSecret, "clickhouse-secret", "s", "", "The API secret to use") + cmd.Flags().StringVarP(&clickhouseApiId, "clickhouse-api-id", "", "", "The API ID to use") + cmd.Flags().StringVarP(&organizationID, "organization-id", "o", "", "The ClickHouse organization ID") return cmd } diff --git a/cmd/ctrlc/root/sync/kubernetes/client.go b/cmd/ctrlc/root/sync/kubernetes/client.go index 7164d23..4aac319 100644 --- a/cmd/ctrlc/root/sync/kubernetes/client.go +++ b/cmd/ctrlc/root/sync/kubernetes/client.go @@ -10,59 +10,59 @@ import ( ) func getKubeConfig() (*rest.Config, string, error) { - // First, try the KUBECONFIG environment variable - kubeconfigPath := os.Getenv("KUBECONFIG") - if kubeconfigPath != "" { + // First, try the KUBECONFIG environment variable + kubeconfigPath := os.Getenv("KUBECONFIG") + if kubeconfigPath != "" { log.Info("Loading kubeconfig from environment variable", "path", kubeconfigPath) - config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) - if err != nil { - return nil, "", err - } - context, err := getCurrentContextName(kubeconfigPath) - return config, context, err - } + config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, "", err + } + context, err := getCurrentContextName(kubeconfigPath) + return config, context, err + } - // Next, try the default location (~/.kube/config) - homeDir, err := os.UserHomeDir() - if err == nil { - kubeconfigPath = filepath.Join(homeDir, ".kube", "config") - if _, err := os.Stat(kubeconfigPath); err == nil { + // Next, try the default location (~/.kube/config) + homeDir, err := os.UserHomeDir() + if err == nil { + kubeconfigPath = filepath.Join(homeDir, ".kube", "config") + if _, err := os.Stat(kubeconfigPath); err == nil { log.Info("Loading kubeconfig from home directory", "path", kubeconfigPath) - config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) - if err != nil { - return nil, "", err - } - context, err := getCurrentContextName(kubeconfigPath) - return config, context, err - } - } + config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, "", err + } + context, err := getCurrentContextName(kubeconfigPath) + return config, context, err + } + } - // Finally, assume we're running in a cluster (inside pod) - log.Info("Loading in-cluster kubeconfig") - config, err := rest.InClusterConfig() - if err != nil { - return nil, "", err - } - - // When running in-cluster, we can get the cluster name from the namespace file - clusterName, err := getInClusterName() - return config, clusterName, err + // Finally, assume we're running in a cluster (inside pod) + log.Info("Loading in-cluster kubeconfig") + config, err := rest.InClusterConfig() + if err != nil { + return nil, "", err + } + + // When running in-cluster, we can get the cluster name from the namespace file + clusterName, err := getInClusterName() + return config, clusterName, err } func getCurrentContextName(kubeconfigPath string) (string, error) { - kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath) - if err != nil { - return "", err - } - return kubeconfig.CurrentContext, nil + kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath) + if err != nil { + return "", err + } + return kubeconfig.CurrentContext, nil } func getInClusterName() (string, error) { - // When running in a pod, you can read the namespace from the service account - nsBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") - if err != nil { - return "unknown-cluster", nil // Return a default value if we can't determine the namespace - } - - return string(nsBytes), nil -} \ No newline at end of file + // When running in a pod, you can read the namespace from the service account + nsBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + return "unknown-cluster", nil // Return a default value if we can't determine the namespace + } + + return string(nsBytes), nil +} diff --git a/cmd/ctrlc/root/sync/kubernetes/kubernetes.go b/cmd/ctrlc/root/sync/kubernetes/kubernetes.go index 094f755..3541cc2 100644 --- a/cmd/ctrlc/root/sync/kubernetes/kubernetes.go +++ b/cmd/ctrlc/root/sync/kubernetes/kubernetes.go @@ -33,7 +33,6 @@ func NewSyncKubernetesCmd() *cobra.Command { return err } - pods, err := clientset.CoreV1().Pods("default").List( context.Background(), metav1.ListOptions{}) if err != nil { @@ -56,13 +55,11 @@ func NewSyncKubernetesCmd() *cobra.Command { log.Info("No pods found in default namespace") } - return nil }, } cmd.Flags().String("cluster", "", "The cluster to sync") - return cmd }