66import typer
77from rich .console import Console
88
9+ from debugwand import container as container_ops
10+ from debugwand import kubernetes as k8s
911from debugwand .operations import (
10- copy_to_pod ,
1112 detect_reload_mode ,
12- exec_command_in_pod ,
1313 find_process_using_port ,
14- find_replacement_pod ,
15- get_and_select_pod_handler ,
16- get_and_select_process_handler ,
17- get_pods_for_service_handler ,
1814 is_port_available ,
1915 kill_process ,
20- list_all_processes_with_details_handler ,
21- list_python_processes_with_details ,
22- monitor_worker_pid ,
2316 prepare_debugpy_script ,
24- select_pid ,
25- select_pod ,
26- wait_for_new_pod ,
2717)
2818from debugwand .types import PodInfo , ProcessInfo
2919from debugwand .ui import (
@@ -55,14 +45,12 @@ def pods(
5545 ),
5646):
5747 if with_pids :
58- pod_list = get_pods_for_service_handler (namespace , service )
48+ pod_list = k8s . get_pods_for_service_handler (namespace , service )
5949
6050 # Collect all pod-process pairs
6151 pod_processes : list [tuple [PodInfo , list [ProcessInfo ]]] = []
6252 for pod in pod_list :
63- processes : list [ProcessInfo ] | None = (
64- list_all_processes_with_details_handler (pod )
65- )
53+ processes : list [ProcessInfo ] | None = k8s .list_python_processes_handler (pod )
6654 if processes :
6755 pod_processes .append ((pod , processes ))
6856
@@ -73,7 +61,7 @@ def pods(
7361 typer .echo ("❌ No running pods with Python processes found." , err = True )
7462 raise typer .Exit (code = 1 )
7563 else :
76- pod_list = get_pods_for_service_handler (namespace , service )
64+ pod_list = k8s . get_pods_for_service_handler (namespace , service )
7765
7866 render_pods_table (pod_list )
7967
@@ -87,26 +75,26 @@ def inject(
8775 script : str = typer .Option (..., "--script" , "-c" , help = "The script to execute." ),
8876):
8977 typer .echo (f"Executing script '{ script } ' in the selected pod..." )
90- pod_list = get_pods_for_service_handler (namespace , service )
91- pod = select_pod (pod_list )
78+ pod_list = k8s . get_pods_for_service_handler (namespace , service )
79+ pod = k8s . select_pod (pod_list )
9280
93- processes : list [ProcessInfo ] | None = list_all_processes_with_details_handler (pod )
81+ processes : list [ProcessInfo ] | None = k8s . list_python_processes_handler (pod )
9482 if not processes :
9583 typer .echo (
9684 "❌ No running Python processes found in the selected pod." , err = True
9785 )
9886 raise typer .Exit (code = 1 )
99- pid = select_pid ( processes )
87+ pid = k8s . get_and_select_process ( pod , None )
10088
10189 # Copy the user's script into the pod
10290 script_basename = os .path .basename (script )
103- copy_to_pod (pod , script , f"/tmp/{ script_basename } " )
91+ k8s . copy_to_pod (pod , script , f"/tmp/{ script_basename } " )
10492
10593 # Copy the attacher script into the pod (used to inject and run the user's script)
10694 attacher_path = Path (__file__ ).parent / "attacher.py"
107- copy_to_pod (pod , str (attacher_path ), "/tmp/attacher.py" )
95+ k8s . copy_to_pod (pod , str (attacher_path ), "/tmp/attacher.py" )
10896
109- exec_command_in_pod (
97+ k8s . exec_command (
11098 pod = pod ,
11199 command = [
112100 "python3" ,
@@ -125,17 +113,17 @@ def _inject_debugpy_into_pod(pod: PodInfo, pid: int, script_path: str) -> None:
125113
126114 # Copy the attacher script into the pod
127115 attacher_path = Path (__file__ ).parent / "attacher.py"
128- copy_to_pod (pod , str (attacher_path ), "/tmp/attacher.py" )
116+ k8s . copy_to_pod (pod , str (attacher_path ), "/tmp/attacher.py" )
129117
130118 # Copy debugpy script into the pod
131- copy_to_pod (pod , script_path , f"/tmp/{ script_basename } " )
119+ k8s . copy_to_pod (pod , script_path , f"/tmp/{ script_basename } " )
132120
133121 print_step (
134122 f"Injecting debugpy into PID [cyan bold]{ pid } [/cyan bold] in pod [blue]{ pod .name } [/blue]..."
135123 )
136124 # Run injection synchronously to capture errors
137125 try :
138- exec_command_in_pod (
126+ k8s . exec_command (
139127 pod = pod ,
140128 command = [
141129 "python3" ,
@@ -274,7 +262,7 @@ def _monitor_and_handle_reload_mode(
274262 Returns (final_pid, should_break) where should_break indicates if we should exit the loop.
275263 """
276264 try :
277- processes = list_python_processes_with_details (pod )
265+ processes = k8s . list_python_processes (pod )
278266 is_reload , _ = detect_reload_mode (processes ) if processes else (False , None )
279267 except Exception :
280268 # Pod might be slow to respond while debugger is attached, or pod died
@@ -290,7 +278,7 @@ def _monitor_and_handle_reload_mode(
290278 # Monitor for worker PID changes while keeping port-forward alive
291279 while port_forward_proc .poll () is None :
292280 try :
293- new_pid = monitor_worker_pid (pod , pid )
281+ new_pid = k8s . monitor_worker_pid (pod , pid )
294282 except Exception as e :
295283 # Pod might have died or become unresponsive
296284 print_info (
@@ -313,10 +301,12 @@ def _monitor_and_handle_reload_mode(
313301 try :
314302 reinject_script_path = prepare_debugpy_script (port = port , wait = False )
315303 reinject_basename = os .path .basename (reinject_script_path )
316- copy_to_pod (pod , reinject_script_path , f"/tmp/{ reinject_basename } " )
304+ k8s .copy_to_pod (
305+ pod , reinject_script_path , f"/tmp/{ reinject_basename } "
306+ )
317307
318308 # Run injection in background (non-blocking)
319- exec_command_in_pod (
309+ k8s . exec_command (
320310 pod = pod ,
321311 command = [
322312 "python3" ,
@@ -363,12 +353,12 @@ def _attempt_reconnect(
363353 """
364354 print_step ("Connection lost, attempting to reconnect..." )
365355 try :
366- new_pod = find_replacement_pod (pod , service , namespace )
356+ new_pod = k8s . find_replacement_pod (pod , service , namespace )
367357 if not new_pod :
368358 print_info ("Could not find replacement pod, waiting..." )
369- new_pod = wait_for_new_pod (service , namespace )
359+ new_pod = k8s . wait_for_new_pod (service , namespace )
370360
371- new_pid = get_and_select_process_handler (pod = new_pod , pid = None )
361+ new_pid = k8s . get_and_select_process_handler (pod = new_pod , pid = None )
372362 print_success (f"Reconnected to new pod: { new_pod .name } " )
373363 print_info ("App is running - connect your debugger to continue debugging" )
374364 return new_pod , new_pid
@@ -381,7 +371,7 @@ def _cleanup_injected_files(pod: PodInfo, script_basename: str) -> None:
381371 """Clean up temporary files injected into the pod."""
382372 try :
383373 print_step ("Cleaning up injected files in the pod..." , prefix = "🧹" )
384- exec_command_in_pod (
374+ k8s . exec_command (
385375 pod = pod ,
386376 command = ["rm" , "-f" , f"/tmp/{ script_basename } " , "/tmp/attacher.py" ],
387377 silent_errors = True ,
@@ -391,23 +381,67 @@ def _cleanup_injected_files(pod: PodInfo, script_basename: str) -> None:
391381 pass
392382
393383
394- @app .command (help = "Start remote debugging session in a Python process within a pod." )
384+ @app .command (
385+ help = "Start remote debugging session in a Python process within a pod or container."
386+ )
395387def debug (
396388 namespace : str = typer .Option (
397- ..., "--namespace" , "-n" , help = "The namespace to use."
389+ None ,
390+ "--namespace" ,
391+ "-n" ,
392+ help = "The Kubernetes namespace to use." ,
393+ rich_help_panel = "Kubernetes Options" ,
394+ ),
395+ service : str = typer .Option (
396+ None ,
397+ "--service" ,
398+ "-s" ,
399+ help = "The Kubernetes service to use." ,
400+ rich_help_panel = "Kubernetes Options" ,
401+ ),
402+ container : str = typer .Option (
403+ None ,
404+ "--container" ,
405+ "-c" ,
406+ help = "Container name or ID to debug." ,
407+ rich_help_panel = "Container Options" ,
398408 ),
399- service : str = typer .Option (..., "--service" , "-s" , help = "The service to use." ),
400409 port : int = typer .Option (
401- 5679 , "--port" , "-p" , help = "The local port to forward for debugging."
410+ 5679 ,
411+ "--port" ,
412+ "-p" ,
413+ help = "The port for debugpy to listen on." ,
414+ rich_help_panel = "Common Options" ,
402415 ),
403416 pid : int = typer .Option (
404417 None ,
405418 "--pid" ,
406419 help = "The PID of the Python process to debug. If not provided, you will be prompted to select." ,
420+ rich_help_panel = "Common Options" ,
407421 ),
408422):
409- pod = get_and_select_pod_handler (service = service , namespace = namespace )
410- pid = get_and_select_process_handler (pod = pod , pid = pid )
423+ if container :
424+ # Container mode - error if k8s options are also provided
425+ if namespace or service :
426+ typer .echo (
427+ "❌ Cannot use --namespace or --service with --container." ,
428+ err = True ,
429+ )
430+ raise typer .Exit (1 )
431+ container_ops .debug (container , port , pid )
432+ return
433+ elif namespace and service :
434+ # Kubernetes mode
435+ pass
436+ else :
437+ typer .echo (
438+ "❌ Either --container or both --namespace and --service are required." ,
439+ err = True ,
440+ )
441+ raise typer .Exit (1 )
442+
443+ pod = k8s .get_and_select_pod_handler (service = service , namespace = namespace )
444+ pid = k8s .get_and_select_process_handler (pod = pod , pid = pid )
411445
412446 # Prepare debugpy script on local filesystem (wait=False means app continues immediately)
413447 temp_script_path = prepare_debugpy_script (port = port , wait = False )
0 commit comments