kx-cli 0.0.1__tar.gz → 0.0.3__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 (57) hide show
  1. kx_cli-0.0.3/LICENSE +21 -0
  2. kx_cli-0.0.3/PKG-INFO +117 -0
  3. kx_cli-0.0.3/README.md +100 -0
  4. kx_cli-0.0.3/pyproject.toml +30 -0
  5. kx_cli-0.0.3/src/kx/commands/delete.py +22 -0
  6. kx_cli-0.0.3/src/kx/commands/describe.py +18 -0
  7. kx_cli-0.0.3/src/kx/commands/edit.py +12 -0
  8. kx_cli-0.0.3/src/kx/commands/events.py +26 -0
  9. kx_cli-0.0.3/src/kx/commands/exec.py +28 -0
  10. kx_cli-0.0.3/src/kx/commands/get.py +46 -0
  11. kx_cli-0.0.3/src/kx/commands/logs.py +42 -0
  12. kx_cli-0.0.3/src/kx/commands/port_forward.py +26 -0
  13. kx_cli-0.0.3/src/kx/commands/state.py +13 -0
  14. kx_cli-0.0.3/src/kx/commands/tree.py +34 -0
  15. kx_cli-0.0.3/src/kx/commands/yaml.py +12 -0
  16. kx_cli-0.0.3/src/kx/console.py +281 -0
  17. kx_cli-0.0.3/src/kx/events.py +23 -0
  18. kx_cli-0.0.3/src/kx/graph.py +231 -0
  19. kx_cli-0.0.3/src/kx/index.py +99 -0
  20. kx_cli-0.0.3/src/kx/k8s.py +8 -0
  21. kx_cli-0.0.3/src/kx/kinds.py +92 -0
  22. kx_cli-0.0.3/src/kx/kubectl.py +34 -0
  23. kx_cli-0.0.3/src/kx/main.py +252 -0
  24. kx_cli-0.0.3/src/kx/state.py +38 -0
  25. kx_cli-0.0.3/src/kx/types.py +7 -0
  26. kx_cli-0.0.3/src/kx_cli.egg-info/PKG-INFO +117 -0
  27. kx_cli-0.0.3/src/kx_cli.egg-info/SOURCES.txt +43 -0
  28. kx_cli-0.0.3/src/kx_cli.egg-info/requires.txt +9 -0
  29. kx_cli-0.0.3/tests/test_cli_get.py +79 -0
  30. kx_cli-0.0.3/tests/test_console.py +158 -0
  31. kx_cli-0.0.3/tests/test_describe.py +46 -0
  32. kx_cli-0.0.3/tests/test_edit.py +38 -0
  33. kx_cli-0.0.3/tests/test_exec.py +61 -0
  34. kx_cli-0.0.3/tests/test_get.py +135 -0
  35. kx_cli-0.0.3/tests/test_index.py +154 -0
  36. kx_cli-0.0.3/tests/test_kinds.py +94 -0
  37. kx_cli-0.0.3/tests/test_logs.py +159 -0
  38. kx_cli-0.0.3/tests/test_port_forward.py +65 -0
  39. kx_cli-0.0.3/tests/test_state.py +102 -0
  40. kx_cli-0.0.1/PKG-INFO +0 -7
  41. kx_cli-0.0.1/pyproject.toml +0 -16
  42. kx_cli-0.0.1/src/kx/events.py +0 -43
  43. kx_cli-0.0.1/src/kx/graph.py +0 -89
  44. kx_cli-0.0.1/src/kx/index.py +0 -56
  45. kx_cli-0.0.1/src/kx/k8s.py +0 -7
  46. kx_cli-0.0.1/src/kx/kubectl.py +0 -16
  47. kx_cli-0.0.1/src/kx/main.py +0 -166
  48. kx_cli-0.0.1/src/kx/state.py +0 -23
  49. kx_cli-0.0.1/src/kx_cli.egg-info/PKG-INFO +0 -7
  50. kx_cli-0.0.1/src/kx_cli.egg-info/SOURCES.txt +0 -16
  51. kx_cli-0.0.1/src/kx_cli.egg-info/requires.txt +0 -3
  52. {kx_cli-0.0.1 → kx_cli-0.0.3}/setup.cfg +0 -0
  53. {kx_cli-0.0.1 → kx_cli-0.0.3}/src/kx/__init__.py +0 -0
  54. /kx_cli-0.0.1/README.md → /kx_cli-0.0.3/src/kx/commands/__init__.py +0 -0
  55. {kx_cli-0.0.1 → kx_cli-0.0.3}/src/kx_cli.egg-info/dependency_links.txt +0 -0
  56. {kx_cli-0.0.1 → kx_cli-0.0.3}/src/kx_cli.egg-info/entry_points.txt +0 -0
  57. {kx_cli-0.0.1 → kx_cli-0.0.3}/src/kx_cli.egg-info/top_level.txt +0 -0
kx_cli-0.0.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joshua Alexander Zillwood
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.
kx_cli-0.0.3/PKG-INFO ADDED
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: kx-cli
3
+ Version: 0.0.3
4
+ Summary: kubectl wrapper with index-based resource selection
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: typer==0.25.1
9
+ Requires-Dist: click
10
+ Requires-Dist: kubernetes
11
+ Requires-Dist: rich
12
+ Provides-Extra: dev
13
+ Requires-Dist: ruff; extra == "dev"
14
+ Requires-Dist: pre-commit; extra == "dev"
15
+ Requires-Dist: pytest; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ <div align="center">
19
+
20
+ ```
21
+ ██╗ ██╗██╗ ██╗
22
+ ██║ ██╔╝╚██╗██╔╝
23
+ █████╔╝ ╚███╔╝
24
+ ██╔═██╗ ██╔██╗
25
+ ██║ ██╗██╔╝ ██╗
26
+ ╚═╝ ╚═╝╚═╝ ╚═╝
27
+ ```
28
+
29
+ **kubectl, indexed.**
30
+
31
+ </div>
32
+
33
+ `kx` is a kubectl wrapper that adds index-based resource selection. Run `kx get <resource>` once, then reference any result by number instead of typing full resource names.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install kx-cli
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ### List resources
44
+
45
+ ```
46
+ kx get <resource> [--match|-m <substring>] [kubectl flags...]
47
+ ```
48
+
49
+ Fetches resources and assigns index numbers. Any extra flags (e.g. `-n <namespace>`, `-A`) are passed through to kubectl. Use `--match`/`-m` to filter results by name (substring, case-insensitive).
50
+
51
+ ```
52
+ $ kx get pods
53
+ X NAME READY STATUS RESTARTS AGE
54
+ 1 api-7d9f4b8c6-xkp2q 1/1 Running 0 2d
55
+ 2 worker-6c8b5f7d9-mnt4r 1/1 Running 3 5h
56
+ 3 postgres-0 1/1 Running 0 12d
57
+ ```
58
+
59
+ All subsequent commands reference resources by their `X` index from the last `kx get`.
60
+
61
+ ### Commands
62
+
63
+ | Command | Description |
64
+ |---|---|
65
+ | `kx get <resource> [--match\|-m <str>] [kubectl flags...]` | List resources with index numbers; optionally filter by name substring |
66
+ | `kx describe <index> [kubectl flags...]` | Show `kubectl describe` output for an indexed resource |
67
+ | `kx events <index>` | Show Kubernetes events for the resource |
68
+ | `kx logs <index> [kubectl flags...]` | Stream logs; aggregates across pods for Deployments, StatefulSets, DaemonSets, and Services |
69
+ | `kx yaml <index>` | Print the raw YAML manifest |
70
+ | `kx exec <index> [cmd] [kubectl flags...]` | Open an interactive shell in a pod (bash → sh fallback); pass a custom command with `cmd` |
71
+ | `kx edit <index> [kubectl flags...]` | Open the resource in your editor via `kubectl edit` |
72
+ | `kx delete <index> [-y]` | Delete the resource (prompts for confirmation; `-y` skips it) |
73
+ | `kx tree <index> [--index\|-i]` | Show the ownership graph for a resource; `--index` assigns indexes to tree nodes |
74
+ | `kx port-forward <index> <port> [kubectl flags...]` | Forward a local port to a resource (supports Pod, Deployment, ReplicaSet, StatefulSet, DaemonSet, Service) |
75
+ | `kx state` | Show the current state (namespace and indexed resources from the last `kx get`) |
76
+
77
+ ### Example workflow
78
+
79
+ ```bash
80
+ # list deployments, pick index 2
81
+ kx get deployments
82
+ kx describe 2
83
+
84
+ # check events on that deployment
85
+ kx events 2
86
+
87
+ # drill into a pod
88
+ kx get pods
89
+ kx logs 1
90
+ kx exec 1 # opens bash/sh
91
+ kx exec 1 -- env # run a specific command
92
+
93
+ # forward local port 8080 to port 80 on a service
94
+ kx get services
95
+ kx port-forward 2 8080:80
96
+
97
+ # clean up
98
+ kx delete 3
99
+ ```
100
+
101
+ ## State
102
+
103
+ `kx` saves the last `get` result to `~/.kx_state.json`. Index-based commands read from this file, so switching namespaces or resource types requires a new `kx get`.
104
+
105
+ ## Development
106
+
107
+ ```bash
108
+ python -m venv .venv
109
+ source .venv/bin/activate
110
+ pip install -e ".[dev]"
111
+ ```
112
+
113
+ Run the CLI directly:
114
+
115
+ ```bash
116
+ python -m kx.main --help
117
+ ```
kx_cli-0.0.3/README.md ADDED
@@ -0,0 +1,100 @@
1
+ <div align="center">
2
+
3
+ ```
4
+ ██╗ ██╗██╗ ██╗
5
+ ██║ ██╔╝╚██╗██╔╝
6
+ █████╔╝ ╚███╔╝
7
+ ██╔═██╗ ██╔██╗
8
+ ██║ ██╗██╔╝ ██╗
9
+ ╚═╝ ╚═╝╚═╝ ╚═╝
10
+ ```
11
+
12
+ **kubectl, indexed.**
13
+
14
+ </div>
15
+
16
+ `kx` is a kubectl wrapper that adds index-based resource selection. Run `kx get <resource>` once, then reference any result by number instead of typing full resource names.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install kx-cli
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### List resources
27
+
28
+ ```
29
+ kx get <resource> [--match|-m <substring>] [kubectl flags...]
30
+ ```
31
+
32
+ Fetches resources and assigns index numbers. Any extra flags (e.g. `-n <namespace>`, `-A`) are passed through to kubectl. Use `--match`/`-m` to filter results by name (substring, case-insensitive).
33
+
34
+ ```
35
+ $ kx get pods
36
+ X NAME READY STATUS RESTARTS AGE
37
+ 1 api-7d9f4b8c6-xkp2q 1/1 Running 0 2d
38
+ 2 worker-6c8b5f7d9-mnt4r 1/1 Running 3 5h
39
+ 3 postgres-0 1/1 Running 0 12d
40
+ ```
41
+
42
+ All subsequent commands reference resources by their `X` index from the last `kx get`.
43
+
44
+ ### Commands
45
+
46
+ | Command | Description |
47
+ |---|---|
48
+ | `kx get <resource> [--match\|-m <str>] [kubectl flags...]` | List resources with index numbers; optionally filter by name substring |
49
+ | `kx describe <index> [kubectl flags...]` | Show `kubectl describe` output for an indexed resource |
50
+ | `kx events <index>` | Show Kubernetes events for the resource |
51
+ | `kx logs <index> [kubectl flags...]` | Stream logs; aggregates across pods for Deployments, StatefulSets, DaemonSets, and Services |
52
+ | `kx yaml <index>` | Print the raw YAML manifest |
53
+ | `kx exec <index> [cmd] [kubectl flags...]` | Open an interactive shell in a pod (bash → sh fallback); pass a custom command with `cmd` |
54
+ | `kx edit <index> [kubectl flags...]` | Open the resource in your editor via `kubectl edit` |
55
+ | `kx delete <index> [-y]` | Delete the resource (prompts for confirmation; `-y` skips it) |
56
+ | `kx tree <index> [--index\|-i]` | Show the ownership graph for a resource; `--index` assigns indexes to tree nodes |
57
+ | `kx port-forward <index> <port> [kubectl flags...]` | Forward a local port to a resource (supports Pod, Deployment, ReplicaSet, StatefulSet, DaemonSet, Service) |
58
+ | `kx state` | Show the current state (namespace and indexed resources from the last `kx get`) |
59
+
60
+ ### Example workflow
61
+
62
+ ```bash
63
+ # list deployments, pick index 2
64
+ kx get deployments
65
+ kx describe 2
66
+
67
+ # check events on that deployment
68
+ kx events 2
69
+
70
+ # drill into a pod
71
+ kx get pods
72
+ kx logs 1
73
+ kx exec 1 # opens bash/sh
74
+ kx exec 1 -- env # run a specific command
75
+
76
+ # forward local port 8080 to port 80 on a service
77
+ kx get services
78
+ kx port-forward 2 8080:80
79
+
80
+ # clean up
81
+ kx delete 3
82
+ ```
83
+
84
+ ## State
85
+
86
+ `kx` saves the last `get` result to `~/.kx_state.json`. Index-based commands read from this file, so switching namespaces or resource types requires a new `kx get`.
87
+
88
+ ## Development
89
+
90
+ ```bash
91
+ python -m venv .venv
92
+ source .venv/bin/activate
93
+ pip install -e ".[dev]"
94
+ ```
95
+
96
+ Run the CLI directly:
97
+
98
+ ```bash
99
+ python -m kx.main --help
100
+ ```
@@ -0,0 +1,30 @@
1
+ [project]
2
+ name = "kx-cli"
3
+ version = "0.0.3"
4
+ description = "kubectl wrapper with index-based resource selection"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "typer==0.25.1",
9
+ "click",
10
+ "kubernetes",
11
+ "rich",
12
+ ]
13
+
14
+ [project.optional-dependencies]
15
+ dev = [
16
+ "ruff",
17
+ "pre-commit",
18
+ "pytest",
19
+ ]
20
+
21
+ [project.scripts]
22
+ kx = "kx.main:app"
23
+
24
+ [build-system]
25
+ requires = ["setuptools>=61"]
26
+ build-backend = "setuptools.build_meta"
27
+
28
+ [tool.pytest.ini_options]
29
+ testpaths = ["tests"]
30
+ pythonpath = ["src"]
@@ -0,0 +1,22 @@
1
+ from kx.kubectl import KubectlServiceProtocol
2
+ from kx.state import StateServiceProtocol
3
+ from kx.types import Confirm
4
+
5
+
6
+ class DeleteCommand:
7
+ def __init__(
8
+ self,
9
+ state: StateServiceProtocol,
10
+ kubectl: KubectlServiceProtocol,
11
+ confirm: Confirm,
12
+ ):
13
+ self.state = state
14
+ self.kubectl = kubectl
15
+ self.confirm = confirm
16
+
17
+ def execute(self, index: int, yes: bool) -> str:
18
+ name, namespace, kind = self.state.fields(index)
19
+ if not yes:
20
+ self.confirm(f"Delete {kind}/{name} in {namespace}?")
21
+ self.kubectl.run(["delete", kind, name, "-n", namespace])
22
+ return f"Deleted {kind}/{name}"
@@ -0,0 +1,18 @@
1
+ from kx.kubectl import KubectlServiceProtocol
2
+ from kx.state import StateServiceProtocol
3
+
4
+
5
+ class DescribeCommand:
6
+ def __init__(
7
+ self,
8
+ state: StateServiceProtocol,
9
+ kubectl: KubectlServiceProtocol,
10
+ ):
11
+ self.state = state
12
+ self.kubectl = kubectl
13
+
14
+ def execute(self, index: int, extra_args: list[str] = []) -> None:
15
+ name, namespace, kind = self.state.fields(index)
16
+ self.kubectl.run_interactive(
17
+ ["describe", kind, name, "-n", namespace, *extra_args]
18
+ )
@@ -0,0 +1,12 @@
1
+ from kx.kubectl import KubectlServiceProtocol
2
+ from kx.state import StateServiceProtocol
3
+
4
+
5
+ class EditCommand:
6
+ def __init__(self, state: StateServiceProtocol, kubectl: KubectlServiceProtocol):
7
+ self.state = state
8
+ self.kubectl = kubectl
9
+
10
+ def execute(self, index: int, extra_args: list[str] = []) -> None:
11
+ name, namespace, kind = self.state.fields(index)
12
+ self.kubectl.run_interactive(["edit", kind, name, "-n", namespace, *extra_args])
@@ -0,0 +1,26 @@
1
+ from kx.events import EventsServiceProtocol
2
+ from kx.state import StateServiceProtocol
3
+
4
+
5
+ class EventsCommand:
6
+ def __init__(self, state: StateServiceProtocol, events: EventsServiceProtocol):
7
+ self.state = state
8
+ self.events = events
9
+
10
+ def execute(self, index: int) -> str:
11
+ name, namespace, kind = self.state.fields(index)
12
+ all_events = self.events.get(namespace)
13
+ filtered = self.events.filter(all_events, name, kind)
14
+
15
+ if not filtered:
16
+ return "No events found"
17
+
18
+ output = []
19
+ for event in filtered:
20
+ obj = event.involved_object
21
+ output.append(
22
+ f"{event.type:8} {event.reason:30} "
23
+ f"{obj.kind:10} {event.metadata.creation_timestamp} "
24
+ f"{event.message}"
25
+ )
26
+ return "\n".join(output)
@@ -0,0 +1,28 @@
1
+ from kx.kinds import Kind
2
+ from kx.kubectl import KubectlServiceProtocol
3
+ from kx.state import StateServiceProtocol
4
+
5
+
6
+ class ExecCommand:
7
+ def __init__(self, state: StateServiceProtocol, kubectl: KubectlServiceProtocol):
8
+ self.state = state
9
+ self.kubectl = kubectl
10
+
11
+ def execute(
12
+ self, index: int, cmd: list[str] | None, extra_args: list[str] = []
13
+ ) -> None:
14
+ name, namespace, kind = self.state.fields(index)
15
+ if kind != Kind.Pod:
16
+ raise ValueError("exec is only supported for pods.")
17
+ if cmd:
18
+ self.kubectl.run_interactive(
19
+ ["exec", "-it", name, "-n", namespace, *extra_args, "--", *cmd]
20
+ )
21
+ else:
22
+ rc = self.kubectl.run_interactive(
23
+ ["exec", "-it", name, "-n", namespace, *extra_args, "--", "bash"]
24
+ )
25
+ if rc != 0:
26
+ self.kubectl.run_interactive(
27
+ ["exec", "-it", name, "-n", namespace, *extra_args, "--", "sh"]
28
+ )
@@ -0,0 +1,46 @@
1
+ from kx.index import IndexServiceProtocol
2
+ from kx.kinds import normalize_kind
3
+ from kx.kubectl import KubectlServiceProtocol
4
+ from kx.state import State, StateServiceProtocol
5
+
6
+
7
+ def _extract_namespace(extra_args: list[str]) -> str | None:
8
+ for index, arg in enumerate(extra_args):
9
+ if arg in ("-n", "--namespace") and index + 1 < len(extra_args):
10
+ return extra_args[index + 1]
11
+ if arg.startswith("--namespace="):
12
+ return arg.split("=", 1)[1]
13
+ return None
14
+
15
+
16
+ class GetCommand:
17
+ def __init__(
18
+ self,
19
+ kubectl: KubectlServiceProtocol,
20
+ state: StateServiceProtocol,
21
+ index: IndexServiceProtocol,
22
+ ):
23
+ self.kubectl = kubectl
24
+ self.state = state
25
+ self.index = index
26
+
27
+ def execute(
28
+ self,
29
+ resource: str,
30
+ filter_term: str | None = None,
31
+ extra_args: list[str] = [],
32
+ ) -> str:
33
+ output = self.kubectl.run(["get", resource, *extra_args])
34
+ if filter_term:
35
+ output = self.index.filter(output, filter_term)
36
+ indexed_output, names = self.index.add(output)
37
+ all_namespaces = any(arg in ("-A", "--all-namespaces") for arg in extra_args)
38
+ if names and not all_namespaces:
39
+ namespace = (
40
+ _extract_namespace(extra_args) or self.kubectl.current_namespace()
41
+ )
42
+ kind = normalize_kind(resource)
43
+ self.state.save(
44
+ State(resources={name: kind for name in names}, namespace=namespace)
45
+ )
46
+ return indexed_output
@@ -0,0 +1,42 @@
1
+ import json
2
+
3
+ from kx.kinds import Kind
4
+ from kx.kubectl import KubectlServiceProtocol
5
+ from kx.state import StateServiceProtocol
6
+
7
+ _AGGREGATE_KINDS = {Kind.Deployment, Kind.StatefulSet, Kind.DaemonSet, Kind.Service}
8
+
9
+
10
+ class LogsCommand:
11
+ def __init__(self, state: StateServiceProtocol, kubectl: KubectlServiceProtocol):
12
+ self.state = state
13
+ self.kubectl = kubectl
14
+
15
+ def execute(self, index: int, extra_args: list[str] = []) -> None:
16
+ name, namespace, kind = self.state.fields(index)
17
+ if kind == Kind.Pod:
18
+ self.kubectl.run_interactive(["logs", name, "-n", namespace, *extra_args])
19
+ elif kind in _AGGREGATE_KINDS:
20
+ selector = self._selector(name, namespace, kind)
21
+ self.kubectl.run_interactive(
22
+ ["logs", "-l", selector, "--prefix=true", "-n", namespace, *extra_args]
23
+ )
24
+ else:
25
+ raise ValueError(f"Logs are not supported for '{kind}'.")
26
+
27
+ def _selector(self, name: str, namespace: str, kind: str) -> str:
28
+ raw = self.kubectl.run(["get", kind, name, "-n", namespace, "-o", "json"])
29
+ obj = json.loads(raw)
30
+ labels = self._extract_labels(obj, kind)
31
+ if not labels:
32
+ raise ValueError(
33
+ f"{kind}/{name} has no pod selector; cannot aggregate logs."
34
+ )
35
+ return ",".join(f"{k}={v}" for k, v in labels.items())
36
+
37
+ @staticmethod
38
+ def _extract_labels(obj: dict, kind: str) -> dict:
39
+ spec = obj.get("spec", {})
40
+ if kind == Kind.Service:
41
+ return spec.get("selector") or {}
42
+ return (spec.get("selector") or {}).get("matchLabels") or {}
@@ -0,0 +1,26 @@
1
+ from kx.kinds import Kind
2
+ from kx.kubectl import KubectlServiceProtocol
3
+ from kx.state import StateServiceProtocol
4
+
5
+ _SUPPORTED_KINDS = {
6
+ Kind.Pod,
7
+ Kind.Deployment,
8
+ Kind.ReplicaSet,
9
+ Kind.StatefulSet,
10
+ Kind.DaemonSet,
11
+ Kind.Service,
12
+ }
13
+
14
+
15
+ class PortForwardCommand:
16
+ def __init__(self, kubectl: KubectlServiceProtocol, state: StateServiceProtocol):
17
+ self.kubectl = kubectl
18
+ self.state = state
19
+
20
+ def execute(self, index: int, port: str, extra_args: list[str] = []) -> None:
21
+ name, namespace, kind = self.state.fields(index)
22
+ if kind not in _SUPPORTED_KINDS:
23
+ raise ValueError(f"port-forward is not supported for '{kind}'.")
24
+ self.kubectl.run_interactive(
25
+ ["port-forward", f"{kind}/{name}", port, "-n", namespace, *extra_args]
26
+ )
@@ -0,0 +1,13 @@
1
+ from dataclasses import asdict
2
+ import json
3
+
4
+ from kx.state import State, StateServiceProtocol
5
+
6
+
7
+ class StateCommand:
8
+ def __init__(self, state: StateServiceProtocol):
9
+ self.state = state
10
+
11
+ def execute(self) -> State:
12
+ state = self.state.load()
13
+ return json.dumps(asdict(state), indent=2)
@@ -0,0 +1,34 @@
1
+ from rich.tree import Tree
2
+
3
+ from kx.kubectl import KubectlServiceProtocol
4
+ from kx.state import State, StateServiceProtocol
5
+ from kx.types import BuildIndexedTree, BuildTree
6
+
7
+
8
+ class TreeCommand:
9
+ def __init__(
10
+ self,
11
+ state: StateServiceProtocol,
12
+ kubectl: KubectlServiceProtocol,
13
+ build_tree: BuildTree,
14
+ build_indexed_tree: BuildIndexedTree,
15
+ ):
16
+ self.state = state
17
+ self.kubectl = kubectl
18
+ self.build_tree = build_tree
19
+ self.build_indexed_tree = build_indexed_tree
20
+
21
+ def execute(self, index: int, indexed: bool = False) -> Tree:
22
+ name, namespace, kind = self.state.fields(index)
23
+ if indexed:
24
+ tree, resources = self.build_indexed_tree(kind, name, namespace)
25
+ if resources:
26
+ self.state.save(
27
+ State(
28
+ resources={name: kind for name, kind in resources},
29
+ namespace=namespace,
30
+ )
31
+ )
32
+ else:
33
+ tree = self.build_tree(kind, name, namespace)
34
+ return tree
@@ -0,0 +1,12 @@
1
+ from kx.kubectl import KubectlServiceProtocol
2
+ from kx.state import StateServiceProtocol
3
+
4
+
5
+ class YamlCommand:
6
+ def __init__(self, state: StateServiceProtocol, kubectl: KubectlServiceProtocol):
7
+ self.state = state
8
+ self.kubectl = kubectl
9
+
10
+ def execute(self, index: int) -> str:
11
+ name, namespace, kind = self.state.fields(index)
12
+ return self.kubectl.run(["get", kind, name, "-n", namespace, "-o", "yaml"])