mirror of
https://github.com/openshift/openshift-mcp-server.git
synced 2025-10-17 14:27:48 +03:00
fix(lint): add golangci-lint make target + lint
Signed-off-by: Marc Nuri <marc@marcnuri.com>
This commit is contained in:
14
Makefile
14
Makefile
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user