kdebug 0.3.3__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.
- {kdebug-0.3.3 → kdebug-0.4.0}/.gitignore +4 -0
- {kdebug-0.3.3 → kdebug-0.4.0}/PKG-INFO +1 -1
- {kdebug-0.3.3 → kdebug-0.4.0}/pyproject.toml +1 -1
- {kdebug-0.3.3 → kdebug-0.4.0}/src/kdebug/__init__.py +1 -1
- {kdebug-0.3.3 → kdebug-0.4.0}/src/kdebug/cli.py +83 -16
- {kdebug-0.3.3 → kdebug-0.4.0}/README.md +0 -0
- {kdebug-0.3.3 → kdebug-0.4.0}/src/kdebug/completions/__init__.py +0 -0
- {kdebug-0.3.3 → kdebug-0.4.0}/src/kdebug/completions/_kdebug +0 -0
- {kdebug-0.3.3 → kdebug-0.4.0}/src/kdebug/completions/kdebug.bash +0 -0
- {kdebug-0.3.3 → kdebug-0.4.0}/src/kdebug/completions/kdebug.fish +0 -0
|
@@ -138,6 +138,19 @@ def get_current_namespace() -> str:
|
|
|
138
138
|
return output if output else "default"
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def validate_cluster_connection(namespace: str) -> Optional[str]:
|
|
142
|
+
"""Validate kubectl can connect to the cluster and namespace exists.
|
|
143
|
+
|
|
144
|
+
Returns None on success, or an error message string on failure.
|
|
145
|
+
"""
|
|
146
|
+
cmd = f"{kubectl_base_cmd()} get namespace {namespace} -o name"
|
|
147
|
+
print_debug_command(cmd)
|
|
148
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
149
|
+
if result.returncode != 0:
|
|
150
|
+
return result.stderr.strip()
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
141
154
|
def get_pod_by_name(pod_name: str, namespace: str) -> Optional[Dict]:
|
|
142
155
|
"""Get pod information by name."""
|
|
143
156
|
print(
|
|
@@ -450,6 +463,12 @@ def select_pod(args) -> Optional[Dict]:
|
|
|
450
463
|
"""Select a pod based on provided arguments."""
|
|
451
464
|
namespace = args.namespace or get_current_namespace()
|
|
452
465
|
|
|
466
|
+
# Validate cluster connection and namespace before proceeding
|
|
467
|
+
error = validate_cluster_connection(namespace)
|
|
468
|
+
if error:
|
|
469
|
+
print(f"{colorize('✗ Error:', Colors.RED)} {error}")
|
|
470
|
+
return None
|
|
471
|
+
|
|
453
472
|
# Direct pod selection
|
|
454
473
|
if args.pod:
|
|
455
474
|
return get_pod_by_name(args.pod, namespace)
|
|
@@ -474,12 +493,12 @@ def select_pod(args) -> Optional[Dict]:
|
|
|
474
493
|
return pods[0]
|
|
475
494
|
|
|
476
495
|
# Interactive mode - no pod or controller specified
|
|
477
|
-
print(f"\n{colorize('Starting interactive pod selection...', Colors.
|
|
496
|
+
print(f"\n{colorize('Starting interactive pod selection...', Colors.CYAN)}")
|
|
478
497
|
|
|
479
498
|
# Direct pod selection via TUI
|
|
480
499
|
pod_name = select_pod_interactive(namespace)
|
|
481
500
|
if not pod_name:
|
|
482
|
-
print(f"\n{colorize('Selection cancelled', Colors.
|
|
501
|
+
print(f"\n{colorize('Selection cancelled', Colors.CYAN)}")
|
|
483
502
|
return None
|
|
484
503
|
|
|
485
504
|
return {"name": pod_name, "namespace": namespace}
|
|
@@ -682,6 +701,42 @@ def check_pod_security_context(pod_name: str, namespace: str) -> Dict:
|
|
|
682
701
|
return {"can_run_as_root": True, "reason": "Unable to parse pod spec"}
|
|
683
702
|
|
|
684
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
|
+
|
|
685
740
|
def launch_debug_container(
|
|
686
741
|
pod_name: str,
|
|
687
742
|
namespace: str,
|
|
@@ -689,6 +744,7 @@ def launch_debug_container(
|
|
|
689
744
|
target_container: Optional[str],
|
|
690
745
|
existing_containers: List[str],
|
|
691
746
|
as_root: bool = False,
|
|
747
|
+
run_as_user: Optional[int] = None,
|
|
692
748
|
) -> Optional[str]:
|
|
693
749
|
"""Launch a debug container attached to the pod and return its name."""
|
|
694
750
|
print(f"Launching debug container for pod {colorize(pod_name, Colors.CYAN)}...")
|
|
@@ -706,12 +762,16 @@ def launch_debug_container(
|
|
|
706
762
|
file=sys.stderr,
|
|
707
763
|
)
|
|
708
764
|
print(f"{colorize('Tip:', Colors.CYAN)} Try without --as-root flag\n")
|
|
709
|
-
|
|
710
|
-
if existing_containers:
|
|
765
|
+
elif run_as_user is not None:
|
|
711
766
|
print(
|
|
712
|
-
f"
|
|
767
|
+
f"Running as UID {colorize(str(run_as_user), Colors.CYAN)} (matching target container)"
|
|
713
768
|
)
|
|
714
769
|
|
|
770
|
+
# if existing_containers:
|
|
771
|
+
# print(
|
|
772
|
+
# f"Existing ephemeral containers: {colorize(', '.join(existing_containers), Colors.BRIGHT_BLACK)}"
|
|
773
|
+
# )
|
|
774
|
+
|
|
715
775
|
# Build kubectl debug command
|
|
716
776
|
cmd_parts = [
|
|
717
777
|
f"nohup {kubectl_base_cmd()} debug -i --tty",
|
|
@@ -731,6 +791,10 @@ def launch_debug_container(
|
|
|
731
791
|
|
|
732
792
|
if as_root:
|
|
733
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
|
+
)
|
|
734
798
|
|
|
735
799
|
cmd_parts.extend(
|
|
736
800
|
[
|
|
@@ -777,13 +841,12 @@ def exec_interactive(
|
|
|
777
841
|
) -> int:
|
|
778
842
|
"""Execute an interactive command in the debug container."""
|
|
779
843
|
print(f"\n{colorize('=' * 60, Colors.BLUE)}")
|
|
780
|
-
print(
|
|
781
|
-
|
|
782
|
-
)
|
|
844
|
+
print(f"{colorize('Starting interactive session', Colors.BOLD)} in:")
|
|
845
|
+
print(f"Pod: {colorize(pod_name, Colors.CYAN)}")
|
|
783
846
|
print(f"Container: {colorize(container_name, Colors.CYAN)}")
|
|
784
|
-
print(f"Command: {colorize(cmd, Colors.
|
|
847
|
+
print(f"Command: {colorize(cmd, Colors.CYAN)}")
|
|
785
848
|
if cd_into:
|
|
786
|
-
print(f"Directory: {colorize(cd_into, Colors.
|
|
849
|
+
print(f"Directory: {colorize(cd_into, Colors.CYAN)}")
|
|
787
850
|
print(f"{colorize('=' * 60, Colors.BLUE)}\n")
|
|
788
851
|
|
|
789
852
|
# If cd_into is specified, wrap command to cd first
|
|
@@ -1022,7 +1085,7 @@ def parse_controller_arg(value: str) -> Tuple[str, str]:
|
|
|
1022
1085
|
controller_type, controller_name = value.split("/", 1)
|
|
1023
1086
|
if not controller_name:
|
|
1024
1087
|
raise argparse.ArgumentTypeError(
|
|
1025
|
-
|
|
1088
|
+
"Missing controller name after '/'. Expected TYPE/NAME (e.g. sts/myapp)."
|
|
1026
1089
|
)
|
|
1027
1090
|
if controller_type.lower() not in CONTROLLER_ALIASES:
|
|
1028
1091
|
valid_types = ", ".join(sorted(CONTROLLER_ALIASES.keys()))
|
|
@@ -1179,15 +1242,13 @@ Usage:
|
|
|
1179
1242
|
)
|
|
1180
1243
|
|
|
1181
1244
|
print(f"\n{colorize('=' * 60, Colors.BLUE)}")
|
|
1245
|
+
print(f"{colorize('Namespace:', Colors.BOLD)} {colorize(namespace, Colors.CYAN)}")
|
|
1182
1246
|
print(f"{colorize('Target Pod:', Colors.BOLD)} {colorize(pod_name, Colors.CYAN)}")
|
|
1183
|
-
print(
|
|
1184
|
-
f"{colorize('Namespace:', Colors.BOLD)} {colorize(namespace, Colors.MAGENTA)}"
|
|
1185
|
-
)
|
|
1186
1247
|
print(
|
|
1187
1248
|
f"{colorize('Target Container:', Colors.BOLD)} {colorize(target_container, Colors.CYAN)}"
|
|
1188
1249
|
)
|
|
1189
1250
|
print(
|
|
1190
|
-
f"{colorize('Debug Image:', Colors.BOLD)} {colorize(args.debug_image, Colors.
|
|
1251
|
+
f"{colorize('Debug Image:', Colors.BOLD)} {colorize(args.debug_image, Colors.CYAN)}"
|
|
1191
1252
|
)
|
|
1192
1253
|
print(f"{colorize('=' * 60, Colors.BLUE)}\n")
|
|
1193
1254
|
|
|
@@ -1201,7 +1262,12 @@ Usage:
|
|
|
1201
1262
|
f"Found existing ephemeral containers: {colorize(', '.join(existing_containers), Colors.BRIGHT_BLACK)}"
|
|
1202
1263
|
)
|
|
1203
1264
|
# For simplicity, we'll create a new one. In production, you might want to reuse.
|
|
1204
|
-
print(f"{colorize('Creating new debug container...', Colors.
|
|
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)
|
|
1205
1271
|
|
|
1206
1272
|
# Launch debug container
|
|
1207
1273
|
debug_container = launch_debug_container(
|
|
@@ -1211,6 +1277,7 @@ Usage:
|
|
|
1211
1277
|
target_container,
|
|
1212
1278
|
existing_containers,
|
|
1213
1279
|
args.as_root,
|
|
1280
|
+
run_as_user,
|
|
1214
1281
|
)
|
|
1215
1282
|
|
|
1216
1283
|
if not debug_container:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|