Skip to content

Commit 3a2de84

Browse files
committed
start refactoring client
1 parent c16b990 commit 3a2de84

File tree

4 files changed

+207
-190
lines changed

4 files changed

+207
-190
lines changed

internal/app.go

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
type Model struct {
3131
config Config
3232
keyMap keymap.KeyMap
33-
allClusterNamespaces []model.ClusterNamespaces
3433
width, height int
3534
initialized bool
3635
stylesLoaded bool

internal/init.go

+15-183
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package internal
33
import (
44
"context"
55
"flag"
6-
"fmt"
76
tea "github.com/charmbracelet/bubbletea/v2"
87
"github.com/charmbracelet/lipgloss/v2"
98
"github.com/robinovitch61/kl/internal/command"
@@ -13,13 +12,8 @@ import (
1312
"github.com/robinovitch61/kl/internal/model"
1413
"github.com/robinovitch61/kl/internal/page"
1514
"github.com/robinovitch61/kl/internal/style"
16-
"k8s.io/client-go/kubernetes"
1715
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc" // register OIDC auth provider
18-
"k8s.io/client-go/tools/clientcmd"
19-
"k8s.io/client-go/tools/clientcmd/api"
2016
"k8s.io/klog/v2"
21-
"os"
22-
"strings"
2317
"time"
2418
)
2519

@@ -40,152 +34,29 @@ func initializedModel(m Model) (Model, tea.Cmd, error) {
4034
// - specifying multiple contexts that point to the same cluster
4135
// - I can't imagine a scenario where this is desired other than wanting multiple namespaces per cluster
4236

43-
m, err := initializeKubeConfig(m)
37+
ctx, cancel := context.WithCancel(context.Background())
38+
m.cancel = cancel
39+
c, err := client.NewClient(
40+
ctx,
41+
m.config.KubeConfigPath,
42+
m.config.Contexts,
43+
m.config.Namespaces,
44+
m.config.AllNamespaces,
45+
)
4446
if err != nil {
4547
return m, nil, err
4648
}
49+
m.client = c
4750

48-
m = initializePages(m)
49-
50-
cmds := createInitialCommands(m)
51-
52-
return m, tea.Batch(cmds...), nil
53-
}
54-
55-
// TODO LEO: make this part of client initialization
56-
func initializeKubeConfig(m Model) (Model, error) {
57-
rawKubeConfig, loadingRules, err := getKubeConfig(m.config.KubeConfigPath)
58-
if err != nil {
59-
return m, err
60-
}
61-
62-
contexts, err := getContexts(m.config.Contexts, rawKubeConfig)
63-
if err != nil {
64-
return m, err
65-
}
66-
dev.Debug(fmt.Sprintf("using contexts %v", contexts))
67-
68-
clusters := getClustersFromContexts(contexts, rawKubeConfig)
69-
70-
clusterToContext, err := validateUniqueClusters(contexts, clusters, rawKubeConfig)
71-
if err != nil {
72-
return m, err
73-
}
74-
75-
allClusterNamespaces := buildClusterNamespaces(m, clusters, clusterToContext, rawKubeConfig)
76-
m.allClusterNamespaces = allClusterNamespaces
77-
logClusterNamespaces(allClusterNamespaces)
78-
79-
clusterToClientSet, err := createClientSets(clusters, clusterToContext, loadingRules)
80-
if err != nil {
81-
return m, err
82-
}
83-
84-
ctx, cancel := context.WithCancel(context.Background())
85-
m.cancel = cancel
86-
m.client = client.NewClient(ctx, clusterToClientSet)
87-
m.entityTree = model.NewEntityTree(m.allClusterNamespaces)
51+
m.entityTree = model.NewEntityTree(m.client.AllClusterNamespaces())
8852

8953
m.termStyleData = style.NewTermStyleData()
9054

91-
return m, nil
92-
}
93-
94-
func getClustersFromContexts(contexts []string, rawKubeConfig api.Config) []string {
95-
var clusters []string
96-
for _, contextName := range contexts {
97-
clusterName := rawKubeConfig.Contexts[contextName].Cluster
98-
clusters = append(clusters, clusterName)
99-
}
100-
return clusters
101-
}
102-
103-
func validateUniqueClusters(contexts []string, clusters []string, rawKubeConfig api.Config) (map[string]string, error) {
104-
clusterToContext := make(map[string]string)
105-
for _, contextName := range contexts {
106-
clusterName := rawKubeConfig.Contexts[contextName].Cluster
107-
if existingContext, exists := clusterToContext[clusterName]; exists {
108-
return nil, fmt.Errorf("contexts %s and %s both specify cluster %s - unclear which auth/namespace to use", existingContext, contextName, clusterName)
109-
}
110-
clusterToContext[clusterName] = contextName
111-
}
112-
return clusterToContext, nil
113-
}
114-
115-
func buildClusterNamespaces(m Model, clusters []string, clusterToContext map[string]string, rawKubeConfig api.Config) []model.ClusterNamespaces {
116-
namespacesString := strings.Trim(strings.TrimSpace(m.config.Namespaces), ",")
117-
var namespaces []string
118-
if len(namespacesString) > 0 {
119-
namespaces = strings.Split(namespacesString, ",")
120-
}
121-
122-
var allClusterNamespaces []model.ClusterNamespaces
123-
for _, cluster := range clusters {
124-
if m.config.AllNamespaces {
125-
cn := model.ClusterNamespaces{Cluster: cluster, Namespaces: []string{""}}
126-
allClusterNamespaces = append(allClusterNamespaces, cn)
127-
} else if len(namespaces) > 0 {
128-
cn := model.ClusterNamespaces{Cluster: cluster, Namespaces: namespaces}
129-
allClusterNamespaces = append(allClusterNamespaces, cn)
130-
} else {
131-
contextName := clusterToContext[cluster]
132-
namespace := rawKubeConfig.Contexts[contextName].Namespace
133-
if namespace == "" {
134-
namespace = "default"
135-
}
136-
cn := model.ClusterNamespaces{Cluster: cluster, Namespaces: []string{namespace}}
137-
allClusterNamespaces = append(allClusterNamespaces, cn)
138-
}
139-
}
140-
return allClusterNamespaces
141-
}
142-
143-
func logClusterNamespaces(allClusterNamespaces []model.ClusterNamespaces) {
144-
for _, cn := range allClusterNamespaces {
145-
for _, namespace := range cn.Namespaces {
146-
dev.Debug(fmt.Sprintf("using cluster '%s' namespace '%s'", cn.Cluster, namespace))
147-
}
148-
}
149-
}
150-
151-
func createClientSets(clusters []string, clusterToContext map[string]string, loadingRules *clientcmd.ClientConfigLoadingRules) (map[string]*kubernetes.Clientset, error) {
152-
clusterToClientSet := make(map[string]*kubernetes.Clientset)
153-
for _, cluster := range clusters {
154-
clientset, err := createClientSetForCluster(cluster, clusterToContext, loadingRules)
155-
if err != nil {
156-
return nil, err
157-
}
158-
clusterToClientSet[cluster] = clientset
159-
}
160-
return clusterToClientSet, nil
161-
}
162-
163-
func createClientSetForCluster(cluster string, clusterToContext map[string]string, loadingRules *clientcmd.ClientConfigLoadingRules) (*kubernetes.Clientset, error) {
164-
contextName, exists := clusterToContext[cluster]
165-
if !exists {
166-
return nil, fmt.Errorf("no context found for cluster %s in kubeconfig", cluster)
167-
}
168-
169-
// create a config override that sets the current context
170-
overrides := &clientcmd.ConfigOverrides{
171-
CurrentContext: contextName,
172-
}
173-
174-
dev.Debug(fmt.Sprintf("using context %s for cluster %s", contextName, cluster))
175-
176-
// create client config with the override
177-
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
178-
config, err := clientConfig.ClientConfig()
179-
if err != nil {
180-
return nil, fmt.Errorf("failed to get client config for cluster %s: %w", cluster, err)
181-
}
55+
m = initializePages(m)
18256

183-
clientset, err := kubernetes.NewForConfig(config)
184-
if err != nil {
185-
return nil, fmt.Errorf("failed to create clientset for cluster %s: %w", cluster, err)
186-
}
57+
cmds := createInitialCommands(m)
18758

188-
return clientset, nil
59+
return m, tea.Batch(cmds...), nil
18960
}
19061

19162
func initializePages(m Model) Model {
@@ -216,7 +87,7 @@ func initializePages(m Model) Model {
21687

21788
func createInitialCommands(m Model) []tea.Cmd {
21889
var cmds []tea.Cmd
219-
for _, clusterNamespaces := range m.allClusterNamespaces {
90+
for _, clusterNamespaces := range m.client.AllClusterNamespaces() {
22091
for _, namespace := range clusterNamespaces.Namespaces {
22192
cmds = append(cmds, command.GetContainerListenerCmd(
22293
m.client,
@@ -237,42 +108,3 @@ func createInitialCommands(m Model) []tea.Cmd {
237108

238109
return cmds
239110
}
240-
241-
// getKubeConfig gets kubeconfig, accounting for multiple file paths
242-
func getKubeConfig(kubeConfigPath string) (api.Config, *clientcmd.ClientConfigLoadingRules, error) {
243-
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
244-
kubeconfigPaths := strings.Split(kubeConfigPath, string(os.PathListSeparator))
245-
dev.Debug(fmt.Sprintf("kubeconfig paths: %v", kubeconfigPaths))
246-
247-
loadingRules.Precedence = kubeconfigPaths
248-
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, nil)
249-
rawKubeConfig, err := clientConfig.RawConfig()
250-
if err != nil {
251-
return api.Config{}, loadingRules, fmt.Errorf("failed to load kubeconfig: %w", err)
252-
}
253-
return rawKubeConfig, loadingRules, nil
254-
}
255-
256-
func getContexts(contextString string, config api.Config) ([]string, error) {
257-
contextsString := strings.Trim(strings.TrimSpace(contextString), ",")
258-
var contexts []string
259-
if len(contextsString) > 0 {
260-
contexts = strings.Split(contextsString, ",")
261-
}
262-
263-
if len(contexts) == 0 && config.CurrentContext != "" {
264-
contexts = []string{config.CurrentContext}
265-
}
266-
267-
if len(contexts) == 0 {
268-
return nil, fmt.Errorf("no contexts specified and no current context found in kubeconfig")
269-
}
270-
271-
for _, c := range contexts {
272-
if _, exists := config.Contexts[c]; !exists {
273-
return nil, fmt.Errorf("context %s not found in kubeconfig", c)
274-
}
275-
}
276-
277-
return contexts, nil
278-
}

internal/k8s/client/client.go

+52-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import (
1919

2020
// Client is an interface for interacting with a Kubernetes cluster
2121
type Client interface {
22+
// AllClusterNamespaces returns all cluster namespaces
23+
AllClusterNamespaces() []model.ClusterNamespaces
24+
2225
// GetContainerListener returns a listener that emits container deltas for a given cluster and namespace
2326
GetContainerListener(
2427
cluster,
@@ -39,15 +42,58 @@ type Client interface {
3942
}
4043

4144
type clientImpl struct {
42-
ctx context.Context
43-
clusterToClientset map[string]*kubernetes.Clientset
45+
ctx context.Context
46+
clusterToClientset map[string]*kubernetes.Clientset
47+
allClusterNamespaces []model.ClusterNamespaces
4448
}
4549

46-
func NewClient(ctx context.Context, clusterToClientset map[string]*kubernetes.Clientset) Client {
47-
return clientImpl{
48-
ctx: ctx,
49-
clusterToClientset: clusterToClientset,
50+
// TODO LEO: make these not just strings
51+
func NewClient(
52+
ctx context.Context,
53+
kubeConfigPath string,
54+
contextString string,
55+
namespaceString string,
56+
useAllNamespaces bool,
57+
) (Client, error) {
58+
rawKubeConfig, loadingRules, err := getKubeConfig(kubeConfigPath)
59+
if err != nil {
60+
return clientImpl{}, err
61+
}
62+
63+
contexts, err := getContexts(contextString, rawKubeConfig)
64+
if err != nil {
65+
return clientImpl{}, err
5066
}
67+
dev.Debug(fmt.Sprintf("using contexts %v", contexts))
68+
69+
clusters := getClustersFromContexts(contexts, rawKubeConfig)
70+
71+
clusterToContext, err := validateUniqueClusters(contexts, clusters, rawKubeConfig)
72+
if err != nil {
73+
return clientImpl{}, err
74+
}
75+
76+
allClusterNamespaces := buildClusterNamespaces(namespaceString, useAllNamespaces, clusters, clusterToContext, rawKubeConfig)
77+
for _, cn := range allClusterNamespaces {
78+
for _, namespace := range cn.Namespaces {
79+
dev.Debug(fmt.Sprintf("using cluster '%s' namespace '%s'", cn.Cluster, namespace))
80+
}
81+
}
82+
83+
clusterToClientSet, err := createClientSets(clusters, clusterToContext, loadingRules)
84+
if err != nil {
85+
return clientImpl{}, err
86+
}
87+
88+
return clientImpl{
89+
ctx: ctx,
90+
clusterToClientset: clusterToClientSet,
91+
allClusterNamespaces: allClusterNamespaces,
92+
}, nil
93+
}
94+
95+
func (c clientImpl) AllClusterNamespaces() []model.ClusterNamespaces {
96+
return c.allClusterNamespaces
5197
}
5298

5399
type ContainerListener struct {

0 commit comments

Comments
 (0)