multiSSH3 6.33__tar.gz → 6.35__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.
- {multissh3-6.33 → multissh3-6.35}/PKG-INFO +9 -5
- multissh3-6.33/multiSSH3.egg-info/PKG-INFO → multissh3-6.35/README.md +8 -30
- {multissh3-6.33 → multissh3-6.35}/mssh_complete.py +41 -9
- multissh3-6.33/README.md → multissh3-6.35/multiSSH3.egg-info/PKG-INFO +34 -4
- {multissh3-6.33 → multissh3-6.35}/multiSSH3.egg-info/SOURCES.txt +3 -0
- {multissh3-6.33 → multissh3-6.35}/multiSSH3.py +508 -132
- {multissh3-6.33 → multissh3-6.35}/tests/complete_shell_harness.py +18 -2
- {multissh3-6.33 → multissh3-6.35}/tests/conftest.py +19 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_cli_option_words.py +102 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_context.py +70 -3
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_diagnose.py +24 -3
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_keys.py +2 -2
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_picker.py +4 -2
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_shells.py +2 -2
- {multissh3-6.33 → multissh3-6.35}/tests/test_curses_event_loop.py +256 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_expand_hostnames.py +15 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_options_after_command.py +14 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_runtime_branch_matrix.py +1 -1
- multissh3-6.35/tests/test_shared_lines.py +219 -0
- multissh3-6.35/tests/test_stopped_commands.py +287 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_stream_reader.py +7 -5
- multissh3-6.35/tests/test_streamed_output.py +228 -0
- multissh3-6.35/tests/test_waiting.py +244 -0
- multissh3-6.33/tests/test_stopped_commands.py +0 -123
- {multissh3-6.33 → multissh3-6.35}/LICENSE +0 -0
- {multissh3-6.33 → multissh3-6.35}/MANIFEST.in +0 -0
- {multissh3-6.33 → multissh3-6.35}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-6.33 → multissh3-6.35}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-6.33 → multissh3-6.35}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-6.33 → multissh3-6.35}/pytest.ini +0 -0
- {multissh3-6.33 → multissh3-6.35}/setup.cfg +0 -0
- {multissh3-6.33 → multissh3-6.35}/setup.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/bench_complete.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/data/run_command_golden.json +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/shells/Dockerfile.bash5.1 +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/shells/Dockerfile.bash5.3 +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/shells/Dockerfile.centos7 +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/shells/Dockerfile.current +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/shells/Dockerfile.legacy +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/shells/Dockerfile.modern +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/shells/run_matrix.sh +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_cli_branches.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_cli_processing.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_closed_output.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_color_helpers.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_command_terminal.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_compact_hostnames.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_blesh.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_cli.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_editor.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_engine.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_eval.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_hosts.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_hoststate.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_index.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_lex.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_mssh_helpers.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_password_spellings.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_probe.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_start.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_store.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_suggest.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_terminal.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_untrusted.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_complete_zsh_options.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_config_cli.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_config_validate.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_config_values.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_control_master_config.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_copy_id.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_curses_branches.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_curses_tui.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_curses_wide_chars.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_file_trust.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_harness_smoke.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_helpers_extra.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_history_replay.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_host.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_host_validity.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_hosts_file.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_identities_only.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_ipmi_lockout_wait.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_ipmi_logins.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_key_path_home.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_login_drop_retry.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_main_lifecycle.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_narrow_terminal.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_no_hosts.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_option_no_effect.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_output_merge.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_output_robustness.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_packaging.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_password_handling.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_passwords_out_of_ps.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_run_command.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_run_command_branches.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_run_command_characterization.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_run_command_exit.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_run_on_hosts.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_run_on_hosts_modes.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_runtime_dir.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_smoke_optional.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_ssh_error_text.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_ssh_timeouts.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_sshpass_exit.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_start_run_on_hosts.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_tui_ssh_notices.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_unavailable_hosts.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_underscore_settings.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/test_wide_wrap.py +0 -0
- {multissh3-6.33 → multissh3-6.35}/tests/vt_grid.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multiSSH3
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.35
|
|
4
4
|
Summary: Run commands on multiple hosts via SSH
|
|
5
5
|
Home-page: https://github.com/yufei-pan/multiSSH3
|
|
6
6
|
Author: Yufei Pan
|
|
@@ -336,6 +336,8 @@ When hostname need to be resolved, mssh will check in the following order:
|
|
|
336
336
|
|
|
337
337
|
An alias's value is split into hosts as the hosts argument is (`,`, spaces, `;`, ...). An alias with an empty value (`eu=''`) stands for no hosts.
|
|
338
338
|
|
|
339
|
+
A member can name its user: with `dbs="postgres@db1,db2"`, mssh logs in to db1 as postgres, and `-u` changes only db2's user. A user typed with the alias replaces it: `mssh root@dbs` logs in to both as root, as a user on the command line wins over ssh config's `User`.
|
|
340
|
+
|
|
339
341
|
Inside its own expansion, directly or through other aliases, an alias's name is not expanded again but taken as a host name, as bash does with aliases: `web="web,web-b"` runs on the host `web` and on `web-b`. When no host has that name, mssh reports the loop and runs on the rest: `Error: alias loop through 'web' (web -> app -> web): 'web' is not a valid hostname or IP address!`
|
|
340
342
|
|
|
341
343
|
|
|
@@ -601,10 +603,12 @@ mssh -q -- web1 grep -r TODO /srv
|
|
|
601
603
|
|
|
602
604
|
### `-ea, --extraargs EXTRAARGS` | _`DEFAULT_EXTRA_ARGS`_
|
|
603
605
|
- Extra arguments to pass to the ssh / rsync / scp command. Put in one string for multiple arguments.
|
|
606
|
+
- The word after `-ea` is its value, also when it starts with `-`: `-ea '-p 2222'`, `-ea -A`. Quote an option together with its value: with `-ea -p 2222` the quotes were forgotten, and mssh stops, as it does for a password option there. ( mssh 6.34 and older need `-ea='-p 2222'`, which still works. )
|
|
604
607
|
- The string is split into arguments as a shell would: quote an argument that contains spaces.
|
|
605
608
|
- For a file copy with rsync, ssh's options that take a value go to the ssh that rsync runs, and the other arguments to rsync: the option followed by its value as the next word (`-o ConnectTimeout=5`, `-p 2222`, `-i FILE`, `-J HOST`, `-F FILE`, `-l USER`, ...), or `-oName=value` and `-pPORT`. scp takes ssh's options itself; ssh's `-p PORT` is given to it as `-P PORT`.
|
|
606
609
|
- Example:
|
|
607
610
|
```bash
|
|
611
|
+
mssh -ea '-p 2222' allServers uptime
|
|
608
612
|
mssh -ea="--delete" -f ./data/ allServers /tmp/data/
|
|
609
613
|
mssh -ea="-o ProxyCommand='ssh -W %h:%p bastion'" allServers uptime
|
|
610
614
|
```
|
|
@@ -715,7 +719,7 @@ mssh -f ./data/ allServers /tmp/data/
|
|
|
715
719
|
|
|
716
720
|
### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
|
|
717
721
|
- Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
|
|
718
|
-
- Ex: `mssh -M -ia
|
|
722
|
+
- Ex: `mssh -M -ia '-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. The word after `-ia` is its value, also when it starts with `-`; quote an option together with its value ( mssh 6.34 and older need `-ia='-I lanplus'` ).
|
|
719
723
|
|
|
720
724
|
### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
|
|
721
725
|
- How `--ipmi` reaches a host:
|
|
@@ -970,7 +974,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
|
|
|
970
974
|
|
|
971
975
|
Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
|
|
972
976
|
|
|
973
|
-
Below is a sample help message output from multiSSH3 6.
|
|
977
|
+
Below is a sample help message output from multiSSH3 6.35 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
|
|
974
978
|
|
|
975
979
|
```bash
|
|
976
980
|
$ mssh -h
|
|
@@ -1001,7 +1005,7 @@ options:
|
|
|
1001
1005
|
The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
|
|
1002
1006
|
-uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
|
|
1003
1007
|
-ea, --extraargs EXTRAARGS
|
|
1004
|
-
Extra arguments to pass to the ssh / rsync / scp command. (Use -ia for ipmi arguments) Put in one string for multiple arguments.
|
|
1008
|
+
Extra arguments to pass to the ssh / rsync / scp command. (Use -ia for ipmi arguments) Put in one string for multiple arguments. The word after -ea is its value, also when it starts with "-": quote an option with its value. Ex. -ea "-p 2222 -A" (default: None)
|
|
1005
1009
|
-11, --oneonone Run one corresponding command on each host. (default: False)
|
|
1006
1010
|
-f, --file FILE The file to be copied to the hosts. Use -f multiple times to copy multiple files
|
|
1007
1011
|
-s, -fs, --file_sync [FILE_SYNC]
|
|
@@ -1026,7 +1030,7 @@ options:
|
|
|
1026
1030
|
-ip, --ipmi_password IPMI_PASSWORD
|
|
1027
1031
|
The password to use to connect to the hosts via ipmi. (default: set, not shown)
|
|
1028
1032
|
-ia, --ipmi_args IPMI_ARGS
|
|
1029
|
-
The arguments passed to ipmitool to use to connect to the hosts via ipmi. (default: )
|
|
1033
|
+
The arguments passed to ipmitool to use to connect to the hosts via ipmi. The word after -ia is its value, also when it starts with "-": quote an option with its value. Ex. -ia "-I lanplus" (default: )
|
|
1030
1034
|
-im, --ipmi_method IPMI_METHOD
|
|
1031
1035
|
The method to use to connect to the hosts via ipmi. (default: ipmitool)
|
|
1032
1036
|
-irlt, --ipmi_retry_lockout_time IPMI_RETRY_LOCKOUT_TIME
|
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: multiSSH3
|
|
3
|
-
Version: 6.33
|
|
4
|
-
Summary: Run commands on multiple hosts via SSH
|
|
5
|
-
Home-page: https://github.com/yufei-pan/multiSSH3
|
|
6
|
-
Author: Yufei Pan
|
|
7
|
-
Author-email: pan@zopyr.us
|
|
8
|
-
License-Expression: GPL-3.0-or-later
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
-
Classifier: Operating System :: MacOS
|
|
12
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
-
Requires-Python: >=3.6
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
License-File: LICENSE
|
|
16
|
-
Dynamic: author
|
|
17
|
-
Dynamic: author-email
|
|
18
|
-
Dynamic: classifier
|
|
19
|
-
Dynamic: description
|
|
20
|
-
Dynamic: description-content-type
|
|
21
|
-
Dynamic: home-page
|
|
22
|
-
Dynamic: license-file
|
|
23
|
-
Dynamic: license-expression
|
|
24
|
-
Dynamic: requires-python
|
|
25
|
-
Dynamic: summary
|
|
26
|
-
|
|
27
1
|
# multiSSH3
|
|
28
2
|
|
|
29
3
|
## Introduction
|
|
@@ -336,6 +310,8 @@ When hostname need to be resolved, mssh will check in the following order:
|
|
|
336
310
|
|
|
337
311
|
An alias's value is split into hosts as the hosts argument is (`,`, spaces, `;`, ...). An alias with an empty value (`eu=''`) stands for no hosts.
|
|
338
312
|
|
|
313
|
+
A member can name its user: with `dbs="postgres@db1,db2"`, mssh logs in to db1 as postgres, and `-u` changes only db2's user. A user typed with the alias replaces it: `mssh root@dbs` logs in to both as root, as a user on the command line wins over ssh config's `User`.
|
|
314
|
+
|
|
339
315
|
Inside its own expansion, directly or through other aliases, an alias's name is not expanded again but taken as a host name, as bash does with aliases: `web="web,web-b"` runs on the host `web` and on `web-b`. When no host has that name, mssh reports the loop and runs on the rest: `Error: alias loop through 'web' (web -> app -> web): 'web' is not a valid hostname or IP address!`
|
|
340
316
|
|
|
341
317
|
|
|
@@ -601,10 +577,12 @@ mssh -q -- web1 grep -r TODO /srv
|
|
|
601
577
|
|
|
602
578
|
### `-ea, --extraargs EXTRAARGS` | _`DEFAULT_EXTRA_ARGS`_
|
|
603
579
|
- Extra arguments to pass to the ssh / rsync / scp command. Put in one string for multiple arguments.
|
|
580
|
+
- The word after `-ea` is its value, also when it starts with `-`: `-ea '-p 2222'`, `-ea -A`. Quote an option together with its value: with `-ea -p 2222` the quotes were forgotten, and mssh stops, as it does for a password option there. ( mssh 6.34 and older need `-ea='-p 2222'`, which still works. )
|
|
604
581
|
- The string is split into arguments as a shell would: quote an argument that contains spaces.
|
|
605
582
|
- For a file copy with rsync, ssh's options that take a value go to the ssh that rsync runs, and the other arguments to rsync: the option followed by its value as the next word (`-o ConnectTimeout=5`, `-p 2222`, `-i FILE`, `-J HOST`, `-F FILE`, `-l USER`, ...), or `-oName=value` and `-pPORT`. scp takes ssh's options itself; ssh's `-p PORT` is given to it as `-P PORT`.
|
|
606
583
|
- Example:
|
|
607
584
|
```bash
|
|
585
|
+
mssh -ea '-p 2222' allServers uptime
|
|
608
586
|
mssh -ea="--delete" -f ./data/ allServers /tmp/data/
|
|
609
587
|
mssh -ea="-o ProxyCommand='ssh -W %h:%p bastion'" allServers uptime
|
|
610
588
|
```
|
|
@@ -715,7 +693,7 @@ mssh -f ./data/ allServers /tmp/data/
|
|
|
715
693
|
|
|
716
694
|
### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
|
|
717
695
|
- Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
|
|
718
|
-
- Ex: `mssh -M -ia
|
|
696
|
+
- Ex: `mssh -M -ia '-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. The word after `-ia` is its value, also when it starts with `-`; quote an option together with its value ( mssh 6.34 and older need `-ia='-I lanplus'` ).
|
|
719
697
|
|
|
720
698
|
### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
|
|
721
699
|
- How `--ipmi` reaches a host:
|
|
@@ -970,7 +948,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
|
|
|
970
948
|
|
|
971
949
|
Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
|
|
972
950
|
|
|
973
|
-
Below is a sample help message output from multiSSH3 6.
|
|
951
|
+
Below is a sample help message output from multiSSH3 6.35 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
|
|
974
952
|
|
|
975
953
|
```bash
|
|
976
954
|
$ mssh -h
|
|
@@ -1001,7 +979,7 @@ options:
|
|
|
1001
979
|
The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
|
|
1002
980
|
-uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
|
|
1003
981
|
-ea, --extraargs EXTRAARGS
|
|
1004
|
-
Extra arguments to pass to the ssh / rsync / scp command. (Use -ia for ipmi arguments) Put in one string for multiple arguments.
|
|
982
|
+
Extra arguments to pass to the ssh / rsync / scp command. (Use -ia for ipmi arguments) Put in one string for multiple arguments. The word after -ea is its value, also when it starts with "-": quote an option with its value. Ex. -ea "-p 2222 -A" (default: None)
|
|
1005
983
|
-11, --oneonone Run one corresponding command on each host. (default: False)
|
|
1006
984
|
-f, --file FILE The file to be copied to the hosts. Use -f multiple times to copy multiple files
|
|
1007
985
|
-s, -fs, --file_sync [FILE_SYNC]
|
|
@@ -1026,7 +1004,7 @@ options:
|
|
|
1026
1004
|
-ip, --ipmi_password IPMI_PASSWORD
|
|
1027
1005
|
The password to use to connect to the hosts via ipmi. (default: set, not shown)
|
|
1028
1006
|
-ia, --ipmi_args IPMI_ARGS
|
|
1029
|
-
The arguments passed to ipmitool to use to connect to the hosts via ipmi. (default: )
|
|
1007
|
+
The arguments passed to ipmitool to use to connect to the hosts via ipmi. The word after -ia is its value, also when it starts with "-": quote an option with its value. Ex. -ia "-I lanplus" (default: )
|
|
1030
1008
|
-im, --ipmi_method IPMI_METHOD
|
|
1031
1009
|
The method to use to connect to the hosts via ipmi. (default: ipmitool)
|
|
1032
1010
|
-irlt, --ipmi_retry_lockout_time IPMI_RETRY_LOCKOUT_TIME
|
|
@@ -16,7 +16,7 @@ import sys
|
|
|
16
16
|
import time
|
|
17
17
|
from bisect import bisect_left
|
|
18
18
|
|
|
19
|
-
version = '0.
|
|
19
|
+
version = '0.8'
|
|
20
20
|
__version__ = version
|
|
21
21
|
PROTOCOL = 'MSSHC1'
|
|
22
22
|
MSSH_NAMES = ('mssh', 'mssh3', 'multissh', 'multissh3', 'multiSSH3')
|
|
@@ -166,6 +166,31 @@ def split_at_cursor(before, after):
|
|
|
166
166
|
|
|
167
167
|
#%% ------------ Option spec ----------------
|
|
168
168
|
PASSWORD_DESTS = frozenset(('password', 'ipmi_password'))
|
|
169
|
+
# options passing options on to another program (-ea, -ia): mssh 6.35+ takes the word after them as
|
|
170
|
+
# their value even when it starts with '-' (its _with_passed_on_values), except as takes_passed_on_value says
|
|
171
|
+
PASSED_ON_DESTS = frozenset(('extraargs', 'ipmi_args'))
|
|
172
|
+
# the letters of the options needing a value of their own, by where they pass them: ssh, ipmitool
|
|
173
|
+
PASSED_ON_BARE_VALUE_LETTERS = {
|
|
174
|
+
'extraargs': frozenset('BbcDEeFIiJLlmOoPpQRSWw'),
|
|
175
|
+
'ipmi_args': frozenset('AbBCdefHIklLmNoOpPRtTUyz'),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
def takes_passed_on_value(dest, text, spec):
|
|
179
|
+
'''
|
|
180
|
+
Whether -ea or -ia (dest) takes text, the word after it, as its value, as mssh's _takes_passed_on_value
|
|
181
|
+
reads it: not a bare option needing a value of its own (-ea -p 2222: the quotes were forgotten), nor a
|
|
182
|
+
word giving mssh's password; mssh stops at those.
|
|
183
|
+
'''
|
|
184
|
+
if not text.startswith('-') or ' ' in text:
|
|
185
|
+
return True
|
|
186
|
+
if len(text) == 2 and text[1] in PASSED_ON_BARE_VALUE_LETTERS[dest]:
|
|
187
|
+
return False
|
|
188
|
+
if text.startswith('-ip'):
|
|
189
|
+
return False
|
|
190
|
+
if text[:2] == '-p' and text[2:].isdigit():
|
|
191
|
+
return True
|
|
192
|
+
option = spec.lookup(text)[0]
|
|
193
|
+
return option is None or option.dest not in PASSWORD_DESTS
|
|
169
194
|
# options naming files mssh reads its hosts and env aliases from, at Enter
|
|
170
195
|
LOOKUP_DESTS = frozenset(('env_file', 'env_files', 'host_file', 'config_file'))
|
|
171
196
|
PATH_DESTS = frozenset(('file', 'file_sync', 'env_file', 'env_files', 'config_file', 'store_config_file', 'host_file', 'history_file'))
|
|
@@ -506,9 +531,12 @@ def assign_roles(words, spec, current=None):
|
|
|
506
531
|
pending = None
|
|
507
532
|
# the word after a password option is its password, whatever it looks like: argparse stops
|
|
508
533
|
# at "-p -secret", but the shell's history keeps the line, and a password is never another word.
|
|
509
|
-
#
|
|
510
|
-
# (-
|
|
511
|
-
|
|
534
|
+
# mssh takes the word after -ea and -ia as their value (PASSED_ON_DESTS). argparse takes a dash
|
|
535
|
+
# word as the value when it holds a space and names no option (-u "-o x"); mssh's -11 makes it
|
|
536
|
+
# read negative numbers as options
|
|
537
|
+
# (a word being typed in an open quote will hold more: "-I lanplus")
|
|
538
|
+
passed_on = option.dest in PASSED_ON_DESTS and (takes_passed_on_value(option.dest, text, spec) or (is_current and token.open_quote))
|
|
539
|
+
if not dashed or option.dest in PASSWORD_DESTS or passed_on or (' ' in text and not spec.names_option(text)):
|
|
512
540
|
roles.append((ROLE_VALUE, option))
|
|
513
541
|
continue
|
|
514
542
|
if text == '--' and not is_current and not after_separator:
|
|
@@ -2577,12 +2605,9 @@ def _run_name(host, typed_user, line_user, own_user=''):
|
|
|
2577
2605
|
The name mssh gives a host in this run: what it records a failure under in unavailable_hosts.csv
|
|
2578
2606
|
and looks up to skip the host. The user typed with the host, else -u (line_user), else none.
|
|
2579
2607
|
own_user: the user an alias member is written with. -u does not replace it, a user typed with the
|
|
2580
|
-
alias
|
|
2608
|
+
alias does (for root@dbs with dbs=postgres@db1 mssh runs root@db1).
|
|
2581
2609
|
'''
|
|
2582
|
-
|
|
2583
|
-
host = own_user + '@' + host
|
|
2584
|
-
line_user = ''
|
|
2585
|
-
user = typed_user or line_user
|
|
2610
|
+
user = typed_user or own_user or line_user
|
|
2586
2611
|
return user + '@' + host if user else host
|
|
2587
2612
|
|
|
2588
2613
|
# characters that can start an expansion the shell makes before mssh sees the word
|
|
@@ -3619,10 +3644,16 @@ class Engine(object):
|
|
|
3619
3644
|
have_hosts = False
|
|
3620
3645
|
run = None # where the unquoted remote command starts in argv
|
|
3621
3646
|
pending = None # nargs of an option still taking the words that follow as values
|
|
3647
|
+
passed_on = None # -ea's or -ia's dest: the word may be its value even when it starts with '-'
|
|
3622
3648
|
paths = False
|
|
3623
3649
|
read = []
|
|
3624
3650
|
stop = len(argv)
|
|
3625
3651
|
for index, token in enumerate(argv):
|
|
3652
|
+
if passed_on:
|
|
3653
|
+
dest, passed_on = passed_on, None
|
|
3654
|
+
if takes_passed_on_value(dest, token.text, self.spec):
|
|
3655
|
+
pending = None
|
|
3656
|
+
continue
|
|
3626
3657
|
if token.text == '--':
|
|
3627
3658
|
stop = index
|
|
3628
3659
|
break
|
|
@@ -3645,6 +3676,7 @@ class Engine(object):
|
|
|
3645
3676
|
option, attached, _ = readings[-1]
|
|
3646
3677
|
if attached is None and option.nargs != '0':
|
|
3647
3678
|
pending = option.nargs
|
|
3679
|
+
passed_on = option.dest if option.dest in PASSED_ON_DESTS else None
|
|
3648
3680
|
if run is None and any(option.dest in ('file', 'file_sync', 'gather_mode') for option, _, _ in readings):
|
|
3649
3681
|
paths = True
|
|
3650
3682
|
if run is not None:
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multiSSH3
|
|
3
|
+
Version: 6.35
|
|
4
|
+
Summary: Run commands on multiple hosts via SSH
|
|
5
|
+
Home-page: https://github.com/yufei-pan/multiSSH3
|
|
6
|
+
Author: Yufei Pan
|
|
7
|
+
Author-email: pan@zopyr.us
|
|
8
|
+
License-Expression: GPL-3.0-or-later
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Requires-Python: >=3.6
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: license-expression
|
|
24
|
+
Dynamic: requires-python
|
|
25
|
+
Dynamic: summary
|
|
26
|
+
|
|
1
27
|
# multiSSH3
|
|
2
28
|
|
|
3
29
|
## Introduction
|
|
@@ -310,6 +336,8 @@ When hostname need to be resolved, mssh will check in the following order:
|
|
|
310
336
|
|
|
311
337
|
An alias's value is split into hosts as the hosts argument is (`,`, spaces, `;`, ...). An alias with an empty value (`eu=''`) stands for no hosts.
|
|
312
338
|
|
|
339
|
+
A member can name its user: with `dbs="postgres@db1,db2"`, mssh logs in to db1 as postgres, and `-u` changes only db2's user. A user typed with the alias replaces it: `mssh root@dbs` logs in to both as root, as a user on the command line wins over ssh config's `User`.
|
|
340
|
+
|
|
313
341
|
Inside its own expansion, directly or through other aliases, an alias's name is not expanded again but taken as a host name, as bash does with aliases: `web="web,web-b"` runs on the host `web` and on `web-b`. When no host has that name, mssh reports the loop and runs on the rest: `Error: alias loop through 'web' (web -> app -> web): 'web' is not a valid hostname or IP address!`
|
|
314
342
|
|
|
315
343
|
|
|
@@ -575,10 +603,12 @@ mssh -q -- web1 grep -r TODO /srv
|
|
|
575
603
|
|
|
576
604
|
### `-ea, --extraargs EXTRAARGS` | _`DEFAULT_EXTRA_ARGS`_
|
|
577
605
|
- Extra arguments to pass to the ssh / rsync / scp command. Put in one string for multiple arguments.
|
|
606
|
+
- The word after `-ea` is its value, also when it starts with `-`: `-ea '-p 2222'`, `-ea -A`. Quote an option together with its value: with `-ea -p 2222` the quotes were forgotten, and mssh stops, as it does for a password option there. ( mssh 6.34 and older need `-ea='-p 2222'`, which still works. )
|
|
578
607
|
- The string is split into arguments as a shell would: quote an argument that contains spaces.
|
|
579
608
|
- For a file copy with rsync, ssh's options that take a value go to the ssh that rsync runs, and the other arguments to rsync: the option followed by its value as the next word (`-o ConnectTimeout=5`, `-p 2222`, `-i FILE`, `-J HOST`, `-F FILE`, `-l USER`, ...), or `-oName=value` and `-pPORT`. scp takes ssh's options itself; ssh's `-p PORT` is given to it as `-P PORT`.
|
|
580
609
|
- Example:
|
|
581
610
|
```bash
|
|
611
|
+
mssh -ea '-p 2222' allServers uptime
|
|
582
612
|
mssh -ea="--delete" -f ./data/ allServers /tmp/data/
|
|
583
613
|
mssh -ea="-o ProxyCommand='ssh -W %h:%p bastion'" allServers uptime
|
|
584
614
|
```
|
|
@@ -689,7 +719,7 @@ mssh -f ./data/ allServers /tmp/data/
|
|
|
689
719
|
|
|
690
720
|
### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
|
|
691
721
|
- Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
|
|
692
|
-
- Ex: `mssh -M -ia
|
|
722
|
+
- Ex: `mssh -M -ia '-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. The word after `-ia` is its value, also when it starts with `-`; quote an option together with its value ( mssh 6.34 and older need `-ia='-I lanplus'` ).
|
|
693
723
|
|
|
694
724
|
### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
|
|
695
725
|
- How `--ipmi` reaches a host:
|
|
@@ -944,7 +974,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
|
|
|
944
974
|
|
|
945
975
|
Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
|
|
946
976
|
|
|
947
|
-
Below is a sample help message output from multiSSH3 6.
|
|
977
|
+
Below is a sample help message output from multiSSH3 6.35 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
|
|
948
978
|
|
|
949
979
|
```bash
|
|
950
980
|
$ mssh -h
|
|
@@ -975,7 +1005,7 @@ options:
|
|
|
975
1005
|
The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
|
|
976
1006
|
-uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
|
|
977
1007
|
-ea, --extraargs EXTRAARGS
|
|
978
|
-
Extra arguments to pass to the ssh / rsync / scp command. (Use -ia for ipmi arguments) Put in one string for multiple arguments.
|
|
1008
|
+
Extra arguments to pass to the ssh / rsync / scp command. (Use -ia for ipmi arguments) Put in one string for multiple arguments. The word after -ea is its value, also when it starts with "-": quote an option with its value. Ex. -ea "-p 2222 -A" (default: None)
|
|
979
1009
|
-11, --oneonone Run one corresponding command on each host. (default: False)
|
|
980
1010
|
-f, --file FILE The file to be copied to the hosts. Use -f multiple times to copy multiple files
|
|
981
1011
|
-s, -fs, --file_sync [FILE_SYNC]
|
|
@@ -1000,7 +1030,7 @@ options:
|
|
|
1000
1030
|
-ip, --ipmi_password IPMI_PASSWORD
|
|
1001
1031
|
The password to use to connect to the hosts via ipmi. (default: set, not shown)
|
|
1002
1032
|
-ia, --ipmi_args IPMI_ARGS
|
|
1003
|
-
The arguments passed to ipmitool to use to connect to the hosts via ipmi. (default: )
|
|
1033
|
+
The arguments passed to ipmitool to use to connect to the hosts via ipmi. The word after -ia is its value, also when it starts with "-": quote an option with its value. Ex. -ia "-I lanplus" (default: )
|
|
1004
1034
|
-im, --ipmi_method IPMI_METHOD
|
|
1005
1035
|
The method to use to connect to the hosts via ipmi. (default: ipmitool)
|
|
1006
1036
|
-irlt, --ipmi_retry_lockout_time IPMI_RETRY_LOCKOUT_TIME
|
|
@@ -83,6 +83,7 @@ tests/test_run_on_hosts.py
|
|
|
83
83
|
tests/test_run_on_hosts_modes.py
|
|
84
84
|
tests/test_runtime_branch_matrix.py
|
|
85
85
|
tests/test_runtime_dir.py
|
|
86
|
+
tests/test_shared_lines.py
|
|
86
87
|
tests/test_smoke_optional.py
|
|
87
88
|
tests/test_ssh_error_text.py
|
|
88
89
|
tests/test_ssh_timeouts.py
|
|
@@ -90,9 +91,11 @@ tests/test_sshpass_exit.py
|
|
|
90
91
|
tests/test_start_run_on_hosts.py
|
|
91
92
|
tests/test_stopped_commands.py
|
|
92
93
|
tests/test_stream_reader.py
|
|
94
|
+
tests/test_streamed_output.py
|
|
93
95
|
tests/test_tui_ssh_notices.py
|
|
94
96
|
tests/test_unavailable_hosts.py
|
|
95
97
|
tests/test_underscore_settings.py
|
|
98
|
+
tests/test_waiting.py
|
|
96
99
|
tests/test_wide_wrap.py
|
|
97
100
|
tests/vt_grid.py
|
|
98
101
|
tests/data/run_command_golden.json
|