kdebug 0.3.4__tar.gz → 0.4.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.
@@ -1,3 +1,7 @@
1
+ backups/
2
+ temp/
3
+ .vscode/
4
+
1
5
  # Byte-compiled / optimized / DLL files
2
6
  __pycache__/
3
7
  *.py[codz]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kdebug
3
- Version: 0.3.4
3
+ Version: 0.4.1
4
4
  Summary: Universal Kubernetes Debug Container Utility
5
5
  Project-URL: Homepage, https://github.com/jessegoodier/kdebug
6
6
  Project-URL: Repository, https://github.com/jessegoodier/kdebug
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
20
20
 
21
21
  Simple utility for launching ephemeral debug containers in Kubernetes pods with interactive shell access, backup capabilities, and a colorful TUI for pod selection.
22
22
 
23
- Similar to kpf <https://github.com/jessegoodier/kpf>, this is a python wrapper around `kubectl debug` and `kubectl cp`.
23
+ Similar to [kpf](https://github.com/jessegoodier/kpf), this is a python wrapper around `kubectl debug` and `kubectl cp`.
24
24
 
25
25
  ## Features
26
26
 
@@ -31,6 +31,18 @@ Similar to kpf <https://github.com/jessegoodier/kpf>, this is a python wrapper a
31
31
  - 🔐 **Root Access Support** - Run debug containers as root when needed
32
32
  - 📦 **Controller Support** - Works with Deployments, StatefulSets, and DaemonSets
33
33
 
34
+ <details open>
35
+ <summary>Demo of the debug TUI</summary>
36
+
37
+ ![debug tui](docs/kdebug-demo_tui.gif)
38
+ </details>
39
+
40
+ <details open>
41
+ <summary>Demo of backups</summary>
42
+
43
+ ![backup demo](docs/kdebug-demo_backups.gif)
44
+ </details>
45
+
34
46
  ## Installation
35
47
 
36
48
  ```bash
@@ -45,7 +57,7 @@ git clone https://github.com/jessegoodier/kdebug.git
45
57
  cd kdebug
46
58
  ```
47
59
 
48
- Then make it is executable and add to something in your PATH
60
+ Then make it executable and add to something in your PATH
49
61
 
50
62
  ```
51
63
  chmod +x bin/kdebug
@@ -113,14 +125,11 @@ These options are passed to all kubectl commands, including those used for tab c
113
125
 
114
126
  When no pod or controller is specified, kdebug launches an interactive menu system:
115
127
 
116
- # Interactive mode - select from all resources in current namespace
117
128
  ```bash
129
+ # Interactive mode - select from all resources in current namespace
118
130
  kdebug
119
- ```
120
131
 
121
132
  # Interactive mode with specific namespace
122
-
123
- ```bash
124
133
  kdebug -n openclaw
125
134
  ```
126
135
 
@@ -270,7 +279,7 @@ kdebug uses a kubecolor-inspired color scheme:
270
279
 
271
280
  ## Requirements
272
281
 
273
- - Python 3.6+
282
+ - Python 3.9+
274
283
  - kubectl configured with cluster access
275
284
  - Kubernetes cluster with ephemeral containers support (v1.23+)
276
285
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Simple utility for launching ephemeral debug containers in Kubernetes pods with interactive shell access, backup capabilities, and a colorful TUI for pod selection.
4
4
 
5
- Similar to kpf <https://github.com/jessegoodier/kpf>, this is a python wrapper around `kubectl debug` and `kubectl cp`.
5
+ Similar to [kpf](https://github.com/jessegoodier/kpf), this is a python wrapper around `kubectl debug` and `kubectl cp`.
6
6
 
7
7
  ## Features
8
8
 
@@ -13,6 +13,18 @@ Similar to kpf <https://github.com/jessegoodier/kpf>, this is a python wrapper a
13
13
  - 🔐 **Root Access Support** - Run debug containers as root when needed
14
14
  - 📦 **Controller Support** - Works with Deployments, StatefulSets, and DaemonSets
15
15
 
16
+ <details open>
17
+ <summary>Demo of the debug TUI</summary>
18
+
19
+ ![debug tui](docs/kdebug-demo_tui.gif)
20
+ </details>
21
+
22
+ <details open>
23
+ <summary>Demo of backups</summary>
24
+
25
+ ![backup demo](docs/kdebug-demo_backups.gif)
26
+ </details>
27
+
16
28
  ## Installation
17
29
 
18
30
  ```bash
@@ -27,7 +39,7 @@ git clone https://github.com/jessegoodier/kdebug.git
27
39
  cd kdebug
28
40
  ```
29
41
 
30
- Then make it is executable and add to something in your PATH
42
+ Then make it executable and add to something in your PATH
31
43
 
32
44
  ```
33
45
  chmod +x bin/kdebug
@@ -95,14 +107,11 @@ These options are passed to all kubectl commands, including those used for tab c
95
107
 
96
108
  When no pod or controller is specified, kdebug launches an interactive menu system:
97
109
 
98
- # Interactive mode - select from all resources in current namespace
99
110
  ```bash
111
+ # Interactive mode - select from all resources in current namespace
100
112
  kdebug
101
- ```
102
113
 
103
114
  # Interactive mode with specific namespace
104
-
105
- ```bash
106
115
  kdebug -n openclaw
107
116
  ```
108
117
 
@@ -252,7 +261,7 @@ kdebug uses a kubecolor-inspired color scheme:
252
261
 
253
262
  ## Requirements
254
263
 
255
- - Python 3.6+
264
+ - Python 3.9+
256
265
  - kubectl configured with cluster access
257
266
  - Kubernetes cluster with ephemeral containers support (v1.23+)
258
267
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "kdebug"
7
- version = "0.3.4"
7
+ version = "0.4.1"
8
8
  description = "Universal Kubernetes Debug Container Utility"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,4 +1,4 @@
1
- from importlib.metadata import version, PackageNotFoundError
1
+ from importlib.metadata import PackageNotFoundError, version
2
2
 
3
3
  try:
4
4
  __version__ = version("kdebug")
@@ -466,9 +466,7 @@ def select_pod(args) -> Optional[Dict]:
466
466
  # Validate cluster connection and namespace before proceeding
467
467
  error = validate_cluster_connection(namespace)
468
468
  if error:
469
- print(
470
- f"{colorize('✗ Error:', Colors.RED)} {error}"
471
- )
469
+ print(f"{colorize('✗ Error:', Colors.RED)} {error}")
472
470
  return None
473
471
 
474
472
  # Direct pod selection
@@ -495,12 +493,12 @@ def select_pod(args) -> Optional[Dict]:
495
493
  return pods[0]
496
494
 
497
495
  # Interactive mode - no pod or controller specified
498
- print(f"\n{colorize('Starting interactive pod selection...', Colors.BRIGHT_CYAN)}")
496
+ print(f"\n{colorize('Starting interactive pod selection...', Colors.CYAN)}")
499
497
 
500
498
  # Direct pod selection via TUI
501
499
  pod_name = select_pod_interactive(namespace)
502
500
  if not pod_name:
503
- print(f"\n{colorize('Selection cancelled', Colors.YELLOW)}")
501
+ print(f"\n{colorize('Selection cancelled', Colors.CYAN)}")
504
502
  return None
505
503
 
506
504
  return {"name": pod_name, "namespace": namespace}
@@ -703,6 +701,42 @@ def check_pod_security_context(pod_name: str, namespace: str) -> Dict:
703
701
  return {"can_run_as_root": True, "reason": "Unable to parse pod spec"}
704
702
 
705
703
 
704
+ def get_container_run_as_user(
705
+ pod_name: str, namespace: str, target_container: Optional[str]
706
+ ) -> Optional[int]:
707
+ """Detect the runAsUser UID from the target container or pod security context."""
708
+ cmd = f"{kubectl_base_cmd()} get pod {pod_name} -n {namespace} -o json"
709
+ output = run_command(cmd, check=False)
710
+
711
+ if not output:
712
+ return None
713
+
714
+ try:
715
+ pod_data = json.loads(output)
716
+ spec = pod_data.get("spec", {})
717
+
718
+ # Check container-level securityContext first (overrides pod-level)
719
+ if target_container:
720
+ for container in spec.get("containers", []):
721
+ if container.get("name") == target_container:
722
+ container_uid = container.get("securityContext", {}).get(
723
+ "runAsUser"
724
+ )
725
+ if container_uid is not None:
726
+ return int(container_uid)
727
+ break
728
+
729
+ # Fall back to pod-level securityContext
730
+ pod_uid = spec.get("securityContext", {}).get("runAsUser")
731
+ if pod_uid is not None:
732
+ return int(pod_uid)
733
+
734
+ except (json.JSONDecodeError, ValueError, TypeError):
735
+ pass
736
+
737
+ return None
738
+
739
+
706
740
  def launch_debug_container(
707
741
  pod_name: str,
708
742
  namespace: str,
@@ -710,6 +744,7 @@ def launch_debug_container(
710
744
  target_container: Optional[str],
711
745
  existing_containers: List[str],
712
746
  as_root: bool = False,
747
+ run_as_user: Optional[int] = None,
713
748
  ) -> Optional[str]:
714
749
  """Launch a debug container attached to the pod and return its name."""
715
750
  print(f"Launching debug container for pod {colorize(pod_name, Colors.CYAN)}...")
@@ -727,12 +762,16 @@ def launch_debug_container(
727
762
  file=sys.stderr,
728
763
  )
729
764
  print(f"{colorize('Tip:', Colors.CYAN)} Try without --as-root flag\n")
730
-
731
- if existing_containers:
765
+ elif run_as_user is not None:
732
766
  print(
733
- f"Existing ephemeral containers: {colorize(', '.join(existing_containers), Colors.BRIGHT_BLACK)}"
767
+ f"Running as UID {colorize(str(run_as_user), Colors.CYAN)} (matching target container)"
734
768
  )
735
769
 
770
+ # if existing_containers:
771
+ # print(
772
+ # f"Existing ephemeral containers: {colorize(', '.join(existing_containers), Colors.BRIGHT_BLACK)}"
773
+ # )
774
+
736
775
  # Build kubectl debug command
737
776
  cmd_parts = [
738
777
  f"nohup {kubectl_base_cmd()} debug -i --tty",
@@ -752,6 +791,10 @@ def launch_debug_container(
752
791
 
753
792
  if as_root:
754
793
  cmd_parts.append('--custom=<(echo \'{"securityContext":{"runAsUser":0}}\')')
794
+ elif run_as_user is not None:
795
+ cmd_parts.append(
796
+ f'--custom=<(echo \'{{"securityContext":{{"runAsUser":{run_as_user}}}}}\')'
797
+ )
755
798
 
756
799
  cmd_parts.extend(
757
800
  [
@@ -798,13 +841,12 @@ def exec_interactive(
798
841
  ) -> int:
799
842
  """Execute an interactive command in the debug container."""
800
843
  print(f"\n{colorize('=' * 60, Colors.BLUE)}")
801
- print(
802
- f"{colorize('Starting interactive session', Colors.BOLD)} in pod {colorize(pod_name, Colors.CYAN)}"
803
- )
844
+ print(f"{colorize('Starting interactive session', Colors.BOLD)} in:")
845
+ print(f"Pod: {colorize(pod_name, Colors.CYAN)}")
804
846
  print(f"Container: {colorize(container_name, Colors.CYAN)}")
805
- print(f"Command: {colorize(cmd, Colors.YELLOW)}")
847
+ print(f"Command: {colorize(cmd, Colors.CYAN)}")
806
848
  if cd_into:
807
- print(f"Directory: {colorize(cd_into, Colors.MAGENTA)}")
849
+ print(f"Directory: {colorize(cd_into, Colors.CYAN)}")
808
850
  print(f"{colorize('=' * 60, Colors.BLUE)}\n")
809
851
 
810
852
  # If cd_into is specified, wrap command to cd first
@@ -883,7 +925,7 @@ def create_backup(
883
925
  f"{kubectl_base_cmd()} exec {pod_name} "
884
926
  f"-n {namespace} "
885
927
  f"-c {container_name} "
886
- f"-- ls -d {backup_path} 2>/dev/null"
928
+ f"-- ls -d /proc/1/root{backup_path} 2>/dev/null"
887
929
  )
888
930
 
889
931
  result = run_command(verify_cmd, check=False)
@@ -903,7 +945,7 @@ def create_backup(
903
945
  f"{kubectl_base_cmd()} exec {pod_name} "
904
946
  f"-n {namespace} "
905
947
  f"-c {container_name} "
906
- f"-- ls -la {parent_dir} 2>/dev/null | head -20"
948
+ f"-- ls -la /proc/1/root{parent_dir} 2>/dev/null | head -20"
907
949
  )
908
950
  parent_result = run_command(parent_cmd, check=False)
909
951
  if parent_result:
@@ -977,7 +1019,7 @@ def create_backup(
977
1019
  f"{kubectl_base_cmd()} cp "
978
1020
  f"-n {namespace} "
979
1021
  f"-c {container_name} "
980
- f"{pod_name}:{backup_path} "
1022
+ f"{pod_name}:/proc/1/root{backup_path} "
981
1023
  f"{local_filename}"
982
1024
  )
983
1025
 
@@ -1043,7 +1085,7 @@ def parse_controller_arg(value: str) -> Tuple[str, str]:
1043
1085
  controller_type, controller_name = value.split("/", 1)
1044
1086
  if not controller_name:
1045
1087
  raise argparse.ArgumentTypeError(
1046
- f"Missing controller name after '/'. Expected TYPE/NAME (e.g. sts/myapp)."
1088
+ "Missing controller name after '/'. Expected TYPE/NAME (e.g. sts/myapp)."
1047
1089
  )
1048
1090
  if controller_type.lower() not in CONTROLLER_ALIASES:
1049
1091
  valid_types = ", ".join(sorted(CONTROLLER_ALIASES.keys()))
@@ -1200,15 +1242,13 @@ Usage:
1200
1242
  )
1201
1243
 
1202
1244
  print(f"\n{colorize('=' * 60, Colors.BLUE)}")
1245
+ print(f"{colorize('Namespace:', Colors.BOLD)} {colorize(namespace, Colors.CYAN)}")
1203
1246
  print(f"{colorize('Target Pod:', Colors.BOLD)} {colorize(pod_name, Colors.CYAN)}")
1204
- print(
1205
- f"{colorize('Namespace:', Colors.BOLD)} {colorize(namespace, Colors.MAGENTA)}"
1206
- )
1207
1247
  print(
1208
1248
  f"{colorize('Target Container:', Colors.BOLD)} {colorize(target_container, Colors.CYAN)}"
1209
1249
  )
1210
1250
  print(
1211
- f"{colorize('Debug Image:', Colors.BOLD)} {colorize(args.debug_image, Colors.BRIGHT_BLACK)}"
1251
+ f"{colorize('Debug Image:', Colors.BOLD)} {colorize(args.debug_image, Colors.CYAN)}"
1212
1252
  )
1213
1253
  print(f"{colorize('=' * 60, Colors.BLUE)}\n")
1214
1254
 
@@ -1222,7 +1262,12 @@ Usage:
1222
1262
  f"Found existing ephemeral containers: {colorize(', '.join(existing_containers), Colors.BRIGHT_BLACK)}"
1223
1263
  )
1224
1264
  # For simplicity, we'll create a new one. In production, you might want to reuse.
1225
- print(f"{colorize('Creating new debug container...', Colors.YELLOW)}")
1265
+ print(f"{colorize('Creating new debug container...', Colors.MAGENTA)}")
1266
+
1267
+ # Detect target container UID for the debug container
1268
+ run_as_user = None
1269
+ if not args.as_root:
1270
+ run_as_user = get_container_run_as_user(pod_name, namespace, target_container)
1226
1271
 
1227
1272
  # Launch debug container
1228
1273
  debug_container = launch_debug_container(
@@ -1232,6 +1277,7 @@ Usage:
1232
1277
  target_container,
1233
1278
  existing_containers,
1234
1279
  args.as_root,
1280
+ run_as_user,
1235
1281
  )
1236
1282
 
1237
1283
  if not debug_container: