Skip to content

Commit 9da29f4

Browse files
authored
refactor(kubernetes): streamline provider configuration and in-cluster detection (#378)
* refactor(kubernetes): streamline provider configuration and in-cluster detection - Removed IsInCluster method from Manager and created function scoped to the runtime environment. As a method, the implementation was not correct. Removed GetAPIServerHost method from Manager which is no used. - **Temporarily** added an `inCluster` field to the Manager struct but should be eventually removed since it doesn't really make sense to hava a Manager in-cluster or out-of-cluster in the multi-cluster scenario. - Provider resolution (resolveStrategy) is now clearer, added complete coverage for all scenarios. - Added additional coverage for provider and manager. Signed-off-by: Marc Nuri <[email protected]> * refactor(kubernetes): update NewManager to accept kubeconfig context and simplify manager creation - Removes Provider.newForContext(context string) method. Signed-off-by: Marc Nuri <[email protected]> --------- Signed-off-by: Marc Nuri <[email protected]>
1 parent b66719e commit 9da29f4

File tree

11 files changed

+305
-301
lines changed

11 files changed

+305
-301
lines changed

internal/test/env.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package test
2+
3+
import (
4+
"os"
5+
"strings"
6+
)
7+
8+
func RestoreEnv(originalEnv []string) {
9+
os.Clearenv()
10+
for _, env := range originalEnv {
11+
if key, value, found := strings.Cut(env, "="); found {
12+
_ = os.Setenv(key, value)
13+
}
14+
}
15+
}

pkg/kubernetes/configuration.go

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package kubernetes
22

33
import (
4+
"github.com/containers/kubernetes-mcp-server/pkg/config"
45
"k8s.io/apimachinery/pkg/runtime"
56
"k8s.io/client-go/rest"
6-
"k8s.io/client-go/tools/clientcmd"
77
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
88
"k8s.io/client-go/tools/clientcmd/api/latest"
99
)
@@ -22,29 +22,13 @@ var InClusterConfig = func() (*rest.Config, error) {
2222
return inClusterConfig, err
2323
}
2424

25-
// resolveKubernetesConfigurations resolves the required kubernetes configurations and sets them in the Kubernetes struct
26-
func resolveKubernetesConfigurations(kubernetes *Manager) error {
27-
// Always set clientCmdConfig
28-
pathOptions := clientcmd.NewDefaultPathOptions()
29-
if kubernetes.staticConfig.KubeConfig != "" {
30-
pathOptions.LoadingRules.ExplicitPath = kubernetes.staticConfig.KubeConfig
25+
func IsInCluster(cfg *config.StaticConfig) bool {
26+
// Even if running in-cluster, if a kubeconfig is provided, we consider it as out-of-cluster
27+
if cfg != nil && cfg.KubeConfig != "" {
28+
return false
3129
}
32-
kubernetes.clientCmdConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
33-
pathOptions.LoadingRules,
34-
&clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}})
35-
var err error
36-
if kubernetes.IsInCluster() {
37-
kubernetes.cfg, err = InClusterConfig()
38-
if err == nil && kubernetes.cfg != nil {
39-
return nil
40-
}
41-
}
42-
// Out of cluster
43-
kubernetes.cfg, err = kubernetes.clientCmdConfig.ClientConfig()
44-
if kubernetes.cfg != nil && kubernetes.cfg.UserAgent == "" {
45-
kubernetes.cfg.UserAgent = rest.DefaultKubernetesUserAgent()
46-
}
47-
return err
30+
restConfig, err := InClusterConfig()
31+
return err == nil && restConfig != nil
4832
}
4933

5034
func (k *Kubernetes) NamespaceOrDefault(namespace string) string {
@@ -54,7 +38,7 @@ func (k *Kubernetes) NamespaceOrDefault(namespace string) string {
5438
// ConfigurationContextsDefault returns the current context name
5539
// TODO: Should be moved to the Provider level ?
5640
func (k *Kubernetes) ConfigurationContextsDefault() (string, error) {
57-
if k.manager.IsInCluster() {
41+
if k.manager.inCluster {
5842
return inClusterKubeConfigDefaultContext, nil
5943
}
6044
cfg, err := k.manager.clientCmdConfig.RawConfig()
@@ -67,7 +51,7 @@ func (k *Kubernetes) ConfigurationContextsDefault() (string, error) {
6751
// ConfigurationContextsList returns the list of available context names
6852
// TODO: Should be moved to the Provider level ?
6953
func (k *Kubernetes) ConfigurationContextsList() (map[string]string, error) {
70-
if k.manager.IsInCluster() {
54+
if k.manager.inCluster {
7155
return map[string]string{inClusterKubeConfigDefaultContext: ""}, nil
7256
}
7357
cfg, err := k.manager.clientCmdConfig.RawConfig()
@@ -93,7 +77,7 @@ func (k *Kubernetes) ConfigurationContextsList() (map[string]string, error) {
9377
func (k *Kubernetes) ConfigurationView(minify bool) (runtime.Object, error) {
9478
var cfg clientcmdapi.Config
9579
var err error
96-
if k.manager.IsInCluster() {
80+
if k.manager.inCluster {
9781
cfg = *clientcmdapi.NewConfig()
9882
cfg.Clusters["cluster"] = &clientcmdapi.Cluster{
9983
Server: k.manager.cfg.Host,

pkg/kubernetes/configuration_test.go

Lines changed: 0 additions & 155 deletions
This file was deleted.

pkg/kubernetes/kubernetes_derived_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ users:
4747
kubeconfig = "` + strings.ReplaceAll(kubeconfigPath, `\`, `\\`) + `"
4848
`)))
4949
s.Run("without authorization header returns original manager", func() {
50-
testManager, err := NewManager(testStaticConfig)
50+
testManager, err := NewManager(testStaticConfig, "")
5151
s.Require().NoErrorf(err, "failed to create test manager: %v", err)
5252
s.T().Cleanup(testManager.Close)
5353

@@ -58,7 +58,7 @@ users:
5858
})
5959

6060
s.Run("with invalid authorization header returns original manager", func() {
61-
testManager, err := NewManager(testStaticConfig)
61+
testManager, err := NewManager(testStaticConfig, "")
6262
s.Require().NoErrorf(err, "failed to create test manager: %v", err)
6363
s.T().Cleanup(testManager.Close)
6464

@@ -70,7 +70,7 @@ users:
7070
})
7171

7272
s.Run("with valid bearer token creates derived manager with correct configuration", func() {
73-
testManager, err := NewManager(testStaticConfig)
73+
testManager, err := NewManager(testStaticConfig, "")
7474
s.Require().NoErrorf(err, "failed to create test manager: %v", err)
7575
s.T().Cleanup(testManager.Close)
7676

@@ -138,7 +138,7 @@ users:
138138
`)))
139139

140140
s.Run("with no authorization header returns oauth token required error", func() {
141-
testManager, err := NewManager(testStaticConfig)
141+
testManager, err := NewManager(testStaticConfig, "")
142142
s.Require().NoErrorf(err, "failed to create test manager: %v", err)
143143
s.T().Cleanup(testManager.Close)
144144

@@ -149,7 +149,7 @@ users:
149149
})
150150

151151
s.Run("with invalid authorization header returns oauth token required error", func() {
152-
testManager, err := NewManager(testStaticConfig)
152+
testManager, err := NewManager(testStaticConfig, "")
153153
s.Require().NoErrorf(err, "failed to create test manager: %v", err)
154154
s.T().Cleanup(testManager.Close)
155155

@@ -161,7 +161,7 @@ users:
161161
})
162162

163163
s.Run("with valid bearer token creates derived manager", func() {
164-
testManager, err := NewManager(testStaticConfig)
164+
testManager, err := NewManager(testStaticConfig, "")
165165
s.Require().NoErrorf(err, "failed to create test manager: %v", err)
166166
s.T().Cleanup(testManager.Close)
167167

pkg/kubernetes/manager.go

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
type Manager struct {
2626
cfg *rest.Config
2727
clientCmdConfig clientcmd.ClientConfig
28+
inCluster bool
2829
discoveryClient discovery.CachedDiscoveryInterface
2930
accessControlClientSet *AccessControlClientset
3031
accessControlRESTMapper *AccessControlRESTMapper
@@ -37,18 +38,37 @@ type Manager struct {
3738
var _ helm.Kubernetes = (*Manager)(nil)
3839
var _ Openshift = (*Manager)(nil)
3940

40-
func NewManager(config *config.StaticConfig) (*Manager, error) {
41+
func NewManager(config *config.StaticConfig, kubeconfigContext string) (*Manager, error) {
4142
k8s := &Manager{
4243
staticConfig: config,
4344
}
44-
if err := resolveKubernetesConfigurations(k8s); err != nil {
45-
return nil, err
45+
pathOptions := clientcmd.NewDefaultPathOptions()
46+
if k8s.staticConfig.KubeConfig != "" {
47+
pathOptions.LoadingRules.ExplicitPath = k8s.staticConfig.KubeConfig
48+
}
49+
k8s.clientCmdConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
50+
pathOptions.LoadingRules,
51+
&clientcmd.ConfigOverrides{
52+
ClusterInfo: clientcmdapi.Cluster{Server: ""},
53+
CurrentContext: kubeconfigContext,
54+
})
55+
var err error
56+
if IsInCluster(k8s.staticConfig) {
57+
k8s.cfg, err = InClusterConfig()
58+
k8s.inCluster = true
59+
} else {
60+
k8s.cfg, err = k8s.clientCmdConfig.ClientConfig()
61+
}
62+
if err != nil || k8s.cfg == nil {
63+
return nil, fmt.Errorf("failed to create kubernetes rest config: %v", err)
64+
}
65+
if k8s.cfg.UserAgent == "" {
66+
k8s.cfg.UserAgent = rest.DefaultKubernetesUserAgent()
4667
}
4768
// TODO: Won't work because not all client-go clients use the shared context (e.g. discovery client uses context.TODO())
4869
//k8s.cfg.Wrap(func(original http.RoundTripper) http.RoundTripper {
4970
// return &impersonateRoundTripper{original}
5071
//})
51-
var err error
5272
k8s.accessControlClientSet, err = NewAccessControlClientset(k8s.cfg, k8s.staticConfig)
5373
if err != nil {
5474
return nil, err
@@ -107,21 +127,6 @@ func (m *Manager) Close() {
107127
}
108128
}
109129

110-
func (m *Manager) GetAPIServerHost() string {
111-
if m.cfg == nil {
112-
return ""
113-
}
114-
return m.cfg.Host
115-
}
116-
117-
func (m *Manager) IsInCluster() bool {
118-
if m.staticConfig.KubeConfig != "" {
119-
return false
120-
}
121-
cfg, err := InClusterConfig()
122-
return err == nil && cfg != nil
123-
}
124-
125130
func (m *Manager) configuredNamespace() string {
126131
if ns, _, nsErr := m.clientCmdConfig.Namespace(); nsErr == nil {
127132
return ns
@@ -221,11 +226,14 @@ func (m *Manager) Derived(ctx context.Context) (*Kubernetes, error) {
221226
return &Kubernetes{manager: m}, nil
222227
}
223228
clientCmdApiConfig.AuthInfos = make(map[string]*clientcmdapi.AuthInfo)
224-
derived := &Kubernetes{manager: &Manager{
225-
clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil),
226-
cfg: derivedCfg,
227-
staticConfig: m.staticConfig,
228-
}}
229+
derived := &Kubernetes{
230+
manager: &Manager{
231+
clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil),
232+
inCluster: m.inCluster,
233+
cfg: derivedCfg,
234+
staticConfig: m.staticConfig,
235+
},
236+
}
229237
derived.manager.accessControlClientSet, err = NewAccessControlClientset(derived.manager.cfg, derived.manager.staticConfig)
230238
if err != nil {
231239
if m.staticConfig.RequireOAuth {

0 commit comments

Comments
 (0)