feat(kubernetes): add support for previousPod container logs (#256)

Add 'previous' parameter to pods_log tool to retrieve logs from terminated containers, equivalent to kubectl logs --previous functionality.
This enables debugging of containers that have restarted due to crashes or updates.

Signed-off-by: Samuel Masuy <samuel.masuy@goto.com>
Co-authored-by: opencode <noreply@opencode.ai>
This commit is contained in:
Samuel Masuy
2025-09-11 03:29:51 -04:00
committed by GitHub
parent 10c82f7bff
commit 6c573f31c8
4 changed files with 41 additions and 2 deletions

View File

@@ -317,6 +317,8 @@ Get the logs of a Kubernetes Pod in the current or provided namespace with the p
- Namespace to get the Pod logs from
- `container` (`string`, optional)
- Name of the Pod container to get logs from
- `previous` (`boolean`, optional)
- Return previous terminated container logs
### `pods_run`

View File

@@ -92,7 +92,7 @@ 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) (string, error) {
func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name, container string, previous bool) (string, error) {
tailLines := int64(256)
pods, err := k.manager.accessControlClientSet.Pods(k.NamespaceOrDefault(namespace))
if err != nil {
@@ -101,6 +101,7 @@ func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name, container str
req := pods.GetLogs(name, &v1.PodLogOptions{
TailLines: &tailLines,
Container: container,
Previous: previous,
})
res := req.Do(ctx)
if res.Error() != nil {

View File

@@ -99,6 +99,7 @@ func (s *Server) initPods() []server.ServerTool {
mcp.WithString("namespace", mcp.Description("Namespace to get the Pod logs from")),
mcp.WithString("name", mcp.Description("Name of the Pod to get the logs from"), mcp.Required()),
mcp.WithString("container", mcp.Description("Name of the Pod container to get the logs from (Optional)")),
mcp.WithBoolean("previous", mcp.Description("Return previous terminated container logs (Optional)")),
// Tool annotations
mcp.WithTitleAnnotation("Pods: Log"),
mcp.WithReadOnlyHintAnnotation(true),
@@ -284,11 +285,16 @@ func (s *Server) podsLog(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.Cal
if container == nil {
container = ""
}
previous := ctr.GetArguments()["previous"]
var previousBool bool
if previous != nil {
previousBool = previous.(bool)
}
derived, err := s.k.Derived(ctx)
if err != nil {
return nil, err
}
ret, err := derived.PodsLog(ctx, ns.(string), name.(string), container.(string))
ret, err := derived.PodsLog(ctx, ns.(string), name.(string), container.(string), previousBool)
if err != nil {
return NewTextResult("", fmt.Errorf("failed to get pod %s log in namespace %s: %v", name, ns, err)), nil
} else if ret == "" {

View File

@@ -719,6 +719,36 @@ func TestPodsLog(t *testing.T) {
return
}
})
podsPreviousLogInNamespace, err := c.callTool("pods_log", map[string]interface{}{
"namespace": "ns-1",
"name": "a-pod-in-ns-1",
"previous": true,
})
t.Run("pods_log with previous=true returns previous pod log", func(t *testing.T) {
if err != nil {
t.Fatalf("call tool failed %v", err)
return
}
if podsPreviousLogInNamespace.IsError {
t.Fatalf("call tool failed")
return
}
})
podsPreviousLogFalse, err := c.callTool("pods_log", map[string]interface{}{
"namespace": "ns-1",
"name": "a-pod-in-ns-1",
"previous": false,
})
t.Run("pods_log with previous=false returns current pod log", func(t *testing.T) {
if err != nil {
t.Fatalf("call tool failed %v", err)
return
}
if podsPreviousLogFalse.IsError {
t.Fatalf("call tool failed")
return
}
})
})
}