Skip to content

Commit b64c714

Browse files
committed
Prepare for packaging. Part I
1 parent 3c423ec commit b64c714

File tree

6 files changed

+216
-1
lines changed

6 files changed

+216
-1
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Compiled python modules.
2+
*.pyc
3+
4+
# Setuptools distribution folder.
5+
/dist/
6+
/build/
7+
8+
# Python egg metadata, regenerated from source files by setuptools.
9+
/*.egg-info

README.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,59 @@
1-
# ssh-commander
1+
# SSH Commander
2+
This command line tool, allows you to execute various commands on several remote hosts,
3+
using the SSH protocol. As a result, you get a per-host generated output of each
4+
command you specified.
5+
6+
You simply specify a plain text file with a list of remote hosts to connect to (domain names
7+
or IP addresses), and a comma-separated list of commands to execute on those. Note that
8+
access credentials (user and password) must be the same for all the target hosts!.
9+
10+
## Requirements
11+
Make sure your system meets the following requirements:
12+
* [Python 3](https://www.python.org/downloads/) (>= 3.4)
13+
* [paramiko](https://github.com/paramiko/paramiko) (tested with v2.6.0)
14+
15+
## Installation
16+
The recommended method for installing this tool, is using `pip`:
17+
```
18+
pip install ssh-commander
19+
```
20+
21+
## Usage
22+
When using `ssh-commander`, respect the following syntax:
23+
```
224
Execute remote commands on several hosts, with SSH.
25+
usage: ssh-commander [-h] [-p PORT] [-v] FILE USER COMMANDS
26+
27+
Excecute remote commands on several hosts, with SSH.
28+
29+
positional arguments:
30+
FILE Plain text file with list of hosts
31+
USER User to login on remote hosts
32+
COMMANDS Comma separated commands to be executed on remote
33+
hosts
34+
35+
optional arguments:
36+
-h, --help show this help message and exit
37+
-p PORT, --port PORT Specify SSH port to connect to hosts
38+
-v, --version Show current version
39+
```
40+
First, remember to create a text file (name it whatever you like), where you
41+
list the target hosts. Its content, may look like this:
42+
```
43+
# This is a comment. It'll be ignored!.
44+
192.168.0.10
45+
192.168.0.11
46+
192.168.0.12
47+
```
48+
Also, note that the password for the user provided, is gonna be asked only
49+
once. Therefore, those credentials, should be valid on all target hosts!.
50+
51+
### Examples
52+
Let's say you have some managed switches (or routers):
53+
```
54+
ssh-commander hosts.txt root "terminal length 0, sh port-security"
55+
```
56+
They could rather be some GNU/Linux servers, as well:
57+
```
58+
ssh-commander hosts.txt foones "hostname, whoami"
59+
```

setup.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from setuptools import setup
2+
3+
with open("README.md", "r") as fh:
4+
long_description = fh.read()
5+
6+
setup(
7+
name='ssh-commander',
8+
version='0.1',
9+
description='Excecute remote commands on several hosts, with SSH',
10+
long_description=long_description,
11+
long_description_content_type="text/markdown",
12+
url='https://github.com/tuxedoar/ssh-commander',
13+
author='tuxedoar',
14+
author_email='tuxedoar@gmail.com',
15+
packages=['ssh-commander'],
16+
entry_points={
17+
"console_scripts": [
18+
"ssh-commander = ssh-commander.ssh-commander:main",
19+
],
20+
},
21+
install_requires=[
22+
'paramiko>=2.4.3'
23+
],
24+
25+
classifiers=[
26+
"Programming Language :: Python :: 3 :: Only",
27+
"Programming Language :: Python :: 3.4",
28+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
29+
"Operating System :: OS Independent",
30+
"Intended Audience :: System Administrators",
31+
"Environment :: Console",
32+
],
33+
)

ssh-commander/__init__.py

Whitespace-only changes.

ssh-commander/_version.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
""" Show version of this program """
2+
__version__ = 'v0.1'

ssh-commander/ssh-commander

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Copyright 2019 by Tuxedoar <tuxedoar@gmail.com>
2+
3+
# LICENSE
4+
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# any later version.
9+
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
import argparse
19+
import sys
20+
import re
21+
import getpass
22+
from time import sleep
23+
from socket import error
24+
from _version import __version__
25+
import paramiko
26+
27+
def main():
28+
""" Setup CLI arguments and call rest of the functions """
29+
parser = argparse.ArgumentParser(
30+
description='Excecute remote commands on several hosts, with SSH.')
31+
parser.add_argument('FILE', help='Plain text file with list of hosts')
32+
parser.add_argument('USER', help='User to login on remote hosts')
33+
parser.add_argument('COMMANDS', help='Comma separated commands to be \
34+
executed on remote hosts')
35+
parser.add_argument('-p', '--port', required=False, action='store',
36+
help='Specify SSH port to connect to hosts')
37+
parser.add_argument('-v', '--version', action='version',
38+
version="%(prog)s {version}".format(version=__version__),
39+
help='Show current version')
40+
args = parser.parse_args()
41+
42+
cmd = args.COMMANDS
43+
SSH_PORT = 22
44+
45+
if args.port:
46+
SSH_PORT = args.port
47+
pw = getpass.getpass('\n Please, enter your password to access hosts: ')
48+
target_hosts = read_hosts_file(args.FILE)
49+
50+
# Start SSH session on each remote host.
51+
for target_host in target_hosts:
52+
try:
53+
remote_shell = setup_ssh_session(args.USER, pw, SSH_PORT, target_host)
54+
exec_remote_commands(remote_shell, cmd)
55+
except (KeyboardInterrupt, \
56+
paramiko.ssh_exception.AuthenticationException, \
57+
paramiko.SSHException, \
58+
error) as e:
59+
print("%s" % (e))
60+
61+
62+
def setup_ssh_session(user, pw, port, remote_host):
63+
""" Setup the SSH session with necessary arguments """
64+
print("\n [+] Connecting to host %s with the user %s ... \n" % (remote_host, user))
65+
my_session = paramiko.SSHClient()
66+
my_session.set_missing_host_key_policy(paramiko.AutoAddPolicy())
67+
my_session.connect(remote_host, username=user, password=pw, port=port, \
68+
look_for_keys=False, allow_agent=False, timeout=10)
69+
remote_shell = my_session.invoke_shell()
70+
return remote_shell
71+
72+
73+
def exec_remote_commands(remote_shell, commands):
74+
""" Execute provided commands on remote hosts! """
75+
remote_commands = commands.split(',')
76+
77+
for command in remote_commands:
78+
# Remove double quotes!.
79+
command = command.replace('\"', '')
80+
remote_shell.send(command+'\n')
81+
# Wait 1 second before sending commands provided by user!.
82+
sleep(1)
83+
# Ouput buffer size in bytes!.
84+
output = remote_shell.recv(8000)
85+
# Split each line for output.
86+
output = output.splitlines()
87+
for lines in output:
88+
print(lines.decode())
89+
90+
91+
def validate_ip_addr(ip_addr):
92+
""" Validate an IP address """
93+
validate_ip = re.search("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip_addr)
94+
return bool(validate_ip)
95+
96+
97+
def read_hosts_file(hosts_file):
98+
""" Read the file and get the IP adresses """
99+
remote_hosts = []
100+
try:
101+
with open(hosts_file, 'r') as file:
102+
for line in file.readlines():
103+
ip = line.strip()
104+
if line and not line.startswith('#'):
105+
print("\n WARNING: The IP %s is NOT valid. Ignored!" % (ip)) \
106+
if not validate_ip_addr(ip) else remote_hosts.append(ip)
107+
return remote_hosts
108+
109+
except IOError:
110+
print("Can't read the specified file. Make sure it exist!.")
111+
sys.exit(2)
112+
113+
if __name__ == "__main__":
114+
main()

0 commit comments

Comments
 (0)