Add bulk edit with regex and export/import folder

This commit is contained in:
Federico Luzzi 2023-09-12 12:33:33 -03:00
parent be40b2accd
commit 98b85628de
6 changed files with 266 additions and 65 deletions

View File

@ -133,6 +133,8 @@ positional arguments:
copy (cp) Copy node
list (ls) List profiles, nodes or folders
bulk Add nodes in bulk
export Export connection folder to Yaml file
import Import connection folder to config from Yaml file
run Run scripts or commands on nodes
config Manage app config
api Start and stop connpy api

View File

@ -39,6 +39,8 @@ Commands:
copy (cp) Copy node
list (ls) List profiles, nodes or folders
bulk Add nodes in bulk
export Export connection folder to Yaml file
import Import connection folder to config from Yaml file
run Run scripts or commands on nodes
config Manage app config
api Start and stop connpy api

View File

@ -1,2 +1,2 @@
__version__ = "3.3.1"
__version__ = "3.4.0"

View File

@ -203,7 +203,7 @@ class configfile:
### Parameters:
- uniques (str/list): Regex string name that will match hostnames
- uniques (str/list): String name that will match hostnames
from the connection manager. It can be a
list of strings.
@ -214,6 +214,8 @@ class configfile:
'''
nodes = {}
if isinstance(uniques, str):
uniques = [uniques]
for i in uniques:
if isinstance(i, dict):
name = list(i.keys())[0]
@ -303,7 +305,7 @@ class configfile:
raise ValueError("filter must be a string or a list of strings")
return nodes
def _getallnodesfull(self, filter = None):
def _getallnodesfull(self, filter = None, extract = True):
#get all nodes on configfile with all their attributes.
nodes = {}
layer1 = {k:v for k,v in self.connections.items() if isinstance(v, dict) and v["type"] == "connection"}
@ -323,19 +325,20 @@ class configfile:
nodes = {k: v for k, v in nodes.items() if any(re.search(pattern, k) for pattern in filter)}
else:
raise ValueError("filter must be a string or a list of strings")
for node, keys in nodes.items():
for key, value in keys.items():
profile = re.search("^@(.*)", str(value))
if profile:
try:
nodes[node][key] = self.profiles[profile.group(1)][key]
except:
nodes[node][key] = ""
elif value == '' and key == "protocol":
try:
nodes[node][key] = config.profiles["default"][key]
except:
nodes[node][key] = "ssh"
if extract:
for node, keys in nodes.items():
for key, value in keys.items():
profile = re.search("^@(.*)", str(value))
if profile:
try:
nodes[node][key] = self.profiles[profile.group(1)][key]
except:
nodes[node][key] = ""
elif value == '' and key == "protocol":
try:
nodes[node][key] = config.profiles["default"][key]
except:
nodes[node][key] = "ssh"
return nodes

View File

@ -13,6 +13,9 @@ from ._version import __version__
from .api import start_api,stop_api,debug_api
from .ai import ai
import yaml
class NoAliasDumper(yaml.SafeDumper):
def ignore_aliases(self, data):
return True
import ast
from rich import print as mdprint
from rich.markdown import Markdown
@ -102,6 +105,14 @@ class connapp:
bulkparser = subparsers.add_parser("bulk", help="Add nodes in bulk")
bulkparser.add_argument("bulk", const="bulk", nargs=0, action=self._store_type, help="Add nodes in bulk")
bulkparser.set_defaults(func=self._func_others)
# EXPORTPARSER
exportparser = subparsers.add_parser("export", help="Export connection folder to Yaml file")
exportparser.add_argument("export", nargs=2, action=self._store_type, help="export [@subfolder]@folder file.yaml", type=self._type_node)
exportparser.set_defaults(func=self._func_export)
# IMPORTPARSER
importparser = subparsers.add_parser("import", help="Import connection folder to config from Yaml file")
importparser.add_argument("import", nargs=1, action=self._store_type, help="import file.yaml", type=self._type_node)
importparser.set_defaults(func=self._func_import)
# AIPARSER
aiparser = subparsers.add_parser("ai", help="Make request to an AI")
aiparser.add_argument("ask", nargs='*', help="Ask connpy AI something")
@ -135,7 +146,7 @@ class connapp:
configcrud.add_argument("--openai-model", dest="model", nargs=1, action=self._store_type, help="Set openai model", metavar="MODEL")
configparser.set_defaults(func=self._func_others)
#Manage sys arguments
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config", "api", "ai"]
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config", "api", "ai", "export", "import"]
profilecmds = ["--add", "-a", "--del", "--rm", "-r", "--mod", "--edit", "-e", "--show", "-s"]
if len(argv) >= 2 and argv[1] == "profile" and argv[0] in profilecmds:
argv[1] = argv[0]
@ -291,27 +302,55 @@ class connapp:
if args.data == None:
print("Missing argument node")
exit(3)
matches = list(filter(lambda k: k == args.data, self.nodes))
# matches = list(filter(lambda k: k == args.data, self.nodes))
matches = self.config._getallnodes(args.data)
if len(matches) == 0:
print("{} not found".format(args.data))
print("No connection found with filter: {}".format(args.data))
exit(2)
node = self.config.getitem(matches[0])
elif len(matches) == 1:
uniques = self.config._explode_unique(args.data)
unique = matches[0]
else:
uniques = {"id": None, "folder": None}
unique = None
print("Editing: {}".format(matches))
node = {}
for i in matches:
node[i] = self.config.getitem(i)
edits = self._questions_edit()
if edits == None:
exit(7)
uniques = self.config._explode_unique(args.data)
updatenode = self._questions_nodes(args.data, uniques, edit=edits)
updatenode = self._questions_nodes(unique, uniques, edit=edits)
if not updatenode:
exit(7)
uniques.update(node)
uniques["type"] = "connection"
if sorted(updatenode.items()) == sorted(uniques.items()):
print("Nothing to do here")
return
if len(matches) == 1:
uniques.update(node[matches[0]])
uniques["type"] = "connection"
if sorted(updatenode.items()) == sorted(uniques.items()):
print("Nothing to do here")
return
else:
self.config._connections_add(**updatenode)
self.config._saveconfig(self.config.file)
print("{} edited succesfully".format(args.data))
else:
self.config._connections_add(**updatenode)
for k in node:
updatednode = self.config._explode_unique(k)
updatednode["type"] = "connection"
updatednode.update(node[k])
editcount = 0
for key, should_edit in edits.items():
if should_edit:
editcount += 1
updatednode[key] = updatenode[key]
if not editcount:
print("Nothing to do here")
return
else:
self.config._connections_add(**updatednode)
self.config._saveconfig(self.config.file)
print("{} edited succesfully".format(args.data))
print("{} edited succesfully".format(matches))
return
def _func_profile(self, args):
@ -526,6 +565,57 @@ class connapp:
self.config._saveconfig(self.config.file)
print("Config saved")
def _func_import(self, args):
if not os.path.exists(args.data[0]):
print("File {} dosn't exists".format(args.data[0]))
exit(14)
question = [inquirer.Confirm("import", message="Are you sure you want to import {} file? This could overwrite your current configuration".format(args.data[0]))]
confirm = inquirer.prompt(question)
if confirm == None:
exit(7)
if confirm["import"]:
try:
with open(args.data[0]) as file:
imported = yaml.load(file, Loader=yaml.FullLoader)
except:
print("failed reading file {}".format(args.data[0]))
exit(10)
for k,v in imported.items():
uniques = self.config._explode_unique(k)
folder = f"@{uniques['folder']}"
matches = list(filter(lambda k: k == folder, self.folders))
if len(matches) == 0:
uniquefolder = self.config._explode_unique(folder)
self.config._folder_add(**uniquefolder)
if "subfolder" in uniques:
subfolder = f"@{uniques['subfolder']}@{uniques['folder']}"
matches = list(filter(lambda k: k == subfolder, self.folders))
if len(matches) == 0:
uniquesubfolder = self.config._explode_unique(subfolder)
self.config._folder_add(**uniquesubfolder)
uniques.update(v)
self.config._connections_add(**uniques)
self.config._saveconfig(self.config.file)
print("File {} imported succesfully".format(args.data[0]))
return
def _func_export(self, args):
matches = list(filter(lambda k: k == args.data[0], self.folders))
if len(matches) == 0:
print("{} folder not found".format(args.data[0]))
exit(2)
if os.path.exists(args.data[1]):
print("File {} already exists".format(args.data[1]))
exit(14)
else:
foldercons = self.config._getallnodesfull(args.data[0], extract = False)
with open(args.data[1], "w") as file:
yaml.dump(foldercons, file, Dumper=NoAliasDumper, default_flow_style=False)
file.close()
print("File {} generated succesfully".format(args.data[1]))
exit()
return
def _func_run(self, args):
if len(args.data) > 1:
args.action = "noderun"
@ -916,7 +1006,7 @@ class connapp:
if "tags" not in defaults:
defaults["tags"] = ""
except:
defaults = { "host":"", "protocol":"", "port":"", "user":"", "options":"", "logs":"" , "tags":""}
defaults = { "host":"", "protocol":"", "port":"", "user":"", "options":"", "logs":"" , "tags":"", "password":""}
node = {}
if edit == None:
edit = { "host":True, "protocol":True, "port":True, "user":True, "password": True,"options":True, "logs":True, "tags":True }
@ -1082,7 +1172,7 @@ class connapp:
if type == "usage":
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 run Run scripts or commands on nodes\n config Manage app config\n api Start and stop connpy api\n ai Make request to an AI"
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 export Export connection folder to Yaml file\n import Import connection folder to config from Yaml file\n run Run scripts or commands on nodes\n config Manage app config\n api Start and stop connpy api\n ai Make request to an AI"
if type == "bashcompletion":
return '''
#Here starts bash completion for conn

View File

@ -58,6 +58,8 @@ Commands:
copy (cp) Copy node
list (ls) List profiles, nodes or folders
bulk Add nodes in bulk
export Export connection folder to Yaml file
import Import connection folder to config from Yaml file
run Run scripts or commands on nodes
config Manage app config
api Start and stop connpy api
@ -290,6 +292,8 @@ Commands:
copy (cp) Copy node
list (ls) List profiles, nodes or folders
bulk Add nodes in bulk
export Export connection folder to Yaml file
import Import connection folder to config from Yaml file
run Run scripts or commands on nodes
config Manage app config
api Start and stop connpy api
@ -1493,7 +1497,7 @@ Categorize the user's request based on the operation they want to perform on
### Parameters:
- uniques (str/list): Regex string name that will match hostnames
- uniques (str/list): String name that will match hostnames
from the connection manager. It can be a
list of strings.
@ -1504,6 +1508,8 @@ Categorize the user's request based on the operation they want to perform on
'''
nodes = {}
if isinstance(uniques, str):
uniques = [uniques]
for i in uniques:
if isinstance(i, dict):
name = list(i.keys())[0]
@ -1593,7 +1599,7 @@ Categorize the user's request based on the operation they want to perform on
raise ValueError("filter must be a string or a list of strings")
return nodes
def _getallnodesfull(self, filter = None):
def _getallnodesfull(self, filter = None, extract = True):
#get all nodes on configfile with all their attributes.
nodes = {}
layer1 = {k:v for k,v in self.connections.items() if isinstance(v, dict) and v["type"] == "connection"}
@ -1613,19 +1619,20 @@ Categorize the user's request based on the operation they want to perform on
nodes = {k: v for k, v in nodes.items() if any(re.search(pattern, k) for pattern in filter)}
else:
raise ValueError("filter must be a string or a list of strings")
for node, keys in nodes.items():
for key, value in keys.items():
profile = re.search("^@(.*)", str(value))
if profile:
try:
nodes[node][key] = self.profiles[profile.group(1)][key]
except:
nodes[node][key] = ""
elif value == '' and key == "protocol":
try:
nodes[node][key] = config.profiles["default"][key]
except:
nodes[node][key] = "ssh"
if extract:
for node, keys in nodes.items():
for key, value in keys.items():
profile = re.search("^@(.*)", str(value))
if profile:
try:
nodes[node][key] = self.profiles[profile.group(1)][key]
except:
nodes[node][key] = ""
elif value == '' and key == "protocol":
try:
nodes[node][key] = config.profiles["default"][key]
except:
nodes[node][key] = "ssh"
return nodes
@ -1740,7 +1747,7 @@ Categorize the user's request based on the operation they want to perform on
<dd>
<div class="desc"><p>Get a group of nodes from configfile which can be passed to node/nodes class</p>
<h3 id="parameters">Parameters:</h3>
<pre><code>- uniques (str/list): Regex string name that will match hostnames
<pre><code>- uniques (str/list): String name that will match hostnames
from the connection manager. It can be a
list of strings.
</code></pre>
@ -1758,7 +1765,7 @@ Categorize the user&#39;s request based on the operation they want to perform on
### Parameters:
- uniques (str/list): Regex string name that will match hostnames
- uniques (str/list): String name that will match hostnames
from the connection manager. It can be a
list of strings.
@ -1769,6 +1776,8 @@ Categorize the user&#39;s request based on the operation they want to perform on
&#39;&#39;&#39;
nodes = {}
if isinstance(uniques, str):
uniques = [uniques]
for i in uniques:
if isinstance(i, dict):
name = list(i.keys())[0]
@ -1883,6 +1892,14 @@ Categorize the user&#39;s request based on the operation they want to perform on
bulkparser = subparsers.add_parser(&#34;bulk&#34;, help=&#34;Add nodes in bulk&#34;)
bulkparser.add_argument(&#34;bulk&#34;, const=&#34;bulk&#34;, nargs=0, action=self._store_type, help=&#34;Add nodes in bulk&#34;)
bulkparser.set_defaults(func=self._func_others)
# EXPORTPARSER
exportparser = subparsers.add_parser(&#34;export&#34;, help=&#34;Export connection folder to Yaml file&#34;)
exportparser.add_argument(&#34;export&#34;, nargs=2, action=self._store_type, help=&#34;export [@subfolder]@folder file.yaml&#34;, type=self._type_node)
exportparser.set_defaults(func=self._func_export)
# IMPORTPARSER
importparser = subparsers.add_parser(&#34;import&#34;, help=&#34;Import connection folder to config from Yaml file&#34;)
importparser.add_argument(&#34;import&#34;, nargs=1, action=self._store_type, help=&#34;import file.yaml&#34;, type=self._type_node)
importparser.set_defaults(func=self._func_import)
# AIPARSER
aiparser = subparsers.add_parser(&#34;ai&#34;, help=&#34;Make request to an AI&#34;)
aiparser.add_argument(&#34;ask&#34;, nargs=&#39;*&#39;, help=&#34;Ask connpy AI something&#34;)
@ -1916,7 +1933,7 @@ Categorize the user&#39;s request based on the operation they want to perform on
configcrud.add_argument(&#34;--openai-model&#34;, dest=&#34;model&#34;, nargs=1, action=self._store_type, help=&#34;Set openai model&#34;, metavar=&#34;MODEL&#34;)
configparser.set_defaults(func=self._func_others)
#Manage sys arguments
commands = [&#34;node&#34;, &#34;profile&#34;, &#34;mv&#34;, &#34;move&#34;,&#34;copy&#34;, &#34;cp&#34;, &#34;bulk&#34;, &#34;ls&#34;, &#34;list&#34;, &#34;run&#34;, &#34;config&#34;, &#34;api&#34;, &#34;ai&#34;]
commands = [&#34;node&#34;, &#34;profile&#34;, &#34;mv&#34;, &#34;move&#34;,&#34;copy&#34;, &#34;cp&#34;, &#34;bulk&#34;, &#34;ls&#34;, &#34;list&#34;, &#34;run&#34;, &#34;config&#34;, &#34;api&#34;, &#34;ai&#34;, &#34;export&#34;, &#34;import&#34;]
profilecmds = [&#34;--add&#34;, &#34;-a&#34;, &#34;--del&#34;, &#34;--rm&#34;, &#34;-r&#34;, &#34;--mod&#34;, &#34;--edit&#34;, &#34;-e&#34;, &#34;--show&#34;, &#34;-s&#34;]
if len(argv) &gt;= 2 and argv[1] == &#34;profile&#34; and argv[0] in profilecmds:
argv[1] = argv[0]
@ -2072,27 +2089,55 @@ Categorize the user&#39;s request based on the operation they want to perform on
if args.data == None:
print(&#34;Missing argument node&#34;)
exit(3)
matches = list(filter(lambda k: k == args.data, self.nodes))
# matches = list(filter(lambda k: k == args.data, self.nodes))
matches = self.config._getallnodes(args.data)
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data))
print(&#34;No connection found with filter: {}&#34;.format(args.data))
exit(2)
node = self.config.getitem(matches[0])
elif len(matches) == 1:
uniques = self.config._explode_unique(args.data)
unique = matches[0]
else:
uniques = {&#34;id&#34;: None, &#34;folder&#34;: None}
unique = None
print(&#34;Editing: {}&#34;.format(matches))
node = {}
for i in matches:
node[i] = self.config.getitem(i)
edits = self._questions_edit()
if edits == None:
exit(7)
uniques = self.config._explode_unique(args.data)
updatenode = self._questions_nodes(args.data, uniques, edit=edits)
updatenode = self._questions_nodes(unique, uniques, edit=edits)
if not updatenode:
exit(7)
uniques.update(node)
uniques[&#34;type&#34;] = &#34;connection&#34;
if sorted(updatenode.items()) == sorted(uniques.items()):
print(&#34;Nothing to do here&#34;)
return
if len(matches) == 1:
uniques.update(node[matches[0]])
uniques[&#34;type&#34;] = &#34;connection&#34;
if sorted(updatenode.items()) == sorted(uniques.items()):
print(&#34;Nothing to do here&#34;)
return
else:
self.config._connections_add(**updatenode)
self.config._saveconfig(self.config.file)
print(&#34;{} edited succesfully&#34;.format(args.data))
else:
self.config._connections_add(**updatenode)
for k in node:
updatednode = self.config._explode_unique(k)
updatednode[&#34;type&#34;] = &#34;connection&#34;
updatednode.update(node[k])
editcount = 0
for key, should_edit in edits.items():
if should_edit:
editcount += 1
updatednode[key] = updatenode[key]
if not editcount:
print(&#34;Nothing to do here&#34;)
return
else:
self.config._connections_add(**updatednode)
self.config._saveconfig(self.config.file)
print(&#34;{} edited succesfully&#34;.format(args.data))
print(&#34;{} edited succesfully&#34;.format(matches))
return
def _func_profile(self, args):
@ -2307,6 +2352,57 @@ Categorize the user&#39;s request based on the operation they want to perform on
self.config._saveconfig(self.config.file)
print(&#34;Config saved&#34;)
def _func_import(self, args):
if not os.path.exists(args.data[0]):
print(&#34;File {} dosn&#39;t exists&#34;.format(args.data[0]))
exit(14)
question = [inquirer.Confirm(&#34;import&#34;, message=&#34;Are you sure you want to import {} file? This could overwrite your current configuration&#34;.format(args.data[0]))]
confirm = inquirer.prompt(question)
if confirm == None:
exit(7)
if confirm[&#34;import&#34;]:
try:
with open(args.data[0]) as file:
imported = yaml.load(file, Loader=yaml.FullLoader)
except:
print(&#34;failed reading file {}&#34;.format(args.data[0]))
exit(10)
for k,v in imported.items():
uniques = self.config._explode_unique(k)
folder = f&#34;@{uniques[&#39;folder&#39;]}&#34;
matches = list(filter(lambda k: k == folder, self.folders))
if len(matches) == 0:
uniquefolder = self.config._explode_unique(folder)
self.config._folder_add(**uniquefolder)
if &#34;subfolder&#34; in uniques:
subfolder = f&#34;@{uniques[&#39;subfolder&#39;]}@{uniques[&#39;folder&#39;]}&#34;
matches = list(filter(lambda k: k == subfolder, self.folders))
if len(matches) == 0:
uniquesubfolder = self.config._explode_unique(subfolder)
self.config._folder_add(**uniquesubfolder)
uniques.update(v)
self.config._connections_add(**uniques)
self.config._saveconfig(self.config.file)
print(&#34;File {} imported succesfully&#34;.format(args.data[0]))
return
def _func_export(self, args):
matches = list(filter(lambda k: k == args.data[0], self.folders))
if len(matches) == 0:
print(&#34;{} folder not found&#34;.format(args.data[0]))
exit(2)
if os.path.exists(args.data[1]):
print(&#34;File {} already exists&#34;.format(args.data[1]))
exit(14)
else:
foldercons = self.config._getallnodesfull(args.data[0], extract = False)
with open(args.data[1], &#34;w&#34;) as file:
yaml.dump(foldercons, file, Dumper=NoAliasDumper, default_flow_style=False)
file.close()
print(&#34;File {} generated succesfully&#34;.format(args.data[1]))
exit()
return
def _func_run(self, args):
if len(args.data) &gt; 1:
args.action = &#34;noderun&#34;
@ -2697,7 +2793,7 @@ Categorize the user&#39;s request based on the operation they want to perform on
if &#34;tags&#34; not in defaults:
defaults[&#34;tags&#34;] = &#34;&#34;
except:
defaults = { &#34;host&#34;:&#34;&#34;, &#34;protocol&#34;:&#34;&#34;, &#34;port&#34;:&#34;&#34;, &#34;user&#34;:&#34;&#34;, &#34;options&#34;:&#34;&#34;, &#34;logs&#34;:&#34;&#34; , &#34;tags&#34;:&#34;&#34;}
defaults = { &#34;host&#34;:&#34;&#34;, &#34;protocol&#34;:&#34;&#34;, &#34;port&#34;:&#34;&#34;, &#34;user&#34;:&#34;&#34;, &#34;options&#34;:&#34;&#34;, &#34;logs&#34;:&#34;&#34; , &#34;tags&#34;:&#34;&#34;, &#34;password&#34;:&#34;&#34;}
node = {}
if edit == None:
edit = { &#34;host&#34;:True, &#34;protocol&#34;:True, &#34;port&#34;:True, &#34;user&#34;:True, &#34;password&#34;: True,&#34;options&#34;:True, &#34;logs&#34;:True, &#34;tags&#34;:True }
@ -2863,7 +2959,7 @@ Categorize the user&#39;s request based on the operation they want to perform on
if type == &#34;usage&#34;:
return &#34;conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]\n conn {profile,move,mv,copy,cp,list,ls,bulk,config} ...&#34;
if type == &#34;end&#34;:
return &#34;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 run Run scripts or commands on nodes\n config Manage app config\n api Start and stop connpy api\n ai Make request to an AI&#34;
return &#34;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 export Export connection folder to Yaml file\n import Import connection folder to config from Yaml file\n run Run scripts or commands on nodes\n config Manage app config\n api Start and stop connpy api\n ai Make request to an AI&#34;
if type == &#34;bashcompletion&#34;:
return &#39;&#39;&#39;
#Here starts bash completion for conn
@ -3050,7 +3146,7 @@ tasks:
</details>
</dd>
<dt id="connpy.connapp.start"><code class="name flex">
<span>def <span class="ident">start</span></span>(<span>self, argv=['connpy', '-o', 'docs/', '--html', '--force'])</span>
<span>def <span class="ident">start</span></span>(<span>self, argv=['connpy', '-o', 'docs/', '--force', '--html'])</span>
</code></dt>
<dd>
<div class="desc"><h3 id="parameters">Parameters:</h3>
@ -3109,6 +3205,14 @@ tasks:
bulkparser = subparsers.add_parser(&#34;bulk&#34;, help=&#34;Add nodes in bulk&#34;)
bulkparser.add_argument(&#34;bulk&#34;, const=&#34;bulk&#34;, nargs=0, action=self._store_type, help=&#34;Add nodes in bulk&#34;)
bulkparser.set_defaults(func=self._func_others)
# EXPORTPARSER
exportparser = subparsers.add_parser(&#34;export&#34;, help=&#34;Export connection folder to Yaml file&#34;)
exportparser.add_argument(&#34;export&#34;, nargs=2, action=self._store_type, help=&#34;export [@subfolder]@folder file.yaml&#34;, type=self._type_node)
exportparser.set_defaults(func=self._func_export)
# IMPORTPARSER
importparser = subparsers.add_parser(&#34;import&#34;, help=&#34;Import connection folder to config from Yaml file&#34;)
importparser.add_argument(&#34;import&#34;, nargs=1, action=self._store_type, help=&#34;import file.yaml&#34;, type=self._type_node)
importparser.set_defaults(func=self._func_import)
# AIPARSER
aiparser = subparsers.add_parser(&#34;ai&#34;, help=&#34;Make request to an AI&#34;)
aiparser.add_argument(&#34;ask&#34;, nargs=&#39;*&#39;, help=&#34;Ask connpy AI something&#34;)
@ -3142,7 +3246,7 @@ tasks:
configcrud.add_argument(&#34;--openai-model&#34;, dest=&#34;model&#34;, nargs=1, action=self._store_type, help=&#34;Set openai model&#34;, metavar=&#34;MODEL&#34;)
configparser.set_defaults(func=self._func_others)
#Manage sys arguments
commands = [&#34;node&#34;, &#34;profile&#34;, &#34;mv&#34;, &#34;move&#34;,&#34;copy&#34;, &#34;cp&#34;, &#34;bulk&#34;, &#34;ls&#34;, &#34;list&#34;, &#34;run&#34;, &#34;config&#34;, &#34;api&#34;, &#34;ai&#34;]
commands = [&#34;node&#34;, &#34;profile&#34;, &#34;mv&#34;, &#34;move&#34;,&#34;copy&#34;, &#34;cp&#34;, &#34;bulk&#34;, &#34;ls&#34;, &#34;list&#34;, &#34;run&#34;, &#34;config&#34;, &#34;api&#34;, &#34;ai&#34;, &#34;export&#34;, &#34;import&#34;]
profilecmds = [&#34;--add&#34;, &#34;-a&#34;, &#34;--del&#34;, &#34;--rm&#34;, &#34;-r&#34;, &#34;--mod&#34;, &#34;--edit&#34;, &#34;-e&#34;, &#34;--show&#34;, &#34;-s&#34;]
if len(argv) &gt;= 2 and argv[1] == &#34;profile&#34; and argv[0] in profilecmds:
argv[1] = argv[0]