Skip to content

Commit aa85f41

Browse files
authored
Merge branch 'main' into main
2 parents 09afb5a + 6342379 commit aa85f41

30 files changed

+1373
-1437
lines changed

build/keycloak.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ keycloak-install:
5050
keycloak-uninstall:
5151
@kubectl delete -f dev/config/keycloak/deployment.yaml 2>/dev/null || true
5252

53+
##@ Keycloak
54+
5355
.PHONY: keycloak-status
5456
keycloak-status: ## Show Keycloak status and connection info
5557
@if kubectl get svc -n $(KEYCLOAK_NAMESPACE) keycloak >/dev/null 2>&1; then \

build/tools.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ $(KIND):
1717
GOBIN=$(PWD)/_output/bin go install sigs.k8s.io/kind@$(KIND_VERSION)
1818

1919
.PHONY: kind
20-
kind: $(KIND) ## Download kind locally if necessary
20+
kind: $(KIND)

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/fsnotify/fsnotify v1.9.0
99
github.com/go-jose/go-jose/v4 v4.1.3
1010
github.com/google/jsonschema-go v0.3.0
11-
github.com/mark3labs/mcp-go v0.42.0
11+
github.com/mark3labs/mcp-go v0.43.0
1212
github.com/pkg/errors v0.9.1
1313
github.com/spf13/afero v1.15.0
1414
github.com/spf13/cobra v1.10.1
@@ -27,7 +27,7 @@ require (
2727
k8s.io/kubectl v0.34.1
2828
k8s.io/metrics v0.34.1
2929
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
30-
sigs.k8s.io/controller-runtime v0.22.3
30+
sigs.k8s.io/controller-runtime v0.22.4
3131
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664
3232
sigs.k8s.io/yaml v1.6.0
3333
)

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
187187
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
188188
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
189189
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
190-
github.com/mark3labs/mcp-go v0.42.0 h1:gk/8nYJh8t3yroCAOBhNbYsM9TCKvkM13I5t5Hfu6Ls=
191-
github.com/mark3labs/mcp-go v0.42.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
190+
github.com/mark3labs/mcp-go v0.43.0 h1:lgiKcWMddh4sngbU+hoWOZ9iAe/qp/m851RQpj3Y7jA=
191+
github.com/mark3labs/mcp-go v0.43.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
192192
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
193193
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
194194
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -453,8 +453,8 @@ k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8
453453
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
454454
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
455455
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
456-
sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y=
457-
sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
456+
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
457+
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
458458
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664 h1:xC7x7FsPURJYhZnWHsWFd7nkdD/WRtQVWPC28FWt85Y=
459459
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250211091558-894df3a7e664/go.mod h1:Cq9jUhwSYol5tNB0O/1vLYxNV9KqnhpvEa6HvJ1w0wY=
460460
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=

internal/test/mcp.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func NewMcpClient(t *testing.T, mcpHttpServer http.Handler, options ...transport
2323
var err error
2424
ret := &McpClient{ctx: t.Context()}
2525
ret.testServer = httptest.NewServer(mcpHttpServer)
26+
options = append(options, transport.WithContinuousListening())
2627
ret.Client, err = client.NewStreamableHttpClient(ret.testServer.URL+"/mcp", options...)
2728
require.NoError(t, err, "Expected no error creating MCP client")
2829
err = ret.Start(t.Context())

pkg/kubernetes/accesscontrol_clientset.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ func (a *AccessControlClientset) DiscoveryClient() discovery.DiscoveryInterface
3939
return a.discoveryClient
4040
}
4141

42+
func (a *AccessControlClientset) Nodes() (corev1.NodeInterface, error) {
43+
gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"}
44+
if !isAllowed(a.staticConfig, gvk) {
45+
return nil, isNotAllowedError(gvk)
46+
}
47+
return a.delegate.CoreV1().Nodes(), nil
48+
}
49+
4250
func (a *AccessControlClientset) NodesLogs(ctx context.Context, name string) (*rest.Request, error) {
4351
gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"}
4452
if !isAllowed(a.staticConfig, gvk) {
@@ -55,6 +63,29 @@ func (a *AccessControlClientset) NodesLogs(ctx context.Context, name string) (*r
5563
AbsPath(url...), nil
5664
}
5765

66+
func (a *AccessControlClientset) NodesMetricses(ctx context.Context, name string, listOptions metav1.ListOptions) (*metrics.NodeMetricsList, error) {
67+
gvk := &schema.GroupVersionKind{Group: metrics.GroupName, Version: metricsv1beta1api.SchemeGroupVersion.Version, Kind: "NodeMetrics"}
68+
if !isAllowed(a.staticConfig, gvk) {
69+
return nil, isNotAllowedError(gvk)
70+
}
71+
versionedMetrics := &metricsv1beta1api.NodeMetricsList{}
72+
var err error
73+
if name != "" {
74+
m, err := a.metricsV1beta1.NodeMetricses().Get(ctx, name, metav1.GetOptions{})
75+
if err != nil {
76+
return nil, fmt.Errorf("failed to get metrics for node %s: %w", name, err)
77+
}
78+
versionedMetrics.Items = []metricsv1beta1api.NodeMetrics{*m}
79+
} else {
80+
versionedMetrics, err = a.metricsV1beta1.NodeMetricses().List(ctx, listOptions)
81+
if err != nil {
82+
return nil, fmt.Errorf("failed to list node metrics: %w", err)
83+
}
84+
}
85+
convertedMetrics := &metrics.NodeMetricsList{}
86+
return convertedMetrics, metricsv1beta1api.Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(versionedMetrics, convertedMetrics, nil)
87+
}
88+
5889
func (a *AccessControlClientset) NodesStatsSummary(ctx context.Context, name string) (*rest.Request, error) {
5990
gvk := &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"}
6091
if !isAllowed(a.staticConfig, gvk) {

pkg/kubernetes/nodes.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ package kubernetes
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/metrics/pkg/apis/metrics"
10+
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
611
)
712

813
func (k *Kubernetes) NodesLog(ctx context.Context, name string, query string, tailLines int64) (string, error) {
@@ -39,6 +44,7 @@ func (k *Kubernetes) NodesLog(ctx context.Context, name string, query string, ta
3944

4045
func (k *Kubernetes) NodesStatsSummary(ctx context.Context, name string) (string, error) {
4146
// Use the node proxy API to access stats summary from the kubelet
47+
// https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/
4248
// This endpoint provides CPU, memory, filesystem, and network statistics
4349

4450
req, err := k.AccessControlClientset().NodesStatsSummary(ctx, name)
@@ -58,3 +64,16 @@ func (k *Kubernetes) NodesStatsSummary(ctx context.Context, name string) (string
5864

5965
return string(rawData), nil
6066
}
67+
68+
type NodesTopOptions struct {
69+
metav1.ListOptions
70+
Name string
71+
}
72+
73+
func (k *Kubernetes) NodesTop(ctx context.Context, options NodesTopOptions) (*metrics.NodeMetricsList, error) {
74+
// TODO, maybe move to mcp Tools setup and omit in case metrics aren't available in the target cluster
75+
if !k.supportsGroupVersion(metrics.GroupName + "/" + metricsv1beta1api.SchemeGroupVersion.Version) {
76+
return nil, errors.New("metrics API is not available")
77+
}
78+
return k.manager.accessControlClientSet.NodesMetricses(ctx, options.Name, options.ListOptions)
79+
}

0 commit comments

Comments
 (0)