From 8f13b0b2bfa7b4c6aaa3e294061ecd7584ca257b Mon Sep 17 00:00:00 2001 From: fluzzi Date: Tue, 29 Mar 2022 18:57:27 -0300 Subject: [PATCH] change yaml to json for speed. Add completion --- conn/configfile.py | 16 ++++---- conn/connapp.py | 93 ++++++++++++++++++++++++++++++++++++++-------- conn/core.py | 5 +-- requirements.txt | 2 +- setup.py | 2 +- 5 files changed, 90 insertions(+), 28 deletions(-) diff --git a/conn/configfile.py b/conn/configfile.py index 28dd0c8..23cfabe 100755 --- a/conn/configfile.py +++ b/conn/configfile.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 #Imports -import yaml +import json import os import re from Crypto.PublicKey import RSA @@ -14,7 +14,7 @@ class configfile: def __init__(self, conf = None, *, key = None): home = os.path.expanduser("~") self.defaultdir = home + '/.config/conn' - self.defaultfile = self.defaultdir + '/config.yaml' + self.defaultfile = self.defaultdir + '/config.json' self.defaultkey = self.defaultdir + '/.osk' Path(self.defaultdir).mkdir(parents=True, exist_ok=True) if conf == None: @@ -39,18 +39,18 @@ class configfile: def loadconfig(self, conf): - ymlconf = open(conf) - return yaml.load(ymlconf.read(), Loader=yaml.CLoader) + jsonconf = open(conf) + return json.load(jsonconf) def createconfig(self, conf): 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: - yaml.dump(defaultconfig, f, explicit_start=True, Dumper=yaml.CDumper) + json.dump(defaultconfig, f, indent = 4) f.close() os.chmod(conf, 0o600) - ymlconf = open(conf) - return yaml.load(ymlconf.read(), Loader=yaml.CLoader) + jsonconf = open(conf) + return json.load(jsonconf) def saveconfig(self, conf): newconfig = {"config":{}, "connections": {}, "profiles": {}} @@ -58,7 +58,7 @@ class configfile: newconfig["connections"] = self.connections newconfig["profiles"] = self.profiles with open(conf, "w") as f: - yaml.dump(newconfig, f, explicit_start=True, Dumper=yaml.CDumper) + json.dump(newconfig, f, indent = 4) f.close() def createkey(self, keyfile): diff --git a/conn/connapp.py b/conn/connapp.py index 423b862..d575903 100755 --- a/conn/connapp.py +++ b/conn/connapp.py @@ -8,7 +8,7 @@ import ast import argparse import sys import inquirer -import yaml +import json #functions and classes @@ -63,6 +63,7 @@ class connapp: 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("--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) #Set default subparser and tune arguments commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "config"] @@ -188,7 +189,13 @@ class connapp: print("{} not found".format(args.data)) exit(2) 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": if args.data == None: print("Missing argument node") @@ -243,7 +250,13 @@ class connapp: print("{} not found".format(args.data[0])) exit(2) 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": matches = list(filter(lambda k: k == args.data[0], self.profiles)) if len(matches) > 0: @@ -356,17 +369,20 @@ class connapp: else: print("0 nodes added") else: - if args.command == "case": - if args.data[0] == "true": - args.data[0] = True - elif args.data[0] == "false": - args.data[0] = False - if args.command == "idletime": - if args.data[0] < 0: - args.data[0] = 0 - self.config.config[args.command] = args.data[0] - self.config.saveconfig(self.config.file) - print("Config saved") + if args.command == "completion": + print(self._help("completion")) + else: + if args.command == "case": + if args.data[0] == "true": + args.data[0] = True + elif args.data[0] == "false": + args.data[0] = False + if args.command == "idletime": + if args.data[0] < 0: + 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): 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} ..." 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" + 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): nodes = [] @@ -672,4 +735,4 @@ class connapp: publickey = key.publickey() encryptor = PKCS1_OAEP.new(publickey) password = encryptor.encrypt(password.encode("utf-8")) - return password + return str(password) diff --git a/conn/core.py b/conn/core.py index df856b1..2c58ef5 100755 --- a/conn/core.py +++ b/conn/core.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 #Imports -import yaml import os import re import pexpect @@ -52,11 +51,11 @@ class node: key = RSA.import_key(open(keyfile).read()) decryptor = PKCS1_OAEP.new(key) for passwd in passwords: - if isinstance(passwd, str): + if not re.match('^b[\"\'].+[\"\']$', passwd): dpass.append(passwd) else: 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) except: raise ValueError("Missing or corrupted key") diff --git a/requirements.txt b/requirements.txt index f191bb5..1ed4b7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ inquirer~=2.9.2 pexpect~=4.8.0 pycryptodome~=3.14.1 -PyYAML~=6.0 +setuptools~=59.4.0 diff --git a/setup.py b/setup.py index ddeafaa..55911af 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( long_description_content_type="text/markdown", url="https://github.com/fluzzi/connpy", 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'], classifiers= [ "Development Status :: 4 - Beta",