mirror of
https://github.com/openshift/openshift-mcp-server.git
synced 2025-10-17 14:27:48 +03:00
feat(pods): add optional tail parameter to pod logs retrieval (#335)
* feat(pods): add tailLines parameter to pod logs retrieval with default 256 lines Signed-off-by: iamsudip <sudip.maji@harness.io> * address review comments Signed-off-by: iamsudip <sudip.maji@harness.io> * test(pods): add tailLines parameter to pod logs retrieval with default 256 lines Signed-off-by: Marc Nuri <marc@marcnuri.com> --------- Signed-off-by: iamsudip <sudip.maji@harness.io> Signed-off-by: Marc Nuri <marc@marcnuri.com> Co-authored-by: Marc Nuri <marc@marcnuri.com>
This commit is contained in:
@@ -17,10 +17,14 @@ import (
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/metrics/pkg/apis/metrics"
|
||||
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/containers/kubernetes-mcp-server/pkg/version"
|
||||
)
|
||||
|
||||
// Default number of lines to retrieve from the end of the logs
|
||||
const DefaultTailLines = int64(100)
|
||||
|
||||
type PodsTopOptions struct {
|
||||
metav1.ListOptions
|
||||
AllNamespaces bool
|
||||
@@ -92,17 +96,26 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
|
||||
k.ResourcesDelete(ctx, &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, namespace, name)
|
||||
}
|
||||
|
||||
func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name, container string, previous bool) (string, error) {
|
||||
tailLines := int64(256)
|
||||
func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name, container string, previous bool, tail int64) (string, error) {
|
||||
pods, err := k.manager.accessControlClientSet.Pods(k.NamespaceOrDefault(namespace))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req := pods.GetLogs(name, &v1.PodLogOptions{
|
||||
TailLines: &tailLines,
|
||||
|
||||
logOptions := &v1.PodLogOptions{
|
||||
Container: container,
|
||||
Previous: previous,
|
||||
})
|
||||
}
|
||||
|
||||
// Only set tailLines if a value is provided (non-zero)
|
||||
if tail > 0 {
|
||||
logOptions.TailLines = &tail
|
||||
} else {
|
||||
// Default to DefaultTailLines lines when not specified
|
||||
logOptions.TailLines = ptr.To(DefaultTailLines)
|
||||
}
|
||||
|
||||
req := pods.GetLogs(name, logOptions)
|
||||
res := req.Do(ctx)
|
||||
if res.Error() != nil {
|
||||
return "", res.Error()
|
||||
|
||||
@@ -756,6 +756,41 @@ func TestPodsLog(t *testing.T) {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Test with tail parameter
|
||||
podsTailLines, err := c.callTool("pods_log", map[string]interface{}{
|
||||
"namespace": "ns-1",
|
||||
"name": "a-pod-in-ns-1",
|
||||
"tail": 50,
|
||||
})
|
||||
t.Run("pods_log with tail=50 returns pod log", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
return
|
||||
}
|
||||
if podsTailLines.IsError {
|
||||
t.Fatalf("call tool failed")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Test with invalid tail parameter
|
||||
podsInvalidTailLines, _ := c.callTool("pods_log", map[string]interface{}{
|
||||
"namespace": "ns-1",
|
||||
"name": "a-pod-in-ns-1",
|
||||
"tail": "invalid",
|
||||
})
|
||||
t.Run("pods_log with invalid tail returns error", func(t *testing.T) {
|
||||
if !podsInvalidTailLines.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
expectedErrorMsg := "failed to parse tail parameter: expected integer"
|
||||
if errMsg := podsInvalidTailLines.Content[0].(mcp.TextContent).Text; !strings.Contains(errMsg, expectedErrorMsg) {
|
||||
t.Fatalf("unexpected error message, expected to contain '%s', got '%s'", expectedErrorMsg, errMsg)
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
6
pkg/mcp/testdata/toolsets-core-tools.json
vendored
6
pkg/mcp/testdata/toolsets-core-tools.json
vendored
@@ -202,6 +202,12 @@
|
||||
"previous": {
|
||||
"description": "Return previous terminated container logs (Optional)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tail": {
|
||||
"default": 100,
|
||||
"description": "Number of lines to retrieve from the end of the logs (Optional, default: 100)",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -308,6 +308,12 @@
|
||||
"previous": {
|
||||
"description": "Return previous terminated container logs (Optional)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tail": {
|
||||
"default": 100,
|
||||
"description": "Number of lines to retrieve from the end of the logs (Optional, default: 100)",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
6
pkg/mcp/testdata/toolsets-full-tools.json
vendored
6
pkg/mcp/testdata/toolsets-full-tools.json
vendored
@@ -308,6 +308,12 @@
|
||||
"previous": {
|
||||
"description": "Return previous terminated container logs (Optional)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tail": {
|
||||
"default": 100,
|
||||
"description": "Number of lines to retrieve from the end of the logs (Optional, default: 100)",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -201,6 +201,12 @@ func initPods() []api.ServerTool {
|
||||
Type: "string",
|
||||
Description: "Name of the Pod container to get the logs from (Optional)",
|
||||
},
|
||||
"tail": {
|
||||
Type: "integer",
|
||||
Description: "Number of lines to retrieve from the end of the logs (Optional, default: 100)",
|
||||
Default: api.ToRawMessage(kubernetes.DefaultTailLines),
|
||||
Minimum: ptr.To(float64(0)),
|
||||
},
|
||||
"previous": {
|
||||
Type: "boolean",
|
||||
Description: "Return previous terminated container logs (Optional)",
|
||||
@@ -396,7 +402,24 @@ func podsLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
|
||||
if previous != nil {
|
||||
previousBool = previous.(bool)
|
||||
}
|
||||
ret, err := params.PodsLog(params, ns.(string), name.(string), container.(string), previousBool)
|
||||
// Extract tailLines parameter
|
||||
tail := params.GetArguments()["tail"]
|
||||
var tailInt int64
|
||||
if tail != nil {
|
||||
// Convert to int64 - safely handle both float64 (JSON number) and int types
|
||||
switch v := tail.(type) {
|
||||
case float64:
|
||||
tailInt = int64(v)
|
||||
case int:
|
||||
tailInt = int64(v)
|
||||
case int64:
|
||||
tailInt = v
|
||||
default:
|
||||
return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: expected integer, got %T", tail)), nil
|
||||
}
|
||||
}
|
||||
|
||||
ret, err := params.PodsLog(params.Context, ns.(string), name.(string), container.(string), previousBool, tailInt)
|
||||
if err != nil {
|
||||
return api.NewToolCallResult("", fmt.Errorf("failed to get pod %s log in namespace %s: %v", name, ns, err)), nil
|
||||
} else if ret == "" {
|
||||
|
||||
Reference in New Issue
Block a user