finish doc 1.0

This commit is contained in:
fluzzi 2022-04-03 18:25:58 -03:00
parent de2c2ab21b
commit 67fa4e1e6d
6 changed files with 1403 additions and 229 deletions

View File

@ -1,6 +1,18 @@
#!/usr/bin/env python3
'''
## Connection manager
conn is a connection manager that allows you to store nodes to connect them fast and password free.
### Features
- You can generate profiles and reference them from nodes using @profilename
so you dont need to edit multiple nodes when changing password or other
information.
- Nodes can be stored on @folder or @subfolder@folder to organize your
devices. Then can be referenced using node@subfolder@folder or node@folder
- Much more!
### Usage
```
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]
conn {profile,move,mv,copy,cp,list,ls,bulk,config} ...
@ -27,7 +39,7 @@ Commands:
config Manage app config
```
#### Manage profiles
### Manage profiles
```
usage: conn profile [-h] (--add | --del | --mod | --show) profile
@ -42,7 +54,7 @@ options:
--show Show profile
```
#### Examples
### Examples
```
conn profile --add office-user
conn --add @office
@ -53,15 +65,68 @@ options:
conn pc@office
conn server
```
## Automation module
the automation module
### Standalone module
```
import conn
router = conn.node("unique name","ip/hostname", user="username", password="pass")
router.run(["term len 0","show run"])
print(router.output)
hasip = router.test("show ip int brief","1.1.1.1")
if hasip:
print("Router has ip 1.1.1.1")
else:
print("router don't has ip 1.1.1.1")
```
### Using manager configuration
```
import conn
conf = conn.configfile()
device = conf.getitem("server@office")
server = conn.node("unique name", **device, config=conf)
result = server.run(["cd /", "ls -la"])
print(result)
```
### Running parallel tasks
```
import conn
conf = conn.configfile()
#You can get the nodes from the config from a folder and fitlering in it
nodes = conf.getitem("@office", ["router1", "router2", "router3"])
#You can also get each node individually:
nodes = {}
nodes["router1"] = conf.getitem("router1@office")
nodes["router2"] = conf.getitem("router2@office")
nodes["router10"] = conf.getitem("router10@datacenter")
#Also, you can create the nodes manually:
nodes = {}
nodes["router1"] = {"host": "1.1.1.1", "user": "username", "password": "pass1"}
nodes["router2"] = {"host": "1.1.1.2", "user": "username", "password": "pass2"}
nodes["router3"] = {"host": "1.1.1.2", "user": "username", "password": "pass3"}
#Finally you run some tasks on the nodes
mynodes = conn.nodes(nodes, config = conf)
result = mynodes.test(["show ip int br"], "1.1.1.2")
for i in result:
print("---" + i + "---")
print(result[i])
print()
# Or for one specific node
mynodes.router1.run(["term len 0". "show run"], folder = "/home/user/logs")
```
'''
from .core import node,nodes
from .configfile import configfile
from .connapp import connapp
from pkg_resources import get_distribution
__all__ = ["node", "nodes", "configfile"]
__all__ = ["node", "nodes", "configfile", "connapp"]
__version__ = "2.0.10"
__author__ = "Federico Luzzi"
__pdoc__ = {
'core': False,
'connapp': False,
}

View File

@ -1,12 +1,10 @@
#!/usr/bin/env python3
import sys
from connapp import connapp
from configfile import configfile
from core import node
from conn import *
def main():
conf = configfile()
connapp(conf, node)
connapp(conf)
if __name__ == '__main__':
sys.exit(main())

View File

@ -10,39 +10,75 @@ from pathlib import Path
#functions and classes
class configfile:
''' This class generates a configfile object. Containts a dictionary storing, config, nodes and profiles, normaly used by connection manager.
def __init__(self, conf = None, *, key = None):
### Attributes:
- file (str): Path/file to config file.
- key (str): Path/file to RSA key file.
- config (dict): Dictionary containing information of connection
manager configuration.
- connections (dict): Dictionary containing all the nodes added to
connection manager.
- profiles (dict): Dictionary containing all the profiles added to
connection manager.
- privatekey (obj): Object containing the private key to encrypt
passwords.
- publickey (obj): Object containing the public key to decrypt
passwords.
'''
def __init__(self, conf = None, key = None):
'''
### Optional Parameters:
- conf (str): Path/file to config file. If left empty default
path is ~/.config/conn/config.json
- key (str): Path/file to RSA key file. If left empty default
path is ~/.config/conn/.osk
'''
home = os.path.expanduser("~")
self.defaultdir = home + '/.config/conn'
self.defaultfile = self.defaultdir + '/config.json'
self.defaultkey = self.defaultdir + '/.osk'
Path(self.defaultdir).mkdir(parents=True, exist_ok=True)
defaultdir = home + '/.config/conn'
defaultfile = defaultdir + '/config.json'
defaultkey = defaultdir + '/.osk'
Path(defaultdir).mkdir(parents=True, exist_ok=True)
if conf == None:
self.file = self.defaultfile
self.file = defaultfile
else:
self.file = conf
if key == None:
self.key = self.defaultkey
self.key = defaultkey
else:
self.key = key
if os.path.exists(self.file):
config = self.loadconfig(self.file)
config = self._loadconfig(self.file)
else:
config = self.createconfig(self.file)
config = self._createconfig(self.file)
self.config = config["config"]
self.connections = config["connections"]
self.profiles = config["profiles"]
if not os.path.exists(self.key):
self.createkey(self.key)
self._createkey(self.key)
self.privatekey = RSA.import_key(open(self.key).read())
self.publickey = self.privatekey.publickey()
def loadconfig(self, conf):
def _loadconfig(self, conf):
#Loads config file
jsonconf = open(conf)
return json.load(jsonconf)
def createconfig(self, conf):
def _createconfig(self, conf):
#Create config file
defaultconfig = {'config': {'case': False, 'idletime': 30}, 'connections': {}, 'profiles': { "default": { "host":"", "protocol":"ssh", "port":"", "user":"", "password":"", "options":"", "logs":"" }}}
if not os.path.exists(conf):
with open(conf, "w") as f:
@ -52,7 +88,8 @@ class configfile:
jsonconf = open(conf)
return json.load(jsonconf)
def saveconfig(self, conf):
def _saveconfig(self, conf):
#Save config file
newconfig = {"config":{}, "connections": {}, "profiles": {}}
newconfig["config"] = self.config
newconfig["connections"] = self.connections
@ -61,7 +98,8 @@ class configfile:
json.dump(newconfig, f, indent = 4)
f.close()
def createkey(self, keyfile):
def _createkey(self, keyfile):
#Create key file
key = RSA.generate(2048)
with open(keyfile,'wb') as f:
f.write(key.export_key('PEM'))
@ -69,6 +107,7 @@ class configfile:
os.chmod(keyfile, 0o600)
def _explode_unique(self, unique):
#Divide unique name into folder, subfolder and id
uniques = unique.split("@")
if not unique.startswith("@"):
result = {"id": uniques[0]}
@ -88,6 +127,26 @@ class configfile:
return result
def getitem(self, unique, keys = None):
'''
Get an node or a group of nodes from configfile which can be passed to node/nodes class
### Parameters:
- unique (str): Unique name of the node or folder in config using
connection manager style: node[@subfolder][@folder]
or [@subfolder]@folder
### Optional Parameters:
- keys (list): In case you pass a folder as unique, you can filter
nodes inside the folder passing a list.
### Returns:
dict: Dictionary containing information of node or multiple dictionaries
of multiple nodes.
'''
uniques = self._explode_unique(unique)
if unique.startswith("@"):
if uniques.keys() >= {"folder", "subfolder"}:
@ -116,6 +175,7 @@ class configfile:
return newnode
def _connections_add(self,*, id, host, folder='', subfolder='', options='', logs='', password='', port='', protocol='', user='', type = "connection" ):
#Add connection from config
if folder == '':
self.connections[id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user, "type": type}
elif folder != '' and subfolder == '':
@ -125,6 +185,7 @@ class configfile:
def _connections_del(self,*, id, folder='', subfolder=''):
#Delete connection from config
if folder == '':
del self.connections[id]
elif folder != '' and subfolder == '':
@ -133,6 +194,7 @@ class configfile:
del self.connections[folder][subfolder][id]
def _folder_add(self,*, folder, subfolder = ''):
#Add Folder from config
if subfolder == '':
if folder not in self.connections:
self.connections[folder] = {"type": "folder"}
@ -141,6 +203,7 @@ class configfile:
self.connections[folder][subfolder] = {"type": "subfolder"}
def _folder_del(self,*, folder, subfolder=''):
#Delete folder from config
if subfolder == '':
del self.connections[folder]
else:
@ -148,8 +211,10 @@ class configfile:
def _profiles_add(self,*, id, host = '', options='', logs='', password='', port='', protocol='', user='' ):
#Add profile from config
self.profiles[id] = {"host": host, "options": options, "logs": logs, "password": password, "port": port, "protocol": protocol, "user": user}
def _profiles_del(self,*, id ):
#Delete profile from config
del self.profiles[id]

View File

@ -9,15 +9,25 @@ import argparse
import sys
import inquirer
import json
from conn import *
from .core import node
#functions and classes
class connapp:
# Class that starts the connection manager app.
def __init__(self, config, node):
#Define the parser for the arguments
''' This class starts the connection manager app. It's normally used by connection manager but you can use it on a script to run the connection manager your way and use a different configfile and key.
'''
def __init__(self, config):
'''
### Parameters:
- config (obj): Object generated with configfile class, it contains
the nodes configuration and the methods to manage
the config file.
'''
self.node = node
self.config = config
self.nodes = self._getallnodes()
@ -134,7 +144,7 @@ class connapp:
self.config._folder_del(**uniques)
else:
self.config._connections_del(**uniques)
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("{} deleted succesfully".format(matches[0]))
elif args.action == "add":
if args.data == None:
@ -166,7 +176,7 @@ class connapp:
print("Folder {} not found".format(uniques["folder"]))
exit(2)
self.config._folder_add(**uniques)
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("{} added succesfully".format(args.data))
if type == "node":
@ -187,7 +197,7 @@ class connapp:
if newnode == False:
exit(7)
self.config._connections_add(**newnode)
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("{} added succesfully".format(args.data))
elif args.action == "show":
if args.data == None:
@ -227,7 +237,7 @@ class connapp:
return
else:
self.config._connections_add(**updatenode)
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("{} edited succesfully".format(args.data))
@ -252,7 +262,7 @@ class connapp:
confirm = inquirer.prompt(question)
if confirm["delete"]:
self.config._profiles_del(id = matches[0])
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("{} deleted succesfully".format(matches[0]))
elif args.action == "show":
matches = list(filter(lambda k: k == args.data[0], self.profiles))
@ -276,7 +286,7 @@ class connapp:
if newprofile == False:
exit(7)
self.config._profiles_add(**newprofile)
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("{} added succesfully".format(args.data[0]))
elif args.action == "mod":
matches = list(filter(lambda k: k == args.data[0], self.profiles))
@ -297,7 +307,7 @@ class connapp:
return
else:
self.config._profiles_add(**updateprofile)
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("{} edited succesfully".format(args.data[0]))
def _func_others(self, args):
@ -331,7 +341,7 @@ class connapp:
self.config._connections_add(**newnode)
if args.command == "move":
self.config._connections_del(**olduniques)
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
if args.command == "move":
print("{} moved succesfully to {}".format(args.data[0],args.data[1]))
if args.command == "cp":
@ -375,7 +385,7 @@ class connapp:
self.config._connections_add(**newnode)
self.nodes = self._getallnodes()
if count > 0:
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("Succesfully added {} nodes".format(count))
else:
print("0 nodes added")
@ -392,7 +402,7 @@ class connapp:
if args.data[0] < 0:
args.data[0] = 0
self.config.config[args.command] = args.data[0]
self.config.saveconfig(self.config.file)
self.config._saveconfig(self.config.file)
print("Config saved")
def _choose(self, list, name, action):
@ -759,7 +769,23 @@ complete -o nosort -F _conn conn
return nodes
def encrypt(self, password, keyfile=None):
#Encrypt password using keyfile
'''
Encrypts password using RSA keyfile
### Parameters:
- password (str): Plaintext password to encrypt.
### Optional Parameters:
- keyfile (str): Path/file to keyfile. Default is config keyfile.
### Returns:
str: Encrypted password.
'''
if keyfile is None:
keyfile = self.config.key
key = RSA.import_key(open(keyfile).read())
@ -768,9 +794,3 @@ complete -o nosort -F _conn conn
password = encryptor.encrypt(password.encode("utf-8"))
return str(password)
def main():
conf = configfile()
connapp(conf, node)
if __name__ == '__main__':
sys.exit(main())

File diff suppressed because it is too large Load Diff

View File

@ -30,4 +30,4 @@ install_requires =
[options.entry_points]
console_scripts =
conn = conn.connapp:main
conn = conn.__main__:main