Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/mcp/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ import (
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kcp"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kiali"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kubevirt"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/netedge"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/observability"
)
66 changes: 66 additions & 0 deletions pkg/toolsets/netedge/coredns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package netedge

import (
"fmt"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
"github.com/google/jsonschema-go/jsonschema"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func initCoreDNS() []api.ServerTool {
return []api.ServerTool{
{
Tool: api.Tool{
Name: "get_coredns_config",
Description: "Retrieve the current CoreDNS configuration (Corefile) from the cluster.",
InputSchema: &jsonschema.Schema{
Type: "object",
},
Annotations: api.ToolAnnotations{
Title: "Get CoreDNS Config",
ReadOnlyHint: ptr.To(true),
DestructiveHint: ptr.To(false),
OpenWorldHint: ptr.To(true),
},
},
Handler: getCoreDNSConfig,
},
}
}

// newClientFunc is the function used to create a controller-runtime client.
// It is a variable to allow overriding in tests.
var newClientFunc = func(config *rest.Config, options client.Options) (client.Client, error) {
return client.New(config, options)
}

func getCoreDNSConfig(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
cfg := params.RESTConfig()
if cfg == nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get REST config")), nil
}

cl, err := newClientFunc(cfg, client.Options{Scheme: kubernetes.Scheme})
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to create controller-runtime client: %w", err)), nil
}

cm := &corev1.ConfigMap{}
err = cl.Get(params.Context, types.NamespacedName{Name: "dns-default", Namespace: "openshift-dns"}, cm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is anyways just OCP - hence the hardcoded ns value is ok?

if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get dns-default ConfigMap: %w", err)), nil
}

corefile, ok := cm.Data["Corefile"]
if !ok {
return api.NewToolCallResult("", fmt.Errorf("corefile not found in dns-default ConfigMap")), nil
}

return api.NewToolCallResult(corefile, nil), nil
}
111 changes: 111 additions & 0 deletions pkg/toolsets/netedge/coredns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package netedge

import (
"context"
"testing"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we group the tests to use the testify/suite pattern, as mentioned on the AGENTS.md file?

e.g. like

  type CoreDNSSuite struct {
      suite.Suite
  }

and have tests registered on that CoreDNSSuite ?

// mockKubernetesClient implements api.KubernetesClient for testing
type mockKubernetesClient struct {
api.KubernetesClient
restConfig *rest.Config
}

func (m *mockKubernetesClient) RESTConfig() *rest.Config {
return m.restConfig
}

func TestGetCoreDNSConfig(t *testing.T) {
tests := []struct {
name string
configMap *corev1.ConfigMap
expectedOutput string
expectError bool
errorContains string
}{
{
name: "success - corefile found",
configMap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "dns-default",
Namespace: "openshift-dns",
},
Data: map[string]string{
"Corefile": "example corefile content",
},
},
expectedOutput: "example corefile content",
expectError: false,
},
{
name: "failure - configmap not found",
configMap: nil,
expectedOutput: "",
expectError: true,
errorContains: "failed to get dns-default ConfigMap",
},
{
name: "failure - Corefile key missing",
configMap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "dns-default",
Namespace: "openshift-dns",
},
Data: map[string]string{
"OtherData": "some data",
},
},
expectedOutput: "",
expectError: true,
errorContains: "corefile not found",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup mock client
objs := []runtime.Object{}
if tt.configMap != nil {
objs = append(objs, tt.configMap)
}

// Override newClientFunc to return fake client
originalNewClientFunc := newClientFunc
defer func() { newClientFunc = originalNewClientFunc }()

newClientFunc = func(config *rest.Config, options client.Options) (client.Client, error) {
return fake.NewClientBuilder().WithRuntimeObjects(objs...).Build(), nil
}

// Call handler
params := api.ToolHandlerParams{
Context: context.Background(),
KubernetesClient: &mockKubernetesClient{restConfig: &rest.Config{}},
}

result, err := getCoreDNSConfig(params)

if tt.expectError {
require.NoError(t, err) // Handler returns error in result, not as return value
require.NotNil(t, result)
require.Error(t, result.Error)
assert.Contains(t, result.Error.Error(), tt.errorContains)
} else {
require.NoError(t, err)
require.NotNil(t, result)
require.NoError(t, result.Error)
assert.Equal(t, tt.expectedOutput, result.Content)
}
})
}
}
40 changes: 40 additions & 0 deletions pkg/toolsets/netedge/toolset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package netedge

import (
"slices"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/containers/kubernetes-mcp-server/pkg/toolsets"
)

// Toolset implements the netedge toolset for Network Ingress & DNS troubleshooting.
type Toolset struct{}

var _ api.Toolset = (*Toolset)(nil)

// GetName returns the name of the toolset.
func (t *Toolset) GetName() string {
return "netedge"
}

// GetDescription returns a human-readable description of the toolset.
func (t *Toolset) GetDescription() string {
return "Network Edge troubleshooting tools (Ingress, DNS, Gateway API)"
}

// GetTools returns all tools provided by this toolset.
func (t *Toolset) GetTools(_ api.Openshift) []api.ServerTool {
return slices.Concat(
initCoreDNS(),
)
}

// GetPrompts returns prompts provided by this toolset.
func (t *Toolset) GetPrompts() []api.ServerPrompt {
// NetEdge toolset presently does not provide prompts
return nil
}

func init() {
toolsets.Register(&Toolset{})
}
6 changes: 6 additions & 0 deletions vendor/k8s.io/client-go/applyconfigurations/OWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

150 changes: 150 additions & 0 deletions vendor/k8s.io/client-go/applyconfigurations/doc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading