kdebug 0.3.4__tar.gz → 0.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.
@@ -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.0
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
@@ -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.0"
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
@@ -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:
File without changes