k8stools 2.2.0__tar.gz → 2.2.1__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.
- {k8stools-2.2.0 → k8stools-2.2.1}/CHANGELOG.md +17 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/PKG-INFO +1 -1
- {k8stools-2.2.0 → k8stools-2.2.1}/pyproject.toml +1 -1
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/__init__.py +1 -1
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/k8s_tools.py +37 -1
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_events.py +14 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/uv.lock +1 -1
- {k8stools-2.2.0 → k8stools-2.2.1}/.claude/settings.local.json +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/.github/workflows/unit-tests.yml +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/.gitignore +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/.python-version +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/.vscode/mcp.json +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/.vscode/settings.json +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/CLAUDE.md +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/LICENSE +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/README.md +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/chat-with-cluster.png +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/designs/mock-state-capture.md +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/docs/ROADMAP.md +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/envrc.template +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/capture.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/fixtures/otel-demo.json +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/mcp_client.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/mcp_server.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/mock_state.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/mock_tools.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/src/k8stools/redaction.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/conftest.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_capture.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_cluster_binding.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_duration_format.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_k8s_tools.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_k8s_tools_realk8s.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_log_decoding.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_mcp_client.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_mock_state.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_mock_tools.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_new_tools.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_redaction.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_replicaset_tool.py +0 -0
- {k8stools-2.2.0 → k8stools-2.2.1}/tests/test_version.py +0 -0
|
@@ -3,6 +3,23 @@
|
|
|
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.2.1 — 2026-09-29
|
|
7
|
+
|
|
8
|
+
### Documentation
|
|
9
|
+
- **Event records can lag behind what they count** (follow-up to issue #8). The
|
|
10
|
+
kubelet counts every occurrence but, by default, writes at most one update per
|
|
11
|
+
object and event type every 5 minutes once a burst of 25 is used up (client-go's
|
|
12
|
+
event spam filter). A crash-looping pod's `Created` record read `last_seen`
|
|
13
|
+
29 minutes while the container had restarted 46 seconds earlier, then its count
|
|
14
|
+
jumped by six in one update. The `get_pod_events` and `get_events` descriptions
|
|
15
|
+
now say so: `count / (first_seen - last_seen)` still gives the rate, since both
|
|
16
|
+
lag together, but `last_seen` is not the time of the last restart (use the
|
|
17
|
+
container status or `PodSummary.last_restart`), and counts for different reasons
|
|
18
|
+
can differ by a few for the same restarts.
|
|
19
|
+
- `PodSummary.last_restart` is described as what it is: time since a container
|
|
20
|
+
last *terminated*. For a container waiting in CrashLoopBackOff, that is its last
|
|
21
|
+
crash, not a restart.
|
|
22
|
+
|
|
6
23
|
## 2.2.0 — 2026-09-28
|
|
7
24
|
|
|
8
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: k8stools
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
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
|
|
@@ -544,7 +544,11 @@ def get_pod_summaries(namespace: Optional[str] = None) -> list[PodSummary]:
|
|
|
544
544
|
restarts : int
|
|
545
545
|
Total number of restarts for all containers in the pod.
|
|
546
546
|
last_restart : Optional[datetime.timedelta]
|
|
547
|
-
Time since
|
|
547
|
+
Time since a container of this pod last terminated - the most
|
|
548
|
+
recent last_state.finished_at across its containers - or None if
|
|
549
|
+
none has. For a container waiting in CrashLoopBackOff this is its
|
|
550
|
+
last crash, not a restart: it has not been started again yet. Taken
|
|
551
|
+
from the kubelet's status, so it is current, unlike event records.
|
|
548
552
|
age : datetime.timedelta
|
|
549
553
|
Age of the pod (current time minus creation timestamp).
|
|
550
554
|
ip : Optional[str]
|
|
@@ -739,6 +743,22 @@ def get_pod_events(pod_name: str, namespace: str = "default") -> list[EventSumma
|
|
|
739
743
|
kubelet waits to restart, so its count is several times the number
|
|
740
744
|
of restarts. count divided by (first_seen - last_seen) is the
|
|
741
745
|
average rate over that window, not the current back-off.
|
|
746
|
+
|
|
747
|
+
A record can lag behind what it counts. By default the kubelet
|
|
748
|
+
writes at most one event update per object and event type
|
|
749
|
+
("Normal" or "Warning") every 5 minutes, once a burst of 25 is
|
|
750
|
+
used up; occurrences in between are counted but only written with
|
|
751
|
+
the next update, which then jumps by several at once. So count and
|
|
752
|
+
last_seen describe the most recent *written* occurrence and can
|
|
753
|
+
trail reality by several occurrences and tens of minutes. They lag
|
|
754
|
+
together, so the rate above still holds, but last_seen is not the
|
|
755
|
+
time of the last restart: for that, use the container status
|
|
756
|
+
(last_state.finished_at, state.started_at from
|
|
757
|
+
get_pod_container_statuses) or PodSummary.last_restart, which come
|
|
758
|
+
from the kubelet's status rather than from events. "Pulled",
|
|
759
|
+
"Created" and "Started" share one write budget, so their counts for
|
|
760
|
+
the same restarts can differ by a few; don't compare counts across
|
|
761
|
+
reasons.
|
|
742
762
|
type : str
|
|
743
763
|
Type of the event.
|
|
744
764
|
reason : str
|
|
@@ -2507,6 +2527,22 @@ def get_events(namespace: Optional[str] = None,
|
|
|
2507
2527
|
kubelet waits to restart, so its count is several times the number
|
|
2508
2528
|
of restarts. count divided by (first_seen - last_seen) is the
|
|
2509
2529
|
average rate over that window, not the current back-off.
|
|
2530
|
+
|
|
2531
|
+
A record can lag behind what it counts. By default the kubelet
|
|
2532
|
+
writes at most one event update per object and event type
|
|
2533
|
+
("Normal" or "Warning") every 5 minutes, once a burst of 25 is
|
|
2534
|
+
used up; occurrences in between are counted but only written with
|
|
2535
|
+
the next update, which then jumps by several at once. So count and
|
|
2536
|
+
last_seen describe the most recent *written* occurrence and can
|
|
2537
|
+
trail reality by several occurrences and tens of minutes. They lag
|
|
2538
|
+
together, so the rate above still holds, but last_seen is not the
|
|
2539
|
+
time of the last restart: for that, use the container status
|
|
2540
|
+
(last_state.finished_at, state.started_at from
|
|
2541
|
+
get_pod_container_statuses) or PodSummary.last_restart, which come
|
|
2542
|
+
from the kubelet's status rather than from events. "Pulled",
|
|
2543
|
+
"Created" and "Started" share one write budget, so their counts for
|
|
2544
|
+
the same restarts can differ by a few; don't compare counts across
|
|
2545
|
+
reasons.
|
|
2510
2546
|
type : str
|
|
2511
2547
|
Type of the event ("Normal" or "Warning").
|
|
2512
2548
|
reason : str
|
|
@@ -126,6 +126,20 @@ def test_docstrings_warn_that_backoff_counts_are_not_restarts():
|
|
|
126
126
|
assert "first_seen" in tool.__doc__ and "count" in tool.__doc__
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
def test_docstrings_warn_that_records_lag_behind_what_they_count():
|
|
130
|
+
"""client-go counts an occurrence before its spam filter decides whether to
|
|
131
|
+
write it, and the filter budgets writes per object and event type (burst 25,
|
|
132
|
+
then one per 5 minutes). On a real crash loop a Created record's last_seen
|
|
133
|
+
read 29 minutes while the container had restarted 46s earlier, then its
|
|
134
|
+
count jumped 595 -> 601. The docstring has to send agents to the container
|
|
135
|
+
status for the time of the last restart."""
|
|
136
|
+
for tool in (k8s_tools.get_pod_events, k8s_tools.get_events):
|
|
137
|
+
doc = tool.__doc__
|
|
138
|
+
assert "lag" in doc and "every 5 minutes" in doc
|
|
139
|
+
assert "last_state.finished_at" in doc and "PodSummary.last_restart" in doc
|
|
140
|
+
assert "don't compare counts across" in doc
|
|
141
|
+
|
|
142
|
+
|
|
129
143
|
# --- capture and replay --------------------------------------------------------
|
|
130
144
|
|
|
131
145
|
def _capture(events):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|