-
Notifications
You must be signed in to change notification settings - Fork 32
NE-2449: add netedge tool and get_coredns_config
#131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
| 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 | ||
| } | ||
| 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" | ||
| ) | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we group the tests to use the e.g. like type CoreDNSSuite struct {
suite.Suite
}and have tests registered on that |
||
| // 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) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| 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{}) | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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?