change yaml to json for speed. Add completion

This commit is contained in:
fluzzi 2022-03-29 18:57:27 -03:00
parent 9f3cb6f6d9
commit 8f13b0b2bf
5 changed files with 90 additions and 28 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
#Imports #Imports
import yaml import json
import os import os
import re import re
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
@ -14,7 +14,7 @@ class configfile:
def __init__(self, conf = None, *, key = None): def __init__(self, conf = None, *, key = None):
home = os.path.expanduser("~") home = os.path.expanduser("~")
self.defaultdir = home + '/.config/conn' self.defaultdir = home + '/.config/conn'
self.defaultfile = self.defaultdir + '/config.yaml' self.defaultfile = self.defaultdir + '/config.json'
self.defaultkey = self.defaultdir + '/.osk' self.defaultkey = self.defaultdir + '/.osk'
Path(self.defaultdir).mkdir(parents=True, exist_ok=True) Path(self.defaultdir).mkdir(parents=True, exist_ok=True)
if conf == None: if conf == None:
@ -39,18 +39,18 @@ class configfile:
def loadconfig(self, conf): def loadconfig(self, conf):
ymlconf = open(conf) jsonconf = open(conf)
return yaml.load(ymlconf.read(), Loader=yaml.CLoader) return json.load(jsonconf)
def createconfig(self, conf): def createconfig(self, conf):
defaultconfig = {'config': {'case': False, 'idletime': 30}, 'connections': {}, 'profiles': { "default": { "host":"", "protocol":"ssh", "port":"", "user":"", "password":"", "options":"", "logs":"" }}} defaultconfig = {'config': {'case': False, 'idletime': 30}, 'connections': {}, 'profiles': { "default": { "host":"", "protocol":"ssh", "port":"", "user":"", "password":"", "options":"", "logs":"" }}}
if not os.path.exists(conf): if not os.path.exists(conf):
with open(conf, "w") as f: with open(conf, "w") as f:
yaml.dump(defaultconfig, f, explicit_start=True, Dumper=yaml.CDumper) json.dump(defaultconfig, f, indent = 4)
f.close() f.close()
os.chmod(conf, 0o600) os.chmod(conf, 0o600)
ymlconf = open(conf) jsonconf = open(conf)
return yaml.load(ymlconf.read(), Loader=yaml.CLoader) return json.load(jsonconf)
def saveconfig(self, conf): def saveconfig(self, conf):
newconfig = {"config":{}, "connections": {}, "profiles": {}} newconfig = {"config":{}, "connections": {}, "profiles": {}}
@ -58,7 +58,7 @@ class configfile:
newconfig["connections"] = self.connections newconfig["connections"] = self.connections
newconfig["profiles"] = self.profiles newconfig["profiles"] = self.profiles
with open(conf, "w") as f: with open(conf, "w") as f:
yaml.dump(newconfig, f, explicit_start=True, Dumper=yaml.CDumper) json.dump(newconfig, f, indent = 4)
f.close() f.close()
def createkey(self, keyfile): def createkey(self, keyfile):

View File

@ -8,7 +8,7 @@ import ast
import argparse import argparse
import sys import sys
import inquirer import inquirer
import yaml import json
#functions and classes #functions and classes
@ -63,6 +63,7 @@ class connapp:
configparser = subparsers.add_parser("config", help="Manage app config") configparser = subparsers.add_parser("config", help="Manage app config")
configparser.add_argument("--allow-uppercase", dest="case", nargs=1, action=self.store_type, help="Allow case sensitive names", choices=["true","false"]) configparser.add_argument("--allow-uppercase", dest="case", nargs=1, action=self.store_type, help="Allow case sensitive names", choices=["true","false"])
configparser.add_argument("--keepalive", dest="idletime", nargs=1, action=self.store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT") configparser.add_argument("--keepalive", dest="idletime", nargs=1, action=self.store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT")
configparser.add_argument("--completion", dest="completion", nargs=0, action=self.store_type, help="Get bash completion configuration for conn")
configparser.set_defaults(func=self._func_others) configparser.set_defaults(func=self._func_others)
#Set default subparser and tune arguments #Set default subparser and tune arguments
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "config"] commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "config"]
@ -188,7 +189,13 @@ class connapp:
print("{} not found".format(args.data)) print("{} not found".format(args.data))
exit(2) exit(2)
node = self.config.getitem(matches[0]) node = self.config.getitem(matches[0])
print(yaml.dump(node, Dumper=yaml.CDumper)) for k, v in node.items():
if isinstance(v, str):
print(k + ": " + v)
else:
print(k + ":")
for i in v:
print(" - " + i)
elif args.action == "mod": elif args.action == "mod":
if args.data == None: if args.data == None:
print("Missing argument node") print("Missing argument node")
@ -243,7 +250,13 @@ class connapp:
print("{} not found".format(args.data[0])) print("{} not found".format(args.data[0]))
exit(2) exit(2)
profile = self.config.profiles[matches[0]] profile = self.config.profiles[matches[0]]
print(yaml.dump(profile, Dumper=yaml.CDumper)) for k, v in profile.items():
if isinstance(v, str):
print(k + ": " + v)
else:
print(k + ":")
for i in v:
print(" - " + i)
elif args.action == "add": elif args.action == "add":
matches = list(filter(lambda k: k == args.data[0], self.profiles)) matches = list(filter(lambda k: k == args.data[0], self.profiles))
if len(matches) > 0: if len(matches) > 0:
@ -356,17 +369,20 @@ class connapp:
else: else:
print("0 nodes added") print("0 nodes added")
else: else:
if args.command == "case": if args.command == "completion":
if args.data[0] == "true": print(self._help("completion"))
args.data[0] = True else:
elif args.data[0] == "false": if args.command == "case":
args.data[0] = False if args.data[0] == "true":
if args.command == "idletime": args.data[0] = True
if args.data[0] < 0: elif args.data[0] == "false":
args.data[0] = 0 args.data[0] = False
self.config.config[args.command] = args.data[0] if args.command == "idletime":
self.config.saveconfig(self.config.file) if args.data[0] < 0:
print("Config saved") args.data[0] = 0
self.config.config[args.command] = args.data[0]
self.config.saveconfig(self.config.file)
print("Config saved")
def _choose(self, list, name, action): def _choose(self, list, name, action):
questions = [inquirer.List(name, message="Pick {} to {}:".format(name,action), choices=list, carousel=True)] questions = [inquirer.List(name, message="Pick {} to {}:".format(name,action), choices=list, carousel=True)]
@ -627,6 +643,53 @@ class connapp:
return "conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]\n conn {profile,move,mv,copy,cp,list,ls,bulk,config} ..." return "conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]\n conn {profile,move,mv,copy,cp,list,ls,bulk,config} ..."
if type == "end": if type == "end":
return "Commands:\n profile Manage profiles\n move (mv) Move node\n copy (cp) Copy node\n list (ls) List profiles, nodes or folders\n bulk Add nodes in bulk\n config Manage app config" return "Commands:\n profile Manage profiles\n move (mv) Move node\n copy (cp) Copy node\n list (ls) List profiles, nodes or folders\n bulk Add nodes in bulk\n config Manage app config"
if type == "completion":
return '''
#Here starts bash completion for conn
#You need jq installed in order to use this
_conn()
{
DATADIR=$HOME/.config/conn
mapfile -t connections < <(jq -r ' .["connections"] | paths as $path | select(getpath($path) == "connection") | $path | [map(select(. != "type"))[-1,-2,-3]] | map(select(. !=null)) | join("@")' $DATADIR/config.json)
mapfile -t folders < <(jq -r ' .["connections"] | paths as $path | select(getpath($path) == "folder" or getpath($path) == "subfolder") | $path | [map(select(. != "type"))[-1,-2]] | map(select(. !=null)) | join("@")' $DATADIR/config.json)
mapfile -t profiles < <(jq -r '.["profiles"] | keys[]' $DATADIR/config.json)
if [ "${#COMP_WORDS[@]}" = "2" ]; then
strings="--add --del --rm --edit --mod mv --show ls cp profile bulk config --help"
strings="$strings ${connections[@]} ${folders[@]/#/@}"
COMPREPLY=($(compgen -W "$strings" -- "${COMP_WORDS[1]}"))
fi
if [ "${#COMP_WORDS[@]}" = "3" ]; then
strings=""
if [ "${COMP_WORDS[1]}" = "profile" ]; then strings="--add --rm --del --edit --mod --show --help"; fi
if [ "${COMP_WORDS[1]}" = "config" ]; then strings="--allow-uppercase --keepalive --completion --help"; fi
if [[ "${COMP_WORDS[1]}" =~ ^--mod|--edit|--show|--add|--rm|--del$ ]]; then strings="profile"; fi
if [[ "${COMP_WORDS[1]}" =~ ^list|ls$ ]]; then strings="profiles nodes folders"; fi
if [[ "${COMP_WORDS[1]}" =~ ^bulk|mv|move|cp|copy$$ ]]; then strings="--help"; fi
if [[ "${COMP_WORDS[1]}" =~ ^--rm|--del$ ]]; then strings="$strings ${folders[@]/#/@}"; fi
if [[ "${COMP_WORDS[1]}" =~ ^--rm|--del|--mod|--edit|mv|move|cp|copy|--show$ ]]; then
strings="$strings ${connections[@]}"
fi
COMPREPLY=($(compgen -W "$strings" -- "${COMP_WORDS[2]}"))
fi
if [ "${#COMP_WORDS[@]}" = "4" ]; then
strings=""
if [ "${COMP_WORDS[1]}" = "profile" ]; then
if [[ "${COMP_WORDS[2]}" =~ ^--rm|--del|--mod|--edit|--show$ ]] ; then
strings="$strings ${profiles[@]}"
fi
fi
if [ "${COMP_WORDS[2]}" = "profile" ]; then
if [[ "${COMP_WORDS[1]}" =~ ^--rm|--remove|--del|--mod|--edit|--show$ ]] ; then
strings="$strings ${profiles[@]}"
fi
fi
COMPREPLY=($(compgen -W "$strings" -- "${COMP_WORDS[3]}"))
fi
}
complete -o nosort -F _conn conn
'''
def _getallnodes(self): def _getallnodes(self):
nodes = [] nodes = []
@ -672,4 +735,4 @@ class connapp:
publickey = key.publickey() publickey = key.publickey()
encryptor = PKCS1_OAEP.new(publickey) encryptor = PKCS1_OAEP.new(publickey)
password = encryptor.encrypt(password.encode("utf-8")) password = encryptor.encrypt(password.encode("utf-8"))
return password return str(password)

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
#Imports #Imports
import yaml
import os import os
import re import re
import pexpect import pexpect
@ -52,11 +51,11 @@ class node:
key = RSA.import_key(open(keyfile).read()) key = RSA.import_key(open(keyfile).read())
decryptor = PKCS1_OAEP.new(key) decryptor = PKCS1_OAEP.new(key)
for passwd in passwords: for passwd in passwords:
if isinstance(passwd, str): if not re.match('^b[\"\'].+[\"\']$', passwd):
dpass.append(passwd) dpass.append(passwd)
else: else:
try: try:
decrypted = decryptor.decrypt(ast.literal_eval(str(passwd))).decode("utf-8") decrypted = decryptor.decrypt(ast.literal_eval(passwd)).decode("utf-8")
dpass.append(decrypted) dpass.append(decrypted)
except: except:
raise ValueError("Missing or corrupted key") raise ValueError("Missing or corrupted key")

View File

@ -1,4 +1,4 @@
inquirer~=2.9.2 inquirer~=2.9.2
pexpect~=4.8.0 pexpect~=4.8.0
pycryptodome~=3.14.1 pycryptodome~=3.14.1
PyYAML~=6.0 setuptools~=59.4.0

View File

@ -18,7 +18,7 @@ setup(
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url="https://github.com/fluzzi/connpy", url="https://github.com/fluzzi/connpy",
packages=find_packages(), packages=find_packages(),
install_requires=["inquirer","pexpect","pycryptodome","PyYAML"], # add any additional packages that install_requires=["inquirer","pexpect","pycryptodome"], # add any additional packages that
keywords=['networking', 'automation', 'ssh', 'telnet', 'connection manager'], keywords=['networking', 'automation', 'ssh', 'telnet', 'connection manager'],
classifiers= [ classifiers= [
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",