refactor(kubernetes): force usage of Derived kubernetes (125)

refactor(kubernetes): force usage of Derived kubernetes

Prevents consumers of the kubernetes package the usage of
public methods on a non-derived config instance.
---
review(kubernetes): force usage of Derived kubernetes

Addresses comment by ardaguclu
This commit is contained in:
Marc Nuri
2025-06-18 06:46:05 +02:00
committed by GitHub
parent 4a3ff2f2ce
commit f138b06ba8
9 changed files with 120 additions and 101 deletions

View File

@@ -21,7 +21,7 @@ var InClusterConfig = func() (*rest.Config, error) {
}
// resolveKubernetesConfigurations resolves the required kubernetes configurations and sets them in the Kubernetes struct
func resolveKubernetesConfigurations(kubernetes *Kubernetes) error {
func resolveKubernetesConfigurations(kubernetes *Manager) error {
// Always set clientCmdConfig
pathOptions := clientcmd.NewDefaultPathOptions()
if kubernetes.Kubeconfig != "" {
@@ -45,56 +45,60 @@ func resolveKubernetesConfigurations(kubernetes *Kubernetes) error {
return err
}
func (k *Kubernetes) IsInCluster() bool {
if k.Kubeconfig != "" {
func (m *Manager) IsInCluster() bool {
if m.Kubeconfig != "" {
return false
}
cfg, err := InClusterConfig()
return err == nil && cfg != nil
}
func (k *Kubernetes) configuredNamespace() string {
if ns, _, nsErr := k.clientCmdConfig.Namespace(); nsErr == nil {
func (m *Manager) configuredNamespace() string {
if ns, _, nsErr := m.clientCmdConfig.Namespace(); nsErr == nil {
return ns
}
return ""
}
func (k *Kubernetes) NamespaceOrDefault(namespace string) string {
func (m *Manager) NamespaceOrDefault(namespace string) string {
if namespace == "" {
return k.configuredNamespace()
return m.configuredNamespace()
}
return namespace
}
func (k *Kubernetes) NamespaceOrDefault(namespace string) string {
return k.manager.NamespaceOrDefault(namespace)
}
// ToRESTConfig returns the rest.Config object (genericclioptions.RESTClientGetter)
func (k *Kubernetes) ToRESTConfig() (*rest.Config, error) {
return k.cfg, nil
func (m *Manager) ToRESTConfig() (*rest.Config, error) {
return m.cfg, nil
}
// ToRawKubeConfigLoader returns the clientcmd.ClientConfig object (genericclioptions.RESTClientGetter)
func (k *Kubernetes) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return k.clientCmdConfig
func (m *Manager) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return m.clientCmdConfig
}
func (k *Kubernetes) ConfigurationView(minify bool) (runtime.Object, error) {
func (m *Manager) ConfigurationView(minify bool) (runtime.Object, error) {
var cfg clientcmdapi.Config
var err error
if k.IsInCluster() {
if m.IsInCluster() {
cfg = *clientcmdapi.NewConfig()
cfg.Clusters["cluster"] = &clientcmdapi.Cluster{
Server: k.cfg.Host,
InsecureSkipTLSVerify: k.cfg.Insecure,
Server: m.cfg.Host,
InsecureSkipTLSVerify: m.cfg.Insecure,
}
cfg.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Token: k.cfg.BearerToken,
Token: m.cfg.BearerToken,
}
cfg.Contexts["context"] = &clientcmdapi.Context{
Cluster: "cluster",
AuthInfo: "user",
}
cfg.CurrentContext = "context"
} else if cfg, err = k.clientCmdConfig.RawConfig(); err != nil {
} else if cfg, err = m.clientCmdConfig.RawConfig(); err != nil {
return nil, err
}
if minify {

View File

@@ -12,10 +12,10 @@ import (
func TestKubernetes_IsInCluster(t *testing.T) {
t.Run("with explicit kubeconfig", func(t *testing.T) {
k := Kubernetes{
m := Manager{
Kubeconfig: "kubeconfig",
}
if k.IsInCluster() {
if m.IsInCluster() {
t.Errorf("expected not in cluster, got in cluster")
}
})
@@ -27,10 +27,10 @@ func TestKubernetes_IsInCluster(t *testing.T) {
defer func() {
InClusterConfig = originalFunction
}()
k := Kubernetes{
m := Manager{
Kubeconfig: "",
}
if !k.IsInCluster() {
if !m.IsInCluster() {
t.Errorf("expected in cluster, got not in cluster")
}
})
@@ -42,10 +42,10 @@ func TestKubernetes_IsInCluster(t *testing.T) {
defer func() {
InClusterConfig = originalFunction
}()
k := Kubernetes{
m := Manager{
Kubeconfig: "",
}
if k.IsInCluster() {
if m.IsInCluster() {
t.Errorf("expected not in cluster, got in cluster")
}
})
@@ -57,10 +57,10 @@ func TestKubernetes_IsInCluster(t *testing.T) {
defer func() {
InClusterConfig = originalFunction
}()
k := Kubernetes{
m := Manager{
Kubeconfig: "",
}
if k.IsInCluster() {
if m.IsInCluster() {
t.Errorf("expected not in cluster, got in cluster")
}
})
@@ -72,8 +72,8 @@ func TestKubernetes_ResolveKubernetesConfigurations_Explicit(t *testing.T) {
t.Skip("Skipping test on non-linux platforms")
}
tempDir := t.TempDir()
k := Kubernetes{Kubeconfig: path.Join(tempDir, "config")}
err := resolveKubernetesConfigurations(&k)
m := Manager{Kubeconfig: path.Join(tempDir, "config")}
err := resolveKubernetesConfigurations(&m)
if err == nil {
t.Errorf("expected error, got nil")
}
@@ -90,8 +90,8 @@ func TestKubernetes_ResolveKubernetesConfigurations_Explicit(t *testing.T) {
if err := os.WriteFile(kubeconfigPath, []byte(""), 0644); err != nil {
t.Fatalf("failed to create kubeconfig file: %v", err)
}
k := Kubernetes{Kubeconfig: kubeconfigPath}
err := resolveKubernetesConfigurations(&k)
m := Manager{Kubeconfig: kubeconfigPath}
err := resolveKubernetesConfigurations(&m)
if err == nil {
t.Errorf("expected error, got nil")
}
@@ -123,16 +123,16 @@ users:
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644); err != nil {
t.Fatalf("failed to create kubeconfig file: %v", err)
}
k := Kubernetes{Kubeconfig: kubeconfigPath}
err := resolveKubernetesConfigurations(&k)
m := Manager{Kubeconfig: kubeconfigPath}
err := resolveKubernetesConfigurations(&m)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if k.cfg == nil {
if m.cfg == nil {
t.Errorf("expected non-nil config, got nil")
}
if k.cfg.Host != "https://example.com" {
t.Errorf("expected host https://example.com, got %s", k.cfg.Host)
if m.cfg.Host != "https://example.com" {
t.Errorf("expected host https://example.com, got %s", m.cfg.Host)
}
})
}

View File

@@ -27,6 +27,10 @@ const (
type CloseWatchKubeConfig func() error
type Kubernetes struct {
manager *Manager
}
type Manager struct {
// Kubeconfig path override
Kubeconfig string
cfg *rest.Config
@@ -38,11 +42,10 @@ type Kubernetes struct {
discoveryClient discovery.CachedDiscoveryInterface
deferredDiscoveryRESTMapper *restmapper.DeferredDiscoveryRESTMapper
dynamicClient *dynamic.DynamicClient
Helm *helm.Helm
}
func NewKubernetes(kubeconfig string) (*Kubernetes, error) {
k8s := &Kubernetes{
func NewManager(kubeconfig string) (*Manager, error) {
k8s := &Manager{
Kubeconfig: kubeconfig,
}
if err := resolveKubernetesConfigurations(k8s); err != nil {
@@ -68,15 +71,14 @@ func NewKubernetes(kubeconfig string) (*Kubernetes, error) {
return nil, err
}
k8s.parameterCodec = runtime.NewParameterCodec(k8s.scheme)
k8s.Helm = helm.NewHelm(k8s)
return k8s, nil
}
func (k *Kubernetes) WatchKubeConfig(onKubeConfigChange func() error) {
if k.clientCmdConfig == nil {
func (m *Manager) WatchKubeConfig(onKubeConfigChange func() error) {
if m.clientCmdConfig == nil {
return
}
kubeConfigFiles := k.clientCmdConfig.ConfigAccess().GetLoadingPrecedence()
kubeConfigFiles := m.clientCmdConfig.ConfigAccess().GetLoadingPrecedence()
if len(kubeConfigFiles) == 0 {
return
}
@@ -102,33 +104,33 @@ func (k *Kubernetes) WatchKubeConfig(onKubeConfigChange func() error) {
}
}
}()
if k.CloseWatchKubeConfig != nil {
_ = k.CloseWatchKubeConfig()
if m.CloseWatchKubeConfig != nil {
_ = m.CloseWatchKubeConfig()
}
k.CloseWatchKubeConfig = watcher.Close
m.CloseWatchKubeConfig = watcher.Close
}
func (k *Kubernetes) Close() {
if k.CloseWatchKubeConfig != nil {
_ = k.CloseWatchKubeConfig()
func (m *Manager) Close() {
if m.CloseWatchKubeConfig != nil {
_ = m.CloseWatchKubeConfig()
}
}
func (k *Kubernetes) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
return k.discoveryClient, nil
func (m *Manager) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
return m.discoveryClient, nil
}
func (k *Kubernetes) ToRESTMapper() (meta.RESTMapper, error) {
return k.deferredDiscoveryRESTMapper, nil
func (m *Manager) ToRESTMapper() (meta.RESTMapper, error) {
return m.deferredDiscoveryRESTMapper, nil
}
func (k *Kubernetes) Derived(ctx context.Context) *Kubernetes {
func (m *Manager) Derived(ctx context.Context) *Kubernetes {
authorization, ok := ctx.Value(AuthorizationHeader).(string)
if !ok || !strings.HasPrefix(authorization, "Bearer ") {
return k
return &Kubernetes{manager: m}
}
klog.V(5).Infof("%s header found (Bearer), using provided bearer token", AuthorizationHeader)
derivedCfg := rest.CopyConfig(k.cfg)
derivedCfg := rest.CopyConfig(m.cfg)
derivedCfg.BearerToken = strings.TrimPrefix(authorization, "Bearer ")
derivedCfg.BearerTokenFile = ""
derivedCfg.Username = ""
@@ -137,28 +139,42 @@ func (k *Kubernetes) Derived(ctx context.Context) *Kubernetes {
derivedCfg.AuthConfigPersister = nil
derivedCfg.ExecProvider = nil
derivedCfg.Impersonate = rest.ImpersonationConfig{}
clientCmdApiConfig, err := k.clientCmdConfig.RawConfig()
clientCmdApiConfig, err := m.clientCmdConfig.RawConfig()
if err != nil {
return k
return &Kubernetes{manager: m}
}
clientCmdApiConfig.AuthInfos = make(map[string]*clientcmdapi.AuthInfo)
derived := &Kubernetes{
Kubeconfig: k.Kubeconfig,
derived := &Kubernetes{manager: &Manager{
Kubeconfig: m.Kubeconfig,
clientCmdConfig: clientcmd.NewDefaultClientConfig(clientCmdApiConfig, nil),
cfg: derivedCfg,
scheme: k.scheme,
parameterCodec: k.parameterCodec,
}
derived.clientSet, err = kubernetes.NewForConfig(derived.cfg)
scheme: m.scheme,
parameterCodec: m.parameterCodec,
}}
derived.manager.clientSet, err = kubernetes.NewForConfig(derived.manager.cfg)
if err != nil {
return k
return &Kubernetes{manager: m}
}
derived.discoveryClient = memory.NewMemCacheClient(discovery.NewDiscoveryClient(derived.clientSet.CoreV1().RESTClient()))
derived.deferredDiscoveryRESTMapper = restmapper.NewDeferredDiscoveryRESTMapper(derived.discoveryClient)
derived.dynamicClient, err = dynamic.NewForConfig(derived.cfg)
derived.manager.discoveryClient = memory.NewMemCacheClient(discovery.NewDiscoveryClient(derived.manager.clientSet.CoreV1().RESTClient()))
derived.manager.deferredDiscoveryRESTMapper = restmapper.NewDeferredDiscoveryRESTMapper(derived.manager.discoveryClient)
derived.manager.dynamicClient, err = dynamic.NewForConfig(derived.manager.cfg)
if err != nil {
return k
return &Kubernetes{manager: m}
}
derived.Helm = helm.NewHelm(derived)
return derived
}
// TODO: check test to see why cache isn't getting invalidated automatically https://github.com/manusa/kubernetes-mcp-server/pull/125#discussion_r2152194784
func (k *Kubernetes) CacheInvalidate() {
if k.manager.discoveryClient != nil {
k.manager.discoveryClient.Invalidate()
}
if k.manager.deferredDiscoveryRESTMapper != nil {
k.manager.deferredDiscoveryRESTMapper.Reset()
}
}
func (k *Kubernetes) NewHelm() *helm.Helm {
// This is a derived Kubernetes, so it already has the Helm initialized
return helm.NewHelm(k.manager)
}

View File

@@ -6,10 +6,10 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
func (k *Kubernetes) IsOpenShift(ctx context.Context) bool {
func (m *Manager) IsOpenShift(ctx context.Context) bool {
// This method should be fast and not block (it's called at startup)
timeoutSeconds := int64(5)
if _, err := k.dynamicClient.Resource(schema.GroupVersionResource{
if _, err := m.dynamicClient.Resource(schema.GroupVersionResource{
Group: "project.openshift.io",
Version: "v1",
Resource: "projects",

View File

@@ -49,7 +49,7 @@ func (k *Kubernetes) PodsGet(ctx context.Context, namespace, name string) (*unst
func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (string, error) {
namespace = k.NamespaceOrDefault(namespace)
pod, err := k.clientSet.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
pod, err := k.manager.clientSet.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", err
}
@@ -62,18 +62,18 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
// Delete managed service
if isManaged {
if sl, _ := k.clientSet.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{
if sl, _ := k.manager.clientSet.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{
LabelSelector: managedLabelSelector.String(),
}); sl != nil {
for _, svc := range sl.Items {
_ = k.clientSet.CoreV1().Services(namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{})
_ = k.manager.clientSet.CoreV1().Services(namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{})
}
}
}
// Delete managed Route
if isManaged && k.supportsGroupVersion("route.openshift.io/v1") {
routeResources := k.dynamicClient.
routeResources := k.manager.dynamicClient.
Resource(schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"}).
Namespace(namespace)
if rl, _ := routeResources.List(ctx, metav1.ListOptions{
@@ -86,12 +86,12 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
}
return "Pod deleted successfully",
k.clientSet.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{})
k.manager.clientSet.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{})
}
func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name, container string) (string, error) {
tailLines := int64(256)
req := k.clientSet.CoreV1().Pods(k.NamespaceOrDefault(namespace)).GetLogs(name, &v1.PodLogOptions{
req := k.manager.clientSet.CoreV1().Pods(k.NamespaceOrDefault(namespace)).GetLogs(name, &v1.PodLogOptions{
TailLines: &tailLines,
Container: container,
})
@@ -197,7 +197,7 @@ func (k *Kubernetes) PodsTop(ctx context.Context, options PodsTopOptions) (*metr
} else {
namespace = k.NamespaceOrDefault(namespace)
}
metricsClient, err := metricsclientset.NewForConfig(k.cfg)
metricsClient, err := metricsclientset.NewForConfig(k.manager.cfg)
if err != nil {
return nil, fmt.Errorf("failed to create metrics client: %w", err)
}
@@ -220,7 +220,7 @@ func (k *Kubernetes) PodsTop(ctx context.Context, options PodsTopOptions) (*metr
func (k *Kubernetes) PodsExec(ctx context.Context, namespace, name, container string, command []string) (string, error) {
namespace = k.NamespaceOrDefault(namespace)
pod, err := k.clientSet.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
pod, err := k.manager.clientSet.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", err
}
@@ -260,18 +260,18 @@ func (k *Kubernetes) PodsExec(ctx context.Context, namespace, name, container st
func (k *Kubernetes) createExecutor(namespace, name string, podExecOptions *v1.PodExecOptions) (remotecommand.Executor, error) {
// Compute URL
// https://github.com/kubernetes/kubectl/blob/5366de04e168bcbc11f5e340d131a9ca8b7d0df4/pkg/cmd/exec/exec.go#L382-L397
req := k.clientSet.CoreV1().RESTClient().
req := k.manager.clientSet.CoreV1().RESTClient().
Post().
Resource("pods").
Namespace(namespace).
Name(name).
SubResource("exec")
req.VersionedParams(podExecOptions, k.parameterCodec)
spdyExec, err := remotecommand.NewSPDYExecutor(k.cfg, "POST", req.URL())
req.VersionedParams(podExecOptions, k.manager.parameterCodec)
spdyExec, err := remotecommand.NewSPDYExecutor(k.manager.cfg, "POST", req.URL())
if err != nil {
return nil, err
}
webSocketExec, err := remotecommand.NewWebSocketExecutor(k.cfg, "GET", req.URL().String())
webSocketExec, err := remotecommand.NewWebSocketExecutor(k.manager.cfg, "GET", req.URL().String())
if err != nil {
return nil, err
}

View File

@@ -36,12 +36,12 @@ func (k *Kubernetes) ResourcesList(ctx context.Context, gvk *schema.GroupVersion
// Check if operation is allowed for all namespaces (applicable for namespaced resources)
isNamespaced, _ := k.isNamespaced(gvk)
if isNamespaced && !k.canIUse(ctx, gvr, namespace, "list") && namespace == "" {
namespace = k.configuredNamespace()
namespace = k.manager.configuredNamespace()
}
if options.AsTable {
return k.resourcesListAsTable(ctx, gvk, gvr, namespace, options)
}
return k.dynamicClient.Resource(*gvr).Namespace(namespace).List(ctx, options.ListOptions)
return k.manager.dynamicClient.Resource(*gvr).Namespace(namespace).List(ctx, options.ListOptions)
}
func (k *Kubernetes) ResourcesGet(ctx context.Context, gvk *schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error) {
@@ -53,7 +53,7 @@ func (k *Kubernetes) ResourcesGet(ctx context.Context, gvk *schema.GroupVersionK
if namespaced, nsErr := k.isNamespaced(gvk); nsErr == nil && namespaced {
namespace = k.NamespaceOrDefault(namespace)
}
return k.dynamicClient.Resource(*gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
return k.manager.dynamicClient.Resource(*gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
}
func (k *Kubernetes) ResourcesCreateOrUpdate(ctx context.Context, resource string) ([]*unstructured.Unstructured, error) {
@@ -79,7 +79,7 @@ func (k *Kubernetes) ResourcesDelete(ctx context.Context, gvk *schema.GroupVersi
if namespaced, nsErr := k.isNamespaced(gvk); nsErr == nil && namespaced {
namespace = k.NamespaceOrDefault(namespace)
}
return k.dynamicClient.Resource(*gvr).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
return k.manager.dynamicClient.Resource(*gvr).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
}
// resourcesListAsTable retrieves a list of resources in a table format.
@@ -98,7 +98,7 @@ func (k *Kubernetes) resourcesListAsTable(ctx context.Context, gvk *schema.Group
}
url = append(url, gvr.Resource)
var table metav1.Table
err := k.clientSet.CoreV1().RESTClient().
err := k.manager.clientSet.CoreV1().RESTClient().
Get().
SetHeader("Accept", strings.Join([]string{
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName),
@@ -106,7 +106,7 @@ func (k *Kubernetes) resourcesListAsTable(ctx context.Context, gvk *schema.Group
"application/json",
}, ",")).
AbsPath(url...).
SpecificallyVersionedParams(&options.ListOptions, k.parameterCodec, schema.GroupVersion{Version: "v1"}).
SpecificallyVersionedParams(&options.ListOptions, k.manager.parameterCodec, schema.GroupVersion{Version: "v1"}).
Do(ctx).Into(&table)
if err != nil {
return nil, err
@@ -141,7 +141,7 @@ func (k *Kubernetes) resourcesCreateOrUpdate(ctx context.Context, resources []*u
if namespaced, nsErr := k.isNamespaced(&gvk); nsErr == nil && namespaced {
namespace = k.NamespaceOrDefault(namespace)
}
resources[i], rErr = k.dynamicClient.Resource(*gvr).Namespace(namespace).Apply(ctx, obj.GetName(), obj, metav1.ApplyOptions{
resources[i], rErr = k.manager.dynamicClient.Resource(*gvr).Namespace(namespace).Apply(ctx, obj.GetName(), obj, metav1.ApplyOptions{
FieldManager: version.BinaryName,
})
if rErr != nil {
@@ -149,14 +149,14 @@ func (k *Kubernetes) resourcesCreateOrUpdate(ctx context.Context, resources []*u
}
// Clear the cache to ensure the next operation is performed on the latest exposed APIs (will change after the CRD creation)
if gvk.Kind == "CustomResourceDefinition" {
k.deferredDiscoveryRESTMapper.Reset()
k.manager.deferredDiscoveryRESTMapper.Reset()
}
}
return resources, nil
}
func (k *Kubernetes) resourceFor(gvk *schema.GroupVersionKind) (*schema.GroupVersionResource, error) {
m, err := k.deferredDiscoveryRESTMapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
m, err := k.manager.deferredDiscoveryRESTMapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
if err != nil {
return nil, err
}
@@ -164,7 +164,7 @@ func (k *Kubernetes) resourceFor(gvk *schema.GroupVersionKind) (*schema.GroupVer
}
func (k *Kubernetes) isNamespaced(gvk *schema.GroupVersionKind) (bool, error) {
apiResourceList, err := k.discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
apiResourceList, err := k.manager.discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
if err != nil {
return false, err
}
@@ -177,14 +177,14 @@ func (k *Kubernetes) isNamespaced(gvk *schema.GroupVersionKind) (bool, error) {
}
func (k *Kubernetes) supportsGroupVersion(groupVersion string) bool {
if _, err := k.discoveryClient.ServerResourcesForGroupVersion(groupVersion); err != nil {
if _, err := k.manager.discoveryClient.ServerResourcesForGroupVersion(groupVersion); err != nil {
return false
}
return true
}
func (k *Kubernetes) canIUse(ctx context.Context, gvr *schema.GroupVersionResource, namespace, verb string) bool {
response, err := k.clientSet.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, &authv1.SelfSubjectAccessReview{
response, err := k.manager.clientSet.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, &authv1.SelfSubjectAccessReview{
Spec: authv1.SelfSubjectAccessReviewSpec{ResourceAttributes: &authv1.ResourceAttributes{
Namespace: namespace,
Verb: verb,

View File

@@ -65,7 +65,7 @@ func (s *Server) helmInstall(ctx context.Context, ctr mcp.CallToolRequest) (*mcp
if v, ok := ctr.GetArguments()["namespace"].(string); ok {
namespace = v
}
ret, err := s.k.Derived(ctx).Helm.Install(ctx, chart, values, name, namespace)
ret, err := s.k.Derived(ctx).NewHelm().Install(ctx, chart, values, name, namespace)
if err != nil {
return NewTextResult("", fmt.Errorf("failed to install helm chart '%s': %w", chart, err)), nil
}
@@ -81,7 +81,7 @@ func (s *Server) helmList(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.Ca
if v, ok := ctr.GetArguments()["namespace"].(string); ok {
namespace = v
}
ret, err := s.k.Derived(ctx).Helm.List(namespace, allNamespaces)
ret, err := s.k.Derived(ctx).NewHelm().List(namespace, allNamespaces)
if err != nil {
return NewTextResult("", fmt.Errorf("failed to list helm releases in namespace '%s': %w", namespace, err)), nil
}
@@ -98,7 +98,7 @@ func (s *Server) helmUninstall(ctx context.Context, ctr mcp.CallToolRequest) (*m
if v, ok := ctr.GetArguments()["namespace"].(string); ok {
namespace = v
}
ret, err := s.k.Derived(ctx).Helm.Uninstall(name, namespace)
ret, err := s.k.Derived(ctx).NewHelm().Uninstall(name, namespace)
if err != nil {
return NewTextResult("", fmt.Errorf("failed to uninstall helm chart '%s': %w", name, err)), nil
}

View File

@@ -26,7 +26,7 @@ type Configuration struct {
type Server struct {
configuration *Configuration
server *server.MCPServer
k *kubernetes.Kubernetes
k *kubernetes.Manager
}
func NewSever(configuration Configuration) (*Server, error) {
@@ -49,7 +49,7 @@ func NewSever(configuration Configuration) (*Server, error) {
}
func (s *Server) reloadKubernetesClient() error {
k, err := kubernetes.NewKubernetes(s.configuration.Kubeconfig)
k, err := kubernetes.NewManager(s.configuration.Kubeconfig)
if err != nil {
return err
}

View File

@@ -87,8 +87,7 @@ func TestPodsTop(t *testing.T) {
})
// Enable metrics API addon
metricsApiAvailable = true
d, _ := c.mcpServer.k.ToDiscoveryClient()
d.Invalidate() // Force discovery client to refresh
c.mcpServer.k.Derived(t.Context()).CacheInvalidate() // Force discovery client to refresh
podsTopDefaults, err := c.callTool("pods_top", map[string]interface{}{})
t.Run("pods_top defaults returns pod metrics from all namespaces", func(t *testing.T) {
if err != nil {