new version add automation in cmd
This commit is contained in:
@ -70,9 +70,9 @@ commands.append("interface lo {id}")
|
|||||||
commands.append("ip add {ip} {mask}")
|
commands.append("ip add {ip} {mask}")
|
||||||
commands.append("end")
|
commands.append("end")
|
||||||
variables = {}
|
variables = {}
|
||||||
variables["router1"] = {"ip": "10.57.57.1"}
|
variables["router1@office"] = {"ip": "10.57.57.1"}
|
||||||
variables["router2"] = {"ip": "10.57.57.2"}
|
variables["router2@office"] = {"ip": "10.57.57.2"}
|
||||||
variables["router3"] = {"ip": "10.57.57.3"}
|
variables["router3@office"] = {"ip": "10.57.57.3"}
|
||||||
variables["__global__"] = {"id": "57"}
|
variables["__global__"] = {"id": "57"}
|
||||||
variables["__global__"]["mask"] = "255.255.255.255"
|
variables["__global__"]["mask"] = "255.255.255.255"
|
||||||
expected = "!"
|
expected = "!"
|
||||||
@ -123,6 +123,7 @@ positional arguments:
|
|||||||
copy (cp) Copy node
|
copy (cp) Copy node
|
||||||
list (ls) List profiles, nodes or folders
|
list (ls) List profiles, nodes or folders
|
||||||
bulk Add nodes in bulk
|
bulk Add nodes in bulk
|
||||||
|
run Run scripts or commands on nodes
|
||||||
config Manage app config
|
config Manage app config
|
||||||
```
|
```
|
||||||
|
|
||||||
|
70
automation-template.yaml
Normal file
70
automation-template.yaml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
tasks:
|
||||||
|
- name: "Config"
|
||||||
|
|
||||||
|
action: 'run' #Action can be test or run. Mandatory
|
||||||
|
|
||||||
|
nodes: #List of nodes to work on. Mandatory
|
||||||
|
- 'router1@office' #You can add specific nodes
|
||||||
|
- '@aws' #entire folders or subfolders
|
||||||
|
- '@office': #or filter inside a filder or subfolder
|
||||||
|
- 'router2'
|
||||||
|
- 'router7'
|
||||||
|
|
||||||
|
commands: #List of commands to send, use {name} to pass variables
|
||||||
|
- 'term len 0'
|
||||||
|
- 'conf t'
|
||||||
|
- 'interface {if}'
|
||||||
|
- 'ip address 10.100.100.{id} 255.255.255.255'
|
||||||
|
- '{commit}'
|
||||||
|
- 'end'
|
||||||
|
|
||||||
|
variables: #Variables to use on commands and expected. Optional
|
||||||
|
__global__: #Global variables to use on all nodes, fallback if missing in the node.
|
||||||
|
commit: ''
|
||||||
|
if: 'loopback100'
|
||||||
|
router1@office:
|
||||||
|
id: 1
|
||||||
|
router2@office:
|
||||||
|
id: 2
|
||||||
|
commit: 'commit'
|
||||||
|
router3@office:
|
||||||
|
id: 3
|
||||||
|
vrouter1@aws:
|
||||||
|
id: 4
|
||||||
|
vrouterN@aws:
|
||||||
|
id: 5
|
||||||
|
|
||||||
|
output: /home/user/logs #Type of output, if null you only get Connection and test result. Choices are: null,stdout,/path/to/folder. Folder path only works on 'run' action.
|
||||||
|
|
||||||
|
options:
|
||||||
|
prompt: r'>$|#$|\$$|>.$|#.$|\$.$' #Optional prompt to check on your devices, default should work on most devices.
|
||||||
|
parallel: 10 #Optional number of nodes to run commands on parallel. Default 10.
|
||||||
|
timeout: 20 #Optional time to wait in seconds for prompt, expected or EOF. Default 20.
|
||||||
|
|
||||||
|
- name: "TestConfig"
|
||||||
|
action: 'test'
|
||||||
|
nodes:
|
||||||
|
- 'router1@office'
|
||||||
|
- '@aws'
|
||||||
|
- '@office':
|
||||||
|
- 'router2'
|
||||||
|
- 'router7'
|
||||||
|
commands:
|
||||||
|
- 'ping 10.100.100.{id}'
|
||||||
|
expected: '!' #Expected text to find when running test action. Mandatory for 'test'
|
||||||
|
variables:
|
||||||
|
router1@office:
|
||||||
|
id: 1
|
||||||
|
router2@office:
|
||||||
|
id: 2
|
||||||
|
commit: 'commit'
|
||||||
|
router3@office:
|
||||||
|
id: 3
|
||||||
|
vrouter1@aws:
|
||||||
|
id: 4
|
||||||
|
vrouterN@aws:
|
||||||
|
id: 5
|
||||||
|
output: null
|
||||||
|
...
|
||||||
|
|
@ -39,6 +39,7 @@ Commands:
|
|||||||
copy (cp) Copy node
|
copy (cp) Copy node
|
||||||
list (ls) List profiles, nodes or folders
|
list (ls) List profiles, nodes or folders
|
||||||
bulk Add nodes in bulk
|
bulk Add nodes in bulk
|
||||||
|
run Run scripts or commands on nodes
|
||||||
config Manage app config
|
config Manage app config
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -132,9 +133,9 @@ commands.append("interface lo {id}")
|
|||||||
commands.append("ip add {ip} {mask}")
|
commands.append("ip add {ip} {mask}")
|
||||||
commands.append("end")
|
commands.append("end")
|
||||||
variables = {}
|
variables = {}
|
||||||
variables["router1"] = {"ip": "10.57.57.1"}
|
variables["router1@office"] = {"ip": "10.57.57.1"}
|
||||||
variables["router2"] = {"ip": "10.57.57.2"}
|
variables["router2@office"] = {"ip": "10.57.57.2"}
|
||||||
variables["router3"] = {"ip": "10.57.57.3"}
|
variables["router3@office"] = {"ip": "10.57.57.3"}
|
||||||
variables["__global__"] = {"id": "57"}
|
variables["__global__"] = {"id": "57"}
|
||||||
variables["__global__"]["mask"] = "255.255.255.255"
|
variables["__global__"]["mask"] = "255.255.255.255"
|
||||||
expected = "!"
|
expected = "!"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
__version__ = "2.0.5"
|
__version__ = "2.1.0"
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ def main():
|
|||||||
wordsnumber = int(sys.argv[1])
|
wordsnumber = int(sys.argv[1])
|
||||||
words = sys.argv[3:]
|
words = sys.argv[3:]
|
||||||
if wordsnumber == 2:
|
if wordsnumber == 2:
|
||||||
strings=["--add", "--del", "--rm", "--edit", "--mod", "--show", "mv", "move", "ls", "list", "cp", "copy", "profile", "bulk", "config", "--help"]
|
strings=["--add", "--del", "--rm", "--edit", "--mod", "--show", "mv", "move", "ls", "list", "cp", "copy", "profile", "run", "bulk", "config", "--help"]
|
||||||
strings.extend(nodes)
|
strings.extend(nodes)
|
||||||
strings.extend(folders)
|
strings.extend(folders)
|
||||||
|
|
||||||
@ -53,11 +53,11 @@ def main():
|
|||||||
strings=["profile"]
|
strings=["profile"]
|
||||||
if words[0] in ["list", "ls"]:
|
if words[0] in ["list", "ls"]:
|
||||||
strings=["profiles", "nodes", "folders"]
|
strings=["profiles", "nodes", "folders"]
|
||||||
if words[0] in ["bulk", "mv", "cp", "copy"]:
|
if words[0] in ["bulk", "mv", "cp", "copy", "run"]:
|
||||||
strings=["--help"]
|
strings=["--help"]
|
||||||
if words[0] in ["--rm", "--del", "-r"]:
|
if words[0] in ["--rm", "--del", "-r"]:
|
||||||
strings.extend(folders)
|
strings.extend(folders)
|
||||||
if words[0] in ["--rm", "--del", "-r", "--mod", "--edit", "-e", "--show", "-s", "mv", "move", "cp", "copy"]:
|
if words[0] in ["--rm", "--del", "-r", "--mod", "--edit", "-e", "--show", "-s", "mv", "move", "cp", "copy", "run"]:
|
||||||
strings.extend(nodes)
|
strings.extend(nodes)
|
||||||
|
|
||||||
elif wordsnumber == 4:
|
elif wordsnumber == 4:
|
||||||
|
@ -5,6 +5,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
#functions and classes
|
#functions and classes
|
||||||
@ -153,7 +154,7 @@ class configfile:
|
|||||||
folder = self.connections[uniques["folder"]][uniques["subfolder"]]
|
folder = self.connections[uniques["folder"]][uniques["subfolder"]]
|
||||||
else:
|
else:
|
||||||
folder = self.connections[uniques["folder"]]
|
folder = self.connections[uniques["folder"]]
|
||||||
newfolder = folder.copy()
|
newfolder = deepcopy(folder)
|
||||||
newfolder.pop("type")
|
newfolder.pop("type")
|
||||||
for node in folder.keys():
|
for node in folder.keys():
|
||||||
if node == "type":
|
if node == "type":
|
||||||
@ -164,9 +165,11 @@ class configfile:
|
|||||||
else:
|
else:
|
||||||
newfolder[node].pop("type")
|
newfolder[node].pop("type")
|
||||||
if keys == None:
|
if keys == None:
|
||||||
|
newfolder = {"{}{}".format(k,unique):v for k,v in newfolder.items()}
|
||||||
return newfolder
|
return newfolder
|
||||||
else:
|
else:
|
||||||
f_newfolder = dict((k, newfolder[k]) for k in keys)
|
f_newfolder = dict((k, newfolder[k]) for k in keys)
|
||||||
|
f_newfolder = {"{}{}".format(k,unique):v for k,v in f_newfolder.items()}
|
||||||
return f_newfolder
|
return f_newfolder
|
||||||
else:
|
else:
|
||||||
if uniques.keys() >= {"folder", "subfolder"}:
|
if uniques.keys() >= {"folder", "subfolder"}:
|
||||||
@ -175,7 +178,7 @@ class configfile:
|
|||||||
node = self.connections[uniques["folder"]][uniques["id"]]
|
node = self.connections[uniques["folder"]][uniques["id"]]
|
||||||
else:
|
else:
|
||||||
node = self.connections[uniques["id"]]
|
node = self.connections[uniques["id"]]
|
||||||
newnode = node.copy()
|
newnode = deepcopy(node)
|
||||||
newnode.pop("type")
|
newnode.pop("type")
|
||||||
return newnode
|
return newnode
|
||||||
|
|
||||||
|
@ -8,8 +8,9 @@ import ast
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import inquirer
|
import inquirer
|
||||||
from .core import node
|
from .core import node,nodes
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
|
import yaml
|
||||||
try:
|
try:
|
||||||
from pyfzf.pyfzf import FzfPrompt
|
from pyfzf.pyfzf import FzfPrompt
|
||||||
except:
|
except:
|
||||||
@ -33,6 +34,7 @@ class connapp:
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
self.node = node
|
self.node = node
|
||||||
|
self.connnodes = nodes
|
||||||
self.config = config
|
self.config = config
|
||||||
self.nodes = self._getallnodes()
|
self.nodes = self._getallnodes()
|
||||||
self.folders = self._getallfolders()
|
self.folders = self._getallfolders()
|
||||||
@ -81,6 +83,11 @@ class connapp:
|
|||||||
bulkparser = subparsers.add_parser("bulk", help="Add nodes in bulk")
|
bulkparser = subparsers.add_parser("bulk", help="Add nodes in bulk")
|
||||||
bulkparser.add_argument("bulk", const="bulk", nargs=0, action=self._store_type, help="Add nodes in bulk")
|
bulkparser.add_argument("bulk", const="bulk", nargs=0, action=self._store_type, help="Add nodes in bulk")
|
||||||
bulkparser.set_defaults(func=self._func_others)
|
bulkparser.set_defaults(func=self._func_others)
|
||||||
|
#RUNPARSER
|
||||||
|
runparser = subparsers.add_parser("run", help="Run scripts or commands on nodes", formatter_class=argparse.RawTextHelpFormatter)
|
||||||
|
runparser.add_argument("run", nargs='+', action=self._store_type, help=self._help("run"), default="run", type=self._type_node)
|
||||||
|
runparser.add_argument("-g","--generate", dest="action", action="store_const", help="Generate yaml file template", const="generate", default="run")
|
||||||
|
runparser.set_defaults(func=self._func_run)
|
||||||
#CONFIGPARSER
|
#CONFIGPARSER
|
||||||
configparser = subparsers.add_parser("config", help="Manage app config")
|
configparser = subparsers.add_parser("config", help="Manage app config")
|
||||||
configcrud = configparser.add_mutually_exclusive_group(required=True)
|
configcrud = configparser.add_mutually_exclusive_group(required=True)
|
||||||
@ -89,8 +96,8 @@ class connapp:
|
|||||||
configcrud.add_argument("--keepalive", dest="idletime", nargs=1, action=self._store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT")
|
configcrud.add_argument("--keepalive", dest="idletime", nargs=1, action=self._store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT")
|
||||||
configcrud.add_argument("--completion", dest="completion", nargs=1, choices=["bash","zsh"], action=self._store_type, help="Get terminal completion configuration for conn")
|
configcrud.add_argument("--completion", dest="completion", nargs=1, choices=["bash","zsh"], action=self._store_type, help="Get terminal completion configuration for conn")
|
||||||
configparser.set_defaults(func=self._func_others)
|
configparser.set_defaults(func=self._func_others)
|
||||||
#Set default subparser and tune arguments
|
#Manage sys arguments
|
||||||
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "config"]
|
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config"]
|
||||||
profilecmds = ["--add", "-a", "--del", "--rm", "-r", "--mod", "--edit", "-e", "--show", "-s"]
|
profilecmds = ["--add", "-a", "--del", "--rm", "-r", "--mod", "--edit", "-e", "--show", "-s"]
|
||||||
if len(sys.argv) >= 3 and sys.argv[2] == "profile" and sys.argv[1] in profilecmds:
|
if len(sys.argv) >= 3 and sys.argv[2] == "profile" and sys.argv[1] in profilecmds:
|
||||||
sys.argv[2] = sys.argv[1]
|
sys.argv[2] = sys.argv[1]
|
||||||
@ -426,6 +433,128 @@ class connapp:
|
|||||||
self.config._saveconfig(self.config.file)
|
self.config._saveconfig(self.config.file)
|
||||||
print("Config saved")
|
print("Config saved")
|
||||||
|
|
||||||
|
def _func_run(self, args):
|
||||||
|
if len(args.data) > 1:
|
||||||
|
command = " ".join(args.data[1:])
|
||||||
|
command = command.split("-")
|
||||||
|
matches = list(filter(lambda k: k == args.data[0], self.nodes))
|
||||||
|
if len(matches) == 0:
|
||||||
|
print("{} not found".format(args.data[0]))
|
||||||
|
exit(2)
|
||||||
|
node = self.config.getitem(matches[0])
|
||||||
|
node = self.node(matches[0],**node, config = self.config)
|
||||||
|
node.run(command)
|
||||||
|
print(node.output)
|
||||||
|
else:
|
||||||
|
if args.action == "generate":
|
||||||
|
if os.path.exists(args.data[0]):
|
||||||
|
print("File {} already exists".format(args.data[0]))
|
||||||
|
exit(14)
|
||||||
|
else:
|
||||||
|
with open(args.data[0], "w") as file:
|
||||||
|
file.write(self._help("generate"))
|
||||||
|
file.close()
|
||||||
|
print("File {} generated succesfully".format(args.data[0]))
|
||||||
|
exit()
|
||||||
|
try:
|
||||||
|
with open(args.data[0]) as file:
|
||||||
|
scripts = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
except:
|
||||||
|
print("failed reading file {}".format(args.data[0]))
|
||||||
|
exit(10)
|
||||||
|
for script in scripts["tasks"]:
|
||||||
|
nodes = {}
|
||||||
|
args = {}
|
||||||
|
try:
|
||||||
|
action = script["action"]
|
||||||
|
except:
|
||||||
|
print("Action is mandatory")
|
||||||
|
exit(11)
|
||||||
|
try:
|
||||||
|
nodelist = script["nodes"]
|
||||||
|
except:
|
||||||
|
print("Nodes list is mandatory")
|
||||||
|
exit(11)
|
||||||
|
# try:
|
||||||
|
for i in nodelist:
|
||||||
|
if isinstance(i, dict):
|
||||||
|
name = list(i.keys())[0]
|
||||||
|
this = self.config.getitem(name, i[name])
|
||||||
|
nodes.update(this)
|
||||||
|
elif i.startswith("@"):
|
||||||
|
this = self.config.getitem(i)
|
||||||
|
nodes.update(this)
|
||||||
|
else:
|
||||||
|
this = self.config.getitem(i)
|
||||||
|
nodes[i] = this
|
||||||
|
nodes = self.connnodes(nodes, config = self.config)
|
||||||
|
# except:
|
||||||
|
# print("Failed getting nodes")
|
||||||
|
# exit(12)
|
||||||
|
try:
|
||||||
|
args["commands"] = script["commands"]
|
||||||
|
except:
|
||||||
|
print("Commands list is mandatory")
|
||||||
|
exit(11)
|
||||||
|
if action == "test":
|
||||||
|
try:
|
||||||
|
args["expected"] = script["expected"]
|
||||||
|
except:
|
||||||
|
print("Expected is mandatory with action 'test'")
|
||||||
|
exit(11)
|
||||||
|
try:
|
||||||
|
args["vars"] = script["variables"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
output = script["output"]
|
||||||
|
except:
|
||||||
|
print("output is mandatory")
|
||||||
|
exit(11)
|
||||||
|
stdout = False
|
||||||
|
if output is None:
|
||||||
|
pass
|
||||||
|
elif output == "stdout":
|
||||||
|
stdout = True
|
||||||
|
elif isinstance(output, str) and action == "run":
|
||||||
|
args["folder"] = output
|
||||||
|
try:
|
||||||
|
options = script["options"]
|
||||||
|
except:
|
||||||
|
options = None
|
||||||
|
if options is not None:
|
||||||
|
thisoptions = {k: v for k, v in options.items() if k in ["prompt", "parallel", "timeout"]}
|
||||||
|
print(thisoptions)
|
||||||
|
args.update(thisoptions)
|
||||||
|
size = str(os.get_terminal_size())
|
||||||
|
p = re.search(r'.*columns=([0-9]+)', size)
|
||||||
|
columns = int(p.group(1))
|
||||||
|
if action == "run":
|
||||||
|
nodes.run(**args)
|
||||||
|
print(script["name"].upper() + "-" * (columns - len(script["name"])))
|
||||||
|
for i in nodes.status.keys():
|
||||||
|
print(" " + i + " " + "-" * (columns - len(i) - 13) + (" PASS(0)" if nodes.status[i] == 0 else " FAIL({})".format(nodes.status[i])))
|
||||||
|
if stdout:
|
||||||
|
for line in nodes.output[i].splitlines():
|
||||||
|
print(" " + line)
|
||||||
|
elif action == "test":
|
||||||
|
nodes.test(**args)
|
||||||
|
print(script["name"].upper() + "-" * (columns - len(script["name"])))
|
||||||
|
for i in nodes.status.keys():
|
||||||
|
print(" " + i + " " + "-" * (columns - len(i) - 13) + (" PASS(0)" if nodes.status[i] == 0 else " FAIL({})".format(nodes.status[i])))
|
||||||
|
if nodes.status[i] == 0:
|
||||||
|
try:
|
||||||
|
myexpected = args["expected"].format(**args["vars"][i])
|
||||||
|
except:
|
||||||
|
myexpected = args["expected"]
|
||||||
|
print(" TEST for '{}' --> ".format(myexpected) + str(nodes.result[i]).upper())
|
||||||
|
if stdout:
|
||||||
|
for line in nodes.output[i].splitlines():
|
||||||
|
print(" " + line)
|
||||||
|
else:
|
||||||
|
print("Wrong action '{}'".format(action))
|
||||||
|
exit(13)
|
||||||
|
|
||||||
def _choose(self, list, name, action):
|
def _choose(self, list, name, action):
|
||||||
#Generates an inquirer list to pick
|
#Generates an inquirer list to pick
|
||||||
if FzfPrompt and self.fzf:
|
if FzfPrompt and self.fzf:
|
||||||
@ -708,7 +837,7 @@ class connapp:
|
|||||||
if type == "usage":
|
if type == "usage":
|
||||||
return "conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]\n conn {profile,move,mv,copy,cp,list,ls,bulk,config} ..."
|
return "conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]\n conn {profile,move,mv,copy,cp,list,ls,bulk,config} ..."
|
||||||
if type == "end":
|
if type == "end":
|
||||||
return "Commands:\n profile Manage profiles\n move (mv) Move node\n copy (cp) Copy node\n list (ls) List profiles, nodes or folders\n bulk Add nodes in bulk\n config Manage app config"
|
return "Commands:\n profile Manage profiles\n move (mv) Move node\n copy (cp) Copy node\n list (ls) List profiles, nodes or folders\n bulk Add nodes in bulk\n run Run scripts or commands on nodes\n config Manage app config"
|
||||||
if type == "bashcompletion":
|
if type == "bashcompletion":
|
||||||
return '''
|
return '''
|
||||||
#Here starts bash completion for conn
|
#Here starts bash completion for conn
|
||||||
@ -735,6 +864,78 @@ compdef _conn conn
|
|||||||
compdef _conn connpy
|
compdef _conn connpy
|
||||||
#Here ends zsh completion for conn
|
#Here ends zsh completion for conn
|
||||||
'''
|
'''
|
||||||
|
if type == "run":
|
||||||
|
return "node[@subfolder][@folder] commmand to run\nRun the specific command on the node and print output\n/path/to/file.yaml\nUse a yaml file to run an automation script"
|
||||||
|
if type == "generate":
|
||||||
|
return '''---
|
||||||
|
tasks:
|
||||||
|
- name: "Config"
|
||||||
|
|
||||||
|
action: 'run' #Action can be test or run. Mandatory
|
||||||
|
|
||||||
|
nodes: #List of nodes to work on. Mandatory
|
||||||
|
- 'router1@office' #You can add specific nodes
|
||||||
|
- '@aws' #entire folders or subfolders
|
||||||
|
- '@office': #or filter inside a filder or subfolder
|
||||||
|
- 'router2'
|
||||||
|
- 'router7'
|
||||||
|
|
||||||
|
commands: #List of commands to send, use {name} to pass variables
|
||||||
|
- 'term len 0'
|
||||||
|
- 'conf t'
|
||||||
|
- 'interface {if}'
|
||||||
|
- 'ip address 10.100.100.{id} 255.255.255.255'
|
||||||
|
- '{commit}'
|
||||||
|
- 'end'
|
||||||
|
|
||||||
|
variables: #Variables to use on commands and expected. Optional
|
||||||
|
__global__: #Global variables to use on all nodes, fallback if missing in the node.
|
||||||
|
commit: ''
|
||||||
|
if: 'loopback100'
|
||||||
|
router1@office:
|
||||||
|
id: 1
|
||||||
|
router2@office:
|
||||||
|
id: 2
|
||||||
|
commit: 'commit'
|
||||||
|
router3@office:
|
||||||
|
id: 3
|
||||||
|
vrouter1@aws:
|
||||||
|
id: 4
|
||||||
|
vrouterN@aws:
|
||||||
|
id: 5
|
||||||
|
|
||||||
|
output: /home/user/logs #Type of output, if null you only get Connection and test result. Choices are: null,stdout,/path/to/folder. Folder path only works on 'run' action.
|
||||||
|
|
||||||
|
options:
|
||||||
|
prompt: r'>$|#$|\$$|>.$|#.$|\$.$' #Optional prompt to check on your devices, default should work on most devices.
|
||||||
|
parallel: 10 #Optional number of nodes to run commands on parallel. Default 10.
|
||||||
|
timeout: 20 #Optional time to wait in seconds for prompt, expected or EOF. Default 20.
|
||||||
|
|
||||||
|
- name: "TestConfig"
|
||||||
|
action: 'test'
|
||||||
|
nodes:
|
||||||
|
- 'router1@office'
|
||||||
|
- '@aws'
|
||||||
|
- '@office':
|
||||||
|
- 'router2'
|
||||||
|
- 'router7'
|
||||||
|
commands:
|
||||||
|
- 'ping 10.100.100.{id}'
|
||||||
|
expected: '!' #Expected text to find when running test action. Mandatory for 'test'
|
||||||
|
variables:
|
||||||
|
router1@office:
|
||||||
|
id: 1
|
||||||
|
router2@office:
|
||||||
|
id: 2
|
||||||
|
commit: 'commit'
|
||||||
|
router3@office:
|
||||||
|
id: 3
|
||||||
|
vrouter1@aws:
|
||||||
|
id: 4
|
||||||
|
vrouterN@aws:
|
||||||
|
id: 5
|
||||||
|
output: null
|
||||||
|
...'''
|
||||||
|
|
||||||
def _getallnodes(self):
|
def _getallnodes(self):
|
||||||
#get all nodes on configfile
|
#get all nodes on configfile
|
||||||
|
@ -10,6 +10,7 @@ from time import sleep
|
|||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
#functions and classes
|
#functions and classes
|
||||||
|
|
||||||
@ -355,16 +356,16 @@ class node:
|
|||||||
self.status = 1
|
self.status = 1
|
||||||
return connect
|
return connect
|
||||||
|
|
||||||
def _connect(self, debug = False, timeout = 10):
|
def _connect(self, debug = False, timeout = 20):
|
||||||
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
|
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
|
||||||
if self.protocol == "ssh":
|
if self.protocol == "ssh":
|
||||||
cmd = "ssh"
|
cmd = "ssh"
|
||||||
if self.idletime > 0:
|
if self.idletime > 0:
|
||||||
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
|
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
|
||||||
if self.user == '':
|
if self.user == '':
|
||||||
cmd = cmd + " -t {}".format(self.host)
|
cmd = cmd + " {}".format(self.host)
|
||||||
else:
|
else:
|
||||||
cmd = cmd + " -t {}".format("@".join([self.user,self.host]))
|
cmd = cmd + " {}".format("@".join([self.user,self.host]))
|
||||||
if self.port != '':
|
if self.port != '':
|
||||||
cmd = cmd + " -p " + self.port
|
cmd = cmd + " -p " + self.port
|
||||||
if self.options != '':
|
if self.options != '':
|
||||||
@ -530,7 +531,7 @@ class nodes:
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
|
|
||||||
###Returns:
|
###Returns:
|
||||||
|
|
||||||
@ -553,7 +554,7 @@ class nodes:
|
|||||||
status = {}
|
status = {}
|
||||||
tasks = []
|
tasks = []
|
||||||
for n in self.nodelist:
|
for n in self.nodelist:
|
||||||
nodesargs[n.unique] = args.copy()
|
nodesargs[n.unique] = deepcopy(args)
|
||||||
if vars != None:
|
if vars != None:
|
||||||
nodesargs[n.unique]["vars"] = {}
|
nodesargs[n.unique]["vars"] = {}
|
||||||
if "__global__" in vars.keys():
|
if "__global__" in vars.keys():
|
||||||
@ -610,7 +611,7 @@ class nodes:
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
|
|
||||||
### Returns:
|
### Returns:
|
||||||
|
|
||||||
@ -632,7 +633,7 @@ class nodes:
|
|||||||
status = {}
|
status = {}
|
||||||
tasks = []
|
tasks = []
|
||||||
for n in self.nodelist:
|
for n in self.nodelist:
|
||||||
nodesargs[n.unique] = args.copy()
|
nodesargs[n.unique] = deepcopy(args)
|
||||||
if vars != None:
|
if vars != None:
|
||||||
nodesargs[n.unique]["vars"] = {}
|
nodesargs[n.unique]["vars"] = {}
|
||||||
if "__global__" in vars.keys():
|
if "__global__" in vars.keys():
|
||||||
|
@ -58,6 +58,7 @@ Commands:
|
|||||||
copy (cp) Copy node
|
copy (cp) Copy node
|
||||||
list (ls) List profiles, nodes or folders
|
list (ls) List profiles, nodes or folders
|
||||||
bulk Add nodes in bulk
|
bulk Add nodes in bulk
|
||||||
|
run Run scripts or commands on nodes
|
||||||
config Manage app config
|
config Manage app config
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="manage-profiles">Manage profiles</h3>
|
<h3 id="manage-profiles">Manage profiles</h3>
|
||||||
@ -140,9 +141,9 @@ commands.append("interface lo {id}")
|
|||||||
commands.append("ip add {ip} {mask}")
|
commands.append("ip add {ip} {mask}")
|
||||||
commands.append("end")
|
commands.append("end")
|
||||||
variables = {}
|
variables = {}
|
||||||
variables["router1"] = {"ip": "10.57.57.1"}
|
variables["router1@office"] = {"ip": "10.57.57.1"}
|
||||||
variables["router2"] = {"ip": "10.57.57.2"}
|
variables["router2@office"] = {"ip": "10.57.57.2"}
|
||||||
variables["router3"] = {"ip": "10.57.57.3"}
|
variables["router3@office"] = {"ip": "10.57.57.3"}
|
||||||
variables["__global__"] = {"id": "57"}
|
variables["__global__"] = {"id": "57"}
|
||||||
variables["__global__"]["mask"] = "255.255.255.255"
|
variables["__global__"]["mask"] = "255.255.255.255"
|
||||||
expected = "!"
|
expected = "!"
|
||||||
@ -197,6 +198,7 @@ Commands:
|
|||||||
copy (cp) Copy node
|
copy (cp) Copy node
|
||||||
list (ls) List profiles, nodes or folders
|
list (ls) List profiles, nodes or folders
|
||||||
bulk Add nodes in bulk
|
bulk Add nodes in bulk
|
||||||
|
run Run scripts or commands on nodes
|
||||||
config Manage app config
|
config Manage app config
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -290,9 +292,9 @@ commands.append("interface lo {id}")
|
|||||||
commands.append("ip add {ip} {mask}")
|
commands.append("ip add {ip} {mask}")
|
||||||
commands.append("end")
|
commands.append("end")
|
||||||
variables = {}
|
variables = {}
|
||||||
variables["router1"] = {"ip": "10.57.57.1"}
|
variables["router1@office"] = {"ip": "10.57.57.1"}
|
||||||
variables["router2"] = {"ip": "10.57.57.2"}
|
variables["router2@office"] = {"ip": "10.57.57.2"}
|
||||||
variables["router3"] = {"ip": "10.57.57.3"}
|
variables["router3@office"] = {"ip": "10.57.57.3"}
|
||||||
variables["__global__"] = {"id": "57"}
|
variables["__global__"] = {"id": "57"}
|
||||||
variables["__global__"]["mask"] = "255.255.255.255"
|
variables["__global__"]["mask"] = "255.255.255.255"
|
||||||
expected = "!"
|
expected = "!"
|
||||||
@ -507,7 +509,7 @@ __pdoc__ = {
|
|||||||
folder = self.connections[uniques["folder"]][uniques["subfolder"]]
|
folder = self.connections[uniques["folder"]][uniques["subfolder"]]
|
||||||
else:
|
else:
|
||||||
folder = self.connections[uniques["folder"]]
|
folder = self.connections[uniques["folder"]]
|
||||||
newfolder = folder.copy()
|
newfolder = deepcopy(folder)
|
||||||
newfolder.pop("type")
|
newfolder.pop("type")
|
||||||
for node in folder.keys():
|
for node in folder.keys():
|
||||||
if node == "type":
|
if node == "type":
|
||||||
@ -518,9 +520,11 @@ __pdoc__ = {
|
|||||||
else:
|
else:
|
||||||
newfolder[node].pop("type")
|
newfolder[node].pop("type")
|
||||||
if keys == None:
|
if keys == None:
|
||||||
|
newfolder = {"{}{}".format(k,unique):v for k,v in newfolder.items()}
|
||||||
return newfolder
|
return newfolder
|
||||||
else:
|
else:
|
||||||
f_newfolder = dict((k, newfolder[k]) for k in keys)
|
f_newfolder = dict((k, newfolder[k]) for k in keys)
|
||||||
|
f_newfolder = {"{}{}".format(k,unique):v for k,v in f_newfolder.items()}
|
||||||
return f_newfolder
|
return f_newfolder
|
||||||
else:
|
else:
|
||||||
if uniques.keys() >= {"folder", "subfolder"}:
|
if uniques.keys() >= {"folder", "subfolder"}:
|
||||||
@ -529,7 +533,7 @@ __pdoc__ = {
|
|||||||
node = self.connections[uniques["folder"]][uniques["id"]]
|
node = self.connections[uniques["folder"]][uniques["id"]]
|
||||||
else:
|
else:
|
||||||
node = self.connections[uniques["id"]]
|
node = self.connections[uniques["id"]]
|
||||||
newnode = node.copy()
|
newnode = deepcopy(node)
|
||||||
newnode.pop("type")
|
newnode.pop("type")
|
||||||
return newnode
|
return newnode
|
||||||
|
|
||||||
@ -629,7 +633,7 @@ __pdoc__ = {
|
|||||||
folder = self.connections[uniques["folder"]][uniques["subfolder"]]
|
folder = self.connections[uniques["folder"]][uniques["subfolder"]]
|
||||||
else:
|
else:
|
||||||
folder = self.connections[uniques["folder"]]
|
folder = self.connections[uniques["folder"]]
|
||||||
newfolder = folder.copy()
|
newfolder = deepcopy(folder)
|
||||||
newfolder.pop("type")
|
newfolder.pop("type")
|
||||||
for node in folder.keys():
|
for node in folder.keys():
|
||||||
if node == "type":
|
if node == "type":
|
||||||
@ -640,9 +644,11 @@ __pdoc__ = {
|
|||||||
else:
|
else:
|
||||||
newfolder[node].pop("type")
|
newfolder[node].pop("type")
|
||||||
if keys == None:
|
if keys == None:
|
||||||
|
newfolder = {"{}{}".format(k,unique):v for k,v in newfolder.items()}
|
||||||
return newfolder
|
return newfolder
|
||||||
else:
|
else:
|
||||||
f_newfolder = dict((k, newfolder[k]) for k in keys)
|
f_newfolder = dict((k, newfolder[k]) for k in keys)
|
||||||
|
f_newfolder = {"{}{}".format(k,unique):v for k,v in f_newfolder.items()}
|
||||||
return f_newfolder
|
return f_newfolder
|
||||||
else:
|
else:
|
||||||
if uniques.keys() >= {"folder", "subfolder"}:
|
if uniques.keys() >= {"folder", "subfolder"}:
|
||||||
@ -651,7 +657,7 @@ __pdoc__ = {
|
|||||||
node = self.connections[uniques["folder"]][uniques["id"]]
|
node = self.connections[uniques["folder"]][uniques["id"]]
|
||||||
else:
|
else:
|
||||||
node = self.connections[uniques["id"]]
|
node = self.connections[uniques["id"]]
|
||||||
newnode = node.copy()
|
newnode = deepcopy(node)
|
||||||
newnode.pop("type")
|
newnode.pop("type")
|
||||||
return newnode</code></pre>
|
return newnode</code></pre>
|
||||||
</details>
|
</details>
|
||||||
@ -688,6 +694,7 @@ __pdoc__ = {
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
self.node = node
|
self.node = node
|
||||||
|
self.connnodes = nodes
|
||||||
self.config = config
|
self.config = config
|
||||||
self.nodes = self._getallnodes()
|
self.nodes = self._getallnodes()
|
||||||
self.folders = self._getallfolders()
|
self.folders = self._getallfolders()
|
||||||
@ -736,6 +743,11 @@ __pdoc__ = {
|
|||||||
bulkparser = subparsers.add_parser("bulk", help="Add nodes in bulk")
|
bulkparser = subparsers.add_parser("bulk", help="Add nodes in bulk")
|
||||||
bulkparser.add_argument("bulk", const="bulk", nargs=0, action=self._store_type, help="Add nodes in bulk")
|
bulkparser.add_argument("bulk", const="bulk", nargs=0, action=self._store_type, help="Add nodes in bulk")
|
||||||
bulkparser.set_defaults(func=self._func_others)
|
bulkparser.set_defaults(func=self._func_others)
|
||||||
|
#RUNPARSER
|
||||||
|
runparser = subparsers.add_parser("run", help="Run scripts or commands on nodes", formatter_class=argparse.RawTextHelpFormatter)
|
||||||
|
runparser.add_argument("run", nargs='+', action=self._store_type, help=self._help("run"), default="run", type=self._type_node)
|
||||||
|
runparser.add_argument("-g","--generate", dest="action", action="store_const", help="Generate yaml file template", const="generate", default="run")
|
||||||
|
runparser.set_defaults(func=self._func_run)
|
||||||
#CONFIGPARSER
|
#CONFIGPARSER
|
||||||
configparser = subparsers.add_parser("config", help="Manage app config")
|
configparser = subparsers.add_parser("config", help="Manage app config")
|
||||||
configcrud = configparser.add_mutually_exclusive_group(required=True)
|
configcrud = configparser.add_mutually_exclusive_group(required=True)
|
||||||
@ -744,8 +756,8 @@ __pdoc__ = {
|
|||||||
configcrud.add_argument("--keepalive", dest="idletime", nargs=1, action=self._store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT")
|
configcrud.add_argument("--keepalive", dest="idletime", nargs=1, action=self._store_type, help="Set keepalive time in seconds, 0 to disable", type=int, metavar="INT")
|
||||||
configcrud.add_argument("--completion", dest="completion", nargs=1, choices=["bash","zsh"], action=self._store_type, help="Get terminal completion configuration for conn")
|
configcrud.add_argument("--completion", dest="completion", nargs=1, choices=["bash","zsh"], action=self._store_type, help="Get terminal completion configuration for conn")
|
||||||
configparser.set_defaults(func=self._func_others)
|
configparser.set_defaults(func=self._func_others)
|
||||||
#Set default subparser and tune arguments
|
#Manage sys arguments
|
||||||
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "config"]
|
commands = ["node", "profile", "mv", "move","copy", "cp", "bulk", "ls", "list", "run", "config"]
|
||||||
profilecmds = ["--add", "-a", "--del", "--rm", "-r", "--mod", "--edit", "-e", "--show", "-s"]
|
profilecmds = ["--add", "-a", "--del", "--rm", "-r", "--mod", "--edit", "-e", "--show", "-s"]
|
||||||
if len(sys.argv) >= 3 and sys.argv[2] == "profile" and sys.argv[1] in profilecmds:
|
if len(sys.argv) >= 3 and sys.argv[2] == "profile" and sys.argv[1] in profilecmds:
|
||||||
sys.argv[2] = sys.argv[1]
|
sys.argv[2] = sys.argv[1]
|
||||||
@ -1081,6 +1093,128 @@ __pdoc__ = {
|
|||||||
self.config._saveconfig(self.config.file)
|
self.config._saveconfig(self.config.file)
|
||||||
print("Config saved")
|
print("Config saved")
|
||||||
|
|
||||||
|
def _func_run(self, args):
|
||||||
|
if len(args.data) > 1:
|
||||||
|
command = " ".join(args.data[1:])
|
||||||
|
command = command.split("-")
|
||||||
|
matches = list(filter(lambda k: k == args.data[0], self.nodes))
|
||||||
|
if len(matches) == 0:
|
||||||
|
print("{} not found".format(args.data[0]))
|
||||||
|
exit(2)
|
||||||
|
node = self.config.getitem(matches[0])
|
||||||
|
node = self.node(matches[0],**node, config = self.config)
|
||||||
|
node.run(command)
|
||||||
|
print(node.output)
|
||||||
|
else:
|
||||||
|
if args.action == "generate":
|
||||||
|
if os.path.exists(args.data[0]):
|
||||||
|
print("File {} already exists".format(args.data[0]))
|
||||||
|
exit(14)
|
||||||
|
else:
|
||||||
|
with open(args.data[0], "w") as file:
|
||||||
|
file.write(self._help("generate"))
|
||||||
|
file.close()
|
||||||
|
print("File {} generated succesfully".format(args.data[0]))
|
||||||
|
exit()
|
||||||
|
try:
|
||||||
|
with open(args.data[0]) as file:
|
||||||
|
scripts = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
except:
|
||||||
|
print("failed reading file {}".format(args.data[0]))
|
||||||
|
exit(10)
|
||||||
|
for script in scripts["tasks"]:
|
||||||
|
nodes = {}
|
||||||
|
args = {}
|
||||||
|
try:
|
||||||
|
action = script["action"]
|
||||||
|
except:
|
||||||
|
print("Action is mandatory")
|
||||||
|
exit(11)
|
||||||
|
try:
|
||||||
|
nodelist = script["nodes"]
|
||||||
|
except:
|
||||||
|
print("Nodes list is mandatory")
|
||||||
|
exit(11)
|
||||||
|
# try:
|
||||||
|
for i in nodelist:
|
||||||
|
if isinstance(i, dict):
|
||||||
|
name = list(i.keys())[0]
|
||||||
|
this = self.config.getitem(name, i[name])
|
||||||
|
nodes.update(this)
|
||||||
|
elif i.startswith("@"):
|
||||||
|
this = self.config.getitem(i)
|
||||||
|
nodes.update(this)
|
||||||
|
else:
|
||||||
|
this = self.config.getitem(i)
|
||||||
|
nodes[i] = this
|
||||||
|
nodes = self.connnodes(nodes, config = self.config)
|
||||||
|
# except:
|
||||||
|
# print("Failed getting nodes")
|
||||||
|
# exit(12)
|
||||||
|
try:
|
||||||
|
args["commands"] = script["commands"]
|
||||||
|
except:
|
||||||
|
print("Commands list is mandatory")
|
||||||
|
exit(11)
|
||||||
|
if action == "test":
|
||||||
|
try:
|
||||||
|
args["expected"] = script["expected"]
|
||||||
|
except:
|
||||||
|
print("Expected is mandatory with action 'test'")
|
||||||
|
exit(11)
|
||||||
|
try:
|
||||||
|
args["vars"] = script["variables"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
output = script["output"]
|
||||||
|
except:
|
||||||
|
print("output is mandatory")
|
||||||
|
exit(11)
|
||||||
|
stdout = False
|
||||||
|
if output is None:
|
||||||
|
pass
|
||||||
|
elif output == "stdout":
|
||||||
|
stdout = True
|
||||||
|
elif isinstance(output, str) and action == "run":
|
||||||
|
args["folder"] = output
|
||||||
|
try:
|
||||||
|
options = script["options"]
|
||||||
|
except:
|
||||||
|
options = None
|
||||||
|
if options is not None:
|
||||||
|
thisoptions = {k: v for k, v in options.items() if k in ["prompt", "parallel", "timeout"]}
|
||||||
|
print(thisoptions)
|
||||||
|
args.update(thisoptions)
|
||||||
|
size = str(os.get_terminal_size())
|
||||||
|
p = re.search(r'.*columns=([0-9]+)', size)
|
||||||
|
columns = int(p.group(1))
|
||||||
|
if action == "run":
|
||||||
|
nodes.run(**args)
|
||||||
|
print(script["name"].upper() + "-" * (columns - len(script["name"])))
|
||||||
|
for i in nodes.status.keys():
|
||||||
|
print(" " + i + " " + "-" * (columns - len(i) - 13) + (" PASS(0)" if nodes.status[i] == 0 else " FAIL({})".format(nodes.status[i])))
|
||||||
|
if stdout:
|
||||||
|
for line in nodes.output[i].splitlines():
|
||||||
|
print(" " + line)
|
||||||
|
elif action == "test":
|
||||||
|
nodes.test(**args)
|
||||||
|
print(script["name"].upper() + "-" * (columns - len(script["name"])))
|
||||||
|
for i in nodes.status.keys():
|
||||||
|
print(" " + i + " " + "-" * (columns - len(i) - 13) + (" PASS(0)" if nodes.status[i] == 0 else " FAIL({})".format(nodes.status[i])))
|
||||||
|
if nodes.status[i] == 0:
|
||||||
|
try:
|
||||||
|
myexpected = args["expected"].format(**args["vars"][i])
|
||||||
|
except:
|
||||||
|
myexpected = args["expected"]
|
||||||
|
print(" TEST for '{}' --> ".format(myexpected) + str(nodes.result[i]).upper())
|
||||||
|
if stdout:
|
||||||
|
for line in nodes.output[i].splitlines():
|
||||||
|
print(" " + line)
|
||||||
|
else:
|
||||||
|
print("Wrong action '{}'".format(action))
|
||||||
|
exit(13)
|
||||||
|
|
||||||
def _choose(self, list, name, action):
|
def _choose(self, list, name, action):
|
||||||
#Generates an inquirer list to pick
|
#Generates an inquirer list to pick
|
||||||
if FzfPrompt and self.fzf:
|
if FzfPrompt and self.fzf:
|
||||||
@ -1363,7 +1497,7 @@ __pdoc__ = {
|
|||||||
if type == "usage":
|
if type == "usage":
|
||||||
return "conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]\n conn {profile,move,mv,copy,cp,list,ls,bulk,config} ..."
|
return "conn [-h] [--add | --del | --mod | --show | --debug] [node|folder]\n conn {profile,move,mv,copy,cp,list,ls,bulk,config} ..."
|
||||||
if type == "end":
|
if type == "end":
|
||||||
return "Commands:\n profile Manage profiles\n move (mv) Move node\n copy (cp) Copy node\n list (ls) List profiles, nodes or folders\n bulk Add nodes in bulk\n config Manage app config"
|
return "Commands:\n profile Manage profiles\n move (mv) Move node\n copy (cp) Copy node\n list (ls) List profiles, nodes or folders\n bulk Add nodes in bulk\n run Run scripts or commands on nodes\n config Manage app config"
|
||||||
if type == "bashcompletion":
|
if type == "bashcompletion":
|
||||||
return '''
|
return '''
|
||||||
#Here starts bash completion for conn
|
#Here starts bash completion for conn
|
||||||
@ -1390,6 +1524,78 @@ compdef _conn conn
|
|||||||
compdef _conn connpy
|
compdef _conn connpy
|
||||||
#Here ends zsh completion for conn
|
#Here ends zsh completion for conn
|
||||||
'''
|
'''
|
||||||
|
if type == "run":
|
||||||
|
return "node[@subfolder][@folder] commmand to run\nRun the specific command on the node and print output\n/path/to/file.yaml\nUse a yaml file to run an automation script"
|
||||||
|
if type == "generate":
|
||||||
|
return '''---
|
||||||
|
tasks:
|
||||||
|
- name: "Config"
|
||||||
|
|
||||||
|
action: 'run' #Action can be test or run. Mandatory
|
||||||
|
|
||||||
|
nodes: #List of nodes to work on. Mandatory
|
||||||
|
- 'router1@office' #You can add specific nodes
|
||||||
|
- '@aws' #entire folders or subfolders
|
||||||
|
- '@office': #or filter inside a filder or subfolder
|
||||||
|
- 'router2'
|
||||||
|
- 'router7'
|
||||||
|
|
||||||
|
commands: #List of commands to send, use {name} to pass variables
|
||||||
|
- 'term len 0'
|
||||||
|
- 'conf t'
|
||||||
|
- 'interface {if}'
|
||||||
|
- 'ip address 10.100.100.{id} 255.255.255.255'
|
||||||
|
- '{commit}'
|
||||||
|
- 'end'
|
||||||
|
|
||||||
|
variables: #Variables to use on commands and expected. Optional
|
||||||
|
__global__: #Global variables to use on all nodes, fallback if missing in the node.
|
||||||
|
commit: ''
|
||||||
|
if: 'loopback100'
|
||||||
|
router1@office:
|
||||||
|
id: 1
|
||||||
|
router2@office:
|
||||||
|
id: 2
|
||||||
|
commit: 'commit'
|
||||||
|
router3@office:
|
||||||
|
id: 3
|
||||||
|
vrouter1@aws:
|
||||||
|
id: 4
|
||||||
|
vrouterN@aws:
|
||||||
|
id: 5
|
||||||
|
|
||||||
|
output: /home/user/logs #Type of output, if null you only get Connection and test result. Choices are: null,stdout,/path/to/folder. Folder path only works on 'run' action.
|
||||||
|
|
||||||
|
options:
|
||||||
|
prompt: r'>$|#$|\$$|>.$|#.$|\$.$' #Optional prompt to check on your devices, default should work on most devices.
|
||||||
|
parallel: 10 #Optional number of nodes to run commands on parallel. Default 10.
|
||||||
|
timeout: 20 #Optional time to wait in seconds for prompt, expected or EOF. Default 20.
|
||||||
|
|
||||||
|
- name: "TestConfig"
|
||||||
|
action: 'test'
|
||||||
|
nodes:
|
||||||
|
- 'router1@office'
|
||||||
|
- '@aws'
|
||||||
|
- '@office':
|
||||||
|
- 'router2'
|
||||||
|
- 'router7'
|
||||||
|
commands:
|
||||||
|
- 'ping 10.100.100.{id}'
|
||||||
|
expected: '!' #Expected text to find when running test action. Mandatory for 'test'
|
||||||
|
variables:
|
||||||
|
router1@office:
|
||||||
|
id: 1
|
||||||
|
router2@office:
|
||||||
|
id: 2
|
||||||
|
commit: 'commit'
|
||||||
|
router3@office:
|
||||||
|
id: 3
|
||||||
|
vrouter1@aws:
|
||||||
|
id: 4
|
||||||
|
vrouterN@aws:
|
||||||
|
id: 5
|
||||||
|
output: null
|
||||||
|
...'''
|
||||||
|
|
||||||
def _getallnodes(self):
|
def _getallnodes(self):
|
||||||
#get all nodes on configfile
|
#get all nodes on configfile
|
||||||
@ -1895,16 +2101,16 @@ compdef _conn connpy
|
|||||||
self.status = 1
|
self.status = 1
|
||||||
return connect
|
return connect
|
||||||
|
|
||||||
def _connect(self, debug = False, timeout = 10):
|
def _connect(self, debug = False, timeout = 20):
|
||||||
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
|
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
|
||||||
if self.protocol == "ssh":
|
if self.protocol == "ssh":
|
||||||
cmd = "ssh"
|
cmd = "ssh"
|
||||||
if self.idletime > 0:
|
if self.idletime > 0:
|
||||||
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
|
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
|
||||||
if self.user == '':
|
if self.user == '':
|
||||||
cmd = cmd + " -t {}".format(self.host)
|
cmd = cmd + " {}".format(self.host)
|
||||||
else:
|
else:
|
||||||
cmd = cmd + " -t {}".format("@".join([self.user,self.host]))
|
cmd = cmd + " {}".format("@".join([self.user,self.host]))
|
||||||
if self.port != '':
|
if self.port != '':
|
||||||
cmd = cmd + " -p " + self.port
|
cmd = cmd + " -p " + self.port
|
||||||
if self.options != '':
|
if self.options != '':
|
||||||
@ -2420,7 +2626,7 @@ compdef _conn connpy
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
|
|
||||||
###Returns:
|
###Returns:
|
||||||
|
|
||||||
@ -2443,7 +2649,7 @@ compdef _conn connpy
|
|||||||
status = {}
|
status = {}
|
||||||
tasks = []
|
tasks = []
|
||||||
for n in self.nodelist:
|
for n in self.nodelist:
|
||||||
nodesargs[n.unique] = args.copy()
|
nodesargs[n.unique] = deepcopy(args)
|
||||||
if vars != None:
|
if vars != None:
|
||||||
nodesargs[n.unique]["vars"] = {}
|
nodesargs[n.unique]["vars"] = {}
|
||||||
if "__global__" in vars.keys():
|
if "__global__" in vars.keys():
|
||||||
@ -2500,7 +2706,7 @@ compdef _conn connpy
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
|
|
||||||
### Returns:
|
### Returns:
|
||||||
|
|
||||||
@ -2522,7 +2728,7 @@ compdef _conn connpy
|
|||||||
status = {}
|
status = {}
|
||||||
tasks = []
|
tasks = []
|
||||||
for n in self.nodelist:
|
for n in self.nodelist:
|
||||||
nodesargs[n.unique] = args.copy()
|
nodesargs[n.unique] = deepcopy(args)
|
||||||
if vars != None:
|
if vars != None:
|
||||||
nodesargs[n.unique]["vars"] = {}
|
nodesargs[n.unique]["vars"] = {}
|
||||||
if "__global__" in vars.keys():
|
if "__global__" in vars.keys():
|
||||||
@ -2584,7 +2790,7 @@ compdef _conn connpy
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="returns">Returns:</h3>
|
<h3 id="returns">Returns:</h3>
|
||||||
<pre><code>dict: Dictionary formed by nodes unique as keys, Output of the
|
<pre><code>dict: Dictionary formed by nodes unique as keys, Output of the
|
||||||
@ -2633,7 +2839,7 @@ compdef _conn connpy
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
|
|
||||||
###Returns:
|
###Returns:
|
||||||
|
|
||||||
@ -2656,7 +2862,7 @@ compdef _conn connpy
|
|||||||
status = {}
|
status = {}
|
||||||
tasks = []
|
tasks = []
|
||||||
for n in self.nodelist:
|
for n in self.nodelist:
|
||||||
nodesargs[n.unique] = args.copy()
|
nodesargs[n.unique] = deepcopy(args)
|
||||||
if vars != None:
|
if vars != None:
|
||||||
nodesargs[n.unique]["vars"] = {}
|
nodesargs[n.unique]["vars"] = {}
|
||||||
if "__global__" in vars.keys():
|
if "__global__" in vars.keys():
|
||||||
@ -2712,7 +2918,7 @@ compdef _conn connpy
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="returns">Returns:</h3>
|
<h3 id="returns">Returns:</h3>
|
||||||
<pre><code>dict: Dictionary formed by nodes unique as keys, value is True if
|
<pre><code>dict: Dictionary formed by nodes unique as keys, value is True if
|
||||||
@ -2759,7 +2965,7 @@ compdef _conn connpy
|
|||||||
number of members.
|
number of members.
|
||||||
|
|
||||||
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
|
||||||
default 10.
|
default 20.
|
||||||
|
|
||||||
### Returns:
|
### Returns:
|
||||||
|
|
||||||
@ -2781,7 +2987,7 @@ compdef _conn connpy
|
|||||||
status = {}
|
status = {}
|
||||||
tasks = []
|
tasks = []
|
||||||
for n in self.nodelist:
|
for n in self.nodelist:
|
||||||
nodesargs[n.unique] = args.copy()
|
nodesargs[n.unique] = deepcopy(args)
|
||||||
if vars != None:
|
if vars != None:
|
||||||
nodesargs[n.unique]["vars"] = {}
|
nodesargs[n.unique]["vars"] = {}
|
||||||
if "__global__" in vars.keys():
|
if "__global__" in vars.keys():
|
||||||
|
Reference in New Issue
Block a user