k8stools 2.2.1__tar.gz → 2.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {k8stools-2.2.1 → k8stools-2.4.0}/CHANGELOG.md +93 -0
  2. {k8stools-2.2.1 → k8stools-2.4.0}/CLAUDE.md +16 -3
  3. {k8stools-2.2.1 → k8stools-2.4.0}/PKG-INFO +39 -7
  4. {k8stools-2.2.1 → k8stools-2.4.0}/README.md +38 -6
  5. {k8stools-2.2.1 → k8stools-2.4.0}/designs/mock-state-capture.md +10 -2
  6. {k8stools-2.2.1 → k8stools-2.4.0}/pyproject.toml +1 -1
  7. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/__init__.py +5 -1
  8. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/capture.py +15 -2
  9. k8stools-2.4.0/src/k8stools/fixtures/otel-demo.json +1282 -0
  10. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/k8s_tools.py +747 -7
  11. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mock_state.py +64 -0
  12. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mock_tools.py +15 -0
  13. k8stools-2.4.0/tests/test_builtin_fixture.py +216 -0
  14. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_capture.py +1 -0
  15. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_duration_format.py +2 -0
  16. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_events.py +3 -2
  17. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_k8s_tools_realk8s.py +96 -0
  18. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_mock_tools.py +5 -5
  19. k8stools-2.4.0/tests/test_node_conditions.py +132 -0
  20. k8stools-2.4.0/tests/test_workload_history.py +376 -0
  21. k8stools-2.4.0/tests/test_workloads.py +233 -0
  22. {k8stools-2.2.1 → k8stools-2.4.0}/uv.lock +1 -1
  23. k8stools-2.2.1/src/k8stools/fixtures/otel-demo.json +0 -570
  24. {k8stools-2.2.1 → k8stools-2.4.0}/.claude/settings.local.json +0 -0
  25. {k8stools-2.2.1 → k8stools-2.4.0}/.github/workflows/unit-tests.yml +0 -0
  26. {k8stools-2.2.1 → k8stools-2.4.0}/.gitignore +0 -0
  27. {k8stools-2.2.1 → k8stools-2.4.0}/.python-version +0 -0
  28. {k8stools-2.2.1 → k8stools-2.4.0}/.vscode/mcp.json +0 -0
  29. {k8stools-2.2.1 → k8stools-2.4.0}/.vscode/settings.json +0 -0
  30. {k8stools-2.2.1 → k8stools-2.4.0}/LICENSE +0 -0
  31. {k8stools-2.2.1 → k8stools-2.4.0}/chat-with-cluster.png +0 -0
  32. {k8stools-2.2.1 → k8stools-2.4.0}/docs/ROADMAP.md +0 -0
  33. {k8stools-2.2.1 → k8stools-2.4.0}/envrc.template +0 -0
  34. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mcp_client.py +0 -0
  35. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mcp_server.py +0 -0
  36. {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/redaction.py +0 -0
  37. {k8stools-2.2.1 → k8stools-2.4.0}/tests/conftest.py +0 -0
  38. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_cluster_binding.py +0 -0
  39. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_k8s_tools.py +0 -0
  40. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_log_decoding.py +0 -0
  41. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_mcp_client.py +0 -0
  42. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_mock_state.py +0 -0
  43. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_new_tools.py +0 -0
  44. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_redaction.py +0 -0
  45. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_replicaset_tool.py +0 -0
  46. {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_version.py +0 -0
@@ -3,6 +3,99 @@
3
3
  All notable changes to k8stools. Versions follow [semantic versioning](https://semver.org/);
4
4
  dates are release-tag dates.
5
5
 
6
+ ## 2.4.0 — 2026-10-05
7
+
8
+ ### Added
9
+ - **`get_workload_history` tool** (issue #11): what changed in a Deployment,
10
+ StatefulSet or DaemonSet, and when. It lists the retained pod-template revisions
11
+ newest first (a Deployment's ReplicaSets; a StatefulSet's or DaemonSet's
12
+ ControllerRevisions), each compared with the one before it:
13
+ - per container, matched by name: image, resource requests and limits,
14
+ command, args, probes, env vars, `envFrom` and volume mounts;
15
+ - for the pod: volumes, node selector and tolerations;
16
+ - any other changed field is named;
17
+ - template label and annotation changes are listed separately, so a Helm
18
+ version-label bump doesn't bury a memory-limit change;
19
+ - a `kubectl rollout restart` is flagged.
20
+
21
+ Env vars are compared by name and shown by name only, never by value. The
22
+ result also lists the ConfigMaps and Secrets the current template uses, and
23
+ how. ConfigMaps get their age and `last_written`. Secrets are named but never
24
+ read. Each result carries the limits of what history can show. `print_workload_history`
25
+ is its companion.
26
+ - Captures store each workload's history (the tool's result, so no env values are
27
+ written); captures from before 2.4.0 replay an images-only history rebuilt from
28
+ their ReplicaSet records, marked `complete: false`. The `--mock` fixture has a
29
+ history for each of its four workloads.
30
+
31
+ ### Changed
32
+ - **A capture needs `list` on `controllerrevisions`** (apps), for StatefulSet and
33
+ DaemonSet histories, and fails without it. The README's role includes it.
34
+
35
+ ### Documentation
36
+ - `get_replicaset_summaries` no longer says the newest replica set's age is always
37
+ when the deployment last changed: a rollback re-activates an older replica set
38
+ under a new revision number.
39
+
40
+ ## 2.3.0 — 2026-10-03
41
+
42
+ ### Added
43
+ - **`conditions_since` on nodes** (issue #9). `get_node_summaries` now reports,
44
+ for each node condition, the time since its status last changed (the API's
45
+ `lastTransitionTime`, which was dropped), next to the unchanged `conditions`
46
+ map. A condition with no transition time is left out. `print_node_summaries`
47
+ gains a READY-SINCE column.
48
+ - The tool description says what it does not show: a restart in which the node
49
+ comes back Ready without being marked NotReady or Unknown in between does not
50
+ reset it. On a minikube cluster stopped and started with its control plane,
51
+ `Ready` stayed "True since the node was created", 167 days, across a reboot.
52
+ It points to the node's `Starting`/`Rebooted` events and the start times of
53
+ kube-proxy and the control-plane containers for dating a node's last start.
54
+ - Captures store it as `conditions_since_seconds` and replay advances it like
55
+ every other age; captures from before 2.3.0 replay it as an empty map. (The
56
+ capture format had no handling for a map of durations: without it the values
57
+ would have been replayed as fixed spans that never advanced.)
58
+ - **`get_daemonset_summaries` tool** (issue #10), like `kubectl get daemonsets -o wide`:
59
+ desired, current, ready, up-to-date, available and misscheduled counts, the pod
60
+ template's node selector and images, update strategy, and age. Until now a
61
+ DaemonSet was visible only through its pods, whose `<name>-<5 chars>` names
62
+ can't be told apart from other names. Captures store DaemonSets; captures from
63
+ before 2.3.0 replay none. `print_daemonset_summaries` is its companion.
64
+ - **`owner` on pods.** `get_pod_summaries` reports each pod's controlling owner
65
+ as `"Kind/name"` (e.g. `"DaemonSet/otel-collector-agent"`), so pods can be
66
+ grouped into workloads without guessing from name suffixes. It is the direct
67
+ owner: a Deployment's pods name a replica set, whose `owner_deployment` names
68
+ the Deployment. `None` for a pod nobody manages; captures from before 2.3.0
69
+ replay `None`. `print_pod_summaries` gains an OWNER column.
70
+ - **The built-in `--mock` fixture shows the 2.3.0 fields** and now describes a
71
+ cluster that could exist. It gains an `otel-collector-agent` DaemonSet and its
72
+ pod, pod owners, node condition times, and event counts. Every workload now has
73
+ its pods (`test-deployment`'s three, and `postgres-0`, which a PVC already
74
+ named as mounting it), services select the pods they front, and the
75
+ `postgres` StatefulSet's headless service exists. It also fixes
76
+ contradictions in the old data that an agent would reason from:
77
+ - the `ad` pod ran its previous revision's image;
78
+ - its last instance's status said it ran 2 seconds while its log covered 2
79
+ minutes;
80
+ - its 93 restarts were more than its age allows at the 5-minute backoff cap
81
+ (now 67);
82
+ - several logs were dated 14 months before the capture;
83
+ - the namespaces were older than anything else in the cluster;
84
+ - `test-pod-123`'s spec declared one container while its status had two.
85
+
86
+ The `ad` container now shows the case `previous=True` is for: it restarted 58
87
+ seconds ago, so its current log is JVM startup and the OOM is only in the
88
+ previous one. A new test file keeps the fixture consistent.
89
+
90
+ ### Changed
91
+ - **A capture needs `list` on `daemonsets`** (apps). `k8s-capture-state` now
92
+ captures DaemonSets, so it fails where the cluster role does not grant them,
93
+ rather than writing a capture that replays as if there were none. Add
94
+ `daemonsets` to the role's apps resources (see README, "Permissions").
95
+
96
+ ### Documentation
97
+ - README lists the read-only RBAC rules that cover every tool.
98
+
6
99
  ## 2.2.1 — 2026-09-29
7
100
 
8
101
  ### Documentation
@@ -14,7 +14,7 @@ src/k8stools/
14
14
  mcp_server.py - MCP server (stdio or streamable-http transport)
15
15
  mcp_client.py - MCP client used for manual testing
16
16
  fixtures/
17
- otel-demo.json - Built-in capture served by `--mock` (real Minikube + OTel Demo)
17
+ otel-demo.json - Built-in capture served by `--mock` (hand-maintained, modeled on Minikube + OTel Demo)
18
18
  tests/
19
19
  test_k8s_tools.py - Unit tests for the original tools (mocked K8s API)
20
20
  test_new_tools.py - Unit tests for the 1.1.0 tools (ConfigMaps, CronJobs/Jobs, PVCs, StatefulSets, events, log enhancements)
@@ -26,6 +26,10 @@ tests/
26
26
  test_mock_tools.py - Tests for mock_tools module (incl. parity with k8s_tools.TOOLS)
27
27
  test_events.py - Event aggregation (count/first_seen), incl. the events.k8s.io fallback
28
28
  test_cluster_binding.py - Cluster selection: configure(), the shared binding, get_cluster_info, server flags
29
+ test_node_conditions.py - NodeSummary.conditions_since (condition transition times), incl. capture replay
30
+ test_workloads.py - get_daemonset_summaries and PodSummary.owner, incl. capture replay
31
+ test_builtin_fixture.py - The built-in fixture describes a cluster that could exist (owners, images, ages, log windows, histories)
32
+ test_workload_history.py - get_workload_history: template diffs, revisions, config refs, no env values or Secret reads, replay
29
33
  test_log_decoding.py - Log decoding at the API boundary (bytes vs str), `previous` plumbing, log redaction
30
34
  test_mcp_client.py - MCP client tests
31
35
  test_version.py - Asserts pyproject and package __version__ agree
@@ -224,6 +228,9 @@ Invariants worth preserving when touching this code:
224
228
  but read back unchanged. Replay tests that freeze the clock can't tell the two
225
229
  apart (frozen elapsed time is zero), so test a new duration field on an
226
230
  advancing clock with a `server_start_time` in the past.
231
+ A `dict[str, Duration]` (`NodeSummary.conditions_since`) is stored as a map
232
+ under `<field>_seconds` and each value advances (the codec looks through a
233
+ dict to its value type).
227
234
  - **`MockState` must match the real tool's documented behavior**, not just its
228
235
  types — filter semantics, result ordering (`get_replicaset_summaries` re-sorts
229
236
  at query time), and which failures raise vs. return empty.
@@ -235,7 +242,13 @@ Invariants worth preserving when touching this code:
235
242
  synthesizes answers for unknown pod names.
236
243
 
237
244
  The built-in fixture lives under `src/k8stools/` (not `tests/`) because
238
- `--mock` serves it, so it has to ship in the wheel.
245
+ `--mock` serves it, so it has to ship in the wheel. It is **hand-maintained**, not
246
+ a raw capture (it began as the pre-2.0 mock data, re-encoded): a real capture is
247
+ megabytes, and a test cluster has no CronJobs, Jobs or PVCs to show. When a tool
248
+ gains a field, add it to the fixture by hand, with values that agree with the
249
+ rest of it. `tests/test_builtin_fixture.py` checks that it does (owners exist and
250
+ run the pod's images, nothing predates its namespace, logs fall inside the
251
+ instance that wrote them, restarts fit the pod's age at the backoff cap).
239
252
 
240
253
  Captures are written **indented, and uncompressed by default**, on purpose: these
241
254
  files get re-captured and re-committed, and git deltas successive versions of
@@ -310,4 +323,4 @@ Example `mcp.json`:
310
323
 
311
324
  - Read-only tools first; state-modifying tools will be kept in a separate module when added
312
325
  - Quality over quantity: well-documented, strongly typed, useful for real monitoring/RCA agents
313
- - Mock data captured from a real Minikube instance running the OpenTelemetry Demo app
326
+ - Mock data modeled on a Minikube instance running the OpenTelemetry Demo app, kept internally consistent by tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: k8stools
3
- Version: 2.2.1
3
+ Version: 2.4.0
4
4
  Summary: Tools and an MCP server for building Kubernetes agents
5
5
  Project-URL: Homepage, https://github.com/BenedatLLC/k8stools
6
6
  Project-URL: Changelog, https://github.com/BenedatLLC/k8stools/blob/main/CHANGELOG.md
@@ -277,6 +277,33 @@ replayable file).
277
277
  What changed in each release is in the
278
278
  [changelog](https://github.com/BenedatLLC/k8stools/blob/main/CHANGELOG.md).
279
279
 
280
+ ### Permissions
281
+
282
+ The tools only read, with the `get` and `list` verbs. A read-only ClusterRole
283
+ that covers every tool:
284
+
285
+ ```yaml
286
+ apiVersion: rbac.authorization.k8s.io/v1
287
+ kind: ClusterRole
288
+ metadata:
289
+ name: k8stools-reader
290
+ rules:
291
+ - apiGroups: [""]
292
+ resources: [namespaces, nodes, pods, pods/log, events, services, configmaps,
293
+ persistentvolumeclaims]
294
+ verbs: [get, list]
295
+ - apiGroups: [apps]
296
+ resources: [deployments, replicasets, statefulsets, daemonsets, controllerrevisions]
297
+ verbs: [get, list]
298
+ - apiGroups: [batch]
299
+ resources: [jobs, cronjobs]
300
+ verbs: [get, list]
301
+ ```
302
+
303
+ `get_cluster_info` also reads the server version (`/version`), which every
304
+ authenticated user can read by default. No tool reads Secrets: `get_workload_history`
305
+ names the Secrets a workload uses, from its pod template, without reading them.
306
+
280
307
  ## Current tools
281
308
 
282
309
  These are the tools we define:
@@ -284,17 +311,19 @@ These are the tools we define:
284
311
  * `get_cluster_info` - which cluster the tools are answering from: kubeconfig context, API server URL and version, or the capture being replayed
285
312
  * `get_namespaces` - get a list of namespaces, like `kubectl get namespace`
286
313
  * `get_node_summaries` - get a list of nodes, like `kubectl get nodes -o wide` (includes capacity/allocatable/conditions/taints/labels)
287
- * `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`
314
+ * `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`, with each pod's controlling owner (e.g. `DaemonSet/otel-collector-agent`)
288
315
  * `get_pod_container_statuses` - return the status for each of the container in a pod
289
316
  * `get_pod_events` - return the events for a pod
290
317
  * `get_pod_spec` - retrieves the spec for a given pod
291
318
  * `get_logs_for_pod_and_container` - retrieves logs from a pod and container (supports `tail`, `since_seconds`, and `previous`). See [Previous-instance log semantics](#previous-instance-log-semantics) for when `previous=True` returns the same text as `previous=False` — it is Kubernetes behavior, not a bug.
292
319
  * `get_deployment_summaries` - get a list of deployments, like `kubectl get deployments`
293
320
  * `get_replicaset_summaries` - get a deployment's replica sets with their revision numbers and images. A deployment's replica sets are its change history: use this to see when a workload last changed and what the change was.
321
+ * `get_workload_history` - what changed in a Deployment, StatefulSet or DaemonSet, and when: each retained revision of its pod template compared with the one before (images, resources, probes, command/args, env var *names*, volumes, scheduling), plus the ConfigMaps and Secrets it references. It states what history cannot show.
294
322
  * `get_service_summaries` - get a list of services, like `kubectl get services` (includes `selector`/labels/annotations)
295
323
  * `get_configmap_summaries` - get a list of ConfigMaps, like `kubectl get configmaps`
296
324
  * `get_configmap` - retrieve the full contents of a single ConfigMap
297
325
  * `get_statefulset_summaries` - get a list of StatefulSets, like `kubectl get statefulsets`
326
+ * `get_daemonset_summaries` - get a list of DaemonSets, like `kubectl get daemonsets -o wide` (includes node selector and images)
298
327
  * `get_cronjob_summaries` - get a list of CronJobs, like `kubectl get cronjobs`
299
328
  * `get_job_summaries` - get a list of Jobs, like `kubectl get jobs`
300
329
  * `get_logs_for_job` - retrieve logs from a Job's most-recent pod
@@ -311,10 +340,12 @@ We also define a set of associated "print_" functions that are helpful in debugg
311
340
  * `print_pod_events`
312
341
  * `print_pod_spec`
313
342
  * `print_deployment_summaries`
343
+ * `print_workload_history`
314
344
  * `print_service_summaries`
315
345
  * `print_configmap_summaries`
316
346
  * `print_configmap`
317
347
  * `print_statefulset_summaries`
348
+ * `print_daemonset_summaries`
318
349
  * `print_cronjob_summaries`
319
350
  * `print_job_summaries`
320
351
  * `print_pvc_summaries`
@@ -501,11 +532,12 @@ data: {"jsonrpc":"2.0","id":1,"result":{"tools":[.... long text elided ...]}}
501
532
  ## Mock tools
502
533
  When building agents, it can be helpful to test them against *mock* versions that do
503
534
  not go against a real cluster, but return realistic values. The module
504
- `k8stools.mock_tools` does just that. The data values were captured when running
505
- against a real Minikube instance running the
535
+ `k8stools.mock_tools` does just that. The data is a small, hand-maintained
536
+ cluster modeled on a Minikube instance running the
506
537
  [Open Telemetry Demo](https://github.com/open-telemetry/opentelemetry-demo)
507
- application. When running the MCP server, this may be enabled by using the
508
- `--mock` command line option.
538
+ application: a crash-looping `ad` service mid-upgrade, a DaemonSet, a CronJob and
539
+ its Job, a StatefulSet with PVCs, and a few events. When running the MCP server,
540
+ this may be enabled by using the `--mock` command line option.
509
541
 
510
542
  The mock serves a *capture* — a JSON snapshot of one cluster — rather than a set of
511
543
  per-tool canned answers, so the tools agree with each other: a pod that is not in the
@@ -580,7 +612,7 @@ Everything the tools can read, so that every tool answers on replay:
580
612
  |---|---|
581
613
  | Cluster | context name, API server URL and version, so `get_cluster_info` answers on replay (not the kubeconfig path, which names a file on the capturing machine) |
582
614
  | Cluster-wide | namespaces, nodes |
583
- | Per namespace | deployments, replica sets, services, statefulsets, cronjobs, jobs, PVCs, events |
615
+ | Per namespace | deployments, replica sets, services, statefulsets, daemonsets, cronjobs, jobs, PVCs, events, and each workload's change history (`get_workload_history`'s result, so no env values are written) |
584
616
  | ConfigMaps | summary **and** full contents, so `get_configmap` works too |
585
617
  | Per pod | summary, labels, container statuses, spec, and per-container logs |
586
618
 
@@ -57,6 +57,33 @@ replayable file).
57
57
  What changed in each release is in the
58
58
  [changelog](https://github.com/BenedatLLC/k8stools/blob/main/CHANGELOG.md).
59
59
 
60
+ ### Permissions
61
+
62
+ The tools only read, with the `get` and `list` verbs. A read-only ClusterRole
63
+ that covers every tool:
64
+
65
+ ```yaml
66
+ apiVersion: rbac.authorization.k8s.io/v1
67
+ kind: ClusterRole
68
+ metadata:
69
+ name: k8stools-reader
70
+ rules:
71
+ - apiGroups: [""]
72
+ resources: [namespaces, nodes, pods, pods/log, events, services, configmaps,
73
+ persistentvolumeclaims]
74
+ verbs: [get, list]
75
+ - apiGroups: [apps]
76
+ resources: [deployments, replicasets, statefulsets, daemonsets, controllerrevisions]
77
+ verbs: [get, list]
78
+ - apiGroups: [batch]
79
+ resources: [jobs, cronjobs]
80
+ verbs: [get, list]
81
+ ```
82
+
83
+ `get_cluster_info` also reads the server version (`/version`), which every
84
+ authenticated user can read by default. No tool reads Secrets: `get_workload_history`
85
+ names the Secrets a workload uses, from its pod template, without reading them.
86
+
60
87
  ## Current tools
61
88
 
62
89
  These are the tools we define:
@@ -64,17 +91,19 @@ These are the tools we define:
64
91
  * `get_cluster_info` - which cluster the tools are answering from: kubeconfig context, API server URL and version, or the capture being replayed
65
92
  * `get_namespaces` - get a list of namespaces, like `kubectl get namespace`
66
93
  * `get_node_summaries` - get a list of nodes, like `kubectl get nodes -o wide` (includes capacity/allocatable/conditions/taints/labels)
67
- * `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`
94
+ * `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`, with each pod's controlling owner (e.g. `DaemonSet/otel-collector-agent`)
68
95
  * `get_pod_container_statuses` - return the status for each of the container in a pod
69
96
  * `get_pod_events` - return the events for a pod
70
97
  * `get_pod_spec` - retrieves the spec for a given pod
71
98
  * `get_logs_for_pod_and_container` - retrieves logs from a pod and container (supports `tail`, `since_seconds`, and `previous`). See [Previous-instance log semantics](#previous-instance-log-semantics) for when `previous=True` returns the same text as `previous=False` — it is Kubernetes behavior, not a bug.
72
99
  * `get_deployment_summaries` - get a list of deployments, like `kubectl get deployments`
73
100
  * `get_replicaset_summaries` - get a deployment's replica sets with their revision numbers and images. A deployment's replica sets are its change history: use this to see when a workload last changed and what the change was.
101
+ * `get_workload_history` - what changed in a Deployment, StatefulSet or DaemonSet, and when: each retained revision of its pod template compared with the one before (images, resources, probes, command/args, env var *names*, volumes, scheduling), plus the ConfigMaps and Secrets it references. It states what history cannot show.
74
102
  * `get_service_summaries` - get a list of services, like `kubectl get services` (includes `selector`/labels/annotations)
75
103
  * `get_configmap_summaries` - get a list of ConfigMaps, like `kubectl get configmaps`
76
104
  * `get_configmap` - retrieve the full contents of a single ConfigMap
77
105
  * `get_statefulset_summaries` - get a list of StatefulSets, like `kubectl get statefulsets`
106
+ * `get_daemonset_summaries` - get a list of DaemonSets, like `kubectl get daemonsets -o wide` (includes node selector and images)
78
107
  * `get_cronjob_summaries` - get a list of CronJobs, like `kubectl get cronjobs`
79
108
  * `get_job_summaries` - get a list of Jobs, like `kubectl get jobs`
80
109
  * `get_logs_for_job` - retrieve logs from a Job's most-recent pod
@@ -91,10 +120,12 @@ We also define a set of associated "print_" functions that are helpful in debugg
91
120
  * `print_pod_events`
92
121
  * `print_pod_spec`
93
122
  * `print_deployment_summaries`
123
+ * `print_workload_history`
94
124
  * `print_service_summaries`
95
125
  * `print_configmap_summaries`
96
126
  * `print_configmap`
97
127
  * `print_statefulset_summaries`
128
+ * `print_daemonset_summaries`
98
129
  * `print_cronjob_summaries`
99
130
  * `print_job_summaries`
100
131
  * `print_pvc_summaries`
@@ -281,11 +312,12 @@ data: {"jsonrpc":"2.0","id":1,"result":{"tools":[.... long text elided ...]}}
281
312
  ## Mock tools
282
313
  When building agents, it can be helpful to test them against *mock* versions that do
283
314
  not go against a real cluster, but return realistic values. The module
284
- `k8stools.mock_tools` does just that. The data values were captured when running
285
- against a real Minikube instance running the
315
+ `k8stools.mock_tools` does just that. The data is a small, hand-maintained
316
+ cluster modeled on a Minikube instance running the
286
317
  [Open Telemetry Demo](https://github.com/open-telemetry/opentelemetry-demo)
287
- application. When running the MCP server, this may be enabled by using the
288
- `--mock` command line option.
318
+ application: a crash-looping `ad` service mid-upgrade, a DaemonSet, a CronJob and
319
+ its Job, a StatefulSet with PVCs, and a few events. When running the MCP server,
320
+ this may be enabled by using the `--mock` command line option.
289
321
 
290
322
  The mock serves a *capture* — a JSON snapshot of one cluster — rather than a set of
291
323
  per-tool canned answers, so the tools agree with each other: a pod that is not in the
@@ -360,7 +392,7 @@ Everything the tools can read, so that every tool answers on replay:
360
392
  |---|---|
361
393
  | Cluster | context name, API server URL and version, so `get_cluster_info` answers on replay (not the kubeconfig path, which names a file on the capturing machine) |
362
394
  | Cluster-wide | namespaces, nodes |
363
- | Per namespace | deployments, replica sets, services, statefulsets, cronjobs, jobs, PVCs, events |
395
+ | Per namespace | deployments, replica sets, services, statefulsets, daemonsets, cronjobs, jobs, PVCs, events, and each workload's change history (`get_workload_history`'s result, so no env values are written) |
364
396
  | ConfigMaps | summary **and** full contents, so `get_configmap` works too |
365
397
  | Per pod | summary, labels, container statuses, spec, and per-container logs |
366
398
 
@@ -151,6 +151,8 @@ A single JSON file per capture. Top-level keys:
151
151
  "services": [ ... ],
152
152
  "configmaps": [ ... ],
153
153
  "statefulsets": [ ... ],
154
+ "daemonsets": [ ... ],
155
+ "workload_histories": [ ... ],
154
156
  "cronjobs": [ ... ],
155
157
  "jobs": [ ... ],
156
158
  "pvcs": [ ... ],
@@ -162,7 +164,12 @@ The `configmaps`..`events` keys were added alongside the 1.1.0 tool batch
162
164
  (ConfigMaps, StatefulSets, CronJobs/Jobs, PVCs, and cluster-wide events). See
163
165
  [Resource records added in 1.1.0](#resource-records-added-in-110) below for their
164
166
  shapes. `replicasets` was added alongside the 1.2.0 tool batch — see
165
- [Resource records added in 1.2.0](#resource-records-added-in-120).
167
+ [Resource records added in 1.2.0](#resource-records-added-in-120). `daemonsets` was added
168
+ in 2.3.0: `DaemonSetSummary` fields with `age` → `age_seconds`; captures without it
169
+ replay no DaemonSets. `workload_histories` was added in 2.4.0: one encoded
170
+ `WorkloadHistory` (the tool's result, not raw pod templates, so env values never
171
+ reach the file) per Deployment, StatefulSet and DaemonSet. Captures without it
172
+ replay an images-only history rebuilt from `replicasets`, with `complete: false`.
166
173
 
167
174
  `redacted` records which mode the capture was taken in — see
168
175
  [Redaction](#redaction). It exists so a consumer can tell without guessing;
@@ -448,6 +455,7 @@ class MockState:
448
455
  def get_configmap_summaries(self, namespace: str | None = None) -> list[ConfigMapSummary]: ...
449
456
  def get_configmap(self, name: str, namespace: str) -> dict[str, Any]: ...
450
457
  def get_statefulset_summaries(self, namespace: str | None = None) -> list[StatefulSetSummary]: ...
458
+ def get_daemonset_summaries(self, namespace: str | None = None) -> list[DaemonSetSummary]: ... # 2.3.0
451
459
  def get_cronjob_summaries(self, namespace: str | None = None) -> list[CronJobSummary]: ...
452
460
  def get_job_summaries(self, namespace: str | None = None) -> list[JobSummary]: ...
453
461
  def get_logs_for_job(self, job_name: str, namespace: str, container_name: str | None = None,
@@ -538,7 +546,7 @@ Behavior:
538
546
  (`configmaps`, `statefulsets`, `cronjobs`, `jobs`, `pvcs`), the 1.2.0 addition
539
547
  (`replicasets`), and a namespace/cluster-wide `get_events` sweep. If
540
548
  `--namespace` is given, only captures namespaced resources (pods/deployments/
541
- replicasets/services/configmaps/statefulsets/cronjobs/jobs/pvcs/events) in those
549
+ replicasets/services/configmaps/statefulsets/daemonsets/cronjobs/jobs/pvcs/events) in those
542
550
  namespaces; namespaces and nodes are always captured in full.
543
551
  3. For each pod and each container, calls `get_logs_for_pod_and_container` unless `--no-logs`. Default log cap: 1000 lines (override with `--max-log-lines`).
544
552
  4. For each container whose `restart_count > 0`, calls the same tool with
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "k8stools"
4
- version = "2.2.1"
4
+ version = "2.4.0"
5
5
  description = "Tools and an MCP server for building Kubernetes agents"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.12"
@@ -34,10 +34,12 @@ These are the tools we define:
34
34
  * get_logs_for_pod_and_container - retrieves logs from a pod and container (supports tail/since/previous)
35
35
  * get_deployment_summaries - get a list of deployments, like `kubectl get deployments`
36
36
  * get_replicaset_summaries - get a deployment's revision history, like `kubectl get replicasets`
37
+ * get_workload_history - a workload's pod-template revisions, compared, and the config it references
37
38
  * get_service_summaries - get a list of services, like `kubectl get services`
38
39
  * get_configmap_summaries - get a list of ConfigMaps, like `kubectl get configmaps`
39
40
  * get_configmap - retrieve the full contents of a single ConfigMap
40
41
  * get_statefulset_summaries - get a list of StatefulSets, like `kubectl get statefulsets`
42
+ * get_daemonset_summaries - get a list of DaemonSets, like `kubectl get daemonsets`
41
43
  * get_cronjob_summaries - get a list of CronJobs, like `kubectl get cronjobs`
42
44
  * get_job_summaries - get a list of Jobs, like `kubectl get jobs`
43
45
  * get_logs_for_job - retrieve logs from a Job's most-recent pod
@@ -55,10 +57,12 @@ We also define a set of associated "print_" functions that are helpful in debugg
55
57
  * print_pod_spec
56
58
  * print_deployment_summaries
57
59
  * print_replicaset_summaries
60
+ * print_workload_history
58
61
  * print_service_summaries
59
62
  * print_configmap_summaries
60
63
  * print_configmap
61
64
  * print_statefulset_summaries
65
+ * print_daemonset_summaries
62
66
  * print_cronjob_summaries
63
67
  * print_job_summaries
64
68
  * print_pvc_summaries
@@ -78,4 +82,4 @@ the built-in OTel Demo snapshot by default, or your own via load_mock_state() or
78
82
  the server's --state-file option.
79
83
  """
80
84
 
81
- __version__ = "2.2.1"
85
+ __version__ = "2.4.0"
@@ -185,6 +185,8 @@ def capture_state(namespaces: Optional[list[str]] = None,
185
185
  "services": [],
186
186
  "configmaps": [],
187
187
  "statefulsets": [],
188
+ "daemonsets": [],
189
+ "workload_histories": [],
188
190
  "cronjobs": [],
189
191
  "jobs": [],
190
192
  "pvcs": [],
@@ -192,10 +194,21 @@ def capture_state(namespaces: Optional[list[str]] = None,
192
194
  }
193
195
 
194
196
  for ns in target_namespaces:
195
- state["deployments"] += _encode_all(redactor(k8s_tools.get_deployment_summaries(ns)), captured_at)
197
+ deployments = k8s_tools.get_deployment_summaries(ns)
198
+ state["deployments"] += _encode_all(redactor(deployments), captured_at)
196
199
  state["replicasets"] += _encode_all(redactor(k8s_tools.get_replicaset_summaries(ns)), captured_at)
197
200
  state["services"] += _encode_all(redactor(k8s_tools.get_service_summaries(ns)), captured_at)
198
- state["statefulsets"] += _encode_all(redactor(k8s_tools.get_statefulset_summaries(ns)), captured_at)
201
+ statefulsets = k8s_tools.get_statefulset_summaries(ns)
202
+ state["statefulsets"] += _encode_all(redactor(statefulsets), captured_at)
203
+ daemonsets = k8s_tools.get_daemonset_summaries(ns)
204
+ state["daemonsets"] += _encode_all(redactor(daemonsets), captured_at)
205
+ # The tool's own result, not raw templates: replay serves it as is, and
206
+ # env values never reach the file.
207
+ for kind, workloads in (("Deployment", deployments), ("StatefulSet", statefulsets),
208
+ ("DaemonSet", daemonsets)):
209
+ for w in workloads:
210
+ state["workload_histories"].append(encode_model(
211
+ redactor(k8s_tools.get_workload_history(w.name, ns, kind)), captured_at))
199
212
  state["cronjobs"] += _encode_all(redactor(k8s_tools.get_cronjob_summaries(ns)), captured_at)
200
213
  state["jobs"] += _encode_all(redactor(k8s_tools.get_job_summaries(ns)), captured_at)
201
214
  state["pvcs"] += _encode_all(redactor(k8s_tools.get_pvc_summaries(ns)), captured_at)