Skip to content

Commit b734155

Browse files
committed
add helm sync support
1 parent b7fd8f5 commit b734155

File tree

5 files changed

+787
-188
lines changed

5 files changed

+787
-188
lines changed

cmd/ctrlc/root/sync/helm/client.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package helm
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
7+
"github.com/charmbracelet/log"
8+
"k8s.io/cli-runtime/pkg/genericclioptions"
9+
"k8s.io/client-go/rest"
10+
"k8s.io/client-go/tools/clientcmd"
11+
)
12+
13+
// getKubeConfig loads Kubernetes configuration and returns both the config and a suggested cluster name.
14+
// It tries multiple sources in order of precedence:
15+
// 1. KUBECONFIG environment variable
16+
// 2. Default location (~/.kube/config)
17+
// 3. In-cluster config (when running inside a Kubernetes pod)
18+
//
19+
// Returns: (*rest.Config, clusterName, error)
20+
func getKubeConfig() (*rest.Config, string, error) {
21+
// Try 1: KUBECONFIG environment variable
22+
kubeconfigPath := os.Getenv("KUBECONFIG")
23+
if kubeconfigPath != "" {
24+
log.Info("Loading kubeconfig from environment variable", "path", kubeconfigPath)
25+
config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
26+
if err != nil {
27+
return nil, "", err
28+
}
29+
clusterName, err := getCurrentContextName(kubeconfigPath)
30+
return config, clusterName, err
31+
}
32+
33+
// Try 2: Default location (~/.kube/config)
34+
homeDir, err := os.UserHomeDir()
35+
if err == nil {
36+
kubeconfigPath = filepath.Join(homeDir, ".kube", "config")
37+
if _, err := os.Stat(kubeconfigPath); err == nil {
38+
log.Info("Loading kubeconfig from home directory", "path", kubeconfigPath)
39+
config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
40+
if err != nil {
41+
return nil, "", err
42+
}
43+
clusterName, err := getCurrentContextName(kubeconfigPath)
44+
return config, clusterName, err
45+
}
46+
}
47+
48+
// Try 3: In-cluster config (running inside a pod)
49+
log.Info("Loading in-cluster kubeconfig")
50+
config, err := rest.InClusterConfig()
51+
if err != nil {
52+
return nil, "", err
53+
}
54+
55+
// When running in-cluster, derive name from the namespace
56+
clusterName, err := getInClusterName()
57+
return config, clusterName, err
58+
}
59+
60+
// getCurrentContextName extracts the current context name from a kubeconfig file.
61+
// This is typically a meaningful cluster name like "production-cluster" or "minikube".
62+
func getCurrentContextName(kubeconfigPath string) (string, error) {
63+
kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath)
64+
if err != nil {
65+
return "", err
66+
}
67+
return kubeconfig.CurrentContext, nil
68+
}
69+
70+
// getInClusterName returns a cluster name when running inside a Kubernetes pod.
71+
// It reads the pod's namespace as a fallback identifier since there's no kubeconfig.
72+
func getInClusterName() (string, error) {
73+
// When running inside a pod, read the namespace from the service account mount
74+
nsBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
75+
if err != nil {
76+
// If we can't read the namespace, return a default
77+
return "unknown-cluster", nil
78+
}
79+
80+
return string(nsBytes), nil
81+
}
82+
83+
// getConfigFlags converts a rest.Config into Helm-compatible ConfigFlags.
84+
// Helm's action package requires genericclioptions.ConfigFlags rather than rest.Config directly.
85+
func getConfigFlags(config *rest.Config, namespace string) *genericclioptions.ConfigFlags {
86+
configFlags := genericclioptions.NewConfigFlags(true)
87+
88+
// Set the Kubernetes API server URL
89+
if config.Host != "" {
90+
host := config.Host
91+
configFlags.APIServer = &host
92+
}
93+
94+
// Set the target namespace (if specified)
95+
if namespace != "" {
96+
configFlags.Namespace = &namespace
97+
}
98+
99+
// Set authentication token (if using token-based auth)
100+
if config.BearerToken != "" {
101+
token := config.BearerToken
102+
configFlags.BearerToken = &token
103+
}
104+
105+
// Set TLS options
106+
if config.TLSClientConfig.Insecure {
107+
insecure := true
108+
configFlags.Insecure = &insecure
109+
}
110+
111+
if len(config.TLSClientConfig.CAData) > 0 {
112+
// CA data is embedded in the config and will be used automatically
113+
// We rely on the kubeconfig file being available rather than creating a temp file
114+
}
115+
116+
// Set kubeconfig path (needed by Helm for some operations)
117+
kubeconfigPath := os.Getenv("KUBECONFIG")
118+
if kubeconfigPath == "" {
119+
homeDir, err := os.UserHomeDir()
120+
if err == nil {
121+
kubeconfigPath = filepath.Join(homeDir, ".kube", "config")
122+
}
123+
}
124+
if kubeconfigPath != "" {
125+
configFlags.KubeConfig = &kubeconfigPath
126+
}
127+
128+
return configFlags
129+
}
130+

0 commit comments

Comments
 (0)