add api
This commit is contained in:
@ -60,6 +60,7 @@ Commands:
|
||||
bulk Add nodes in bulk
|
||||
run Run scripts or commands on nodes
|
||||
config Manage app config
|
||||
api Start and stop connpy api
|
||||
</code></pre>
|
||||
<h3 id="manage-profiles">Manage profiles</h3>
|
||||
<pre><code>usage: conn profile [-h] (--add | --del | --mod | --show) profile
|
||||
@ -85,6 +86,50 @@ options:
|
||||
conn pc@office
|
||||
conn server
|
||||
</code></pre>
|
||||
<h2 id="http-api">http API</h2>
|
||||
<p>With the Connpy API you can run commands on devices using http requests</p>
|
||||
<h3 id="1-list-nodes">1. List Nodes</h3>
|
||||
<p><strong>Endpoint</strong>: <code>/list_nodes</code></p>
|
||||
<p><strong>Method</strong>: <code>POST</code></p>
|
||||
<p><strong>Description</strong>: This route returns a list of nodes. It can also filter the list based on a given keyword.</p>
|
||||
<h4 id="request-body">Request Body:</h4>
|
||||
<pre><code class="language-json">{
|
||||
"filter": "<keyword>"
|
||||
}
|
||||
</code></pre>
|
||||
<ul>
|
||||
<li><code>filter</code> (optional): A keyword to filter the list of nodes. It returns only the nodes that contain the keyword. If not provided, the route will return the entire list of nodes.</li>
|
||||
</ul>
|
||||
<h4 id="response">Response:</h4>
|
||||
<ul>
|
||||
<li>A JSON array containing the filtered list of nodes.</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h3 id="2-run-commands">2. Run Commands</h3>
|
||||
<p><strong>Endpoint</strong>: <code>/run_commands</code></p>
|
||||
<p><strong>Method</strong>: <code>POST</code></p>
|
||||
<p><strong>Description</strong>: This route runs commands on selected nodes based on the provided action, nodes, and commands. It also supports executing tests by providing expected results.</p>
|
||||
<h4 id="request-body_1">Request Body:</h4>
|
||||
<pre><code class="language-json">{
|
||||
"action": "<action>",
|
||||
"nodes": "<nodes>",
|
||||
"commands": "<commands>",
|
||||
"expected": "<expected>",
|
||||
"options": "<options>"
|
||||
}
|
||||
</code></pre>
|
||||
<ul>
|
||||
<li><code>action</code> (required): The action to be performed. Possible values: <code>run</code> or <code>test</code>.</li>
|
||||
<li><code><a title="connpy.nodes" href="#connpy.nodes">nodes</a></code> (required): A list of nodes or a single node on which the commands will be executed. The nodes can be specified as individual node names or a node group with the <code>@</code> prefix. Node groups can also be specified as arrays with a list of nodes inside the group.</li>
|
||||
<li><code>commands</code> (required): A list of commands to be executed on the specified nodes.</li>
|
||||
<li><code>expected</code> (optional, only used when the action is <code>test</code>): A single expected result for the test.</li>
|
||||
<li><code>options</code> (optional): Array to pass options to the run command, options are: <code>prompt</code>, <code>parallel</code>, <code>timeout</code>
|
||||
</li>
|
||||
</ul>
|
||||
<h4 id="response_1">Response:</h4>
|
||||
<ul>
|
||||
<li>A JSON object with the results of the executed commands on the nodes.</li>
|
||||
</ul>
|
||||
<h2 id="automation-module">Automation module</h2>
|
||||
<p>the automation module</p>
|
||||
<h3 id="standalone-module">Standalone module</h3>
|
||||
@ -200,6 +245,7 @@ Commands:
|
||||
bulk Add nodes in bulk
|
||||
run Run scripts or commands on nodes
|
||||
config Manage app config
|
||||
api Start and stop connpy api
|
||||
```
|
||||
|
||||
### Manage profiles
|
||||
@ -229,7 +275,62 @@ options:
|
||||
conn pc@office
|
||||
conn server
|
||||
```
|
||||
## http API
|
||||
With the Connpy API you can run commands on devices using http requests
|
||||
|
||||
### 1. List Nodes
|
||||
|
||||
**Endpoint**: `/list_nodes`
|
||||
|
||||
**Method**: `POST`
|
||||
|
||||
**Description**: This route returns a list of nodes. It can also filter the list based on a given keyword.
|
||||
|
||||
#### Request Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"filter": "<keyword>"
|
||||
}
|
||||
```
|
||||
|
||||
* `filter` (optional): A keyword to filter the list of nodes. It returns only the nodes that contain the keyword. If not provided, the route will return the entire list of nodes.
|
||||
|
||||
#### Response:
|
||||
|
||||
- A JSON array containing the filtered list of nodes.
|
||||
|
||||
---
|
||||
|
||||
### 2. Run Commands
|
||||
|
||||
**Endpoint**: `/run_commands`
|
||||
|
||||
**Method**: `POST`
|
||||
|
||||
**Description**: This route runs commands on selected nodes based on the provided action, nodes, and commands. It also supports executing tests by providing expected results.
|
||||
|
||||
#### Request Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "<action>",
|
||||
"nodes": "<nodes>",
|
||||
"commands": "<commands>",
|
||||
"expected": "<expected>",
|
||||
"options": "<options>"
|
||||
}
|
||||
```
|
||||
|
||||
* `action` (required): The action to be performed. Possible values: `run` or `test`.
|
||||
* `nodes` (required): A list of nodes or a single node on which the commands will be executed. The nodes can be specified as individual node names or a node group with the `@` prefix. Node groups can also be specified as arrays with a list of nodes inside the group.
|
||||
* `commands` (required): A list of commands to be executed on the specified nodes.
|
||||
* `expected` (optional, only used when the action is `test`): A single expected result for the test.
|
||||
* `options` (optional): Array to pass options to the run command, options are: `prompt`, `parallel`, `timeout`
|
||||
|
||||
#### Response:
|
||||
|
||||
- A JSON object with the results of the executed commands on the nodes.
|
||||
## Automation module
|
||||
the automation module
|
||||
|
||||
@ -316,6 +417,7 @@ __author__ = "Federico Luzzi"
|
||||
__pdoc__ = {
|
||||
'core': False,
|
||||
'completion': False,
|
||||
'api': False
|
||||
}</code></pre>
|
||||
</details>
|
||||
</section>
|
||||
@ -404,9 +506,17 @@ __pdoc__ = {
|
||||
'''
|
||||
home = os.path.expanduser("~")
|
||||
defaultdir = home + '/.config/conn'
|
||||
defaultfile = defaultdir + '/config.json'
|
||||
defaultkey = defaultdir + '/.osk'
|
||||
Path(defaultdir).mkdir(parents=True, exist_ok=True)
|
||||
pathfile = defaultdir + '/.folder'
|
||||
try:
|
||||
with open(pathfile, "r") as f:
|
||||
configdir = f.read().strip()
|
||||
except:
|
||||
with open(pathfile, "w") as f:
|
||||
f.write(str(defaultdir))
|
||||
configdir = defaultdir
|
||||
defaultfile = configdir + '/config.json'
|
||||
defaultkey = configdir + '/.osk'
|
||||
if conf == None:
|
||||
self.file = defaultfile
|
||||
else:
|
||||
@ -587,7 +697,47 @@ __pdoc__ = {
|
||||
|
||||
def _profiles_del(self,*, id ):
|
||||
#Delete profile from config
|
||||
del self.profiles[id]</code></pre>
|
||||
del self.profiles[id]
|
||||
|
||||
def _getallnodes(self):
|
||||
#get all nodes on configfile
|
||||
nodes = []
|
||||
layer1 = [k for k,v in self.connections.items() if isinstance(v, dict) and v["type"] == "connection"]
|
||||
folders = [k for k,v in self.connections.items() if isinstance(v, dict) and v["type"] == "folder"]
|
||||
nodes.extend(layer1)
|
||||
for f in folders:
|
||||
layer2 = [k + "@" + f for k,v in self.connections[f].items() if isinstance(v, dict) and v["type"] == "connection"]
|
||||
nodes.extend(layer2)
|
||||
subfolders = [k for k,v in self.connections[f].items() if isinstance(v, dict) and v["type"] == "subfolder"]
|
||||
for s in subfolders:
|
||||
layer3 = [k + "@" + s + "@" + f for k,v in self.connections[f][s].items() if isinstance(v, dict) and v["type"] == "connection"]
|
||||
nodes.extend(layer3)
|
||||
return nodes
|
||||
|
||||
def _getallfolders(self):
|
||||
#get all folders on configfile
|
||||
folders = ["@" + k for k,v in self.connections.items() if isinstance(v, dict) and v["type"] == "folder"]
|
||||
subfolders = []
|
||||
for f in folders:
|
||||
s = ["@" + k + f for k,v in self.connections[f[1:]].items() if isinstance(v, dict) and v["type"] == "subfolder"]
|
||||
subfolders.extend(s)
|
||||
folders.extend(subfolders)
|
||||
return folders
|
||||
|
||||
def _profileused(self, profile):
|
||||
#Check if profile is used before deleting it
|
||||
nodes = []
|
||||
layer1 = [k for k,v in self.connections.items() if isinstance(v, dict) and v["type"] == "connection" and ("@" + profile in v.values() or ( isinstance(v["password"],list) and "@" + profile in v["password"]))]
|
||||
folders = [k for k,v in self.connections.items() if isinstance(v, dict) and v["type"] == "folder"]
|
||||
nodes.extend(layer1)
|
||||
for f in folders:
|
||||
layer2 = [k + "@" + f for k,v in self.connections[f].items() if isinstance(v, dict) and v["type"] == "connection" and ("@" + profile in v.values() or ( isinstance(v["password"],list) and "@" + profile in v["password"]))]
|
||||
nodes.extend(layer2)
|
||||
subfolders = [k for k,v in self.connections[f].items() if isinstance(v, dict) and v["type"] == "subfolder"]
|
||||
for s in subfolders:
|
||||
layer3 = [k + "@" + s + "@" + f for k,v in self.connections[f][s].items() if isinstance(v, dict) and v["type"] == "connection" and ("@" + profile in v.values() or ( isinstance(v["password"],list) and "@" + profile in v["password"]))]
|
||||
nodes.extend(layer3)
|
||||
return nodes</code></pre>
|
||||
</details>
|
||||
<h3>Methods</h3>
|
||||
<dl>
|
||||
@ -703,8 +853,8 @@ __pdoc__ = {
|
||||
self.node = node
|
||||
self.connnodes = nodes
|
||||
self.config = config
|
||||
self.nodes = self._getallnodes()
|
||||
self.folders = self._getallfolders()
|
||||
self.nodes = self.config._getallnodes()
|
||||
self.folders = self.config._getallfolders()
|
||||
self.profiles = list(self.config.profiles.keys())
|
||||
self.case = self.config.config["case"]
|
||||
try:
|
||||
@ -766,6 +916,13 @@ __pdoc__ = {
|
||||
runparser.add_argument("run", nargs='+', action=self._store_type, help=self._help("run"), default="run")
|
||||
runparser.add_argument("-g","--generate", dest="action", action="store_const", help="Generate yaml file template", const="generate", default="run")
|
||||
runparser.set_defaults(func=self._func_run)
|
||||
#APIPARSER
|
||||
apiparser = subparsers.add_parser("api", help="Start and stop connpy api")
|
||||
apicrud = apiparser.add_mutually_exclusive_group(required=True)
|
||||
apicrud.add_argument("--start", dest="start", nargs=0, action=self._store_type, help="Start conppy api")
|
||||
apicrud.add_argument("--restart", dest="restart", nargs=0, action=self._store_type, help="Restart conppy api")
|
||||
apicrud.add_argument("--stop", dest="stop", nargs=0, action=self._store_type, help="Stop conppy api")
|
||||
apiparser.set_defaults(func=self._func_api)
|
||||
#CONFIGPARSER
|
||||
configparser = subparsers.add_parser("config", help="Manage app config")
|
||||
configcrud = configparser.add_mutually_exclusive_group(required=True)
|
||||
@ -773,9 +930,10 @@ __pdoc__ = {
|
||||
configcrud.add_argument("--fzf", dest="fzf", nargs=1, action=self._store_type, help="Use fzf for lists", choices=["true","false"])
|
||||
configcrud.add_argument("--keepalive", dest="idletime", nargs=1, action=self._store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT")
|
||||
configcrud.add_argument("--completion", dest="completion", nargs=1, choices=["bash","zsh"], action=self._store_type, help="Get terminal completion configuration for conn")
|
||||
configcrud.add_argument("--configfolder", dest="configfolder", nargs=1, action=self._store_type, help="Set the default location for config file", metavar="FOLDER")
|
||||
configparser.set_defaults(func=self._func_others)
|
||||
#Manage sys arguments
|
||||
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config"]
|
||||
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config", "api"]
|
||||
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]
|
||||
@ -964,7 +1122,7 @@ __pdoc__ = {
|
||||
if matches[0] == "default":
|
||||
print("Can't delete default profile")
|
||||
exit(6)
|
||||
usedprofile = self._profileused(matches[0])
|
||||
usedprofile = self.config._profileused(matches[0])
|
||||
if len(usedprofile) > 0:
|
||||
print("Profile {} used in the following nodes:".format(matches[0]))
|
||||
print(", ".join(usedprofile))
|
||||
@ -1026,7 +1184,7 @@ __pdoc__ = {
|
||||
|
||||
def _func_others(self, args):
|
||||
#Function called when using other commands
|
||||
actions = {"ls": self._ls, "move": self._mvcp, "cp": self._mvcp, "bulk": self._bulk, "completion": self._completion, "case": self._case, "fzf": self._fzf, "idletime": self._idletime}
|
||||
actions = {"ls": self._ls, "move": self._mvcp, "cp": self._mvcp, "bulk": self._bulk, "completion": self._completion, "case": self._case, "fzf": self._fzf, "idletime": self._idletime, "configfolder": self._configfolder}
|
||||
return actions.get(args.command)(args)
|
||||
|
||||
def _ls(self, args):
|
||||
@ -1100,7 +1258,7 @@ __pdoc__ = {
|
||||
newnode["password"] = newnodes["password"]
|
||||
count +=1
|
||||
self.config._connections_add(**newnode)
|
||||
self.nodes = self._getallnodes()
|
||||
self.nodes = self.config._getallnodes()
|
||||
if count > 0:
|
||||
self.config._saveconfig(self.config.file)
|
||||
print("Succesfully added {} nodes".format(count))
|
||||
@ -1132,6 +1290,16 @@ __pdoc__ = {
|
||||
args.data[0] = 0
|
||||
self._change_settings(args.command, args.data[0])
|
||||
|
||||
def _configfolder(self, args):
|
||||
if not os.path.isdir(args.data[0]):
|
||||
raise argparse.ArgumentTypeError(f"readable_dir:{args.data[0]} is not a valid path")
|
||||
else:
|
||||
pathfile = defaultdir + "/.folder"
|
||||
folder = os.path.abspath(args.data[0]).rstrip('/')
|
||||
with open(pathfile, "w") as f:
|
||||
f.write(str(folder))
|
||||
print("Config saved")
|
||||
|
||||
def _change_settings(self, name, value):
|
||||
self.config.config[name] = value
|
||||
self.config._saveconfig(self.config.file)
|
||||
@ -1143,6 +1311,13 @@ __pdoc__ = {
|
||||
actions = {"noderun": self._node_run, "generate": self._yaml_generate, "run": self._yaml_run}
|
||||
return actions.get(args.action)(args)
|
||||
|
||||
def _func_api(self, args):
|
||||
if args.command == "stop" or args.command == "restart":
|
||||
stop_api()
|
||||
if args.command == "start" or args.command == "restart":
|
||||
start_api()
|
||||
return
|
||||
|
||||
def _node_run(self, args):
|
||||
command = " ".join(args.data[1:])
|
||||
command = command.split("-")
|
||||
@ -1533,7 +1708,7 @@ __pdoc__ = {
|
||||
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"
|
||||
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"
|
||||
if type == "bashcompletion":
|
||||
return '''
|
||||
#Here starts bash completion for conn
|
||||
@ -1633,46 +1808,6 @@ tasks:
|
||||
output: null
|
||||
...'''
|
||||
|
||||
def _getallnodes(self):
|
||||
#get all nodes on configfile
|
||||
nodes = []
|
||||
layer1 = [k for k,v in self.config.connections.items() if isinstance(v, dict) and v["type"] == "connection"]
|
||||
folders = [k for k,v in self.config.connections.items() if isinstance(v, dict) and v["type"] == "folder"]
|
||||
nodes.extend(layer1)
|
||||
for f in folders:
|
||||
layer2 = [k + "@" + f for k,v in self.config.connections[f].items() if isinstance(v, dict) and v["type"] == "connection"]
|
||||
nodes.extend(layer2)
|
||||
subfolders = [k for k,v in self.config.connections[f].items() if isinstance(v, dict) and v["type"] == "subfolder"]
|
||||
for s in subfolders:
|
||||
layer3 = [k + "@" + s + "@" + f for k,v in self.config.connections[f][s].items() if isinstance(v, dict) and v["type"] == "connection"]
|
||||
nodes.extend(layer3)
|
||||
return nodes
|
||||
|
||||
def _getallfolders(self):
|
||||
#get all folders on configfile
|
||||
folders = ["@" + k for k,v in self.config.connections.items() if isinstance(v, dict) and v["type"] == "folder"]
|
||||
subfolders = []
|
||||
for f in folders:
|
||||
s = ["@" + k + f for k,v in self.config.connections[f[1:]].items() if isinstance(v, dict) and v["type"] == "subfolder"]
|
||||
subfolders.extend(s)
|
||||
folders.extend(subfolders)
|
||||
return folders
|
||||
|
||||
def _profileused(self, profile):
|
||||
#Check if profile is used before deleting it
|
||||
nodes = []
|
||||
layer1 = [k for k,v in self.config.connections.items() if isinstance(v, dict) and v["type"] == "connection" and ("@" + profile in v.values() or ( isinstance(v["password"],list) and "@" + profile in v["password"]))]
|
||||
folders = [k for k,v in self.config.connections.items() if isinstance(v, dict) and v["type"] == "folder"]
|
||||
nodes.extend(layer1)
|
||||
for f in folders:
|
||||
layer2 = [k + "@" + f for k,v in self.config.connections[f].items() if isinstance(v, dict) and v["type"] == "connection" and ("@" + profile in v.values() or ( isinstance(v["password"],list) and "@" + profile in v["password"]))]
|
||||
nodes.extend(layer2)
|
||||
subfolders = [k for k,v in self.config.connections[f].items() if isinstance(v, dict) and v["type"] == "subfolder"]
|
||||
for s in subfolders:
|
||||
layer3 = [k + "@" + s + "@" + f for k,v in self.config.connections[f][s].items() if isinstance(v, dict) and v["type"] == "connection" and ("@" + profile in v.values() or ( isinstance(v["password"],list) and "@" + profile in v["password"]))]
|
||||
nodes.extend(layer3)
|
||||
return nodes
|
||||
|
||||
def encrypt(self, password, keyfile=None):
|
||||
'''
|
||||
Encrypts password using RSA keyfile
|
||||
@ -1751,7 +1886,7 @@ tasks:
|
||||
</details>
|
||||
</dd>
|
||||
<dt id="connpy.connapp.start"><code class="name flex">
|
||||
<span>def <span class="ident">start</span></span>(<span>self, argv=['-f', '-o', 'docs/', '--html', 'connpy'])</span>
|
||||
<span>def <span class="ident">start</span></span>(<span>self, argv=['--html', 'connpy', '-o', 'docs', '-f'])</span>
|
||||
</code></dt>
|
||||
<dd>
|
||||
<div class="desc"><h3 id="parameters">Parameters:</h3>
|
||||
@ -1815,6 +1950,13 @@ tasks:
|
||||
runparser.add_argument("run", nargs='+', action=self._store_type, help=self._help("run"), default="run")
|
||||
runparser.add_argument("-g","--generate", dest="action", action="store_const", help="Generate yaml file template", const="generate", default="run")
|
||||
runparser.set_defaults(func=self._func_run)
|
||||
#APIPARSER
|
||||
apiparser = subparsers.add_parser("api", help="Start and stop connpy api")
|
||||
apicrud = apiparser.add_mutually_exclusive_group(required=True)
|
||||
apicrud.add_argument("--start", dest="start", nargs=0, action=self._store_type, help="Start conppy api")
|
||||
apicrud.add_argument("--restart", dest="restart", nargs=0, action=self._store_type, help="Restart conppy api")
|
||||
apicrud.add_argument("--stop", dest="stop", nargs=0, action=self._store_type, help="Stop conppy api")
|
||||
apiparser.set_defaults(func=self._func_api)
|
||||
#CONFIGPARSER
|
||||
configparser = subparsers.add_parser("config", help="Manage app config")
|
||||
configcrud = configparser.add_mutually_exclusive_group(required=True)
|
||||
@ -1822,9 +1964,10 @@ tasks:
|
||||
configcrud.add_argument("--fzf", dest="fzf", nargs=1, action=self._store_type, help="Use fzf for lists", choices=["true","false"])
|
||||
configcrud.add_argument("--keepalive", dest="idletime", nargs=1, action=self._store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT")
|
||||
configcrud.add_argument("--completion", dest="completion", nargs=1, choices=["bash","zsh"], action=self._store_type, help="Get terminal completion configuration for conn")
|
||||
configcrud.add_argument("--configfolder", dest="configfolder", nargs=1, action=self._store_type, help="Set the default location for config file", metavar="FOLDER")
|
||||
configparser.set_defaults(func=self._func_others)
|
||||
#Manage sys arguments
|
||||
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config"]
|
||||
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config", "api"]
|
||||
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]
|
||||
@ -3180,6 +3323,19 @@ tasks:
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#http-api">http API</a><ul>
|
||||
<li><a href="#1-list-nodes">1. List Nodes</a><ul>
|
||||
<li><a href="#request-body">Request Body:</a></li>
|
||||
<li><a href="#response">Response:</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#2-run-commands">2. Run Commands</a><ul>
|
||||
<li><a href="#request-body_1">Request Body:</a></li>
|
||||
<li><a href="#response_1">Response:</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#automation-module">Automation module</a><ul>
|
||||
<li><a href="#standalone-module">Standalone module</a></li>
|
||||
<li><a href="#using-manager-configuration">Using manager configuration</a></li>
|
||||
|
Reference in New Issue
Block a user