kube-query 0.1.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 (47) hide show
  1. kube_query-0.1.0/LICENSE +21 -0
  2. kube_query-0.1.0/PKG-INFO +160 -0
  3. kube_query-0.1.0/README.md +135 -0
  4. kube_query-0.1.0/pyproject.toml +75 -0
  5. kube_query-0.1.0/setup.cfg +4 -0
  6. kube_query-0.1.0/src/kube_query/__init__.py +3 -0
  7. kube_query-0.1.0/src/kube_query/access.py +85 -0
  8. kube_query-0.1.0/src/kube_query/cleanup.py +115 -0
  9. kube_query-0.1.0/src/kube_query/cli.py +81 -0
  10. kube_query-0.1.0/src/kube_query/cli_kit.py +175 -0
  11. kube_query-0.1.0/src/kube_query/commands/__init__.py +92 -0
  12. kube_query-0.1.0/src/kube_query/commands/_impl.py +147 -0
  13. kube_query-0.1.0/src/kube_query/commands/api_resources.py +66 -0
  14. kube_query-0.1.0/src/kube_query/commands/contexts.py +94 -0
  15. kube_query-0.1.0/src/kube_query/commands/describe.py +168 -0
  16. kube_query-0.1.0/src/kube_query/commands/eso_cmd.py +44 -0
  17. kube_query-0.1.0/src/kube_query/commands/events_cmd.py +67 -0
  18. kube_query-0.1.0/src/kube_query/commands/get.py +52 -0
  19. kube_query-0.1.0/src/kube_query/commands/groups.py +166 -0
  20. kube_query-0.1.0/src/kube_query/commands/helm_cmd.py +109 -0
  21. kube_query-0.1.0/src/kube_query/commands/logs.py +73 -0
  22. kube_query-0.1.0/src/kube_query/commands/overview.py +347 -0
  23. kube_query-0.1.0/src/kube_query/commands/tree_cmd.py +47 -0
  24. kube_query-0.1.0/src/kube_query/commands/version.py +57 -0
  25. kube_query-0.1.0/src/kube_query/config.py +210 -0
  26. kube_query-0.1.0/src/kube_query/discovery.py +191 -0
  27. kube_query-0.1.0/src/kube_query/errors.py +45 -0
  28. kube_query-0.1.0/src/kube_query/eso.py +81 -0
  29. kube_query-0.1.0/src/kube_query/events.py +104 -0
  30. kube_query-0.1.0/src/kube_query/helm.py +260 -0
  31. kube_query-0.1.0/src/kube_query/http.py +101 -0
  32. kube_query-0.1.0/src/kube_query/output.py +215 -0
  33. kube_query-0.1.0/src/kube_query/py.typed +0 -0
  34. kube_query-0.1.0/src/kube_query/summarize.py +391 -0
  35. kube_query-0.1.0/src/kube_query/tree.py +210 -0
  36. kube_query-0.1.0/src/kube_query.egg-info/PKG-INFO +160 -0
  37. kube_query-0.1.0/src/kube_query.egg-info/SOURCES.txt +45 -0
  38. kube_query-0.1.0/src/kube_query.egg-info/dependency_links.txt +1 -0
  39. kube_query-0.1.0/src/kube_query.egg-info/entry_points.txt +3 -0
  40. kube_query-0.1.0/src/kube_query.egg-info/requires.txt +7 -0
  41. kube_query-0.1.0/src/kube_query.egg-info/top_level.txt +1 -0
  42. kube_query-0.1.0/tests/test_cleanup.py +79 -0
  43. kube_query-0.1.0/tests/test_discovery.py +136 -0
  44. kube_query-0.1.0/tests/test_helm.py +101 -0
  45. kube_query-0.1.0/tests/test_output.py +55 -0
  46. kube_query-0.1.0/tests/test_smoke.py +450 -0
  47. kube_query-0.1.0/tests/test_summarize.py +119 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kube-query contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: kube-query
3
+ Version: 0.1.0
4
+ Summary: Read-only Kubernetes query CLI - compact YAML/JSON/rich/markdown output, built for humans and AI agents
5
+ License-Expression: MIT
6
+ Keywords: kubernetes,kubectl,cli,query,read-only,ai-agents,troubleshooting,helm,eks,external-secrets
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: System Administrators
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: System :: Clustering
14
+ Classifier: Topic :: System :: Systems Administration
15
+ Requires-Python: >=3.13
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: click>=8.1
19
+ Requires-Dist: kubernetes>=28.1.0
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: rich>=13.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # kube-query
27
+
28
+ **`kq` — read-only Kubernetes queries for humans and AI agents.**
29
+
30
+ kubectl is a loaded gun in an agent's hands. kube-query is the safe scope:
31
+ it **captures cluster state and diagnoses problems**, and *cannot* create,
32
+ update or delete anything — the HTTP layer implements `GET` and nothing else.
33
+ Compact YAML by default, with rich tables, markdown, JSON and ownership
34
+ trees on request. Built-ins, shortnames, CRDs, Helm releases and External
35
+ Secrets Operator — one small, fast binary-ish CLI.
36
+
37
+ ```
38
+ kq p l -A # all pods, all namespaces
39
+ kq p desc web-5d9c6 -n prod # status + reasons + restarts + events, one doc
40
+ kq logs web-5d9c6 -n prod -c app --previous
41
+ kq tree deploy/web -n prod # Deployment -> ReplicaSet -> Pods, health per node
42
+ kq overview # whole-cluster snapshot, problems first
43
+ ```
44
+
45
+ > kq is the **diagnosis** half of the loop. The **fix** half belongs to other
46
+ > tools (kubectl apply, gitops, helm) — by design.
47
+
48
+ ## Why
49
+
50
+ | Problem with `kubectl` for agents | kube-query |
51
+ |---|---|
52
+ | can `delete`/`scale`/`apply` — one hallucinated flag away from an incident | GET-only; no mutating code path exists |
53
+ | `-o yaml` dumps managedFields/uid/annotations noise into context | compact by default, `-v`/`-vv` for more |
54
+ | output shaped for terminals, not reasoning | yaml/json data, md reports, tree views |
55
+ | 100+ verbs to misuse | ~15 read verbs with aliases (`kq p l`, `kq d l`, `kq c l`) |
56
+ | helm/ESO/CRD troubleshooting needs extra tools | helm releases, ESO status and CRDs built in |
57
+
58
+ ## Install
59
+
60
+ Requires Python 3.13+.
61
+
62
+ ```bash
63
+ pipx install kube-query # or: uv tool install kube-query
64
+ kq ctx ls # verify — reads your existing kubeconfig
65
+ ```
66
+
67
+ From source:
68
+
69
+ ```bash
70
+ git clone <repo> && cd kube-query
71
+ make setup # .venv (py3.13) + dev deps
72
+ .venv/bin/kq overview
73
+ ```
74
+
75
+ No cluster to try it on? `make demo` boots a fake API server with broken
76
+ pods, a stuck deployment, helm releases and ESO resources to poke at.
77
+
78
+ ## Commands
79
+
80
+ | Command (aliases) | Purpose |
81
+ |---|---|
82
+ | `kq overview` (`ov`, `o`, `health`) | cluster snapshot: problem pods, nodes, deployments, PVCs, warnings |
83
+ | `kq pod` (`pods`, `p`, `po`) | `l [REGEX] [-A\|-n\|-l\|--field-selector]` · `NAME` · `desc NAME` · `logs NAME` |
84
+ | `kq deploy` (`deployments`, `dep`, `d`) | same shape for deployments |
85
+ | `kq ns` (`n`) · `kq node` (`no`) · `kq svc` · `kq cm` | namespaces, nodes, services, configmaps |
86
+ | `kq secret` (`sec`) | values hidden; `kq sec NAME --reveal` decodes (explicit opt-in) |
87
+ | `kq ev` (`events`, `e`) | events, newest first: `--type Warning --since 2h --for pod/NAME` |
88
+ | `kq get RESOURCE[/NAME]` (`g`) | any resource incl. CRDs; `kq api` lists all types |
89
+ | `kq describe` (`desc`) | diagnosis bundle: summary + object + children + events |
90
+ | `kq tree RESOURCE/NAME` (`t`) | ownership tree (`deploy/web`, `cronjob/x`, `node/y`) |
91
+ | `kq helm` (`h`) | releases read-only from Helm's release Secrets; `show NAME` adds values |
92
+ | `kq eso` | ExternalSecret sync status (`--stores` for SecretStores) |
93
+ | `kq ctx` (`c`) | kubeconfig contexts (`kq c l`), no API calls |
94
+ | `kq ver` | client + server version (which cluster am I on?) |
95
+
96
+ Shared flags: `-n NS` · `-A` · `-l SELECTOR` · `--field-selector` · `-f/--filter REGEX`
97
+ · `-o yaml|json|rich|md|tree` (default yaml, `KQ_OUTPUT` env) · `-v`/`-vv`.
98
+
99
+ ## Output
100
+
101
+ ```bash
102
+ kq overview -o rich # aligned, colored tables (humans)
103
+ kq p l -A -o md # markdown tables (tickets, chat, agent reports)
104
+ kq p web-5d9c6 -o json # machine-readable
105
+ kq d l -o tree # ownership view
106
+ ```
107
+
108
+ Stdout is always the payload (parseable); commentary goes to stderr as
109
+ `#`-prefixed notes. Exit codes: `0` ok, `1` error (one actionable sentence),
110
+ `2` usage.
111
+
112
+ ## For AI agents
113
+
114
+ Give your agent (Claude Code, opencode, codex, …) the one-file skill:
115
+ [`SKILL.md`](SKILL.md) — command map, the 5-move triage loop, output
116
+ contract, failure-signature table, and rules of engagement. Suggested
117
+ system-prompt line:
118
+
119
+ > Use `kq` (read-only) to inspect the Kubernetes cluster: start with
120
+ > `kq overview`, then `kq p desc <pod>`, `kq logs`, `kq tree`, `kq ev`.
121
+ > Never attempt cluster changes with kq; propose them for kubectl instead.
122
+
123
+ ## Documentation
124
+
125
+ | Doc | Contents |
126
+ |---|---|
127
+ | [docs/usage.md](docs/usage.md) | full manual: every command, flag, format, exit code |
128
+ | [docs/tutorial.md](docs/tutorial.md) | hands-on: debugging a broken deployment end-to-end |
129
+ | [docs/architecture.md](docs/architecture.md) | module map + data flow diagrams |
130
+ | [docs/extending.md](docs/extending.md) | add commands, columns, formats (with examples) |
131
+ | [docs/roadmap.md](docs/roadmap.md) | what's next + explicit non-goals |
132
+ | [CHANGELOG.md](CHANGELOG.md) | releases |
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ make setup # venv + deps
138
+ make check # the full gate: ruff lint + format check + pytest + build
139
+ make demo # fake-cluster playground
140
+ make tag # run the gate, then create v<version> tag
141
+ git push origin vX.Y.Z # CI publishes to PyPI (trusted publishing)
142
+ ```
143
+
144
+ Pipeline: `make check` mirrors CI exactly; pushing a `v*` tag runs the same
145
+ gate, builds sdist+wheel, verifies tag/version match, and publishes via
146
+ GitHub Actions to PyPI — no stored secrets (OIDC trusted publishing).
147
+
148
+ Tests are network-free: unit suites plus smoke tests that drive the real CLI
149
+ against an in-process fake Kubernetes API server.
150
+
151
+ ## Safety model
152
+
153
+ 1. `kube_query/http.py` exposes `raw_get` only — there is no verb parameter anywhere.
154
+ 2. `kube_query/access.py` builds list/get/log paths only.
155
+ 3. Secret values are redacted unless `--reveal` is passed explicitly.
156
+ 4. Your RBAC still applies — kq reads exactly what your credentials allow.
157
+
158
+ ## License
159
+
160
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,135 @@
1
+ # kube-query
2
+
3
+ **`kq` — read-only Kubernetes queries for humans and AI agents.**
4
+
5
+ kubectl is a loaded gun in an agent's hands. kube-query is the safe scope:
6
+ it **captures cluster state and diagnoses problems**, and *cannot* create,
7
+ update or delete anything — the HTTP layer implements `GET` and nothing else.
8
+ Compact YAML by default, with rich tables, markdown, JSON and ownership
9
+ trees on request. Built-ins, shortnames, CRDs, Helm releases and External
10
+ Secrets Operator — one small, fast binary-ish CLI.
11
+
12
+ ```
13
+ kq p l -A # all pods, all namespaces
14
+ kq p desc web-5d9c6 -n prod # status + reasons + restarts + events, one doc
15
+ kq logs web-5d9c6 -n prod -c app --previous
16
+ kq tree deploy/web -n prod # Deployment -> ReplicaSet -> Pods, health per node
17
+ kq overview # whole-cluster snapshot, problems first
18
+ ```
19
+
20
+ > kq is the **diagnosis** half of the loop. The **fix** half belongs to other
21
+ > tools (kubectl apply, gitops, helm) — by design.
22
+
23
+ ## Why
24
+
25
+ | Problem with `kubectl` for agents | kube-query |
26
+ |---|---|
27
+ | can `delete`/`scale`/`apply` — one hallucinated flag away from an incident | GET-only; no mutating code path exists |
28
+ | `-o yaml` dumps managedFields/uid/annotations noise into context | compact by default, `-v`/`-vv` for more |
29
+ | output shaped for terminals, not reasoning | yaml/json data, md reports, tree views |
30
+ | 100+ verbs to misuse | ~15 read verbs with aliases (`kq p l`, `kq d l`, `kq c l`) |
31
+ | helm/ESO/CRD troubleshooting needs extra tools | helm releases, ESO status and CRDs built in |
32
+
33
+ ## Install
34
+
35
+ Requires Python 3.13+.
36
+
37
+ ```bash
38
+ pipx install kube-query # or: uv tool install kube-query
39
+ kq ctx ls # verify — reads your existing kubeconfig
40
+ ```
41
+
42
+ From source:
43
+
44
+ ```bash
45
+ git clone <repo> && cd kube-query
46
+ make setup # .venv (py3.13) + dev deps
47
+ .venv/bin/kq overview
48
+ ```
49
+
50
+ No cluster to try it on? `make demo` boots a fake API server with broken
51
+ pods, a stuck deployment, helm releases and ESO resources to poke at.
52
+
53
+ ## Commands
54
+
55
+ | Command (aliases) | Purpose |
56
+ |---|---|
57
+ | `kq overview` (`ov`, `o`, `health`) | cluster snapshot: problem pods, nodes, deployments, PVCs, warnings |
58
+ | `kq pod` (`pods`, `p`, `po`) | `l [REGEX] [-A\|-n\|-l\|--field-selector]` · `NAME` · `desc NAME` · `logs NAME` |
59
+ | `kq deploy` (`deployments`, `dep`, `d`) | same shape for deployments |
60
+ | `kq ns` (`n`) · `kq node` (`no`) · `kq svc` · `kq cm` | namespaces, nodes, services, configmaps |
61
+ | `kq secret` (`sec`) | values hidden; `kq sec NAME --reveal` decodes (explicit opt-in) |
62
+ | `kq ev` (`events`, `e`) | events, newest first: `--type Warning --since 2h --for pod/NAME` |
63
+ | `kq get RESOURCE[/NAME]` (`g`) | any resource incl. CRDs; `kq api` lists all types |
64
+ | `kq describe` (`desc`) | diagnosis bundle: summary + object + children + events |
65
+ | `kq tree RESOURCE/NAME` (`t`) | ownership tree (`deploy/web`, `cronjob/x`, `node/y`) |
66
+ | `kq helm` (`h`) | releases read-only from Helm's release Secrets; `show NAME` adds values |
67
+ | `kq eso` | ExternalSecret sync status (`--stores` for SecretStores) |
68
+ | `kq ctx` (`c`) | kubeconfig contexts (`kq c l`), no API calls |
69
+ | `kq ver` | client + server version (which cluster am I on?) |
70
+
71
+ Shared flags: `-n NS` · `-A` · `-l SELECTOR` · `--field-selector` · `-f/--filter REGEX`
72
+ · `-o yaml|json|rich|md|tree` (default yaml, `KQ_OUTPUT` env) · `-v`/`-vv`.
73
+
74
+ ## Output
75
+
76
+ ```bash
77
+ kq overview -o rich # aligned, colored tables (humans)
78
+ kq p l -A -o md # markdown tables (tickets, chat, agent reports)
79
+ kq p web-5d9c6 -o json # machine-readable
80
+ kq d l -o tree # ownership view
81
+ ```
82
+
83
+ Stdout is always the payload (parseable); commentary goes to stderr as
84
+ `#`-prefixed notes. Exit codes: `0` ok, `1` error (one actionable sentence),
85
+ `2` usage.
86
+
87
+ ## For AI agents
88
+
89
+ Give your agent (Claude Code, opencode, codex, …) the one-file skill:
90
+ [`SKILL.md`](SKILL.md) — command map, the 5-move triage loop, output
91
+ contract, failure-signature table, and rules of engagement. Suggested
92
+ system-prompt line:
93
+
94
+ > Use `kq` (read-only) to inspect the Kubernetes cluster: start with
95
+ > `kq overview`, then `kq p desc <pod>`, `kq logs`, `kq tree`, `kq ev`.
96
+ > Never attempt cluster changes with kq; propose them for kubectl instead.
97
+
98
+ ## Documentation
99
+
100
+ | Doc | Contents |
101
+ |---|---|
102
+ | [docs/usage.md](docs/usage.md) | full manual: every command, flag, format, exit code |
103
+ | [docs/tutorial.md](docs/tutorial.md) | hands-on: debugging a broken deployment end-to-end |
104
+ | [docs/architecture.md](docs/architecture.md) | module map + data flow diagrams |
105
+ | [docs/extending.md](docs/extending.md) | add commands, columns, formats (with examples) |
106
+ | [docs/roadmap.md](docs/roadmap.md) | what's next + explicit non-goals |
107
+ | [CHANGELOG.md](CHANGELOG.md) | releases |
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ make setup # venv + deps
113
+ make check # the full gate: ruff lint + format check + pytest + build
114
+ make demo # fake-cluster playground
115
+ make tag # run the gate, then create v<version> tag
116
+ git push origin vX.Y.Z # CI publishes to PyPI (trusted publishing)
117
+ ```
118
+
119
+ Pipeline: `make check` mirrors CI exactly; pushing a `v*` tag runs the same
120
+ gate, builds sdist+wheel, verifies tag/version match, and publishes via
121
+ GitHub Actions to PyPI — no stored secrets (OIDC trusted publishing).
122
+
123
+ Tests are network-free: unit suites plus smoke tests that drive the real CLI
124
+ against an in-process fake Kubernetes API server.
125
+
126
+ ## Safety model
127
+
128
+ 1. `kube_query/http.py` exposes `raw_get` only — there is no verb parameter anywhere.
129
+ 2. `kube_query/access.py` builds list/get/log paths only.
130
+ 3. Secret values are redacted unless `--reveal` is passed explicitly.
131
+ 4. Your RBAC still applies — kq reads exactly what your credentials allow.
132
+
133
+ ## License
134
+
135
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kube-query"
7
+ version = "0.1.0"
8
+ description = "Read-only Kubernetes query CLI - compact YAML/JSON/rich/markdown output, built for humans and AI agents"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.13"
13
+ keywords = ["kubernetes", "kubectl", "cli", "query", "read-only", "ai-agents", "troubleshooting", "helm", "eks", "external-secrets"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: System Administrators",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: System :: Clustering",
22
+ "Topic :: System :: Systems Administration",
23
+ ]
24
+ dependencies = [
25
+ "click>=8.1",
26
+ "kubernetes>=28.1.0",
27
+ "pyyaml>=6.0",
28
+ "rich>=13.0",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = ["pytest>=8"]
33
+
34
+ [project.scripts]
35
+ kube-query = "kube_query.cli:main"
36
+ kq = "kube_query.cli:main"
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["src"]
40
+
41
+ [tool.setuptools.package-data]
42
+ kube_query = ["py.typed"]
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ addopts = "-q"
47
+
48
+ [tool.ruff]
49
+ line-length = 120
50
+ target-version = "py313"
51
+ src = ["src", "tests"]
52
+
53
+ [tool.ruff.lint]
54
+ select = [
55
+ "E", # pycodestyle errors
56
+ "W", # pycodestyle warnings
57
+ "F", # pyflakes
58
+ "I", # isort
59
+ "UP", # pyupgrade
60
+ "B", # flake8-bugbear
61
+ "C4", # flake8-comprehensions
62
+ "SIM", # flake8-simplify
63
+ "RUF", # ruff-specific
64
+ ]
65
+ ignore = [
66
+ "E501", # long lines handled by formatter
67
+ "SIM108", # ternary rewrites hurt readability here
68
+ "SIM105", # contextlib.suppress rewrites hurt readability
69
+ ]
70
+
71
+ [tool.ruff.lint.per-file-ignores]
72
+ "tests/*" = ["E402"] # conftest-style imports after sys.path tweaks
73
+
74
+ [tool.ruff.format]
75
+ quote-style = "double"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """kube-query - read-only Kubernetes query CLI for humans and AI agents."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,85 @@
1
+ """Object-level access: list/get any resource by its ApiResource, read pod logs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote
6
+
7
+ from .config import Ctx
8
+ from .discovery import ApiResource
9
+ from .errors import KqApiError, KqError
10
+ from .http import get_json, get_text
11
+
12
+
13
+ def resource_path(entry: ApiResource, namespace: str | None = None, name: str | None = None) -> str:
14
+ if entry.group:
15
+ base = f"/apis/{quote(entry.api_version, safe='/')}"
16
+ else:
17
+ base = f"/api/{entry.version}"
18
+ if entry.namespaced and namespace:
19
+ base += f"/namespaces/{quote(namespace, safe='')}"
20
+ base += f"/{entry.plural}"
21
+ if name:
22
+ base += f"/{quote(name, safe='')}"
23
+ return base
24
+
25
+
26
+ def list_objects(
27
+ ctx: Ctx,
28
+ entry: ApiResource,
29
+ *,
30
+ namespace: str | None = None,
31
+ label_selector: str | None = None,
32
+ field_selector: str | None = None,
33
+ ) -> list[dict]:
34
+ """List objects. ``namespace=None`` on a namespaced resource lists all namespaces."""
35
+ if not entry.namespaced and namespace:
36
+ namespace = None
37
+ params = {"labelSelector": label_selector, "fieldSelector": field_selector}
38
+ data = get_json(ctx.api_client, resource_path(entry, namespace), params)
39
+ items = data.get("items")
40
+ if not isinstance(items, list):
41
+ raise KqError(f"unexpected response while listing {entry.plural}")
42
+ return items
43
+
44
+
45
+ def get_object(ctx: Ctx, entry: ApiResource, name: str, *, namespace: str | None = None) -> dict:
46
+ if entry.namespaced and not namespace:
47
+ raise KqError(f"internal: namespace required to fetch a namespaced {entry.kind}")
48
+ try:
49
+ return get_json(ctx.api_client, resource_path(entry, namespace, name))
50
+ except KqApiError as e:
51
+ if e.status == 404:
52
+ where = f" in namespace '{namespace}'" if entry.namespaced else ""
53
+ scope = f" -n {namespace}" if entry.namespaced else ""
54
+ alt = " (or -A for all namespaces)" if entry.namespaced else ""
55
+ raise KqError(
56
+ f"{entry.kind} '{name}' not found{where}.\n"
57
+ f" Hint: list candidates with 'kq get {entry.plural}{scope}'{alt}."
58
+ ) from None
59
+ raise
60
+
61
+
62
+ def pod_logs(
63
+ ctx: Ctx,
64
+ namespace: str,
65
+ pod: str,
66
+ *,
67
+ container: str | None = None,
68
+ tail: int | None = None,
69
+ previous: bool = False,
70
+ since_seconds: int | None = None,
71
+ timestamps: bool = False,
72
+ ) -> str:
73
+ path = f"/api/v1/namespaces/{quote(namespace, safe='')}/pods/{quote(pod, safe='')}/log"
74
+ params = {
75
+ "container": container,
76
+ "tailLines": tail,
77
+ "previous": "true" if previous else None,
78
+ "sinceSeconds": since_seconds,
79
+ "timestamps": "true" if timestamps else None,
80
+ }
81
+ return get_text(ctx.api_client, path, params)
82
+
83
+
84
+ def server_version(ctx: Ctx) -> dict:
85
+ return get_json(ctx.api_client, "/version")
@@ -0,0 +1,115 @@
1
+ """Output compaction: strip agent-irrelevant noise from Kubernetes objects.
2
+
3
+ Verbosity levels::
4
+
5
+ 0 (default) drop managedFields, uid, resourceVersion, generation,
6
+ selfLink, noisy annotations, and all empty values
7
+ 1 (-v) drop managedFields and noisy annotations only
8
+ 2 (-vv) raw object, nothing is stripped
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import base64
14
+
15
+ NOISY_META = ("managedFields", "selfLink", "resourceVersion", "uid", "generation")
16
+ NOISY_ANNOTATIONS = (
17
+ "kubectl.kubernetes.io/last-applied-configuration",
18
+ "deployment.kubernetes.io/revision",
19
+ "deprecated.daemonset.template.generation",
20
+ )
21
+ _EMPTY = ("", {}, [], None)
22
+
23
+
24
+ def compact(obj: dict | list, verbose: int = 0) -> dict | list:
25
+ if verbose >= 2:
26
+ return obj
27
+ obj = _strip_noisy(obj)
28
+ if verbose == 0:
29
+ obj = _drop_empty(obj)
30
+ return obj
31
+
32
+
33
+ def _strip_noisy(node):
34
+ if isinstance(node, dict):
35
+ out = {}
36
+ for k, v in node.items():
37
+ if k == "metadata" and isinstance(v, dict):
38
+ v = {mk: mv for mk, mv in v.items() if mk != "managedFields"}
39
+ ann = v.get("annotations")
40
+ if isinstance(ann, dict):
41
+ ann = {a: b for a, b in ann.items() if a not in NOISY_ANNOTATIONS}
42
+ if ann:
43
+ v["annotations"] = ann
44
+ else:
45
+ v.pop("annotations", None)
46
+ out[k] = _strip_noisy(v)
47
+ continue
48
+ out[k] = _strip_noisy(v)
49
+ return out
50
+ if isinstance(node, list):
51
+ return [_strip_noisy(x) for x in node]
52
+ return node
53
+
54
+
55
+ def _drop_empty(node):
56
+ if isinstance(node, dict):
57
+ out = {}
58
+ for k, v in node.items():
59
+ if k == "metadata" and isinstance(v, dict):
60
+ v = {mk: mv for mk, mv in v.items() if mk not in NOISY_META}
61
+ v = _drop_empty(v)
62
+ if v in _EMPTY:
63
+ continue
64
+ out[k] = v
65
+ return out
66
+ if isinstance(node, list):
67
+ return [_drop_empty(x) for x in node]
68
+ return node
69
+
70
+
71
+ # -- display helpers for ConfigMaps / Secrets ------------------------------
72
+
73
+
74
+ def cap_map_values(obj: dict, limit: int = 2000) -> dict:
75
+ """Cap long string values in data maps (ConfigMap display) in place."""
76
+ for key in ("data", "stringData", "binaryData"):
77
+ m = obj.get(key)
78
+ if not isinstance(m, dict):
79
+ continue
80
+ for k, v in list(m.items()):
81
+ if isinstance(v, str) and len(v) > limit:
82
+ m[k] = v[:limit] + f" ...[truncated {len(v) - limit} of {len(v)} chars; -v to see full]"
83
+ return obj
84
+
85
+
86
+ def redact_map_values(obj: dict) -> dict:
87
+ """Hide secret values in place; keys and sizes stay visible."""
88
+ for key in ("data", "stringData", "binaryData"):
89
+ m = obj.get(key)
90
+ if not isinstance(m, dict):
91
+ continue
92
+ obj[key] = {k: (f"<{len(v)} chars hidden -- use --reveal>" if isinstance(v, str) else v) for k, v in m.items()}
93
+ return obj
94
+
95
+
96
+ def reveal_map_values(obj: dict) -> dict:
97
+ """Base64-decode secret data values in place (binary values stay encoded)."""
98
+ m = obj.get("data")
99
+ if isinstance(m, dict):
100
+ out = {}
101
+ for k, v in m.items():
102
+ if not isinstance(v, str):
103
+ out[k] = v
104
+ continue
105
+ try:
106
+ raw = base64.b64decode(v)
107
+ except Exception:
108
+ out[k] = v
109
+ continue
110
+ try:
111
+ out[k] = raw.decode("utf-8")
112
+ except UnicodeDecodeError:
113
+ out[k] = f"<{len(raw)} bytes binary; base64: {v[:100]}>"
114
+ obj["data"] = out
115
+ return obj
@@ -0,0 +1,81 @@
1
+ """kq — read-only Kubernetes queries for humans and AI agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from . import __version__
8
+ from .cli_kit import KqGroup
9
+ from .commands import register
10
+ from .config import Settings
11
+ from .output import FORMATS
12
+
13
+ EPILOG = """\
14
+ \b
15
+ USAGE PATTERNS
16
+ kq overview cluster snapshot: problems first
17
+ kq p l [-A|-n NS] [REGEX] list pods (d=deploy, n=ns, no=node,
18
+ kq p NAME / p desc NAME inspect sec=secret, cm, svc, ev)
19
+ kq logs NAME [-c C] [--tail N] pod logs kq tree deploy/NAME
20
+ kq get RESOURCE[/NAME] any resource incl. CRDs; kq api lists all
21
+ kq helm ls -A / kq eso -A helm releases / ExternalSecret status
22
+
23
+ \b
24
+ OPTIONS
25
+ -o yaml|json|rich|md|tree output (default: compact yaml)
26
+ -n NS | -A | -l SELECTOR | -f RX scope: namespace / all / labels / name regex
27
+ -v (system metadata) -vv (raw) verbosity
28
+ --ctx NAME / --kubeconfig PATH cluster selection
29
+
30
+ \b
31
+ ROLE
32
+ kq CAPTURES cluster state for diagnosis; fixing belongs to other tools
33
+ (kubectl apply, gitops, helm). kq only sends GET requests - it can
34
+ never create, update or delete anything.
35
+ """
36
+
37
+
38
+ @click.group(
39
+ cls=KqGroup,
40
+ context_settings={"help_option_names": ["-h", "--help"]},
41
+ )
42
+ @click.version_option(__version__, "--version", prog_name="kube-query")
43
+ @click.option("--ctx", "--context", "context", default=None, help="Kubeconfig context to use.")
44
+ @click.option("--kubeconfig", default=None, type=click.Path(exists=True), help="Path to a kubeconfig file.")
45
+ @click.option("-n", "--ns", "--namespace", "namespace", default=None, help="Default namespace for this invocation.")
46
+ @click.option(
47
+ "-A", "--all-namespaces", "all_namespaces", is_flag=True, default=False, help="All namespaces (where applicable)."
48
+ )
49
+ @click.option(
50
+ "-o",
51
+ "--output",
52
+ "output_fmt",
53
+ type=click.Choice(FORMATS),
54
+ default=None,
55
+ help="Output format: yaml, json, rich, md, tree. [env: KQ_OUTPUT]",
56
+ )
57
+ @click.option("-v", "--verbose", count=True, help="Verbosity: -v keep system metadata, -vv raw object.")
58
+ @click.pass_context
59
+ def main(ctx, context, kubeconfig, namespace, all_namespaces, output_fmt, verbose):
60
+ """kq - read-only Kubernetes queries for AI agents and humans.
61
+
62
+ Compact yaml by default; rich tables, markdown, json and ownership
63
+ trees on request. Built-ins, shortnames, CRDs, Helm releases and
64
+ External Secrets Operator. Start with: kq overview
65
+ """
66
+ ctx.obj = Settings(
67
+ context=context,
68
+ kubeconfig=kubeconfig,
69
+ namespace=namespace,
70
+ output=output_fmt,
71
+ verbose=verbose,
72
+ all_namespaces=all_namespaces,
73
+ )
74
+
75
+
76
+ main.epilog = EPILOG
77
+ register(main)
78
+
79
+
80
+ if __name__ == "__main__":
81
+ main()