1- import asyncio
21import importlib .metadata
32
43import typer
1211 assign_groups_to_hosts ,
1312 check_path_exists ,
1413 list_configuration ,
14+ print_dry_run_results ,
1515 print_error ,
1616 print_message ,
1717 print_ssh_results ,
@@ -29,21 +29,31 @@ def all(
2929 timeout : int = typer .Option (
3030 10 , help = "Timeout in seconds for SSH command execution."
3131 ),
32+ dry_run : bool = typer .Option (
33+ False , help = "Show command and host info without executing."
34+ ),
3235):
3336 """
3437 Run a shell command on all configured hosts concurrently.
3538
3639 Args:
3740 cmd (str): The shell command to execute remotely.
3841 timeout (int): Timeout (in seconds) for SSH command execution.
42+ dry_run (bool): Show command and host info without executing.
3943 """
4044
4145 try :
4246 config = Config ()
4347
4448 ssh_client = SSHClient ()
45- results = ssh_client .begin (cmd , config .configured_hosts (), timeout )
46- print_ssh_results (results )
49+ if dry_run :
50+ dry_run_results = ssh_client .begin_dry_run_exec (
51+ cmd , config .configured_hosts (), "exec"
52+ )
53+ print_dry_run_results (dry_run_results )
54+ else :
55+ results = ssh_client .begin (cmd , config .configured_hosts (), timeout )
56+ print_ssh_results (results )
4757 except ConfigError as e :
4858 print_error (e , True )
4959
@@ -57,6 +67,9 @@ def group(
5767 timeout : int = typer .Option (
5868 10 , help = "Timeout in seconds for SSH command execution."
5969 ),
70+ dry_run : bool = typer .Option (
71+ False , help = "Show command and host info without executing."
72+ ),
6073):
6174 """
6275 Run a shell command on all hosts within the specified group concurrently.
@@ -65,14 +78,19 @@ def group(
6578 name (str): The name of the host group to target.
6679 cmd (str): The shell command to execute remotely.
6780 timeout (int): Timeout (in seconds) for both SSH connection and command execution.
81+ dry_run (bool): Show command and host info without executing.
6882 """
6983 try :
7084 config = Config ()
7185 hosts = config .get_hosts_by_group (name )
7286
7387 ssh_client = SSHClient ()
74- results = ssh_client .begin (cmd , hosts , timeout )
75- print_ssh_results (results )
88+ if dry_run :
89+ dry_run_results = ssh_client .begin_dry_run_exec (cmd , hosts , "exec" )
90+ print_dry_run_results (dry_run_results )
91+ else :
92+ results = ssh_client .begin (cmd , hosts , timeout )
93+ print_ssh_results (results )
7694 except ConfigError as e :
7795 print_error (e , True )
7896
@@ -143,11 +161,14 @@ def push(
143161 remote_path : str = typer .Argument (
144162 ..., help = "The remote destination path where the file/directory will be placed."
145163 ),
146- all : bool = typer .Option (False , "--all" , help = "Push to all configured hosts." ),
147- group : str = typer .Option ("--group " , help = "Push to a specific group of hosts." ),
148- host : str = typer .Option ("--host " , help = "Push to a single specific host." ),
164+ all : bool = typer .Option (False , help = "Push to all configured hosts." ),
165+ group : str = typer .Option ("" , help = "Push to a specific group of hosts." ),
166+ host : str = typer .Option ("" , help = "Push to a single specific host." ),
149167 recurse : bool = typer .Option (
150- False , "--recurse" , help = "Recursively push a directory and its contents."
168+ False , help = "Recursively push a directory and its contents."
169+ ),
170+ dry_run : bool = typer .Option (
171+ False , help = "Show transfer and host info without executing."
151172 ),
152173):
153174 """
@@ -163,8 +184,9 @@ def push(
163184 group (str): Push to a specified group of hosts.
164185 host (str): Push to a specified individual host.
165186 recurse (bool): If True, recursively push a directory and all its contents.
187+ dry_run (bool): Show transfer and host info without executing.
166188 """
167- options = [all , bool (group ), bool (host )]
189+ options = [all , bool (group != "" ), bool (host != "" )]
168190 if sum (options ) != 1 :
169191 print_error (
170192 "You must specify exactly one of --all, --group, or --host." ,
@@ -185,20 +207,24 @@ def push(
185207 else (
186208 config .get_hosts_by_group (group )
187209 if group
188- else [host_obj ]
189- if host_obj is not None
190- else []
210+ else [host_obj ] if host_obj is not None else []
191211 )
192212 )
193213
194214 if not hosts :
195215 return print_error ("Invalid host or group" )
196216
197- results = ssh_client .begin_transfer (
198- local_path , remote_path , hosts , FileTransferAction .PUSH , recurse
199- )
217+ if dry_run :
218+ results = ssh_client .begin_dry_run_transfer (
219+ hosts , local_path , remote_path , "push"
220+ )
221+ print_dry_run_results (results )
222+ else :
223+ results = ssh_client .begin_transfer (
224+ local_path , remote_path , hosts , FileTransferAction .PUSH , recurse
225+ )
200226
201- print_ssh_results (results )
227+ print_ssh_results (results )
202228 except ConfigError as e :
203229 print_error (e , True )
204230
@@ -212,12 +238,13 @@ def pull(
212238 ..., help = "The local destination path where the file/directory will be placed."
213239 ),
214240 all : bool = typer .Option (False , "--all" , help = "Pull from all configured hosts." ),
215- group : str = typer .Option (
216- "" , "--group" , help = "Pull from a specific group of hosts."
217- ),
218- host : str = typer .Option ("--host" , help = "Pull from a single specific host." ),
241+ group : str = typer .Option ("" , help = "Pull from a specific group of hosts." ),
242+ host : str = typer .Option ("" , help = "Pull from a single specific host." ),
219243 recurse : bool = typer .Option (
220- False , "--recurse" , help = "Recursively pull a directory and its contents."
244+ False , help = "Recursively pull a directory and its contents."
245+ ),
246+ dry_run : bool = typer .Option (
247+ False , help = "Show transfer and host info without executing."
221248 ),
222249):
223250 """
@@ -233,6 +260,7 @@ def pull(
233260 group (str): Pull from a specified group of hosts.
234261 host (str): Pull from a specified individual host.
235262 recurse (bool): If True, recursively pull directories and all their contents.
263+ dry_run (bool): Show transfer and host info without executing.
236264 """
237265 options = [all , bool (group ), bool (host )]
238266 if sum (options ) != 1 :
@@ -255,24 +283,28 @@ def pull(
255283 else (
256284 config .get_hosts_by_group (group )
257285 if group
258- else [host_obj ]
259- if host_obj is not None
260- else []
286+ else [host_obj ] if host_obj is not None else []
261287 )
262288 )
263289
264290 if not hosts :
265291 return print_error ("Invalid host or group" )
266292
267- results = ssh_client .begin_transfer (
268- local_path ,
269- remote_path ,
270- hosts ,
271- FileTransferAction .PULL ,
272- recurse ,
273- )
293+ if dry_run :
294+ results = ssh_client .begin_dry_run_transfer (
295+ hosts , local_path , remote_path , "pull"
296+ )
297+ print_dry_run_results (results )
298+ else :
299+ results = ssh_client .begin_transfer (
300+ local_path ,
301+ remote_path ,
302+ hosts ,
303+ FileTransferAction .PULL ,
304+ recurse ,
305+ )
274306
275- print_ssh_results (results )
307+ print_ssh_results (results )
276308 except ConfigError as e :
277309 print_error (e , True )
278310
@@ -290,7 +322,7 @@ def ls(
290322 with_status (bool): Whether to include network reachability status for each host.
291323 """
292324 try :
293- asyncio . run ( list_configuration (with_status ) )
325+ list_configuration (with_status )
294326 except ConfigError as e :
295327 print_error (e , True )
296328
0 commit comments