fix(lint): add golangci-lint make target + lint

Signed-off-by: Marc Nuri <marc@marcnuri.com>
This commit is contained in:
Marc Nuri
2025-07-22 14:22:19 +02:00
committed by GitHub
parent a3e8818ffe
commit 3fbfd8d7cb
18 changed files with 91 additions and 85 deletions

View File

@@ -15,6 +15,9 @@ LD_FLAGS = -s -w \
-X '$(PACKAGE)/pkg/version.BinaryName=$(BINARY_NAME)'
COMMON_BUILD_ARGS = -ldflags "$(LD_FLAGS)"
GOLANGCI_LINT = $(shell pwd)/_output/tools/bin/golangci-lint
GOLANGCI_LINT_VERSION ?= v2.2.2
# NPM version should not append the -dirty flag
NPM_VERSION ?= $(shell echo $(shell git describe --tags --always) | sed 's/^v//')
OSES = darwin linux windows
@@ -97,3 +100,14 @@ format: ## Format the code
.PHONY: tidy
tidy: ## Tidy up the go modules
go mod tidy
.PHONY: golangci-lint
golangci-lint: ## Download and install golangci-lint if not already installed
@[ -f $(GOLANGCI_LINT) ] || { \
set -e ;\
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\
}
.PHONY: lint
lint: golangci-lint ## Lint the code
$(GOLANGCI_LINT) run --verbose --print-resources-usage

View File

@@ -45,11 +45,14 @@ func (h *Helm) Install(ctx context.Context, chart string, values map[string]inte
install.Timeout = 5 * time.Minute
install.DryRun = false
chartRequested, err := install.ChartPathOptions.LocateChart(chart, cli.New())
chartRequested, err := install.LocateChart(chart, cli.New())
if err != nil {
return "", err
}
chartLoaded, err := loader.Load(chartRequested)
if err != nil {
return "", err
}
installedRelease, err := install.RunWithContext(ctx, chartLoaded, values)
if err != nil {

View File

@@ -113,11 +113,11 @@ func NewMCPServer(streams genericiooptions.IOStreams) *cobra.Command {
cmd.Flags().BoolVar(&o.ReadOnly, "read-only", o.ReadOnly, "If true, only tools annotated with readOnlyHint=true are exposed")
cmd.Flags().BoolVar(&o.DisableDestructive, "disable-destructive", o.DisableDestructive, "If true, tools annotated with destructiveHint=true are disabled")
cmd.Flags().BoolVar(&o.RequireOAuth, "require-oauth", o.RequireOAuth, "If true, requires OAuth authorization as defined in the Model Context Protocol (MCP) specification. This flag is ignored if transport type is stdio")
cmd.Flags().MarkHidden("require-oauth")
_ = cmd.Flags().MarkHidden("require-oauth")
cmd.Flags().StringVar(&o.AuthorizationURL, "authorization-url", o.AuthorizationURL, "OAuth authorization server URL for protected resource endpoint. If not provided, the Kubernetes API server host will be used. Only valid if require-oauth is enabled.")
cmd.Flags().MarkHidden("authorization-url")
_ = cmd.Flags().MarkHidden("authorization-url")
cmd.Flags().StringVar(&o.ServerURL, "server-url", o.ServerURL, "Server URL of this application. Optional. If set, this url will be served in protected resource metadata endpoint and tokens will be validated with this audience. If not set, expected audience is kubernetes-mcp-server. Only valid if require-oauth is enabled.")
cmd.Flags().MarkHidden("server-url")
_ = cmd.Flags().MarkHidden("server-url")
return cmd
}
@@ -228,11 +228,11 @@ func (m *MCPServerOptions) Validate() error {
func (m *MCPServerOptions) Run() error {
profile := mcp.ProfileFromString(m.Profile)
if profile == nil {
return fmt.Errorf("Invalid profile name: %s, valid names are: %s\n", m.Profile, strings.Join(mcp.ProfileNames, ", "))
return fmt.Errorf("invalid profile name: %s, valid names are: %s", m.Profile, strings.Join(mcp.ProfileNames, ", "))
}
listOutput := output.FromString(m.StaticConfig.ListOutput)
if listOutput == nil {
return fmt.Errorf("Invalid output name: %s, valid names are: %s\n", m.StaticConfig.ListOutput, strings.Join(output.Names, ", "))
return fmt.Errorf("invalid output name: %s, valid names are: %s", m.StaticConfig.ListOutput, strings.Join(output.Names, ", "))
}
klog.V(1).Info("Starting kubernetes-mcp-server")
klog.V(1).Infof(" - Config: %s", m.ConfigPath)
@@ -261,7 +261,7 @@ func (m *MCPServerOptions) Run() error {
StaticConfig: m.StaticConfig,
})
if err != nil {
return fmt.Errorf("Failed to initialize MCP server: %w\n", err)
return fmt.Errorf("failed to initialize MCP server: %w", err)
}
defer mcpServer.Close()

View File

@@ -106,6 +106,7 @@ func (m *Manager) ConfigurationView(minify bool) (runtime.Object, error) {
return nil, err
}
}
//nolint:staticcheck
if err = clientcmdapi.FlattenConfig(&cfg); err != nil {
// ignore error
//return "", err

View File

@@ -2,10 +2,12 @@ package kubernetes
import "net/http"
// nolint:unused
type impersonateRoundTripper struct {
delegate http.RoundTripper
}
// nolint:unused
func (irt *impersonateRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// TODO: Solution won't work with discoveryclient which uses context.TODO() instead of the passed-in context
if v, ok := req.Context().Value(OAuthAuthorizationHeader).(string); ok {

View File

@@ -26,9 +26,11 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)
type HeaderKey string
const (
CustomAuthorizationHeader = "kubernetes-authorization"
OAuthAuthorizationHeader = "Authorization"
CustomAuthorizationHeader = HeaderKey("kubernetes-authorization")
OAuthAuthorizationHeader = HeaderKey("Authorization")
CustomUserAgent = "kubernetes-mcp-server/bearer-token-auth"
)
@@ -155,10 +157,10 @@ func (m *Manager) Derived(ctx context.Context) (*Kubernetes, error) {
APIPath: m.cfg.APIPath,
// Copy only server verification TLS settings (CA bundle and server name)
TLSClientConfig: rest.TLSClientConfig{
Insecure: m.cfg.TLSClientConfig.Insecure,
ServerName: m.cfg.TLSClientConfig.ServerName,
CAFile: m.cfg.TLSClientConfig.CAFile,
CAData: m.cfg.TLSClientConfig.CAData,
Insecure: m.cfg.Insecure,
ServerName: m.cfg.ServerName,
CAFile: m.cfg.CAFile,
CAData: m.cfg.CAData,
},
BearerToken: strings.TrimPrefix(authorization, "Bearer "),
// pass custom UserAgent to identify the client

View File

@@ -137,17 +137,17 @@ users:
t.Errorf("expected Timeout %v, got %v", originalCfg.Timeout, derivedCfg.Timeout)
}
if derivedCfg.TLSClientConfig.Insecure != originalCfg.TLSClientConfig.Insecure {
t.Errorf("expected TLS Insecure %v, got %v", originalCfg.TLSClientConfig.Insecure, derivedCfg.TLSClientConfig.Insecure)
if derivedCfg.Insecure != originalCfg.Insecure {
t.Errorf("expected TLS Insecure %v, got %v", originalCfg.Insecure, derivedCfg.Insecure)
}
if derivedCfg.TLSClientConfig.ServerName != originalCfg.TLSClientConfig.ServerName {
t.Errorf("expected TLS ServerName %s, got %s", originalCfg.TLSClientConfig.ServerName, derivedCfg.TLSClientConfig.ServerName)
if derivedCfg.ServerName != originalCfg.ServerName {
t.Errorf("expected TLS ServerName %s, got %s", originalCfg.ServerName, derivedCfg.ServerName)
}
if derivedCfg.TLSClientConfig.CAFile != originalCfg.TLSClientConfig.CAFile {
t.Errorf("expected TLS CAFile %s, got %s", originalCfg.TLSClientConfig.CAFile, derivedCfg.TLSClientConfig.CAFile)
if derivedCfg.CAFile != originalCfg.CAFile {
t.Errorf("expected TLS CAFile %s, got %s", originalCfg.CAFile, derivedCfg.CAFile)
}
if string(derivedCfg.TLSClientConfig.CAData) != string(originalCfg.TLSClientConfig.CAData) {
t.Errorf("expected TLS CAData %s, got %s", string(originalCfg.TLSClientConfig.CAData), string(derivedCfg.TLSClientConfig.CAData))
if string(derivedCfg.CAData) != string(originalCfg.CAData) {
t.Errorf("expected TLS CAData %s, got %s", string(originalCfg.CAData), string(derivedCfg.CAData))
}
if derivedCfg.BearerToken != testBearerToken {
@@ -160,17 +160,17 @@ users:
// Verify that sensitive fields are NOT copied to prevent credential leakage
// The derived config should only use the bearer token from the Authorization header
// and not inherit any authentication credentials from the original kubeconfig
if derivedCfg.TLSClientConfig.CertFile != "" {
t.Errorf("expected TLS CertFile to be empty, got %s", derivedCfg.TLSClientConfig.CertFile)
if derivedCfg.CertFile != "" {
t.Errorf("expected TLS CertFile to be empty, got %s", derivedCfg.CertFile)
}
if derivedCfg.TLSClientConfig.KeyFile != "" {
t.Errorf("expected TLS KeyFile to be empty, got %s", derivedCfg.TLSClientConfig.KeyFile)
if derivedCfg.KeyFile != "" {
t.Errorf("expected TLS KeyFile to be empty, got %s", derivedCfg.KeyFile)
}
if len(derivedCfg.TLSClientConfig.CertData) != 0 {
t.Errorf("expected TLS CertData to be empty, got %v", derivedCfg.TLSClientConfig.CertData)
if len(derivedCfg.CertData) != 0 {
t.Errorf("expected TLS CertData to be empty, got %v", derivedCfg.CertData)
}
if len(derivedCfg.TLSClientConfig.KeyData) != 0 {
t.Errorf("expected TLS KeyData to be empty, got %v", derivedCfg.TLSClientConfig.KeyData)
if len(derivedCfg.KeyData) != 0 {
t.Errorf("expected TLS KeyData to be empty, got %v", derivedCfg.KeyData)
}
if derivedCfg.Username != "" {

View File

@@ -25,12 +25,9 @@ import (
apiextensionsv1spec "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/scale"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
toolswatch "k8s.io/client-go/tools/watch"
@@ -71,7 +68,7 @@ func TestMain(m *testing.M) {
}
envTestEnv.CheckCoherence()
workflows.Use{}.Do(envTestEnv)
versionDir := envTestEnv.Platform.Platform.BaseName(*envTestEnv.Version.AsConcrete())
versionDir := envTestEnv.Platform.BaseName(*envTestEnv.Version.AsConcrete())
envTest = &envtest.Environment{
BinaryAssetsDirectory: filepath.Join(envTestDir, "k8s", versionDir),
}
@@ -190,9 +187,9 @@ func (c *mcpContext) withKubeConfig(rc *rest.Config) *api.Config {
fakeConfig.AuthInfos["additional-auth"] = api.NewAuthInfo()
if rc != nil {
fakeConfig.Clusters["fake"].Server = rc.Host
fakeConfig.Clusters["fake"].CertificateAuthorityData = rc.TLSClientConfig.CAData
fakeConfig.AuthInfos["fake"].ClientKeyData = rc.TLSClientConfig.KeyData
fakeConfig.AuthInfos["fake"].ClientCertificateData = rc.TLSClientConfig.CertData
fakeConfig.Clusters["fake"].CertificateAuthorityData = rc.CAData
fakeConfig.AuthInfos["fake"].ClientKeyData = rc.KeyData
fakeConfig.AuthInfos["fake"].ClientCertificateData = rc.CertData
}
fakeConfig.Contexts["fake-context"] = api.NewContext()
fakeConfig.Contexts["fake-context"].Cluster = "fake"
@@ -264,18 +261,6 @@ func (c *mcpContext) newKubernetesClient() *kubernetes.Clientset {
return kubernetes.NewForConfigOrDie(envTestRestConfig)
}
func (c *mcpContext) newRestClient(groupVersion *schema.GroupVersion) *rest.RESTClient {
config := *envTestRestConfig
config.GroupVersion = groupVersion
config.APIPath = "/api"
config.NegotiatedSerializer = serializer.NewCodecFactory(scale.NewScaleConverter().Scheme()).WithoutConversion()
rc, err := rest.RESTClientFor(&config)
if err != nil {
panic(err)
}
return rc
}
// newApiExtensionsClient creates a new ApiExtensions client with the envTest kubeconfig
func (c *mcpContext) newApiExtensionsClient() *apiextensionsv1.ApiextensionsV1Client {
return apiextensionsv1.NewForConfigOrDie(envTestRestConfig)
@@ -286,6 +271,9 @@ func (c *mcpContext) crdApply(resource string) error {
apiExtensionsV1Client := c.newApiExtensionsClient()
var crd = &apiextensionsv1spec.CustomResourceDefinition{}
err := json.Unmarshal([]byte(resource), crd)
if err != nil {
return fmt.Errorf("failed to create CRD %v", err)
}
_, err = apiExtensionsV1Client.CustomResourceDefinitions().Create(c.ctx, crd, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("failed to create CRD %v", err)

View File

@@ -12,7 +12,7 @@ import (
func (s *Server) initConfiguration() []server.ServerTool {
tools := []server.ServerTool{
{mcp.NewTool("configuration_view",
{Tool: mcp.NewTool("configuration_view",
mcp.WithDescription("Get the current Kubernetes configuration content as a kubeconfig YAML"),
mcp.WithBoolean("minified", mcp.Description("Return a minified version of the configuration. "+
"If set to true, keeps only the current-context and the relevant pieces of the configuration for that context. "+
@@ -23,7 +23,7 @@ func (s *Server) initConfiguration() []server.ServerTool {
mcp.WithReadOnlyHintAnnotation(true),
mcp.WithDestructiveHintAnnotation(false),
mcp.WithOpenWorldHintAnnotation(true),
), s.configurationView},
), Handler: s.configurationView},
}
return tools
}

View File

@@ -12,7 +12,7 @@ import (
func (s *Server) initEvents() []server.ServerTool {
return []server.ServerTool{
{mcp.NewTool("events_list",
{Tool: mcp.NewTool("events_list",
mcp.WithDescription("List all the Kubernetes events in the current cluster from all namespaces"),
mcp.WithString("namespace",
mcp.Description("Optional Namespace to retrieve the events from. If not provided, will list events from all namespaces")),
@@ -21,7 +21,7 @@ func (s *Server) initEvents() []server.ServerTool {
mcp.WithReadOnlyHintAnnotation(true),
mcp.WithDestructiveHintAnnotation(false),
mcp.WithOpenWorldHintAnnotation(true),
), s.eventsList},
), Handler: s.eventsList},
}
}

View File

@@ -10,7 +10,7 @@ import (
func (s *Server) initHelm() []server.ServerTool {
return []server.ServerTool{
{mcp.NewTool("helm_install",
{Tool: mcp.NewTool("helm_install",
mcp.WithDescription("Install a Helm chart in the current or provided namespace"),
mcp.WithString("chart", mcp.Description("Chart reference to install (for example: stable/grafana, oci://ghcr.io/nginxinc/charts/nginx-ingress)"), mcp.Required()),
mcp.WithObject("values", mcp.Description("Values to pass to the Helm chart (Optional)")),
@@ -22,8 +22,8 @@ func (s *Server) initHelm() []server.ServerTool {
mcp.WithDestructiveHintAnnotation(false),
mcp.WithIdempotentHintAnnotation(false), // TODO: consider replacing implementation with equivalent to: helm upgrade --install
mcp.WithOpenWorldHintAnnotation(true),
), s.helmInstall},
{mcp.NewTool("helm_list",
), Handler: s.helmInstall},
{Tool: mcp.NewTool("helm_list",
mcp.WithDescription("List all the Helm releases in the current or provided namespace (or in all namespaces if specified)"),
mcp.WithString("namespace", mcp.Description("Namespace to list Helm releases from (Optional, all namespaces if not provided)")),
mcp.WithBoolean("all_namespaces", mcp.Description("If true, lists all Helm releases in all namespaces ignoring the namespace argument (Optional)")),
@@ -32,8 +32,8 @@ func (s *Server) initHelm() []server.ServerTool {
mcp.WithReadOnlyHintAnnotation(true),
mcp.WithDestructiveHintAnnotation(false),
mcp.WithOpenWorldHintAnnotation(true),
), s.helmList},
{mcp.NewTool("helm_uninstall",
), Handler: s.helmList},
{Tool: mcp.NewTool("helm_uninstall",
mcp.WithDescription("Uninstall a Helm release in the current or provided namespace"),
mcp.WithString("name", mcp.Description("Name of the Helm release to uninstall"), mcp.Required()),
mcp.WithString("namespace", mcp.Description("Namespace to uninstall the Helm release from (Optional, current namespace if not provided)")),
@@ -43,7 +43,7 @@ func (s *Server) initHelm() []server.ServerTool {
mcp.WithDestructiveHintAnnotation(true),
mcp.WithIdempotentHintAnnotation(true),
mcp.WithOpenWorldHintAnnotation(true),
), s.helmUninstall},
), Handler: s.helmUninstall},
}
}

View File

@@ -152,13 +152,13 @@ func NewTextResult(content string, err error) *mcp.CallToolResult {
func contextFunc(ctx context.Context, r *http.Request) context.Context {
// Get the standard Authorization header (OAuth compliant)
authHeader := r.Header.Get(internalk8s.OAuthAuthorizationHeader)
authHeader := r.Header.Get(string(internalk8s.OAuthAuthorizationHeader))
if authHeader != "" {
return context.WithValue(ctx, internalk8s.OAuthAuthorizationHeader, authHeader)
}
// Fallback to custom header for backward compatibility
customAuthHeader := r.Header.Get(internalk8s.CustomAuthorizationHeader)
customAuthHeader := r.Header.Get(string(internalk8s.CustomAuthorizationHeader))
if customAuthHeader != "" {
return context.WithValue(ctx, internalk8s.OAuthAuthorizationHeader, customAuthHeader)
}

View File

@@ -28,13 +28,9 @@ func TestWatchKubeConfig(t *testing.T) {
// When
f, _ := os.OpenFile(filepath.Join(c.tempDir, "config"), os.O_APPEND|os.O_WRONLY, 0644)
_, _ = f.WriteString("\n")
for {
if notification != nil {
break
}
for notification == nil {
select {
case <-withTimeout.Done():
break
default:
time.Sleep(100 * time.Millisecond)
}
@@ -94,7 +90,7 @@ func TestSseHeaders(t *testing.T) {
w.WriteHeader(404)
}))
testCaseWithContext(t, &mcpContext{before: before}, func(c *mcpContext) {
c.callTool("pods_list", map[string]interface{}{})
_, _ = c.callTool("pods_list", map[string]interface{}{})
t.Run("DiscoveryClient propagates headers to Kube API", func(t *testing.T) {
if len(pathHeaders) == 0 {
t.Fatalf("No requests were made to Kube API")
@@ -117,7 +113,7 @@ func TestSseHeaders(t *testing.T) {
t.Fatalf("Overridden header Authorization not found in request to /api/v1/namespaces/default/pods")
}
})
c.callTool("pods_delete", map[string]interface{}{"name": "a-pod-to-delete"})
_, _ = c.callTool("pods_delete", map[string]interface{}{"name": "a-pod-to-delete"})
t.Run("kubernetes.Interface propagates headers to Kube API", func(t *testing.T) {
if len(pathHeaders) == 0 {
t.Fatalf("No requests were made to Kube API")

View File

@@ -127,7 +127,7 @@ func (s *Server) podsListInAllNamespaces(ctx context.Context, ctr mcp.CallToolRe
AsTable: s.configuration.ListOutput.AsTable(),
}
if labelSelector != nil {
resourceListOptions.ListOptions.LabelSelector = labelSelector.(string)
resourceListOptions.LabelSelector = labelSelector.(string)
}
derived, err := s.k.Derived(ctx)
if err != nil {
@@ -150,7 +150,7 @@ func (s *Server) podsListInNamespace(ctx context.Context, ctr mcp.CallToolReques
}
labelSelector := ctr.GetArguments()["labelSelector"]
if labelSelector != nil {
resourceListOptions.ListOptions.LabelSelector = labelSelector.(string)
resourceListOptions.LabelSelector = labelSelector.(string)
}
derived, err := s.k.Derived(ctx)
if err != nil {

View File

@@ -482,7 +482,7 @@ func TestPodsDelete(t *testing.T) {
})
t.Run("pods_delete with name and nil namespace deletes Pod", func(t *testing.T) {
p, pErr := kc.CoreV1().Pods("default").Get(c.ctx, "a-pod-to-delete", metav1.GetOptions{})
if pErr == nil && p != nil && p.ObjectMeta.DeletionTimestamp == nil {
if pErr == nil && p != nil && p.DeletionTimestamp == nil {
t.Errorf("Pod not deleted")
return
}
@@ -512,7 +512,7 @@ func TestPodsDelete(t *testing.T) {
})
t.Run("pods_delete with name and namespace deletes Pod", func(t *testing.T) {
p, pErr := kc.CoreV1().Pods("ns-1").Get(c.ctx, "a-pod-to-delete-in-ns-1", metav1.GetOptions{})
if pErr == nil && p != nil && p.ObjectMeta.DeletionTimestamp == nil {
if pErr == nil && p != nil && p.DeletionTimestamp == nil {
t.Errorf("Pod not deleted")
return
}
@@ -549,12 +549,12 @@ func TestPodsDelete(t *testing.T) {
})
t.Run("pods_delete with managed pod deletes Pod and Service", func(t *testing.T) {
p, pErr := kc.CoreV1().Pods("default").Get(c.ctx, "a-managed-pod-to-delete", metav1.GetOptions{})
if pErr == nil && p != nil && p.ObjectMeta.DeletionTimestamp == nil {
if pErr == nil && p != nil && p.DeletionTimestamp == nil {
t.Errorf("Pod not deleted")
return
}
s, sErr := kc.CoreV1().Services("default").Get(c.ctx, "a-managed-service-to-delete", metav1.GetOptions{})
if sErr == nil && s != nil && s.ObjectMeta.DeletionTimestamp == nil {
if sErr == nil && s != nil && s.DeletionTimestamp == nil {
t.Errorf("Service not deleted")
return
}
@@ -621,7 +621,7 @@ func TestPodsDeleteInOpenShift(t *testing.T) {
})
t.Run("pods_delete with managed pod in OpenShift deletes Pod and Route", func(t *testing.T) {
p, pErr := kc.CoreV1().Pods("default").Get(c.ctx, "a-managed-pod-to-delete-in-openshift", metav1.GetOptions{})
if pErr == nil && p != nil && p.ObjectMeta.DeletionTimestamp == nil {
if pErr == nil && p != nil && p.DeletionTimestamp == nil {
t.Errorf("Pod not deleted")
return
}

View File

@@ -112,7 +112,7 @@ func TestPodsTopMetricsAvailable(t *testing.T) {
if podsTopDefaults.IsError {
t.Fatalf("call tool failed %s", textContent)
}
expectedHeaders := regexp.MustCompile("(?m)^\\s*NAMESPACE\\s+POD\\s+NAME\\s+CPU\\(cores\\)\\s+MEMORY\\(bytes\\)\\s*$")
expectedHeaders := regexp.MustCompile(`(?m)^\s*NAMESPACE\s+POD\s+NAME\s+CPU\(cores\)\s+MEMORY\(bytes\)\s*$`)
if !expectedHeaders.MatchString(textContent) {
t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders.String(), textContent)
}
@@ -126,7 +126,7 @@ func TestPodsTopMetricsAvailable(t *testing.T) {
t.Errorf("Expected row '%s' not found in output:\n%s", row, textContent)
}
}
expectedTotal := regexp.MustCompile("(?m)^\\s+600m\\s+900Mi\\s*$")
expectedTotal := regexp.MustCompile(`(?m)^\s+600m\s+900Mi\s*$`)
if !expectedTotal.MatchString(textContent) {
t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent)
}
@@ -148,7 +148,7 @@ func TestPodsTopMetricsAvailable(t *testing.T) {
t.Errorf("Expected row '%s' not found in output:\n%s", row, textContent)
}
}
expectedTotal := regexp.MustCompile("(?m)^\\s+40m\\s+60Mi\\s*$")
expectedTotal := regexp.MustCompile(`(?m)^\s+40m\s+60Mi\s*$`)
if !expectedTotal.MatchString(textContent) {
t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent)
}
@@ -161,11 +161,11 @@ func TestPodsTopMetricsAvailable(t *testing.T) {
t.Fatalf("call tool failed %v", err)
}
textContent := podsTopNamespace.Content[0].(mcp.TextContent).Text
expectedRow := regexp.MustCompile("ns-5\\s+pod-ns-5-1\\s+container-1\\s+10m\\s+20Mi")
expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-1\s+container-1\s+10m\s+20Mi`)
if !expectedRow.MatchString(textContent) {
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent)
}
expectedTotal := regexp.MustCompile("(?m)^\\s+10m\\s+20Mi\\s*$")
expectedTotal := regexp.MustCompile(`(?m)^\s+10m\s+20Mi\s*$`)
if !expectedTotal.MatchString(textContent) {
t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent)
}
@@ -179,11 +179,11 @@ func TestPodsTopMetricsAvailable(t *testing.T) {
t.Fatalf("call tool failed %v", err)
}
textContent := podsTopNamespaceName.Content[0].(mcp.TextContent).Text
expectedRow := regexp.MustCompile("ns-5\\s+pod-ns-5-5\\s+container-1\\s+13m\\s+37Mi")
expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-5\s+container-1\s+13m\s+37Mi`)
if !expectedRow.MatchString(textContent) {
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent)
}
expectedTotal := regexp.MustCompile("(?m)^\\s+13m\\s+37Mi\\s*$")
expectedTotal := regexp.MustCompile(`(?m)^\s+13m\s+37Mi\s*$`)
if !expectedTotal.MatchString(textContent) {
t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent)
}
@@ -196,11 +196,11 @@ func TestPodsTopMetricsAvailable(t *testing.T) {
t.Fatalf("call tool failed %v", err)
}
textContent := podsTopNamespaceLabelSelector.Content[0].(mcp.TextContent).Text
expectedRow := regexp.MustCompile("ns-5\\s+pod-ns-5-42\\s+container-1\\s+42m\\s+42Mi")
expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-42\s+container-1\s+42m\s+42Mi`)
if !expectedRow.MatchString(textContent) {
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent)
}
expectedTotal := regexp.MustCompile("(?m)^\\s+42m\\s+42Mi\\s*$")
expectedTotal := regexp.MustCompile(`(?m)^\s+42m\s+42Mi\s*$`)
if !expectedTotal.MatchString(textContent) {
t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent)
}

View File

@@ -116,7 +116,7 @@ func (s *Server) resourcesList(ctx context.Context, ctr mcp.CallToolRequest) (*m
if !ok {
return NewTextResult("", fmt.Errorf("labelSelector is not a string")), nil
}
resourceListOptions.ListOptions.LabelSelector = l
resourceListOptions.LabelSelector = l
}
gvk, err := parseGroupVersionKind(ctr.GetArguments())
if err != nil {

View File

@@ -736,7 +736,7 @@ func TestResourcesDelete(t *testing.T) {
})
t.Run("resources_delete with valid namespaced resource deletes Namespace", func(t *testing.T) {
ns, err := client.CoreV1().Namespaces().Get(c.ctx, "ns-to-delete", metav1.GetOptions{})
if err == nil && ns != nil && ns.ObjectMeta.DeletionTimestamp == nil {
if err == nil && ns != nil && ns.DeletionTimestamp == nil {
t.Fatalf("Namespace not deleted")
return
}