connpy/docs/conn/index.html

2523 lines
119 KiB
HTML
Raw Normal View History

2022-04-03 09:38:00 -03:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.10.0" />
<title>conn API documentation</title>
2022-04-03 18:25:58 -03:00
<meta name="description" content="Connection manager …" />
2022-04-03 09:38:00 -03:00
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
2022-04-03 18:25:58 -03:00
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/greyscale.min.css" crossorigin>
2022-04-03 09:38:00 -03:00
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
2022-04-03 10:26:08 -03:00
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
2022-04-03 09:38:00 -03:00
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Package <code>conn</code></h1>
</header>
<section id="section-intro">
2022-04-03 12:00:35 -03:00
<h2 id="connection-manager">Connection manager</h2>
2022-04-03 18:25:58 -03:00
<p>conn is a connection manager that allows you to store nodes to connect them fast and password free.</p>
<h3 id="features">Features</h3>
<pre><code>- 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!
</code></pre>
<h3 id="usage">Usage</h3>
2022-04-03 12:00:35 -03:00
<pre><code>usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]
conn {profile,move,mv,copy,cp,list,ls,bulk,config} ...
positional arguments:
node|folder node[@subfolder][@folder]
Connect to specific node or show all matching nodes
[@subfolder][@folder]
Show all available connections globaly or in specified path
Options:
-h, --help show this help message and exit
--add Add new node[@subfolder][@folder] or [@subfolder]@folder
--del, --rm Delete node[@subfolder][@folder] or [@subfolder]@folder
--mod, --edit Modify node[@subfolder][@folder]
--show Show node[@subfolder][@folder]
--debug, -d Display all conections steps
Commands:
profile Manage profiles
move (mv) Move node
copy (cp) Copy node
list (ls) List profiles, nodes or folders
bulk Add nodes in bulk
config Manage app config
</code></pre>
2022-04-03 18:25:58 -03:00
<h3 id="manage-profiles">Manage profiles</h3>
2022-04-03 12:00:35 -03:00
<pre><code>usage: conn profile [-h] (--add | --del | --mod | --show) profile
positional arguments:
profile Name of profile to manage
options:
-h, --help show this help message and exit
--add Add new profile
--del, --rm Delete profile
--mod, --edit Modify profile
--show Show profile
</code></pre>
2022-04-03 18:25:58 -03:00
<h3 id="examples">Examples</h3>
2022-04-03 12:00:35 -03:00
<pre><code> conn profile --add office-user
conn --add @office
conn --add @datacenter@office
conn --add server@datacenter@office
conn --add pc@office
conn --show server@datacenter@office
conn pc@office
conn server
</code></pre>
2022-04-03 18:25:58 -03:00
<h2 id="automation-module">Automation module</h2>
<p>the automation module</p>
<h3 id="standalone-module">Standalone module</h3>
<pre><code>import conn
router = conn.node(&quot;unique name&quot;,&quot;ip/hostname&quot;, user=&quot;username&quot;, password=&quot;pass&quot;)
router.run([&quot;term len 0&quot;,&quot;show run&quot;])
print(router.output)
hasip = router.test(&quot;show ip int brief&quot;,&quot;1.1.1.1&quot;)
if hasip:
print(&quot;Router has ip 1.1.1.1&quot;)
else:
print(&quot;router don't has ip 1.1.1.1&quot;)
</code></pre>
<h3 id="using-manager-configuration">Using manager configuration</h3>
<pre><code>import conn
conf = conn.configfile()
device = conf.getitem(&quot;server@office&quot;)
server = conn.node(&quot;unique name&quot;, **device, config=conf)
result = server.run([&quot;cd /&quot;, &quot;ls -la&quot;])
print(result)
</code></pre>
<h3 id="running-parallel-tasks">Running parallel tasks</h3>
<pre><code>import conn
conf = conn.configfile()
#You can get the nodes from the config from a folder and fitlering in it
nodes = conf.getitem(&quot;@office&quot;, [&quot;router1&quot;, &quot;router2&quot;, &quot;router3&quot;])
#You can also get each node individually:
nodes = {}
nodes[&quot;router1&quot;] = conf.getitem(&quot;router1@office&quot;)
nodes[&quot;router2&quot;] = conf.getitem(&quot;router2@office&quot;)
nodes[&quot;router10&quot;] = conf.getitem(&quot;router10@datacenter&quot;)
#Also, you can create the nodes manually:
nodes = {}
nodes[&quot;router1&quot;] = {&quot;host&quot;: &quot;1.1.1.1&quot;, &quot;user&quot;: &quot;username&quot;, &quot;password&quot;: &quot;pass1&quot;}
nodes[&quot;router2&quot;] = {&quot;host&quot;: &quot;1.1.1.2&quot;, &quot;user&quot;: &quot;username&quot;, &quot;password&quot;: &quot;pass2&quot;}
nodes[&quot;router3&quot;] = {&quot;host&quot;: &quot;1.1.1.2&quot;, &quot;user&quot;: &quot;username&quot;, &quot;password&quot;: &quot;pass3&quot;}
#Finally you run some tasks on the nodes
mynodes = conn.nodes(nodes, config = conf)
result = mynodes.test([&quot;show ip int br&quot;], &quot;1.1.1.2&quot;)
for i in result:
print(&quot;---&quot; + i + &quot;---&quot;)
print(result[i])
print()
# Or for one specific node
mynodes.router1.run([&quot;term len 0&quot;. &quot;show run&quot;], folder = &quot;/home/user/logs&quot;)
</code></pre>
2022-04-03 09:38:00 -03:00
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">#!/usr/bin/env python3
&#39;&#39;&#39;
2022-04-03 12:00:35 -03:00
## Connection manager
2022-04-03 18:25:58 -03:00
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
2022-04-03 12:00:35 -03:00
```
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]
conn {profile,move,mv,copy,cp,list,ls,bulk,config} ...
positional arguments:
node|folder node[@subfolder][@folder]
Connect to specific node or show all matching nodes
[@subfolder][@folder]
Show all available connections globaly or in specified path
Options:
-h, --help show this help message and exit
--add Add new node[@subfolder][@folder] or [@subfolder]@folder
--del, --rm Delete node[@subfolder][@folder] or [@subfolder]@folder
--mod, --edit Modify node[@subfolder][@folder]
--show Show node[@subfolder][@folder]
--debug, -d Display all conections steps
Commands:
profile Manage profiles
move (mv) Move node
copy (cp) Copy node
list (ls) List profiles, nodes or folders
bulk Add nodes in bulk
config Manage app config
```
2022-04-03 18:25:58 -03:00
### Manage profiles
2022-04-03 12:00:35 -03:00
```
usage: conn profile [-h] (--add | --del | --mod | --show) profile
positional arguments:
profile Name of profile to manage
options:
-h, --help show this help message and exit
--add Add new profile
--del, --rm Delete profile
--mod, --edit Modify profile
--show Show profile
```
2022-04-03 18:25:58 -03:00
### Examples
2022-04-03 12:00:35 -03:00
```
conn profile --add office-user
conn --add @office
conn --add @datacenter@office
conn --add server@datacenter@office
conn --add pc@office
conn --show server@datacenter@office
conn pc@office
conn server
```
2022-04-03 18:25:58 -03:00
## Automation module
the automation module
### Standalone module
```
import conn
router = conn.node(&#34;unique name&#34;,&#34;ip/hostname&#34;, user=&#34;username&#34;, password=&#34;pass&#34;)
router.run([&#34;term len 0&#34;,&#34;show run&#34;])
print(router.output)
hasip = router.test(&#34;show ip int brief&#34;,&#34;1.1.1.1&#34;)
if hasip:
print(&#34;Router has ip 1.1.1.1&#34;)
else:
print(&#34;router don&#39;t has ip 1.1.1.1&#34;)
```
### Using manager configuration
```
import conn
conf = conn.configfile()
device = conf.getitem(&#34;server@office&#34;)
server = conn.node(&#34;unique name&#34;, **device, config=conf)
result = server.run([&#34;cd /&#34;, &#34;ls -la&#34;])
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(&#34;@office&#34;, [&#34;router1&#34;, &#34;router2&#34;, &#34;router3&#34;])
#You can also get each node individually:
nodes = {}
nodes[&#34;router1&#34;] = conf.getitem(&#34;router1@office&#34;)
nodes[&#34;router2&#34;] = conf.getitem(&#34;router2@office&#34;)
nodes[&#34;router10&#34;] = conf.getitem(&#34;router10@datacenter&#34;)
#Also, you can create the nodes manually:
nodes = {}
nodes[&#34;router1&#34;] = {&#34;host&#34;: &#34;1.1.1.1&#34;, &#34;user&#34;: &#34;username&#34;, &#34;password&#34;: &#34;pass1&#34;}
nodes[&#34;router2&#34;] = {&#34;host&#34;: &#34;1.1.1.2&#34;, &#34;user&#34;: &#34;username&#34;, &#34;password&#34;: &#34;pass2&#34;}
nodes[&#34;router3&#34;] = {&#34;host&#34;: &#34;1.1.1.2&#34;, &#34;user&#34;: &#34;username&#34;, &#34;password&#34;: &#34;pass3&#34;}
#Finally you run some tasks on the nodes
mynodes = conn.nodes(nodes, config = conf)
result = mynodes.test([&#34;show ip int br&#34;], &#34;1.1.1.2&#34;)
for i in result:
print(&#34;---&#34; + i + &#34;---&#34;)
print(result[i])
print()
# Or for one specific node
mynodes.router1.run([&#34;term len 0&#34;. &#34;show run&#34;], folder = &#34;/home/user/logs&#34;)
```
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
from .core import node,nodes
from .configfile import configfile
from .connapp import connapp
from pkg_resources import get_distribution
2022-04-03 18:25:58 -03:00
__all__ = [&#34;node&#34;, &#34;nodes&#34;, &#34;configfile&#34;, &#34;connapp&#34;]
2022-04-03 09:38:00 -03:00
__version__ = &#34;2.0.10&#34;
__author__ = &#34;Federico Luzzi&#34;
__pdoc__ = {
&#39;core&#39;: False,
}</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="conn.configfile"><code class="flex name class">
<span>class <span class="ident">configfile</span></span>
2022-04-03 18:25:58 -03:00
<span>(</span><span>conf=None, key=None)</span>
2022-04-03 09:38:00 -03:00
</code></dt>
<dd>
2022-04-03 18:25:58 -03:00
<div class="desc"><p>This class generates a configfile object. Containts a dictionary storing, config, nodes and profiles, normaly used by connection manager.</p>
<h3 id="attributes">Attributes:</h3>
<pre><code>- 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.
</code></pre>
<h3 id="optional-parameters">Optional Parameters:</h3>
<pre><code>- 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
</code></pre></div>
2022-04-03 09:38:00 -03:00
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class configfile:
2022-04-03 18:25:58 -03:00
&#39;&#39;&#39; This class generates a configfile object. Containts a dictionary storing, config, nodes and profiles, normaly used by connection manager.
### 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.
&#39;&#39;&#39;
def __init__(self, conf = None, key = None):
&#39;&#39;&#39;
### 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
&#39;&#39;&#39;
2022-04-03 09:38:00 -03:00
home = os.path.expanduser(&#34;~&#34;)
2022-04-03 18:25:58 -03:00
defaultdir = home + &#39;/.config/conn&#39;
defaultfile = defaultdir + &#39;/config.json&#39;
defaultkey = defaultdir + &#39;/.osk&#39;
Path(defaultdir).mkdir(parents=True, exist_ok=True)
2022-04-03 09:38:00 -03:00
if conf == None:
2022-04-03 18:25:58 -03:00
self.file = defaultfile
2022-04-03 09:38:00 -03:00
else:
self.file = conf
if key == None:
2022-04-03 18:25:58 -03:00
self.key = defaultkey
2022-04-03 09:38:00 -03:00
else:
self.key = key
if os.path.exists(self.file):
2022-04-03 18:25:58 -03:00
config = self._loadconfig(self.file)
2022-04-03 09:38:00 -03:00
else:
2022-04-03 18:25:58 -03:00
config = self._createconfig(self.file)
2022-04-03 09:38:00 -03:00
self.config = config[&#34;config&#34;]
self.connections = config[&#34;connections&#34;]
self.profiles = config[&#34;profiles&#34;]
if not os.path.exists(self.key):
2022-04-03 18:25:58 -03:00
self._createkey(self.key)
2022-04-03 09:38:00 -03:00
self.privatekey = RSA.import_key(open(self.key).read())
self.publickey = self.privatekey.publickey()
2022-04-03 18:25:58 -03:00
def _loadconfig(self, conf):
#Loads config file
2022-04-03 09:38:00 -03:00
jsonconf = open(conf)
return json.load(jsonconf)
2022-04-03 18:25:58 -03:00
def _createconfig(self, conf):
#Create config file
2022-04-03 09:38:00 -03:00
defaultconfig = {&#39;config&#39;: {&#39;case&#39;: False, &#39;idletime&#39;: 30}, &#39;connections&#39;: {}, &#39;profiles&#39;: { &#34;default&#34;: { &#34;host&#34;:&#34;&#34;, &#34;protocol&#34;:&#34;ssh&#34;, &#34;port&#34;:&#34;&#34;, &#34;user&#34;:&#34;&#34;, &#34;password&#34;:&#34;&#34;, &#34;options&#34;:&#34;&#34;, &#34;logs&#34;:&#34;&#34; }}}
if not os.path.exists(conf):
with open(conf, &#34;w&#34;) as f:
json.dump(defaultconfig, f, indent = 4)
f.close()
os.chmod(conf, 0o600)
jsonconf = open(conf)
return json.load(jsonconf)
2022-04-03 18:25:58 -03:00
def _saveconfig(self, conf):
#Save config file
2022-04-03 09:38:00 -03:00
newconfig = {&#34;config&#34;:{}, &#34;connections&#34;: {}, &#34;profiles&#34;: {}}
newconfig[&#34;config&#34;] = self.config
newconfig[&#34;connections&#34;] = self.connections
newconfig[&#34;profiles&#34;] = self.profiles
with open(conf, &#34;w&#34;) as f:
json.dump(newconfig, f, indent = 4)
f.close()
2022-04-03 18:25:58 -03:00
def _createkey(self, keyfile):
#Create key file
2022-04-03 09:38:00 -03:00
key = RSA.generate(2048)
with open(keyfile,&#39;wb&#39;) as f:
f.write(key.export_key(&#39;PEM&#39;))
f.close()
os.chmod(keyfile, 0o600)
def _explode_unique(self, unique):
2022-04-03 18:25:58 -03:00
#Divide unique name into folder, subfolder and id
2022-04-03 09:38:00 -03:00
uniques = unique.split(&#34;@&#34;)
if not unique.startswith(&#34;@&#34;):
result = {&#34;id&#34;: uniques[0]}
else:
result = {}
if len(uniques) == 2:
result[&#34;folder&#34;] = uniques[1]
if result[&#34;folder&#34;] == &#34;&#34;:
return False
elif len(uniques) == 3:
result[&#34;folder&#34;] = uniques[2]
result[&#34;subfolder&#34;] = uniques[1]
if result[&#34;folder&#34;] == &#34;&#34; or result[&#34;subfolder&#34;] == &#34;&#34;:
return False
elif len(uniques) &gt; 3:
return False
return result
def getitem(self, unique, keys = None):
2022-04-03 18:25:58 -03:00
&#39;&#39;&#39;
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.
&#39;&#39;&#39;
uniques = self._explode_unique(unique)
if unique.startswith(&#34;@&#34;):
if uniques.keys() &gt;= {&#34;folder&#34;, &#34;subfolder&#34;}:
folder = self.connections[uniques[&#34;folder&#34;]][uniques[&#34;subfolder&#34;]]
2022-04-03 09:38:00 -03:00
else:
2022-04-03 18:25:58 -03:00
folder = self.connections[uniques[&#34;folder&#34;]]
newfolder = folder.copy()
newfolder.pop(&#34;type&#34;)
for node in newfolder.keys():
if &#34;type&#34; in newfolder[node].keys():
newfolder[node].pop(&#34;type&#34;)
if keys == None:
return newfolder
else:
f_newfolder = dict((k, newfolder[k]) for k in keys)
return f_newfolder
else:
if uniques.keys() &gt;= {&#34;folder&#34;, &#34;subfolder&#34;}:
node = self.connections[uniques[&#34;folder&#34;]][uniques[&#34;subfolder&#34;]][uniques[&#34;id&#34;]]
elif &#34;folder&#34; in uniques.keys():
node = self.connections[uniques[&#34;folder&#34;]][uniques[&#34;id&#34;]]
else:
node = self.connections[uniques[&#34;id&#34;]]
newnode = node.copy()
newnode.pop(&#34;type&#34;)
return newnode
2022-04-03 09:38:00 -03:00
def _connections_add(self,*, id, host, folder=&#39;&#39;, subfolder=&#39;&#39;, options=&#39;&#39;, logs=&#39;&#39;, password=&#39;&#39;, port=&#39;&#39;, protocol=&#39;&#39;, user=&#39;&#39;, type = &#34;connection&#34; ):
2022-04-03 18:25:58 -03:00
#Add connection from config
2022-04-03 09:38:00 -03:00
if folder == &#39;&#39;:
self.connections[id] = {&#34;host&#34;: host, &#34;options&#34;: options, &#34;logs&#34;: logs, &#34;password&#34;: password, &#34;port&#34;: port, &#34;protocol&#34;: protocol, &#34;user&#34;: user, &#34;type&#34;: type}
elif folder != &#39;&#39; and subfolder == &#39;&#39;:
self.connections[folder][id] = {&#34;host&#34;: host, &#34;options&#34;: options, &#34;logs&#34;: logs, &#34;password&#34;: password, &#34;port&#34;: port, &#34;protocol&#34;: protocol, &#34;user&#34;: user, &#34;type&#34;: type}
elif folder != &#39;&#39; and subfolder != &#39;&#39;:
self.connections[folder][subfolder][id] = {&#34;host&#34;: host, &#34;options&#34;: options, &#34;logs&#34;: logs, &#34;password&#34;: password, &#34;port&#34;: port, &#34;protocol&#34;: protocol, &#34;user&#34;: user, &#34;type&#34;: type}
def _connections_del(self,*, id, folder=&#39;&#39;, subfolder=&#39;&#39;):
2022-04-03 18:25:58 -03:00
#Delete connection from config
2022-04-03 09:38:00 -03:00
if folder == &#39;&#39;:
del self.connections[id]
elif folder != &#39;&#39; and subfolder == &#39;&#39;:
del self.connections[folder][id]
elif folder != &#39;&#39; and subfolder != &#39;&#39;:
del self.connections[folder][subfolder][id]
def _folder_add(self,*, folder, subfolder = &#39;&#39;):
2022-04-03 18:25:58 -03:00
#Add Folder from config
2022-04-03 09:38:00 -03:00
if subfolder == &#39;&#39;:
if folder not in self.connections:
self.connections[folder] = {&#34;type&#34;: &#34;folder&#34;}
else:
if subfolder not in self.connections[folder]:
self.connections[folder][subfolder] = {&#34;type&#34;: &#34;subfolder&#34;}
def _folder_del(self,*, folder, subfolder=&#39;&#39;):
2022-04-03 18:25:58 -03:00
#Delete folder from config
2022-04-03 09:38:00 -03:00
if subfolder == &#39;&#39;:
del self.connections[folder]
else:
del self.connections[folder][subfolder]
def _profiles_add(self,*, id, host = &#39;&#39;, options=&#39;&#39;, logs=&#39;&#39;, password=&#39;&#39;, port=&#39;&#39;, protocol=&#39;&#39;, user=&#39;&#39; ):
2022-04-03 18:25:58 -03:00
#Add profile from config
2022-04-03 09:38:00 -03:00
self.profiles[id] = {&#34;host&#34;: host, &#34;options&#34;: options, &#34;logs&#34;: logs, &#34;password&#34;: password, &#34;port&#34;: port, &#34;protocol&#34;: protocol, &#34;user&#34;: user}
def _profiles_del(self,*, id ):
2022-04-03 18:25:58 -03:00
#Delete profile from config
2022-04-03 09:38:00 -03:00
del self.profiles[id]</code></pre>
</details>
<h3>Methods</h3>
<dl>
2022-04-03 18:25:58 -03:00
<dt id="conn.configfile.getitem"><code class="name flex">
<span>def <span class="ident">getitem</span></span>(<span>self, unique, keys=None)</span>
2022-04-03 09:38:00 -03:00
</code></dt>
<dd>
2022-04-03 18:25:58 -03:00
<div class="desc"><p>Get an node or a group of nodes from configfile which can be passed to node/nodes class</p>
<h3 id="parameters">Parameters:</h3>
<pre><code>- unique (str): Unique name of the node or folder in config using
connection manager style: node[@subfolder][@folder]
or [@subfolder]@folder
</code></pre>
<h3 id="optional-parameters">Optional Parameters:</h3>
<pre><code>- keys (list): In case you pass a folder as unique, you can filter
nodes inside the folder passing a list.
</code></pre>
<h3 id="returns">Returns:</h3>
<pre><code>dict: Dictionary containing information of node or multiple dictionaries
of multiple nodes.
</code></pre></div>
2022-04-03 09:38:00 -03:00
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
2022-04-03 18:25:58 -03:00
<pre><code class="python">def getitem(self, unique, keys = None):
&#39;&#39;&#39;
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.
&#39;&#39;&#39;
uniques = self._explode_unique(unique)
if unique.startswith(&#34;@&#34;):
if uniques.keys() &gt;= {&#34;folder&#34;, &#34;subfolder&#34;}:
folder = self.connections[uniques[&#34;folder&#34;]][uniques[&#34;subfolder&#34;]]
else:
folder = self.connections[uniques[&#34;folder&#34;]]
newfolder = folder.copy()
newfolder.pop(&#34;type&#34;)
for node in newfolder.keys():
if &#34;type&#34; in newfolder[node].keys():
newfolder[node].pop(&#34;type&#34;)
if keys == None:
return newfolder
else:
f_newfolder = dict((k, newfolder[k]) for k in keys)
return f_newfolder
else:
if uniques.keys() &gt;= {&#34;folder&#34;, &#34;subfolder&#34;}:
node = self.connections[uniques[&#34;folder&#34;]][uniques[&#34;subfolder&#34;]][uniques[&#34;id&#34;]]
elif &#34;folder&#34; in uniques.keys():
node = self.connections[uniques[&#34;folder&#34;]][uniques[&#34;id&#34;]]
else:
node = self.connections[uniques[&#34;id&#34;]]
newnode = node.copy()
newnode.pop(&#34;type&#34;)
return newnode</code></pre>
2022-04-03 09:38:00 -03:00
</details>
</dd>
2022-04-03 18:25:58 -03:00
</dl>
2022-04-03 09:38:00 -03:00
</dd>
2022-04-03 18:25:58 -03:00
<dt id="conn.connapp"><code class="flex name class">
<span>class <span class="ident">connapp</span></span>
<span>(</span><span>config)</span>
2022-04-03 09:38:00 -03:00
</code></dt>
<dd>
2022-04-03 18:25:58 -03:00
<div class="desc"><p>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.</p>
<h3 id="parameters">Parameters:</h3>
<pre><code>- config (obj): Object generated with configfile class, it contains
the nodes configuration and the methods to manage
the config file.
</code></pre></div>
2022-04-03 09:38:00 -03:00
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
2022-04-03 18:25:58 -03:00
<pre><code class="python">class connapp:
&#39;&#39;&#39; This class starts the connection manager app. It&#39;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.
&#39;&#39;&#39;
def __init__(self, config):
&#39;&#39;&#39;
### Parameters:
- config (obj): Object generated with configfile class, it contains
the nodes configuration and the methods to manage
the config file.
&#39;&#39;&#39;
self.node = node
self.config = config
self.nodes = self._getallnodes()
self.folders = self._getallfolders()
self.profiles = list(self.config.profiles.keys())
self.case = self.config.config[&#34;case&#34;]
#DEFAULTPARSER
defaultparser = argparse.ArgumentParser(prog = &#34;conn&#34;, description = &#34;SSH and Telnet connection manager&#34;, formatter_class=argparse.RawTextHelpFormatter)
subparsers = defaultparser.add_subparsers(title=&#34;Commands&#34;)
#NODEPARSER
nodeparser = subparsers.add_parser(&#34;node&#34;,usage=self._help(&#34;usage&#34;), help=self._help(&#34;node&#34;),epilog=self._help(&#34;end&#34;), formatter_class=argparse.RawTextHelpFormatter)
nodecrud = nodeparser.add_mutually_exclusive_group()
nodeparser.add_argument(&#34;node&#34;, metavar=&#34;node|folder&#34;, nargs=&#39;?&#39;, default=None, action=self._store_type, type=self._type_node, help=self._help(&#34;node&#34;))
nodecrud.add_argument(&#34;--add&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Add new node[@subfolder][@folder] or [@subfolder]@folder&#34;, const=&#34;add&#34;, default=&#34;connect&#34;)
nodecrud.add_argument(&#34;--del&#34;, &#34;--rm&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Delete node[@subfolder][@folder] or [@subfolder]@folder&#34;, const=&#34;del&#34;, default=&#34;connect&#34;)
nodecrud.add_argument(&#34;--mod&#34;, &#34;--edit&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Modify node[@subfolder][@folder]&#34;, const=&#34;mod&#34;, default=&#34;connect&#34;)
nodecrud.add_argument(&#34;--show&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show node[@subfolder][@folder]&#34;, const=&#34;show&#34;, default=&#34;connect&#34;)
nodecrud.add_argument(&#34;--debug&#34;, &#34;-d&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Display all conections steps&#34;, const=&#34;debug&#34;, default=&#34;connect&#34;)
nodeparser.set_defaults(func=self._func_node)
#PROFILEPARSER
profileparser = subparsers.add_parser(&#34;profile&#34;, help=&#34;Manage profiles&#34;)
profileparser.add_argument(&#34;profile&#34;, nargs=1, action=self._store_type, type=self._type_profile, help=&#34;Name of profile to manage&#34;)
profilecrud = profileparser.add_mutually_exclusive_group(required=True)
profilecrud.add_argument(&#34;--add&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Add new profile&#34;, const=&#34;add&#34;)
profilecrud.add_argument(&#34;--del&#34;, &#34;--rm&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Delete profile&#34;, const=&#34;del&#34;)
profilecrud.add_argument(&#34;--mod&#34;, &#34;--edit&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Modify profile&#34;, const=&#34;mod&#34;)
profilecrud.add_argument(&#34;--show&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show profile&#34;, const=&#34;show&#34;)
profileparser.set_defaults(func=self._func_profile)
#MOVEPARSER
moveparser = subparsers.add_parser(&#34;move&#34;, aliases=[&#34;mv&#34;], help=&#34;Move node&#34;)
moveparser.add_argument(&#34;move&#34;, nargs=2, action=self._store_type, help=&#34;Move node[@subfolder][@folder] dest_node[@subfolder][@folder]&#34;, default=&#34;move&#34;, type=self._type_node)
moveparser.set_defaults(func=self._func_others)
#COPYPARSER
copyparser = subparsers.add_parser(&#34;copy&#34;, aliases=[&#34;cp&#34;], help=&#34;Copy node&#34;)
copyparser.add_argument(&#34;cp&#34;, nargs=2, action=self._store_type, help=&#34;Copy node[@subfolder][@folder] new_node[@subfolder][@folder]&#34;, default=&#34;cp&#34;, type=self._type_node)
copyparser.set_defaults(func=self._func_others)
#LISTPARSER
lsparser = subparsers.add_parser(&#34;list&#34;, aliases=[&#34;ls&#34;], help=&#34;List profiles, nodes or folders&#34;)
lsparser.add_argument(&#34;ls&#34;, action=self._store_type, choices=[&#34;profiles&#34;,&#34;nodes&#34;,&#34;folders&#34;], help=&#34;List profiles, nodes or folders&#34;, default=False)
lsparser.set_defaults(func=self._func_others)
#BULKPARSER
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)
#CONFIGPARSER
configparser = subparsers.add_parser(&#34;config&#34;, help=&#34;Manage app config&#34;)
configparser.add_argument(&#34;--allow-uppercase&#34;, dest=&#34;case&#34;, nargs=1, action=self._store_type, help=&#34;Allow case sensitive names&#34;, choices=[&#34;true&#34;,&#34;false&#34;])
configparser.add_argument(&#34;--keepalive&#34;, dest=&#34;idletime&#34;, nargs=1, action=self._store_type, help=&#34;Set keepalive time in seconds, 0 to disable&#34;, type=int, metavar=&#34;INT&#34;)
configparser.add_argument(&#34;--completion&#34;, dest=&#34;completion&#34;, nargs=0, action=self._store_type, help=&#34;Get bash completion configuration for conn&#34;)
configparser.set_defaults(func=self._func_others)
#Set default subparser and tune 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;config&#34;]
profilecmds = [&#34;--add&#34;, &#34;--del&#34;, &#34;--rm&#34;, &#34;--mod&#34;, &#34;--edit&#34;, &#34;--show&#34;]
if len(sys.argv) &gt;= 3 and sys.argv[2] == &#34;profile&#34; and sys.argv[1] in profilecmds:
sys.argv[2] = sys.argv[1]
sys.argv[1] = &#34;profile&#34;
if len(sys.argv) &lt; 2 or sys.argv[1] not in commands:
sys.argv.insert(1,&#34;node&#34;)
args = defaultparser.parse_args()
args.func(args)
class _store_type(argparse.Action):
#Custom store type for cli app.
def __call__(self, parser, args, values, option_string=None):
setattr(args, &#34;data&#34;, values)
delattr(args,self.dest)
setattr(args, &#34;command&#34;, self.dest)
def _func_node(self, args):
#Function called when connecting or managing nodes.
if not self.case and args.data != None:
args.data = args.data.lower()
if args.action == &#34;connect&#34; or args.action == &#34;debug&#34;:
if args.data == None:
matches = self.nodes
if len(matches) == 0:
print(&#34;There are no nodes created&#34;)
print(&#34;try: conn --help&#34;)
exit(9)
2022-04-03 09:38:00 -03:00
else:
2022-04-03 18:25:58 -03:00
if args.data.startswith(&#34;@&#34;):
matches = list(filter(lambda k: args.data in k, self.nodes))
else:
matches = list(filter(lambda k: k.startswith(args.data), self.nodes))
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data))
exit(2)
elif len(matches) &gt; 1:
matches[0] = self._choose(matches,&#34;node&#34;, &#34;connect&#34;)
if matches[0] == None:
exit(7)
node = self.config.getitem(matches[0])
node = self.node(matches[0],**node, config = self.config)
if args.action == &#34;debug&#34;:
node.interact(debug = True)
2022-04-03 09:38:00 -03:00
else:
2022-04-03 18:25:58 -03:00
node.interact()
elif args.action == &#34;del&#34;:
if args.data == None:
print(&#34;Missing argument node&#34;)
exit(3)
elif args.data.startswith(&#34;@&#34;):
matches = list(filter(lambda k: k == args.data, self.folders))
else:
matches = list(filter(lambda k: k == args.data, self.nodes))
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data))
exit(2)
question = [inquirer.Confirm(&#34;delete&#34;, message=&#34;Are you sure you want to delete {}?&#34;.format(matches[0]))]
confirm = inquirer.prompt(question)
if confirm[&#34;delete&#34;]:
uniques = self.config._explode_unique(matches[0])
if args.data.startswith(&#34;@&#34;):
self.config._folder_del(**uniques)
else:
self.config._connections_del(**uniques)
self.config._saveconfig(self.config.file)
print(&#34;{} deleted succesfully&#34;.format(matches[0]))
elif args.action == &#34;add&#34;:
if args.data == None:
print(&#34;Missing argument node&#34;)
exit(3)
elif args.data.startswith(&#34;@&#34;):
type = &#34;folder&#34;
matches = list(filter(lambda k: k == args.data, self.folders))
reversematches = list(filter(lambda k: &#34;@&#34; + k == args.data, self.nodes))
else:
type = &#34;node&#34;
matches = list(filter(lambda k: k == args.data, self.nodes))
reversematches = list(filter(lambda k: k == &#34;@&#34; + args.data, self.folders))
if len(matches) &gt; 0:
print(&#34;{} already exist&#34;.format(matches[0]))
exit(4)
if len(reversematches) &gt; 0:
print(&#34;{} already exist&#34;.format(reversematches[0]))
exit(4)
else:
if type == &#34;folder&#34;:
uniques = self.config._explode_unique(args.data)
if uniques == False:
print(&#34;Invalid folder {}&#34;.format(args.data))
exit(5)
if &#34;subfolder&#34; in uniques.keys():
parent = &#34;@&#34; + uniques[&#34;folder&#34;]
if parent not in self.folders:
print(&#34;Folder {} not found&#34;.format(uniques[&#34;folder&#34;]))
exit(2)
self.config._folder_add(**uniques)
self.config._saveconfig(self.config.file)
print(&#34;{} added succesfully&#34;.format(args.data))
if type == &#34;node&#34;:
nodefolder = args.data.partition(&#34;@&#34;)
nodefolder = &#34;@&#34; + nodefolder[2]
if nodefolder not in self.folders and nodefolder != &#34;@&#34;:
print(nodefolder + &#34; not found&#34;)
exit(2)
uniques = self.config._explode_unique(args.data)
if uniques == False:
print(&#34;Invalid node {}&#34;.format(args.data))
exit(5)
print(&#34;You can use the configured setting in a profile using @profilename.&#34;)
print(&#34;You can also leave empty any value except hostname/IP.&#34;)
print(&#34;You can pass 1 or more passwords using comma separated @profiles&#34;)
print(&#34;You can use this variables on logging file name: ${id} ${unique} ${host} ${port} ${user} ${protocol}&#34;)
newnode = self._questions_nodes(args.data, uniques)
if newnode == False:
exit(7)
self.config._connections_add(**newnode)
self.config._saveconfig(self.config.file)
print(&#34;{} added succesfully&#34;.format(args.data))
elif args.action == &#34;show&#34;:
if args.data == None:
print(&#34;Missing argument node&#34;)
exit(3)
matches = list(filter(lambda k: k == args.data, self.nodes))
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data))
exit(2)
node = self.config.getitem(matches[0])
for k, v in node.items():
if isinstance(v, str):
print(k + &#34;: &#34; + v)
else:
print(k + &#34;:&#34;)
for i in v:
print(&#34; - &#34; + i)
elif args.action == &#34;mod&#34;:
if args.data == None:
print(&#34;Missing argument node&#34;)
exit(3)
matches = list(filter(lambda k: k == args.data, self.nodes))
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data))
exit(2)
node = self.config.getitem(matches[0])
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)
if not updatenode:
exit(7)
uniques.update(node)
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))
def _func_profile(self, args):
#Function called when managing profiles
if not self.case:
args.data[0] = args.data[0].lower()
if args.action == &#34;del&#34;:
matches = list(filter(lambda k: k == args.data[0], self.profiles))
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data[0]))
exit(2)
if matches[0] == &#34;default&#34;:
print(&#34;Can&#39;t delete default profile&#34;)
exit(6)
usedprofile = self._profileused(matches[0])
if len(usedprofile) &gt; 0:
print(&#34;Profile {} used in the following nodes:&#34;.format(matches[0]))
print(&#34;, &#34;.join(usedprofile))
exit(8)
question = [inquirer.Confirm(&#34;delete&#34;, message=&#34;Are you sure you want to delete {}?&#34;.format(matches[0]))]
confirm = inquirer.prompt(question)
if confirm[&#34;delete&#34;]:
self.config._profiles_del(id = matches[0])
self.config._saveconfig(self.config.file)
print(&#34;{} deleted succesfully&#34;.format(matches[0]))
elif args.action == &#34;show&#34;:
matches = list(filter(lambda k: k == args.data[0], self.profiles))
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data[0]))
exit(2)
profile = self.config.profiles[matches[0]]
for k, v in profile.items():
if isinstance(v, str):
print(k + &#34;: &#34; + v)
else:
print(k + &#34;:&#34;)
for i in v:
print(&#34; - &#34; + i)
elif args.action == &#34;add&#34;:
matches = list(filter(lambda k: k == args.data[0], self.profiles))
if len(matches) &gt; 0:
print(&#34;Profile {} Already exist&#34;.format(matches[0]))
exit(4)
newprofile = self._questions_profiles(args.data[0])
if newprofile == False:
exit(7)
self.config._profiles_add(**newprofile)
self.config._saveconfig(self.config.file)
print(&#34;{} added succesfully&#34;.format(args.data[0]))
elif args.action == &#34;mod&#34;:
matches = list(filter(lambda k: k == args.data[0], self.profiles))
if len(matches) == 0:
print(&#34;{} not found&#34;.format(args.data[0]))
exit(2)
profile = self.config.profiles[matches[0]]
oldprofile = {&#34;id&#34;: matches[0]}
oldprofile.update(profile)
edits = self._questions_edit()
if edits == None:
exit(7)
updateprofile = self._questions_profiles(matches[0], edit=edits)
if not updateprofile:
exit(7)
if sorted(updateprofile.items()) == sorted(oldprofile.items()):
print(&#34;Nothing to do here&#34;)
return
else:
self.config._profiles_add(**updateprofile)
self.config._saveconfig(self.config.file)
print(&#34;{} edited succesfully&#34;.format(args.data[0]))
def _func_others(self, args):
#Function called when using other commands
if args.command == &#34;ls&#34;:
print(*getattr(self, args.data), sep=&#34;\n&#34;)
elif args.command == &#34;move&#34; or args.command == &#34;cp&#34;:
if not self.case:
args.data[0] = args.data[0].lower()
args.data[1] = args.data[1].lower()
source = list(filter(lambda k: k == args.data[0], self.nodes))
dest = list(filter(lambda k: k == args.data[1], self.nodes))
if len(source) != 1:
print(&#34;{} not found&#34;.format(args.data[0]))
exit(2)
if len(dest) &gt; 0:
print(&#34;Node {} Already exist&#34;.format(args.data[1]))
exit(4)
nodefolder = args.data[1].partition(&#34;@&#34;)
nodefolder = &#34;@&#34; + nodefolder[2]
if nodefolder not in self.folders and nodefolder != &#34;@&#34;:
print(&#34;{} not found&#34;.format(nodefolder))
exit(2)
olduniques = self.config._explode_unique(args.data[0])
newuniques = self.config._explode_unique(args.data[1])
if newuniques == False:
print(&#34;Invalid node {}&#34;.format(args.data[1]))
exit(5)
node = self.config.getitem(source[0])
newnode = {**newuniques, **node}
self.config._connections_add(**newnode)
if args.command == &#34;move&#34;:
self.config._connections_del(**olduniques)
self.config._saveconfig(self.config.file)
if args.command == &#34;move&#34;:
print(&#34;{} moved succesfully to {}&#34;.format(args.data[0],args.data[1]))
if args.command == &#34;cp&#34;:
print(&#34;{} copied succesfully to {}&#34;.format(args.data[0],args.data[1]))
elif args.command == &#34;bulk&#34;:
newnodes = self._questions_bulk()
if newnodes == False:
exit(7)
if not self.case:
newnodes[&#34;location&#34;] = newnodes[&#34;location&#34;].lower()
newnodes[&#34;ids&#34;] = newnodes[&#34;ids&#34;].lower()
ids = newnodes[&#34;ids&#34;].split(&#34;,&#34;)
hosts = newnodes[&#34;host&#34;].split(&#34;,&#34;)
count = 0
for n in ids:
unique = n + newnodes[&#34;location&#34;]
matches = list(filter(lambda k: k == unique, self.nodes))
reversematches = list(filter(lambda k: k == &#34;@&#34; + unique, self.folders))
if len(matches) &gt; 0:
print(&#34;Node {} already exist, ignoring it&#34;.format(unique))
continue
if len(reversematches) &gt; 0:
print(&#34;Folder with name {} already exist, ignoring it&#34;.format(unique))
continue
newnode = {&#34;id&#34;: n}
if newnodes[&#34;location&#34;] != &#34;&#34;:
location = self.config._explode_unique(newnodes[&#34;location&#34;])
newnode.update(location)
if len(hosts) &gt; 1:
index = ids.index(n)
newnode[&#34;host&#34;] = hosts[index]
else:
newnode[&#34;host&#34;] = hosts[0]
newnode[&#34;protocol&#34;] = newnodes[&#34;protocol&#34;]
newnode[&#34;port&#34;] = newnodes[&#34;port&#34;]
newnode[&#34;options&#34;] = newnodes[&#34;options&#34;]
newnode[&#34;logs&#34;] = newnodes[&#34;logs&#34;]
newnode[&#34;user&#34;] = newnodes[&#34;user&#34;]
newnode[&#34;password&#34;] = newnodes[&#34;password&#34;]
count +=1
self.config._connections_add(**newnode)
self.nodes = self._getallnodes()
if count &gt; 0:
self.config._saveconfig(self.config.file)
print(&#34;Succesfully added {} nodes&#34;.format(count))
else:
print(&#34;0 nodes added&#34;)
2022-04-03 09:38:00 -03:00
else:
2022-04-03 18:25:58 -03:00
if args.command == &#34;completion&#34;:
print(self._help(&#34;completion&#34;))
2022-04-03 09:38:00 -03:00
else:
2022-04-03 18:25:58 -03:00
if args.command == &#34;case&#34;:
if args.data[0] == &#34;true&#34;:
args.data[0] = True
elif args.data[0] == &#34;false&#34;:
args.data[0] = False
if args.command == &#34;idletime&#34;:
if args.data[0] &lt; 0:
args.data[0] = 0
self.config.config[args.command] = args.data[0]
self.config._saveconfig(self.config.file)
print(&#34;Config saved&#34;)
def _choose(self, list, name, action):
#Generates an inquirer list to pick
questions = [inquirer.List(name, message=&#34;Pick {} to {}:&#34;.format(name,action), choices=list, carousel=True)]
answer = inquirer.prompt(questions)
if answer == None:
return
else:
return answer[name]
def _host_validation(self, answers, current, regex = &#34;^.+$&#34;):
#Validate hostname in inquirer when managing nodes
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def _profile_protocol_validation(self, answers, current, regex = &#34;(^ssh$|^telnet$|^$)&#34;):
#Validate protocol in inquirer when managing profiles
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick between ssh, telnet or leave empty&#34;)
return True
def _protocol_validation(self, answers, current, regex = &#34;(^ssh$|^telnet$|^$|^@.+$)&#34;):
#Validate protocol in inquirer when managing nodes
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick between ssh, telnet, leave empty or @profile&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def _profile_port_validation(self, answers, current, regex = &#34;(^[0-9]*$)&#34;):
#Validate port in inquirer when managing profiles
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile o leave empty&#34;)
try:
port = int(current)
except:
port = 0
if current != &#34;&#34; and not 1 &lt;= int(port) &lt;= 65535:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535 or leave empty&#34;)
return True
def _port_validation(self, answers, current, regex = &#34;(^[0-9]*$|^@.+$)&#34;):
#Validate port in inquirer when managing nodes
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile or leave empty&#34;)
try:
port = int(current)
except:
port = 0
if current.startswith(&#34;@&#34;):
if current[1:] not in self.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
elif current != &#34;&#34; and not 1 &lt;= int(port) &lt;= 65535:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile o leave empty&#34;)
return True
def _pass_validation(self, answers, current, regex = &#34;(^@.+$)&#34;):
#Validate password in inquirer
profiles = current.split(&#34;,&#34;)
for i in profiles:
if not re.match(regex, i) or i[1:] not in self.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(i))
return True
def _default_validation(self, answers, current):
#Default validation type used in multiples questions in inquirer
if current.startswith(&#34;@&#34;):
if current[1:] not in self.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def _bulk_node_validation(self, answers, current, regex = &#34;^[0-9a-zA-Z_.,$#-]+$&#34;):
#Validation of nodes when running bulk command
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def _bulk_folder_validation(self, answers, current):
#Validation of folders when running bulk command
if not self.case:
current = current.lower()
matches = list(filter(lambda k: k == current, self.folders))
if current != &#34;&#34; and len(matches) == 0:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Location {} don&#39;t exist&#34;.format(current))
return True
def _bulk_host_validation(self, answers, current, regex = &#34;^.+$&#34;):
#Validate hostname when running bulk command
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
hosts = current.split(&#34;,&#34;)
nodes = answers[&#34;ids&#34;].split(&#34;,&#34;)
if len(hosts) &gt; 1 and len(hosts) != len(nodes):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Hosts list should be the same length of nodes list&#34;)
return True
def _questions_edit(self):
#Inquirer questions when editing nodes or profiles
questions = []
questions.append(inquirer.Confirm(&#34;host&#34;, message=&#34;Edit Hostname/IP?&#34;))
questions.append(inquirer.Confirm(&#34;protocol&#34;, message=&#34;Edit Protocol?&#34;))
questions.append(inquirer.Confirm(&#34;port&#34;, message=&#34;Edit Port?&#34;))
questions.append(inquirer.Confirm(&#34;options&#34;, message=&#34;Edit Options?&#34;))
questions.append(inquirer.Confirm(&#34;logs&#34;, message=&#34;Edit logging path/file?&#34;))
questions.append(inquirer.Confirm(&#34;user&#34;, message=&#34;Edit User?&#34;))
questions.append(inquirer.Confirm(&#34;password&#34;, message=&#34;Edit password?&#34;))
answers = inquirer.prompt(questions)
return answers
def _questions_nodes(self, unique, uniques = None, edit = None):
#Questions when adding or editing nodes
try:
defaults = self.config.getitem(unique)
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; }
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 }
questions = []
if edit[&#34;host&#34;]:
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add Hostname or IP&#34;, validate=self._host_validation, default=defaults[&#34;host&#34;]))
else:
node[&#34;host&#34;] = defaults[&#34;host&#34;]
if edit[&#34;protocol&#34;]:
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol&#34;, validate=self._protocol_validation, default=defaults[&#34;protocol&#34;]))
else:
node[&#34;protocol&#34;] = defaults[&#34;protocol&#34;]
if edit[&#34;port&#34;]:
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self._port_validation, default=defaults[&#34;port&#34;]))
else:
node[&#34;port&#34;] = defaults[&#34;port&#34;]
if edit[&#34;options&#34;]:
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol&#34;, validate=self._default_validation, default=defaults[&#34;options&#34;]))
else:
node[&#34;options&#34;] = defaults[&#34;options&#34;]
if edit[&#34;logs&#34;]:
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, validate=self._default_validation, default=defaults[&#34;logs&#34;]))
else:
node[&#34;logs&#34;] = defaults[&#34;logs&#34;]
if edit[&#34;user&#34;]:
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, validate=self._default_validation, default=defaults[&#34;user&#34;]))
else:
node[&#34;user&#34;] = defaults[&#34;user&#34;]
if edit[&#34;password&#34;]:
questions.append(inquirer.List(&#34;password&#34;, message=&#34;Password: Use a local password, no password or a list of profiles to reference?&#34;, choices=[&#34;Local Password&#34;, &#34;Profiles&#34;, &#34;No Password&#34;]))
else:
node[&#34;password&#34;] = defaults[&#34;password&#34;]
answer = inquirer.prompt(questions)
if answer == None:
return False
if &#34;password&#34; in answer.keys():
if answer[&#34;password&#34;] == &#34;Local Password&#34;:
passq = [inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;)]
passa = inquirer.prompt(passq)
if passa == None:
return False
answer[&#34;password&#34;] = self.encrypt(passa[&#34;password&#34;])
elif answer[&#34;password&#34;] == &#34;Profiles&#34;:
passq = [(inquirer.Text(&#34;password&#34;, message=&#34;Set a @profile or a comma separated list of @profiles&#34;, validate=self._pass_validation))]
passa = inquirer.prompt(passq)
if passa == None:
return False
answer[&#34;password&#34;] = passa[&#34;password&#34;].split(&#34;,&#34;)
elif answer[&#34;password&#34;] == &#34;No Password&#34;:
answer[&#34;password&#34;] = &#34;&#34;
result = {**uniques, **answer, **node}
result[&#34;type&#34;] = &#34;connection&#34;
return result
def _questions_profiles(self, unique, edit = None):
#Questions when adding or editing profiles
try:
defaults = self.config.profiles[unique]
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; }
profile = {}
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 }
questions = []
if edit[&#34;host&#34;]:
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add Hostname or IP&#34;, default=defaults[&#34;host&#34;]))
else:
profile[&#34;host&#34;] = defaults[&#34;host&#34;]
if edit[&#34;protocol&#34;]:
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol&#34;, validate=self._profile_protocol_validation, default=defaults[&#34;protocol&#34;]))
else:
profile[&#34;protocol&#34;] = defaults[&#34;protocol&#34;]
if edit[&#34;port&#34;]:
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self._profile_port_validation, default=defaults[&#34;port&#34;]))
else:
profile[&#34;port&#34;] = defaults[&#34;port&#34;]
if edit[&#34;options&#34;]:
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol&#34;, default=defaults[&#34;options&#34;]))
else:
profile[&#34;options&#34;] = defaults[&#34;options&#34;]
if edit[&#34;logs&#34;]:
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, default=defaults[&#34;logs&#34;]))
else:
profile[&#34;logs&#34;] = defaults[&#34;logs&#34;]
if edit[&#34;user&#34;]:
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, default=defaults[&#34;user&#34;]))
else:
profile[&#34;user&#34;] = defaults[&#34;user&#34;]
if edit[&#34;password&#34;]:
questions.append(inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;))
else:
profile[&#34;password&#34;] = defaults[&#34;password&#34;]
answer = inquirer.prompt(questions)
if answer == None:
return False
if &#34;password&#34; in answer.keys():
if answer[&#34;password&#34;] != &#34;&#34;:
answer[&#34;password&#34;] = self.encrypt(answer[&#34;password&#34;])
result = {**answer, **profile}
result[&#34;id&#34;] = unique
return result
def _questions_bulk(self):
#Questions when using bulk command
questions = []
questions.append(inquirer.Text(&#34;ids&#34;, message=&#34;add a comma separated list of nodes to add&#34;, validate=self._bulk_node_validation))
questions.append(inquirer.Text(&#34;location&#34;, message=&#34;Add a @folder, @subfolder@folder or leave empty&#34;, validate=self._bulk_folder_validation))
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add comma separated list of Hostnames or IPs&#34;, validate=self._bulk_host_validation))
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol&#34;, validate=self._protocol_validation))
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self._port_validation))
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol&#34;, validate=self._default_validation))
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, validate=self._default_validation))
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, validate=self._default_validation))
questions.append(inquirer.List(&#34;password&#34;, message=&#34;Password: Use a local password, no password or a list of profiles to reference?&#34;, choices=[&#34;Local Password&#34;, &#34;Profiles&#34;, &#34;No Password&#34;]))
answer = inquirer.prompt(questions)
if answer == None:
return False
if &#34;password&#34; in answer.keys():
if answer[&#34;password&#34;] == &#34;Local Password&#34;:
passq = [inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;)]
passa = inquirer.prompt(passq)
answer[&#34;password&#34;] = self.encrypt(passa[&#34;password&#34;])
elif answer[&#34;password&#34;] == &#34;Profiles&#34;:
passq = [(inquirer.Text(&#34;password&#34;, message=&#34;Set a @profile or a comma separated list of @profiles&#34;, validate=self._pass_validation))]
passa = inquirer.prompt(passq)
answer[&#34;password&#34;] = passa[&#34;password&#34;].split(&#34;,&#34;)
elif answer[&#34;password&#34;] == &#34;No Password&#34;:
answer[&#34;password&#34;] = &#34;&#34;
answer[&#34;type&#34;] = &#34;connection&#34;
return answer
def _type_node(self, arg_value, pat=re.compile(r&#34;^[0-9a-zA-Z_.$@#-]+$&#34;)):
if not pat.match(arg_value):
raise argparse.ArgumentTypeError
return arg_value
def _type_profile(self, arg_value, pat=re.compile(r&#34;^[0-9a-zA-Z_.$#-]+$&#34;)):
if not pat.match(arg_value):
raise argparse.ArgumentTypeError
return arg_value
def _help(self, type):
#Store text for help and other commands
if type == &#34;node&#34;:
return &#34;node[@subfolder][@folder]\nConnect to specific node or show all matching nodes\n[@subfolder][@folder]\nShow all available connections globaly or in specified path&#34;
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 config Manage app config&#34;
if type == &#34;completion&#34;:
return &#39;&#39;&#39;
#Here starts bash completion for conn
#You need jq installed in order to use this
_conn()
{
DATADIR=$HOME/.config/conn
mapfile -t connections &lt; &lt;(jq -r &#39; .[&#34;connections&#34;] | paths as $path | select(getpath($path) == &#34;connection&#34;) | $path | [map(select(. != &#34;type&#34;))[-1,-2,-3]] | map(select(. !=null)) | join(&#34;@&#34;)&#39; $DATADIR/config.json)
mapfile -t folders &lt; &lt;(jq -r &#39; .[&#34;connections&#34;] | paths as $path | select(getpath($path) == &#34;folder&#34; or getpath($path) == &#34;subfolder&#34;) | $path | [map(select(. != &#34;type&#34;))[-1,-2]] | map(select(. !=null)) | join(&#34;@&#34;)&#39; $DATADIR/config.json)
mapfile -t profiles &lt; &lt;(jq -r &#39;.[&#34;profiles&#34;] | keys[]&#39; $DATADIR/config.json)
if [ &#34;${#COMP_WORDS[@]}&#34; = &#34;2&#34; ]; then
strings=&#34;--add --del --rm --edit --mod mv --show ls cp profile bulk config --help&#34;
strings=&#34;$strings ${connections[@]} ${folders[@]/#/@}&#34;
COMPREPLY=($(compgen -W &#34;$strings&#34; -- &#34;${COMP_WORDS[1]}&#34;))
fi
if [ &#34;${#COMP_WORDS[@]}&#34; = &#34;3&#34; ]; then
strings=&#34;&#34;
if [ &#34;${COMP_WORDS[1]}&#34; = &#34;profile&#34; ]; then strings=&#34;--add --rm --del --edit --mod --show --help&#34;; fi
if [ &#34;${COMP_WORDS[1]}&#34; = &#34;config&#34; ]; then strings=&#34;--allow-uppercase --keepalive --completion --help&#34;; fi
if [[ &#34;${COMP_WORDS[1]}&#34; =~ ^--mod|--edit|--show|--add|--rm|--del$ ]]; then strings=&#34;profile&#34;; fi
if [[ &#34;${COMP_WORDS[1]}&#34; =~ ^list|ls$ ]]; then strings=&#34;profiles nodes folders&#34;; fi
if [[ &#34;${COMP_WORDS[1]}&#34; =~ ^bulk|mv|move|cp|copy$$ ]]; then strings=&#34;--help&#34;; fi
if [[ &#34;${COMP_WORDS[1]}&#34; =~ ^--rm|--del$ ]]; then strings=&#34;$strings ${folders[@]/#/@}&#34;; fi
if [[ &#34;${COMP_WORDS[1]}&#34; =~ ^--rm|--del|--mod|--edit|mv|move|cp|copy|--show$ ]]; then
strings=&#34;$strings ${connections[@]}&#34;
fi
COMPREPLY=($(compgen -W &#34;$strings&#34; -- &#34;${COMP_WORDS[2]}&#34;))
fi
if [ &#34;${#COMP_WORDS[@]}&#34; = &#34;4&#34; ]; then
strings=&#34;&#34;
if [ &#34;${COMP_WORDS[1]}&#34; = &#34;profile&#34; ]; then
if [[ &#34;${COMP_WORDS[2]}&#34; =~ ^--rm|--del|--mod|--edit|--show$ ]] ; then
strings=&#34;$strings ${profiles[@]}&#34;
fi
fi
if [ &#34;${COMP_WORDS[2]}&#34; = &#34;profile&#34; ]; then
if [[ &#34;${COMP_WORDS[1]}&#34; =~ ^--rm|--remove|--del|--mod|--edit|--show$ ]] ; then
strings=&#34;$strings ${profiles[@]}&#34;
fi
fi
COMPREPLY=($(compgen -W &#34;$strings&#34; -- &#34;${COMP_WORDS[3]}&#34;))
fi
}
complete -o nosort -F _conn conn
&#39;&#39;&#39;
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[&#34;type&#34;] == &#34;connection&#34;]
folders = [k for k,v in self.config.connections.items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;folder&#34;]
nodes.extend(layer1)
for f in folders:
layer2 = [k + &#34;@&#34; + f for k,v in self.config.connections[f].items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;connection&#34;]
nodes.extend(layer2)
subfolders = [k for k,v in self.config.connections[f].items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;subfolder&#34;]
for s in subfolders:
layer3 = [k + &#34;@&#34; + s + &#34;@&#34; + f for k,v in self.config.connections[f][s].items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;connection&#34;]
nodes.extend(layer3)
return nodes
def _getallfolders(self):
#get all folders on configfile
folders = [&#34;@&#34; + k for k,v in self.config.connections.items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;folder&#34;]
subfolders = []
for f in folders:
s = [&#34;@&#34; + k + f for k,v in self.config.connections[f[1:]].items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;subfolder&#34;]
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[&#34;type&#34;] == &#34;connection&#34; and (&#34;@&#34; + profile in v.values() or ( isinstance(v[&#34;password&#34;],list) and &#34;@&#34; + profile in v[&#34;password&#34;]))]
folders = [k for k,v in self.config.connections.items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;folder&#34;]
nodes.extend(layer1)
for f in folders:
layer2 = [k + &#34;@&#34; + f for k,v in self.config.connections[f].items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;connection&#34; and (&#34;@&#34; + profile in v.values() or ( isinstance(v[&#34;password&#34;],list) and &#34;@&#34; + profile in v[&#34;password&#34;]))]
nodes.extend(layer2)
subfolders = [k for k,v in self.config.connections[f].items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;subfolder&#34;]
for s in subfolders:
layer3 = [k + &#34;@&#34; + s + &#34;@&#34; + f for k,v in self.config.connections[f][s].items() if isinstance(v, dict) and v[&#34;type&#34;] == &#34;connection&#34; and (&#34;@&#34; + profile in v.values() or ( isinstance(v[&#34;password&#34;],list) and &#34;@&#34; + profile in v[&#34;password&#34;]))]
nodes.extend(layer3)
return nodes
def encrypt(self, password, keyfile=None):
&#39;&#39;&#39;
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.
&#39;&#39;&#39;
if keyfile is None:
keyfile = self.config.key
key = RSA.import_key(open(keyfile).read())
publickey = key.publickey()
encryptor = PKCS1_OAEP.new(publickey)
password = encryptor.encrypt(password.encode(&#34;utf-8&#34;))
return str(password)</code></pre>
2022-04-03 09:38:00 -03:00
</details>
2022-04-03 18:25:58 -03:00
<h3>Methods</h3>
<dl>
<dt id="conn.connapp.encrypt"><code class="name flex">
<span>def <span class="ident">encrypt</span></span>(<span>self, password, keyfile=None)</span>
2022-04-03 09:38:00 -03:00
</code></dt>
<dd>
2022-04-03 18:25:58 -03:00
<div class="desc"><p>Encrypts password using RSA keyfile</p>
<h3 id="parameters">Parameters:</h3>
<pre><code>- password (str): Plaintext password to encrypt.
</code></pre>
<h3 id="optional-parameters">Optional Parameters:</h3>
<pre><code>- keyfile (str): Path/file to keyfile. Default is config keyfile.
</code></pre>
<h3 id="returns">Returns:</h3>
<pre><code>str: Encrypted password.
</code></pre></div>
2022-04-03 09:38:00 -03:00
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
2022-04-03 18:25:58 -03:00
<pre><code class="python">def encrypt(self, password, keyfile=None):
&#39;&#39;&#39;
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.
&#39;&#39;&#39;
if keyfile is None:
keyfile = self.config.key
key = RSA.import_key(open(keyfile).read())
publickey = key.publickey()
encryptor = PKCS1_OAEP.new(publickey)
password = encryptor.encrypt(password.encode(&#34;utf-8&#34;))
return str(password)</code></pre>
2022-04-03 09:38:00 -03:00
</details>
</dd>
</dl>
</dd>
<dt id="conn.node"><code class="flex name class">
<span>class <span class="ident">node</span></span>
<span>(</span><span>unique, host, options='', logs='', password='', port='', protocol='', user='', config='')</span>
</code></dt>
<dd>
<div class="desc"><p>This class generates a node object. Containts all the information and methods to connect and interact with a device using ssh or telnet.</p>
2022-04-03 10:26:08 -03:00
<h3 id="attributes">Attributes:</h3>
<pre><code>- output (str): Output of the commands you ran with run or test
method.
- result(bool): True if expected value is found after running
the commands using test method.
2022-04-03 09:38:00 -03:00
</code></pre>
2022-04-03 10:26:08 -03:00
<h3 id="parameters">Parameters:</h3>
<pre><code>- unique (str): Unique name to assign to the node.
- host (str): IP address or hostname of the node.
2022-04-03 09:38:00 -03:00
</code></pre>
2022-04-03 10:26:08 -03:00
<h3 id="optional-parameters">Optional Parameters:</h3>
<pre><code>- options (str): Additional options to pass the ssh/telnet for
connection.
- logs (str): Path/file for storing the logs. You can use
${unique},${host}, ${port}, ${user}, ${protocol}
as variables.
- password (str): Encrypted or plaintext password.
- port (str): Port to connect to node, default 22 for ssh and 23
for telnet.
- protocol (str): Select ssh or telnet. Default is ssh.
- user (str): Username to of the node.
- config (obj): Pass the object created with class configfile with
key for decryption and extra configuration if you
are using connection manager.
2022-04-03 09:38:00 -03:00
</code></pre></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class node:
&#39;&#39;&#39; This class generates a node object. Containts all the information and methods to connect and interact with a device using ssh or telnet.
2022-04-03 10:26:08 -03:00
### Attributes:
- output (str): Output of the commands you ran with run or test
method.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- result(bool): True if expected value is found after running
the commands using test method.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
def __init__(self, unique, host, options=&#39;&#39;, logs=&#39;&#39;, password=&#39;&#39;, port=&#39;&#39;, protocol=&#39;&#39;, user=&#39;&#39;, config=&#39;&#39;):
&#39;&#39;&#39;
2022-04-03 10:26:08 -03:00
### Parameters:
- unique (str): Unique name to assign to the node.
- host (str): IP address or hostname of the node.
### Optional Parameters:
- options (str): Additional options to pass the ssh/telnet for
connection.
- logs (str): Path/file for storing the logs. You can use
${unique},${host}, ${port}, ${user}, ${protocol}
as variables.
- password (str): Encrypted or plaintext password.
- port (str): Port to connect to node, default 22 for ssh and 23
for telnet.
- protocol (str): Select ssh or telnet. Default is ssh.
- user (str): Username to of the node.
- config (obj): Pass the object created with class configfile with
key for decryption and extra configuration if you
are using connection manager.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
if config == &#39;&#39;:
self.idletime = 0
self.key = None
else:
self.idletime = config.config[&#34;idletime&#34;]
self.key = config.key
self.unique = unique
attr = {&#34;host&#34;: host, &#34;logs&#34;: logs, &#34;options&#34;:options, &#34;port&#34;: port, &#34;protocol&#34;: protocol, &#34;user&#34;: user}
for key in attr:
profile = re.search(&#34;^@(.*)&#34;, attr[key])
if profile and config != &#39;&#39;:
setattr(self,key,config.profiles[profile.group(1)][key])
elif attr[key] == &#39;&#39; and key == &#34;protocol&#34;:
try:
setattr(self,key,config.profiles[&#34;default&#34;][key])
except:
setattr(self,key,&#34;ssh&#34;)
else:
setattr(self,key,attr[key])
if isinstance(password,list):
self.password = []
for i, s in enumerate(password):
profile = re.search(&#34;^@(.*)&#34;, password[i])
if profile and config != &#39;&#39;:
self.password.append(config.profiles[profile.group(1)][&#34;password&#34;])
else:
self.password = [password]
def __passtx(self, passwords, *, keyfile=None):
# decrypts passwords, used by other methdos.
dpass = []
if keyfile is None:
keyfile = self.key
if keyfile is not None:
key = RSA.import_key(open(keyfile).read())
decryptor = PKCS1_OAEP.new(key)
for passwd in passwords:
if not re.match(&#39;^b[\&#34;\&#39;].+[\&#34;\&#39;]$&#39;, passwd):
dpass.append(passwd)
else:
try:
decrypted = decryptor.decrypt(ast.literal_eval(passwd)).decode(&#34;utf-8&#34;)
dpass.append(decrypted)
except:
raise ValueError(&#34;Missing or corrupted key&#34;)
return dpass
def _logfile(self, logfile = None):
# translate logs variables and generate logs path.
if logfile == None:
logfile = self.logs
logfile = logfile.replace(&#34;${unique}&#34;, self.unique)
logfile = logfile.replace(&#34;${host}&#34;, self.host)
logfile = logfile.replace(&#34;${port}&#34;, self.port)
logfile = logfile.replace(&#34;${user}&#34;, self.user)
logfile = logfile.replace(&#34;${protocol}&#34;, self.protocol)
now = datetime.datetime.now()
dateconf = re.search(r&#39;\$\{date \&#39;(.*)\&#39;}&#39;, logfile)
if dateconf:
logfile = re.sub(r&#39;\$\{date (.*)}&#39;,now.strftime(dateconf.group(1)), logfile)
return logfile
def _logclean(self, logfile, var = False):
#Remove special ascii characters and other stuff from logfile.
if var == False:
t = open(logfile, &#34;r&#34;).read()
else:
t = logfile
t = t.replace(&#34;\n&#34;,&#34;&#34;,1).replace(&#34;\a&#34;,&#34;&#34;)
t = t.replace(&#39;\n\n&#39;, &#39;\n&#39;)
t = re.sub(r&#39;.\[K&#39;, &#39;&#39;, t)
while True:
tb = re.sub(&#39;.\b&#39;, &#39;&#39;, t, count=1)
if len(t) == len(tb):
break
t = tb
ansi_escape = re.compile(r&#39;\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/ ]*[@-~])&#39;)
t = ansi_escape.sub(&#39;&#39;, t)
if var == False:
d = open(logfile, &#34;w&#34;)
d.write(t)
d.close()
return
else:
return t
def interact(self, debug = False):
&#39;&#39;&#39;
Allow user to interact with the node directly, mostly used by connection manager.
2022-04-03 10:26:08 -03:00
### Optional Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- debug (bool): If True, display all the connecting information
before interact. Default False.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
connect = self._connect(debug = debug)
if connect == True:
size = re.search(&#39;columns=([0-9]+).*lines=([0-9]+)&#39;,str(os.get_terminal_size()))
self.child.setwinsize(int(size.group(2)),int(size.group(1)))
print(&#34;Connected to &#34; + self.unique + &#34; at &#34; + self.host + (&#34;:&#34; if self.port != &#39;&#39; else &#39;&#39;) + self.port + &#34; via: &#34; + self.protocol)
if &#39;logfile&#39; in dir(self):
self.child.logfile_read = open(self.logfile, &#34;wb&#34;)
elif debug:
self.child.logfile_read = None
if &#39;missingtext&#39; in dir(self):
print(self.child.after.decode(), end=&#39;&#39;)
self.child.interact()
if &#34;logfile&#34; in dir(self) and not debug:
self._logclean(self.logfile)
else:
print(connect)
exit(1)
def run(self, commands,*, folder = &#39;&#39;, prompt = r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, stdout = False):
&#39;&#39;&#39;
Run a command or list of commands on the node and return the output.
2022-04-03 10:26:08 -03:00
### Parameters:
- commands (str/list): Commands to run on the node. Should be
str or a list of str.
### Optional Named Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- folder (str): Path where output log should be stored, leave
empty to disable logging.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while
routers use &#34;&gt;&#34; or &#34;#&#34;. The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- stdout (bool):Set True to send the command output to stdout.
default False.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
### Returns:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
str: Output of the commands you ran on the node.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
connect = self._connect()
if connect == True:
expects = [prompt, pexpect.EOF]
output = &#39;&#39;
if isinstance(commands, list):
for c in commands:
result = self.child.expect(expects)
self.child.sendline(c)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
else:
result = self.child.expect(expects)
self.child.sendline(commands)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
result = self.child.expect(expects)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
self.child.close()
output = output.lstrip()
if stdout == True:
print(output)
if folder != &#39;&#39;:
with open(folder + &#34;/&#34; + self.unique, &#34;w&#34;) as f:
f.write(output)
f.close()
self._logclean(folder + &#34;/&#34; + self.unique)
self.output = output
return output
else:
self.output = connect
return connect
def test(self, commands, expected, *, prompt = r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;):
&#39;&#39;&#39;
Run a command or list of commands on the node, then check if expected value appears on the output after the last command.
2022-04-03 10:26:08 -03:00
### Parameters:
- commands (str/list): Commands to run on the node. Should be
str or list of str.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- expected (str) : Expected text to appear after running
all the commands on the node.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
### Optional Named Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while
routers use &#34;&gt;&#34; or &#34;#&#34;. The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-03 09:38:00 -03:00
### Returns:
2022-04-03 10:26:08 -03:00
bool: true if expected value is found after running the commands
false if prompt is found before.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
connect = self._connect()
if connect == True:
expects = [prompt, pexpect.EOF]
output = &#39;&#39;
if isinstance(commands, list):
for c in commands:
result = self.child.expect(expects)
self.child.sendline(c)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
else:
self.child.expect(expects)
self.child.sendline(commands)
output = output + self.child.before.decode() + self.child.after.decode()
expects = [expected, prompt, pexpect.EOF]
results = self.child.expect(expects)
if results == 0:
self.child.close()
self.result = True
output = output + self.child.before.decode() + self.child.after.decode()
output = output.lstrip()
self.output = output
return True
if results in [1, 2]:
self.child.close()
self.result = False
if results == 1:
output = output + self.child.before.decode() + self.child.after.decode()
elif results == 2:
output = output + self.child.before.decode()
output = output.lstrip()
self.output = output
return False
else:
self.result = None
self.output = connect
return connect
def _connect(self, debug = False):
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
if self.protocol == &#34;ssh&#34;:
cmd = &#34;ssh&#34;
if self.idletime &gt; 0:
cmd = cmd + &#34; -o ServerAliveInterval=&#34; + str(self.idletime)
if self.user == &#39;&#39;:
cmd = cmd + &#34; -t {}&#34;.format(self.host)
else:
cmd = cmd + &#34; -t {}&#34;.format(&#34;@&#34;.join([self.user,self.host]))
if self.port != &#39;&#39;:
cmd = cmd + &#34; -p &#34; + self.port
if self.options != &#39;&#39;:
cmd = cmd + &#34; &#34; + self.options
if self.logs != &#39;&#39;:
self.logfile = self._logfile()
if self.password[0] != &#39;&#39;:
passwords = self.__passtx(self.password)
else:
passwords = []
expects = [&#39;yes/no&#39;, &#39;refused&#39;, &#39;supported&#39;, &#39;cipher&#39;, &#39;sage&#39;, &#39;timeout&#39;, &#39;unavailable&#39;, &#39;closed&#39;, &#39;[p|P]assword:|[u|U]sername:&#39;, r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, &#39;suspend&#39;, pexpect.EOF, &#34;No route to host&#34;, &#34;resolve hostname&#34;, &#34;no matching host key&#34;]
elif self.protocol == &#34;telnet&#34;:
cmd = &#34;telnet &#34; + self.host
if self.port != &#39;&#39;:
cmd = cmd + &#34; &#34; + self.port
if self.options != &#39;&#39;:
cmd = cmd + &#34; &#34; + self.options
if self.logs != &#39;&#39;:
self.logfile = self._logfile()
if self.password[0] != &#39;&#39;:
passwords = self.__passtx(self.password)
else:
passwords = []
expects = [&#39;[u|U]sername:&#39;, &#39;refused&#39;, &#39;supported&#39;, &#39;cipher&#39;, &#39;sage&#39;, &#39;timeout&#39;, &#39;unavailable&#39;, &#39;closed&#39;, &#39;[p|P]assword:&#39;, r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, &#39;suspend&#39;, pexpect.EOF, &#34;No route to host&#34;, &#34;resolve hostname&#34;, &#34;no matching host key&#34;]
else:
raise ValueError(&#34;Invalid protocol: &#34; + self.protocol)
child = pexpect.spawn(cmd)
if debug:
child.logfile_read = sys.stdout.buffer
if len(passwords) &gt; 0:
loops = len(passwords)
else:
loops = 1
endloop = False
for i in range(0, loops):
while True:
results = child.expect(expects)
if results == 0:
if self.protocol == &#34;ssh&#34;:
child.sendline(&#39;yes&#39;)
elif self.protocol == &#34;telnet&#34;:
if self.user != &#39;&#39;:
child.sendline(self.user)
else:
self.missingtext = True
break
if results in [1, 2, 3, 4, 5, 6, 7, 12, 13, 14]:
child.close()
return &#34;Connection failed code:&#34; + str(results)
if results == 8:
if len(passwords) &gt; 0:
child.sendline(passwords[i])
else:
self.missingtext = True
break
if results in [9, 11]:
endloop = True
child.sendline()
break
if results == 10:
child.sendline(&#34;\r&#34;)
sleep(2)
if endloop:
break
child.readline(0)
self.child = child
return True</code></pre>
</details>
<h3>Methods</h3>
<dl>
<dt id="conn.node.interact"><code class="name flex">
<span>def <span class="ident">interact</span></span>(<span>self, debug=False)</span>
</code></dt>
<dd>
<div class="desc"><p>Allow user to interact with the node directly, mostly used by connection manager.</p>
2022-04-03 10:26:08 -03:00
<h3 id="optional-parameters">Optional Parameters:</h3>
<pre><code>- debug (bool): If True, display all the connecting information
before interact. Default False.
2022-04-03 09:38:00 -03:00
</code></pre></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def interact(self, debug = False):
&#39;&#39;&#39;
Allow user to interact with the node directly, mostly used by connection manager.
2022-04-03 10:26:08 -03:00
### Optional Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- debug (bool): If True, display all the connecting information
before interact. Default False.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
connect = self._connect(debug = debug)
if connect == True:
size = re.search(&#39;columns=([0-9]+).*lines=([0-9]+)&#39;,str(os.get_terminal_size()))
self.child.setwinsize(int(size.group(2)),int(size.group(1)))
print(&#34;Connected to &#34; + self.unique + &#34; at &#34; + self.host + (&#34;:&#34; if self.port != &#39;&#39; else &#39;&#39;) + self.port + &#34; via: &#34; + self.protocol)
if &#39;logfile&#39; in dir(self):
self.child.logfile_read = open(self.logfile, &#34;wb&#34;)
elif debug:
self.child.logfile_read = None
if &#39;missingtext&#39; in dir(self):
print(self.child.after.decode(), end=&#39;&#39;)
self.child.interact()
if &#34;logfile&#34; in dir(self) and not debug:
self._logclean(self.logfile)
else:
print(connect)
exit(1)</code></pre>
</details>
</dd>
<dt id="conn.node.run"><code class="name flex">
<span>def <span class="ident">run</span></span>(<span>self, commands, *, folder='', prompt=&#x27;&gt;$|#$|\\$$|&gt;.$|#.$|\\$.$&#x27;, stdout=False)</span>
</code></dt>
<dd>
<div class="desc"><p>Run a command or list of commands on the node and return the output.</p>
2022-04-03 10:26:08 -03:00
<h3 id="parameters">Parameters:</h3>
<pre><code>- commands (str/list): Commands to run on the node. Should be
str or a list of str.
2022-04-03 09:38:00 -03:00
</code></pre>
2022-04-03 10:26:08 -03:00
<h3 id="optional-named-parameters">Optional Named Parameters:</h3>
<pre><code>- folder (str): Path where output log should be stored, leave
empty to disable logging.
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses "&gt;" or EOF while
routers use "&gt;" or "#". The default value should
work for most nodes. Change it if your connection
need some special symbol.
- stdout (bool):Set True to send the command output to stdout.
default False.
2022-04-03 09:38:00 -03:00
</code></pre>
2022-04-03 10:26:08 -03:00
<h3 id="returns">Returns:</h3>
<pre><code>str: Output of the commands you ran on the node.
2022-04-03 09:38:00 -03:00
</code></pre></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def run(self, commands,*, folder = &#39;&#39;, prompt = r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;, stdout = False):
&#39;&#39;&#39;
Run a command or list of commands on the node and return the output.
2022-04-03 10:26:08 -03:00
### Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- commands (str/list): Commands to run on the node. Should be
str or a list of str.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
### Optional Named Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- folder (str): Path where output log should be stored, leave
empty to disable logging.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while
routers use &#34;&gt;&#34; or &#34;#&#34;. The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- stdout (bool):Set True to send the command output to stdout.
default False.
### Returns:
str: Output of the commands you ran on the node.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
connect = self._connect()
if connect == True:
expects = [prompt, pexpect.EOF]
output = &#39;&#39;
if isinstance(commands, list):
for c in commands:
result = self.child.expect(expects)
self.child.sendline(c)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
else:
result = self.child.expect(expects)
self.child.sendline(commands)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
result = self.child.expect(expects)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
self.child.close()
output = output.lstrip()
if stdout == True:
print(output)
if folder != &#39;&#39;:
with open(folder + &#34;/&#34; + self.unique, &#34;w&#34;) as f:
f.write(output)
f.close()
self._logclean(folder + &#34;/&#34; + self.unique)
self.output = output
return output
else:
self.output = connect
return connect</code></pre>
</details>
</dd>
<dt id="conn.node.test"><code class="name flex">
<span>def <span class="ident">test</span></span>(<span>self, commands, expected, *, prompt=&#x27;&gt;$|#$|\\$$|&gt;.$|#.$|\\$.$&#x27;)</span>
</code></dt>
<dd>
<div class="desc"><p>Run a command or list of commands on the node, then check if expected value appears on the output after the last command.</p>
2022-04-03 10:26:08 -03:00
<h3 id="parameters">Parameters:</h3>
<pre><code>- commands (str/list): Commands to run on the node. Should be
str or list of str.
- expected (str) : Expected text to appear after running
all the commands on the node.
2022-04-03 09:38:00 -03:00
</code></pre>
2022-04-03 10:26:08 -03:00
<h3 id="optional-named-parameters">Optional Named Parameters:</h3>
<pre><code>- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses "&gt;" or EOF while
routers use "&gt;" or "#". The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-03 09:38:00 -03:00
</code></pre>
<h3 id="returns">Returns:</h3>
2022-04-03 10:26:08 -03:00
<pre><code>bool: true if expected value is found after running the commands
false if prompt is found before.
2022-04-03 09:38:00 -03:00
</code></pre></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test(self, commands, expected, *, prompt = r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39;):
&#39;&#39;&#39;
Run a command or list of commands on the node, then check if expected value appears on the output after the last command.
2022-04-03 10:26:08 -03:00
### Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- commands (str/list): Commands to run on the node. Should be
str or list of str.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
- expected (str) : Expected text to appear after running
all the commands on the node.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
### Optional Named Parameters:
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while
routers use &#34;&gt;&#34; or &#34;#&#34;. The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-03 09:38:00 -03:00
### Returns:
2022-04-03 10:26:08 -03:00
bool: true if expected value is found after running the commands
false if prompt is found before.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
connect = self._connect()
if connect == True:
expects = [prompt, pexpect.EOF]
output = &#39;&#39;
if isinstance(commands, list):
for c in commands:
result = self.child.expect(expects)
self.child.sendline(c)
if result == 0:
output = output + self.child.before.decode() + self.child.after.decode()
if result == 1:
output = output + self.child.before.decode()
else:
self.child.expect(expects)
self.child.sendline(commands)
output = output + self.child.before.decode() + self.child.after.decode()
expects = [expected, prompt, pexpect.EOF]
results = self.child.expect(expects)
if results == 0:
self.child.close()
self.result = True
output = output + self.child.before.decode() + self.child.after.decode()
output = output.lstrip()
self.output = output
return True
if results in [1, 2]:
self.child.close()
self.result = False
if results == 1:
output = output + self.child.before.decode() + self.child.after.decode()
elif results == 2:
output = output + self.child.before.decode()
output = output.lstrip()
self.output = output
return False
else:
self.result = None
self.output = connect
return connect</code></pre>
</details>
</dd>
</dl>
</dd>
<dt id="conn.nodes"><code class="flex name class">
<span>class <span class="ident">nodes</span></span>
<span>(</span><span>nodes: dict, config='')</span>
</code></dt>
<dd>
<div class="desc"><p>This class generates a nodes object. Contains a list of node class objects and methods to run multiple tasks on nodes simultaneously.</p>
<h3 id="attributes">Attributes:</h3>
<pre><code>- nodelist (list): List of node class objects passed to the init
function.
- output (dict): Dictionary formed by nodes unique as keys,
output of the commands you ran on the node as
value. Created after running methods run or test.
- result (dict): Dictionary formed by nodes unique as keys, value
is True if expected value is found after running
the commands, False if prompt is found before.
Created after running method test.
- &lt;unique&gt; (obj): For each item in nodelist, there is an attribute
generated with the node unique.
</code></pre>
<h3 id="parameters">Parameters:</h3>
<pre><code>- nodes (dict): Dictionary formed by node information:
Keys: Unique name for each node.
Mandatory Subkeys: host(str).
Optional Subkeys: options(str), logs(str), password(str),
port(str), protocol(str), user(str).
For reference on subkeys check node class.
</code></pre>
2022-04-03 10:26:08 -03:00
<h3 id="optional-parameters">Optional Parameters:</h3>
2022-04-03 09:38:00 -03:00
<pre><code>- config (obj): Pass the object created with class configfile with key
for decryption and extra configuration if you are using
connection manager.
</code></pre></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class nodes:
&#39;&#39;&#39; This class generates a nodes object. Contains a list of node class objects and methods to run multiple tasks on nodes simultaneously.
### Attributes:
- nodelist (list): List of node class objects passed to the init
function.
- output (dict): Dictionary formed by nodes unique as keys,
output of the commands you ran on the node as
value. Created after running methods run or test.
- result (dict): Dictionary formed by nodes unique as keys, value
is True if expected value is found after running
the commands, False if prompt is found before.
Created after running method test.
- &lt;unique&gt; (obj): For each item in nodelist, there is an attribute
generated with the node unique.
&#39;&#39;&#39;
def __init__(self, nodes: dict, config = &#39;&#39;):
&#39;&#39;&#39;
### Parameters:
- nodes (dict): Dictionary formed by node information:
Keys: Unique name for each node.
Mandatory Subkeys: host(str).
Optional Subkeys: options(str), logs(str), password(str),
port(str), protocol(str), user(str).
For reference on subkeys check node class.
2022-04-03 10:26:08 -03:00
### Optional Parameters:
2022-04-03 09:38:00 -03:00
- config (obj): Pass the object created with class configfile with key
for decryption and extra configuration if you are using
connection manager.
&#39;&#39;&#39;
self.nodelist = []
self.config = config
for n in nodes:
this = node(n, **nodes[n], config = config)
self.nodelist.append(this)
setattr(self,n,this)
def _splitlist(self, lst, n):
#split a list in lists of n members.
for i in range(0, len(lst), n):
yield lst[i:i + n]
def run(self, commands,*, folder = None, prompt = None, stdout = None, parallel = 10):
&#39;&#39;&#39;
Run a command or list of commands on all the nodes in nodelist.
2022-04-03 10:26:08 -03:00
### Parameters:
commands (str/list): Commands to run on the node. Should be str or
list of str.
### Optional Named Parameters:
folder (str): Path where output log should be stored, leave empty
to disable logging.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while routers
use &#34;&gt;&#34; or &#34;#&#34;. The default value should work for
most nodes. Change it if your connection need some
special symbol.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
stdout (bool): Set True to send the command output to stdout.
Default False.
parallel (int): Number of nodes to run the commands simultaneously.
Default is 10, if there are more nodes that this
value, nodes are groups in groups with max this
number of members.
###Returns:
dict: Dictionary formed by nodes unique as keys, Output of the
commands you ran on the node as value.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
args = {}
args[&#34;commands&#34;] = commands
if folder != None:
args[&#34;folder&#34;] = folder
if prompt != None:
args[&#34;prompt&#34;] = prompt
if stdout != None:
args[&#34;stdout&#34;] = stdout
output = {}
tasks = []
for n in self.nodelist:
tasks.append(threading.Thread(target=n.run, kwargs=args))
taskslist = list(self._splitlist(tasks, parallel))
for t in taskslist:
for i in t:
i.start()
for i in t:
i.join()
for i in self.nodelist:
output[i.unique] = i.output
self.output = output
return output
def test(self, commands, expected, *, prompt = None, parallel = 10):
&#39;&#39;&#39;
Run a command or list of commands on all the nodes in nodelist, then check if expected value appears on the output after the last command.
2022-04-03 10:26:08 -03:00
### Parameters:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
commands (str/list): Commands to run on the node. Should be str or
list of str.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
expected (str) : Expected text to appear after running all the
commands on the node.
### Optional Named Parameters:
prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while
routers use &#34;&gt;&#34; or &#34;#&#34;. The default value should
work for most nodes. Change it if your connection
need some special symbol.
### Returns:
dict: Dictionary formed by nodes unique as keys, value is True if
expected value is found after running the commands, False
if prompt is found before.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
args = {}
args[&#34;commands&#34;] = commands
args[&#34;expected&#34;] = expected
if prompt != None:
args[&#34;prompt&#34;] = prompt
output = {}
result = {}
tasks = []
for n in self.nodelist:
tasks.append(threading.Thread(target=n.test, kwargs=args))
taskslist = list(self._splitlist(tasks, parallel))
for t in taskslist:
for i in t:
i.start()
for i in t:
i.join()
for i in self.nodelist:
result[i.unique] = i.result
output[i.unique] = i.output
self.output = output
self.result = result
return result</code></pre>
</details>
<h3>Methods</h3>
<dl>
<dt id="conn.nodes.run"><code class="name flex">
<span>def <span class="ident">run</span></span>(<span>self, commands, *, folder=None, prompt=None, stdout=None, parallel=10)</span>
</code></dt>
<dd>
<div class="desc"><p>Run a command or list of commands on all the nodes in nodelist.</p>
2022-04-03 10:26:08 -03:00
<h3 id="parameters">Parameters:</h3>
<pre><code>commands (str/list): Commands to run on the node. Should be str or
list of str.
</code></pre>
<h3 id="optional-named-parameters">Optional Named Parameters:</h3>
<pre><code>folder (str): Path where output log should be stored, leave empty
to disable logging.
prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses "&gt;" or EOF while routers
use "&gt;" or "#". The default value should work for
most nodes. Change it if your connection need some
special symbol.
stdout (bool): Set True to send the command output to stdout.
Default False.
parallel (int): Number of nodes to run the commands simultaneously.
Default is 10, if there are more nodes that this
value, nodes are groups in groups with max this
number of members.
</code></pre>
<h3 id="returns">Returns:</h3>
<pre><code>dict: Dictionary formed by nodes unique as keys, Output of the
commands you ran on the node as value.
</code></pre></div>
2022-04-03 09:38:00 -03:00
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def run(self, commands,*, folder = None, prompt = None, stdout = None, parallel = 10):
&#39;&#39;&#39;
Run a command or list of commands on all the nodes in nodelist.
2022-04-03 10:26:08 -03:00
### Parameters:
commands (str/list): Commands to run on the node. Should be str or
list of str.
### Optional Named Parameters:
folder (str): Path where output log should be stored, leave empty
to disable logging.
prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while routers
use &#34;&gt;&#34; or &#34;#&#34;. The default value should work for
most nodes. Change it if your connection need some
special symbol.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
stdout (bool): Set True to send the command output to stdout.
Default False.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
parallel (int): Number of nodes to run the commands simultaneously.
Default is 10, if there are more nodes that this
value, nodes are groups in groups with max this
number of members.
###Returns:
dict: Dictionary formed by nodes unique as keys, Output of the
commands you ran on the node as value.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
args = {}
args[&#34;commands&#34;] = commands
if folder != None:
args[&#34;folder&#34;] = folder
if prompt != None:
args[&#34;prompt&#34;] = prompt
if stdout != None:
args[&#34;stdout&#34;] = stdout
output = {}
tasks = []
for n in self.nodelist:
tasks.append(threading.Thread(target=n.run, kwargs=args))
taskslist = list(self._splitlist(tasks, parallel))
for t in taskslist:
for i in t:
i.start()
for i in t:
i.join()
for i in self.nodelist:
output[i.unique] = i.output
self.output = output
return output</code></pre>
</details>
</dd>
<dt id="conn.nodes.test"><code class="name flex">
<span>def <span class="ident">test</span></span>(<span>self, commands, expected, *, prompt=None, parallel=10)</span>
</code></dt>
<dd>
<div class="desc"><p>Run a command or list of commands on all the nodes in nodelist, then check if expected value appears on the output after the last command.</p>
2022-04-03 10:26:08 -03:00
<h3 id="parameters">Parameters:</h3>
<pre><code>commands (str/list): Commands to run on the node. Should be str or
list of str.
expected (str) : Expected text to appear after running all the
commands on the node.
</code></pre>
<h3 id="optional-named-parameters">Optional Named Parameters:</h3>
<pre><code>prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses "&gt;" or EOF while
routers use "&gt;" or "#". The default value should
work for most nodes. Change it if your connection
need some special symbol.
</code></pre>
<h3 id="returns">Returns:</h3>
<pre><code>dict: Dictionary formed by nodes unique as keys, value is True if
expected value is found after running the commands, False
if prompt is found before.
</code></pre></div>
2022-04-03 09:38:00 -03:00
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test(self, commands, expected, *, prompt = None, parallel = 10):
&#39;&#39;&#39;
Run a command or list of commands on all the nodes in nodelist, then check if expected value appears on the output after the last command.
2022-04-03 10:26:08 -03:00
### Parameters:
commands (str/list): Commands to run on the node. Should be str or
list of str.
expected (str) : Expected text to appear after running all the
commands on the node.
### Optional Named Parameters:
prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses &#34;&gt;&#34; or EOF while
routers use &#34;&gt;&#34; or &#34;#&#34;. The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
### Returns:
2022-04-03 09:38:00 -03:00
2022-04-03 10:26:08 -03:00
dict: Dictionary formed by nodes unique as keys, value is True if
expected value is found after running the commands, False
if prompt is found before.
2022-04-03 09:38:00 -03:00
&#39;&#39;&#39;
args = {}
args[&#34;commands&#34;] = commands
args[&#34;expected&#34;] = expected
if prompt != None:
args[&#34;prompt&#34;] = prompt
output = {}
result = {}
tasks = []
for n in self.nodelist:
tasks.append(threading.Thread(target=n.test, kwargs=args))
taskslist = list(self._splitlist(tasks, parallel))
for t in taskslist:
for i in t:
i.start()
for i in t:
i.join()
for i in self.nodelist:
result[i.unique] = i.result
output[i.unique] = i.output
self.output = output
self.result = result
return result</code></pre>
</details>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
2022-04-03 12:00:35 -03:00
<ul>
<li><a href="#connection-manager">Connection manager</a><ul>
2022-04-03 18:25:58 -03:00
<li><a href="#features">Features</a></li>
<li><a href="#usage">Usage</a></li>
2022-04-03 12:00:35 -03:00
<li><a href="#manage-profiles">Manage profiles</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</li>
2022-04-03 18:25:58 -03:00
<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>
<li><a href="#running-parallel-tasks">Running parallel tasks</a></li>
</ul>
</li>
2022-04-03 12:00:35 -03:00
</ul>
2022-04-03 09:38:00 -03:00
</div>
<ul id="index">
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="conn.configfile" href="#conn.configfile">configfile</a></code></h4>
<ul class="">
<li><code><a title="conn.configfile.getitem" href="#conn.configfile.getitem">getitem</a></code></li>
2022-04-03 18:25:58 -03:00
</ul>
</li>
<li>
<h4><code><a title="conn.connapp" href="#conn.connapp">connapp</a></code></h4>
<ul class="">
<li><code><a title="conn.connapp.encrypt" href="#conn.connapp.encrypt">encrypt</a></code></li>
2022-04-03 09:38:00 -03:00
</ul>
</li>
<li>
<h4><code><a title="conn.node" href="#conn.node">node</a></code></h4>
<ul class="">
<li><code><a title="conn.node.interact" href="#conn.node.interact">interact</a></code></li>
<li><code><a title="conn.node.run" href="#conn.node.run">run</a></code></li>
<li><code><a title="conn.node.test" href="#conn.node.test">test</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="conn.nodes" href="#conn.nodes">nodes</a></code></h4>
<ul class="">
<li><code><a title="conn.nodes.run" href="#conn.nodes.run">run</a></code></li>
<li><code><a title="conn.nodes.test" href="#conn.nodes.test">test</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>