Compare commits
No commits in common. "4d8244a10f4285e4874cd09772ea822e87c4a9c0" and "3365acb4735fca1fd0d3d7050fa0b13cdb307f6f" have entirely different histories.
4d8244a10f
...
3365acb473
3
.gitignore
vendored
3
.gitignore
vendored
@ -130,6 +130,3 @@ dmypy.json
|
|||||||
|
|
||||||
#clients
|
#clients
|
||||||
*sync_client*
|
*sync_client*
|
||||||
|
|
||||||
#App
|
|
||||||
connpy-completion-helper
|
|
||||||
|
55
README.md
55
README.md
@ -9,8 +9,7 @@
|
|||||||
[](https://github.com/fluzzi/connpy/blob/main/LICENSE)
|
[](https://github.com/fluzzi/connpy/blob/main/LICENSE)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
|
|
||||||
Connpy is a SSH, SFTP, Telnet, kubectl, and Docker pod connection manager and automation module for Linux, Mac, and Docker.
|
Connpy is a ssh and telnet connection manager and automation module for Linux, Mac and Docker
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -44,34 +43,33 @@ Connpy integrates with Google services for backup purposes:
|
|||||||
|
|
||||||
For more detailed information, please read our [Privacy Policy](https://connpy.gederico.dynu.net/fluzzi32/connpy/src/branch/main/PRIVATE_POLICY.md).
|
For more detailed information, please read our [Privacy Policy](https://connpy.gederico.dynu.net/fluzzi32/connpy/src/branch/main/PRIVATE_POLICY.md).
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
- Manage connections using SSH, SFTP, Telnet, kubectl, and Docker exec.
|
- You can generate profiles and reference them from nodes using @profilename so you dont
|
||||||
- Set contexts to manage specific nodes from specific contexts (work/home/clients/etc).
|
need to edit multiple nodes when changing password or other information.
|
||||||
- You can generate profiles and reference them from nodes using @profilename so you don't
|
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. Then can
|
||||||
need to edit multiple nodes when changing passwords or other information.
|
be referenced using node@subfolder@folder or node@folder
|
||||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. They can
|
- If you have too many nodes. Get completion script using: conn config --completion.
|
||||||
be referenced using node@subfolder@folder or node@folder.
|
Or use fzf installing pyfzf and running conn config --fzf true
|
||||||
- If you have too many nodes, get a completion script using: conn config --completion.
|
- Create in bulk, copy, move, export and import nodes for easy management.
|
||||||
Or use fzf by installing pyfzf and running conn config --fzf true.
|
- Run automation scripts in network devices.
|
||||||
- Create in bulk, copy, move, export, and import nodes for easy management.
|
- use GPT AI to help you manage your devices.
|
||||||
- Run automation scripts on network devices.
|
|
||||||
- Use GPT AI to help you manage your devices.
|
|
||||||
- Add plugins with your own scripts.
|
- Add plugins with your own scripts.
|
||||||
- Much more!
|
- Much more!
|
||||||
|
|
||||||
### Usage:
|
### Usage:
|
||||||
```
|
```
|
||||||
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
||||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config,sync,context} ...
|
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
node|folder node[@subfolder][@folder]
|
node|folder node[@subfolder][@folder]
|
||||||
Connect to specific node or show all matching nodes
|
Connect to specific node or show all matching nodes
|
||||||
[@subfolder][@folder]
|
[@subfolder][@folder]
|
||||||
Show all available connections globally or in specified path
|
Show all available connections globaly or in specified path
|
||||||
|
```
|
||||||
|
|
||||||
options:
|
### Options:
|
||||||
|
```
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-v, --version Show version
|
-v, --version Show version
|
||||||
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
||||||
@ -80,8 +78,10 @@ options:
|
|||||||
-s, --show Show node[@subfolder][@folder]
|
-s, --show Show node[@subfolder][@folder]
|
||||||
-d, --debug Display all conections steps
|
-d, --debug Display all conections steps
|
||||||
-t, --sftp Connects using sftp instead of ssh
|
-t, --sftp Connects using sftp instead of ssh
|
||||||
|
```
|
||||||
|
|
||||||
Commands:
|
### Commands:
|
||||||
|
```
|
||||||
profile Manage profiles
|
profile Manage profiles
|
||||||
move(mv) Move node
|
move(mv) Move node
|
||||||
copy(cp) Copy node
|
copy(cp) Copy node
|
||||||
@ -95,7 +95,6 @@ Commands:
|
|||||||
plugin Manage plugins
|
plugin Manage plugins
|
||||||
config Manage app config
|
config Manage app config
|
||||||
sync Sync config with Google
|
sync Sync config with Google
|
||||||
context Manage contexts with regex matching
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manage profiles:
|
### Manage profiles:
|
||||||
@ -116,26 +115,14 @@ options:
|
|||||||
|
|
||||||
### Examples:
|
### Examples:
|
||||||
```
|
```
|
||||||
#Add new profile
|
|
||||||
conn profile --add office-user
|
conn profile --add office-user
|
||||||
#Add new folder
|
|
||||||
conn --add @office
|
conn --add @office
|
||||||
#Add new subfolder
|
|
||||||
conn --add @datacenter@office
|
conn --add @datacenter@office
|
||||||
#Add node to subfolder
|
|
||||||
conn --add server@datacenter@office
|
conn --add server@datacenter@office
|
||||||
#Add node to folder
|
|
||||||
conn --add pc@office
|
conn --add pc@office
|
||||||
#Show node information
|
|
||||||
conn --show server@datacenter@office
|
conn --show server@datacenter@office
|
||||||
#Connect to nodes
|
|
||||||
conn pc@office
|
conn pc@office
|
||||||
conn server
|
conn server
|
||||||
#Create and set new context
|
|
||||||
conn context -a office .*@office
|
|
||||||
conn context --set office
|
|
||||||
#Run a command in a node
|
|
||||||
conn run server ls -la
|
|
||||||
```
|
```
|
||||||
## Plugin Requirements for Connpy
|
## Plugin Requirements for Connpy
|
||||||
|
|
||||||
|
@ -2,35 +2,32 @@
|
|||||||
'''
|
'''
|
||||||
## Connection manager
|
## Connection manager
|
||||||
|
|
||||||
Connpy is a SSH, SFTP, Telnet, kubectl, and Docker pod connection manager and automation module for Linux, Mac, and Docker.
|
Connpy is a connection manager that allows you to store nodes to connect them fast and password free.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
- Manage connections using SSH, SFTP, Telnet, kubectl, and Docker exec.
|
- You can generate profiles and reference them from nodes using @profilename so you dont
|
||||||
- Set contexts to manage specific nodes from specific contexts (work/home/clients/etc).
|
need to edit multiple nodes when changing password or other information.
|
||||||
- You can generate profiles and reference them from nodes using @profilename so you don't
|
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. Then can
|
||||||
need to edit multiple nodes when changing passwords or other information.
|
be referenced using node@subfolder@folder or node@folder
|
||||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. They can
|
- If you have too many nodes. Get completion script using: conn config --completion.
|
||||||
be referenced using node@subfolder@folder or node@folder.
|
Or use fzf installing pyfzf and running conn config --fzf true
|
||||||
- If you have too many nodes, get a completion script using: conn config --completion.
|
- Create in bulk, copy, move, export and import nodes for easy management.
|
||||||
Or use fzf by installing pyfzf and running conn config --fzf true.
|
- Run automation scripts in network devices.
|
||||||
- Create in bulk, copy, move, export, and import nodes for easy management.
|
- use GPT AI to help you manage your devices.
|
||||||
- Run automation scripts on network devices.
|
|
||||||
- Use GPT AI to help you manage your devices.
|
|
||||||
- Add plugins with your own scripts.
|
- Add plugins with your own scripts.
|
||||||
- Much more!
|
- Much more!
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
||||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config,sync,context} ...
|
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
node|folder node[@subfolder][@folder]
|
node|folder node[@subfolder][@folder]
|
||||||
Connect to specific node or show all matching nodes
|
Connect to specific node or show all matching nodes
|
||||||
[@subfolder][@folder]
|
[@subfolder][@folder]
|
||||||
Show all available connections globally or in specified path
|
Show all available connections globaly or in specified path
|
||||||
|
Options:
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-v, --version Show version
|
-v, --version Show version
|
||||||
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
||||||
@ -54,7 +51,6 @@ Commands:
|
|||||||
plugin Manage plugins
|
plugin Manage plugins
|
||||||
config Manage app config
|
config Manage app config
|
||||||
sync Sync config with Google
|
sync Sync config with Google
|
||||||
context Manage contexts with regex matching
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manage profiles
|
### Manage profiles
|
||||||
@ -75,26 +71,14 @@ options:
|
|||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
```
|
```
|
||||||
#Add new profile
|
|
||||||
conn profile --add office-user
|
conn profile --add office-user
|
||||||
#Add new folder
|
|
||||||
conn --add @office
|
conn --add @office
|
||||||
#Add new subfolder
|
|
||||||
conn --add @datacenter@office
|
conn --add @datacenter@office
|
||||||
#Add node to subfolder
|
|
||||||
conn --add server@datacenter@office
|
conn --add server@datacenter@office
|
||||||
#Add node to folder
|
|
||||||
conn --add pc@office
|
conn --add pc@office
|
||||||
#Show node information
|
|
||||||
conn --show server@datacenter@office
|
conn --show server@datacenter@office
|
||||||
#Connect to nodes
|
|
||||||
conn pc@office
|
conn pc@office
|
||||||
conn server
|
conn server
|
||||||
#Create and set new context
|
|
||||||
conn context -a office .*@office
|
|
||||||
conn context --set office
|
|
||||||
#Run a command in a node
|
|
||||||
conn run server ls -la
|
|
||||||
```
|
```
|
||||||
## Plugin Requirements for Connpy
|
## Plugin Requirements for Connpy
|
||||||
### General Structure
|
### General Structure
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
__version__ = "4.1.0"
|
__version__ = "4.0.3"
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify
|
||||||
from flask_cors import CORS
|
|
||||||
from connpy import configfile, node, nodes, hooks
|
from connpy import configfile, node, nodes, hooks
|
||||||
from connpy.ai import ai as myai
|
from connpy.ai import ai as myai
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
@ -7,7 +6,6 @@ import os
|
|||||||
import signal
|
import signal
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
|
||||||
conf = configfile()
|
conf = configfile()
|
||||||
|
|
||||||
PID_FILE1 = "/run/connpy.pid"
|
PID_FILE1 = "/run/connpy.pid"
|
||||||
|
@ -8,7 +8,7 @@ import sys
|
|||||||
import inquirer
|
import inquirer
|
||||||
from .core import node,nodes
|
from .core import node,nodes
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
from .api import start_api,stop_api,debug_api,app
|
from .api import start_api,stop_api,debug_api
|
||||||
from .ai import ai
|
from .ai import ai
|
||||||
from .plugins import Plugins
|
from .plugins import Plugins
|
||||||
import yaml
|
import yaml
|
||||||
@ -42,7 +42,6 @@ class connapp:
|
|||||||
the config file.
|
the config file.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
self.app = app
|
|
||||||
self.node = node
|
self.node = node
|
||||||
self.nodes = nodes
|
self.nodes = nodes
|
||||||
self.start_api = start_api
|
self.start_api = start_api
|
||||||
@ -313,7 +312,11 @@ class connapp:
|
|||||||
if uniques == False:
|
if uniques == False:
|
||||||
print("Invalid node {}".format(args.data))
|
print("Invalid node {}".format(args.data))
|
||||||
exit(5)
|
exit(5)
|
||||||
self._print_instructions()
|
print("You can use the configured setting in a profile using @profilename.")
|
||||||
|
print("You can also leave empty any value except hostname/IP.")
|
||||||
|
print("You can pass 1 or more passwords using comma separated @profiles")
|
||||||
|
print("You can use this variables on logging file name: ${id} ${unique} ${host} ${port} ${user} ${protocol}")
|
||||||
|
print("Some useful tags to set for automation are 'os', 'screen_length_command', and 'prompt'.")
|
||||||
newnode = self._questions_nodes(args.data, uniques)
|
newnode = self._questions_nodes(args.data, uniques)
|
||||||
if newnode == False:
|
if newnode == False:
|
||||||
exit(7)
|
exit(7)
|
||||||
@ -1078,16 +1081,16 @@ class connapp:
|
|||||||
raise inquirer.errors.ValidationError("", reason="Profile {} don't exist".format(current))
|
raise inquirer.errors.ValidationError("", reason="Profile {} don't exist".format(current))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _profile_protocol_validation(self, answers, current, regex = "(^ssh$|^telnet$|^kubectl$|^docker$|^$)"):
|
def _profile_protocol_validation(self, answers, current, regex = "(^ssh$|^telnet$|^$)"):
|
||||||
#Validate protocol in inquirer when managing profiles
|
#Validate protocol in inquirer when managing profiles
|
||||||
if not re.match(regex, current):
|
if not re.match(regex, current):
|
||||||
raise inquirer.errors.ValidationError("", reason="Pick between ssh, telnet, kubectl, docker or leave empty")
|
raise inquirer.errors.ValidationError("", reason="Pick between ssh, telnet or leave empty")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _protocol_validation(self, answers, current, regex = "(^ssh$|^telnet$|^kubectl$|^docker$|^$|^@.+$)"):
|
def _protocol_validation(self, answers, current, regex = "(^ssh$|^telnet$|^$|^@.+$)"):
|
||||||
#Validate protocol in inquirer when managing nodes
|
#Validate protocol in inquirer when managing nodes
|
||||||
if not re.match(regex, current):
|
if not re.match(regex, current):
|
||||||
raise inquirer.errors.ValidationError("", reason="Pick between ssh, telnet, kubectl, docker leave empty or @profile")
|
raise inquirer.errors.ValidationError("", reason="Pick between ssh, telnet, leave empty or @profile")
|
||||||
if current.startswith("@"):
|
if current.startswith("@"):
|
||||||
if current[1:] not in self.profiles:
|
if current[1:] not in self.profiles:
|
||||||
raise inquirer.errors.ValidationError("", reason="Profile {} don't exist".format(current))
|
raise inquirer.errors.ValidationError("", reason="Profile {} don't exist".format(current))
|
||||||
@ -1108,7 +1111,7 @@ class connapp:
|
|||||||
def _port_validation(self, answers, current, regex = "(^[0-9]*$|^@.+$)"):
|
def _port_validation(self, answers, current, regex = "(^[0-9]*$|^@.+$)"):
|
||||||
#Validate port in inquirer when managing nodes
|
#Validate port in inquirer when managing nodes
|
||||||
if not re.match(regex, current):
|
if not re.match(regex, current):
|
||||||
raise inquirer.errors.ValidationError("", reason="Pick a port between 1-6553/app5, @profile or leave empty")
|
raise inquirer.errors.ValidationError("", reason="Pick a port between 1-65535, @profile or leave empty")
|
||||||
try:
|
try:
|
||||||
port = int(current)
|
port = int(current)
|
||||||
except:
|
except:
|
||||||
@ -1214,7 +1217,7 @@ class connapp:
|
|||||||
#Inquirer questions when editing nodes or profiles
|
#Inquirer questions when editing nodes or profiles
|
||||||
questions = []
|
questions = []
|
||||||
questions.append(inquirer.Confirm("host", message="Edit Hostname/IP?"))
|
questions.append(inquirer.Confirm("host", message="Edit Hostname/IP?"))
|
||||||
questions.append(inquirer.Confirm("protocol", message="Edit Protocol/app?"))
|
questions.append(inquirer.Confirm("protocol", message="Edit Protocol?"))
|
||||||
questions.append(inquirer.Confirm("port", message="Edit Port?"))
|
questions.append(inquirer.Confirm("port", message="Edit Port?"))
|
||||||
questions.append(inquirer.Confirm("options", message="Edit Options?"))
|
questions.append(inquirer.Confirm("options", message="Edit Options?"))
|
||||||
questions.append(inquirer.Confirm("logs", message="Edit logging path/file?"))
|
questions.append(inquirer.Confirm("logs", message="Edit logging path/file?"))
|
||||||
@ -1244,7 +1247,7 @@ class connapp:
|
|||||||
else:
|
else:
|
||||||
node["host"] = defaults["host"]
|
node["host"] = defaults["host"]
|
||||||
if edit["protocol"]:
|
if edit["protocol"]:
|
||||||
questions.append(inquirer.Text("protocol", message="Select Protocol/app", validate=self._protocol_validation, default=defaults["protocol"]))
|
questions.append(inquirer.Text("protocol", message="Select Protocol", validate=self._protocol_validation, default=defaults["protocol"]))
|
||||||
else:
|
else:
|
||||||
node["protocol"] = defaults["protocol"]
|
node["protocol"] = defaults["protocol"]
|
||||||
if edit["port"]:
|
if edit["port"]:
|
||||||
@ -1252,7 +1255,7 @@ class connapp:
|
|||||||
else:
|
else:
|
||||||
node["port"] = defaults["port"]
|
node["port"] = defaults["port"]
|
||||||
if edit["options"]:
|
if edit["options"]:
|
||||||
questions.append(inquirer.Text("options", message="Pass extra options to protocol/app", validate=self._default_validation, default=defaults["options"]))
|
questions.append(inquirer.Text("options", message="Pass extra options to protocol", validate=self._default_validation, default=defaults["options"]))
|
||||||
else:
|
else:
|
||||||
node["options"] = defaults["options"]
|
node["options"] = defaults["options"]
|
||||||
if edit["logs"]:
|
if edit["logs"]:
|
||||||
@ -1318,7 +1321,7 @@ class connapp:
|
|||||||
else:
|
else:
|
||||||
profile["host"] = defaults["host"]
|
profile["host"] = defaults["host"]
|
||||||
if edit["protocol"]:
|
if edit["protocol"]:
|
||||||
questions.append(inquirer.Text("protocol", message="Select Protocol/app", validate=self._profile_protocol_validation, default=defaults["protocol"]))
|
questions.append(inquirer.Text("protocol", message="Select Protocol", validate=self._profile_protocol_validation, default=defaults["protocol"]))
|
||||||
else:
|
else:
|
||||||
profile["protocol"] = defaults["protocol"]
|
profile["protocol"] = defaults["protocol"]
|
||||||
if edit["port"]:
|
if edit["port"]:
|
||||||
@ -1326,7 +1329,7 @@ class connapp:
|
|||||||
else:
|
else:
|
||||||
profile["port"] = defaults["port"]
|
profile["port"] = defaults["port"]
|
||||||
if edit["options"]:
|
if edit["options"]:
|
||||||
questions.append(inquirer.Text("options", message="Pass extra options to protocol/app", default=defaults["options"]))
|
questions.append(inquirer.Text("options", message="Pass extra options to protocol", default=defaults["options"]))
|
||||||
else:
|
else:
|
||||||
profile["options"] = defaults["options"]
|
profile["options"] = defaults["options"]
|
||||||
if edit["logs"]:
|
if edit["logs"]:
|
||||||
@ -1367,9 +1370,9 @@ class connapp:
|
|||||||
questions.append(inquirer.Text("ids", message="add a comma separated list of nodes to add", validate=self._bulk_node_validation))
|
questions.append(inquirer.Text("ids", message="add a comma separated list of nodes to add", validate=self._bulk_node_validation))
|
||||||
questions.append(inquirer.Text("location", message="Add a @folder, @subfolder@folder or leave empty", validate=self._bulk_folder_validation))
|
questions.append(inquirer.Text("location", message="Add a @folder, @subfolder@folder or leave empty", validate=self._bulk_folder_validation))
|
||||||
questions.append(inquirer.Text("host", message="Add comma separated list of Hostnames or IPs", validate=self._bulk_host_validation))
|
questions.append(inquirer.Text("host", message="Add comma separated list of Hostnames or IPs", validate=self._bulk_host_validation))
|
||||||
questions.append(inquirer.Text("protocol", message="Select Protocol/app", validate=self._protocol_validation))
|
questions.append(inquirer.Text("protocol", message="Select Protocol", validate=self._protocol_validation))
|
||||||
questions.append(inquirer.Text("port", message="Select Port Number", validate=self._port_validation))
|
questions.append(inquirer.Text("port", message="Select Port Number", validate=self._port_validation))
|
||||||
questions.append(inquirer.Text("options", message="Pass extra options to protocol/app", validate=self._default_validation))
|
questions.append(inquirer.Text("options", message="Pass extra options to protocol", validate=self._default_validation))
|
||||||
questions.append(inquirer.Text("logs", message="Pick logging path/file ", validate=self._default_validation))
|
questions.append(inquirer.Text("logs", message="Pick logging path/file ", validate=self._default_validation))
|
||||||
questions.append(inquirer.Text("tags", message="Add tags dictionary", validate=self._tags_validation))
|
questions.append(inquirer.Text("tags", message="Add tags dictionary", validate=self._tags_validation))
|
||||||
questions.append(inquirer.Text("jumphost", message="Add Jumphost node", validate=self._jumphost_validation))
|
questions.append(inquirer.Text("jumphost", message="Add Jumphost node", validate=self._jumphost_validation))
|
||||||
@ -1549,45 +1552,3 @@ tasks:
|
|||||||
output: null
|
output: null
|
||||||
...'''
|
...'''
|
||||||
|
|
||||||
def _print_instructions(self):
|
|
||||||
instructions = """
|
|
||||||
Welcome to Connpy node Addition Wizard!
|
|
||||||
|
|
||||||
Here are some important instructions and tips for configuring your new node:
|
|
||||||
|
|
||||||
1. **Profiles**:
|
|
||||||
- You can use the configured settings in a profile using `@profilename`.
|
|
||||||
|
|
||||||
2. **Available Protocols and Apps**:
|
|
||||||
- ssh
|
|
||||||
- telnet
|
|
||||||
- kubectl (`kubectl exec`)
|
|
||||||
- docker (`docker exec`)
|
|
||||||
|
|
||||||
3. **Optional Values**:
|
|
||||||
- You can leave any value empty except for the hostname/IP.
|
|
||||||
|
|
||||||
4. **Passwords**:
|
|
||||||
- You can pass one or more passwords using comma-separated `@profiles`.
|
|
||||||
|
|
||||||
5. **Logging**:
|
|
||||||
- You can use the following variables in the logging file name:
|
|
||||||
- `${id}`
|
|
||||||
- `${unique}`
|
|
||||||
- `${host}`
|
|
||||||
- `${port}`
|
|
||||||
- `${user}`
|
|
||||||
- `${protocol}`
|
|
||||||
|
|
||||||
6. **Well-Known Tags**:
|
|
||||||
- `os`: Identified by AI to generate commands based on the operating system.
|
|
||||||
- `screen_length_command`: Used by automation to avoid pagination on different devices (e.g., `terminal length 0` for Cisco devices).
|
|
||||||
- `prompt`: Replaces default app prompt to identify the end of output or where the user can start inputting commands.
|
|
||||||
- `kube_command`: Replaces the default command (`/bin/bash`) for `kubectl exec`.
|
|
||||||
- `docker_command`: Replaces the default command for `docker exec`.
|
|
||||||
|
|
||||||
Please follow these instructions carefully to ensure proper configuration of your new node.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# print(instructions)
|
|
||||||
mdprint(Markdown(instructions))
|
|
||||||
|
205
connpy/core.py
205
connpy/core.py
@ -57,7 +57,7 @@ class node:
|
|||||||
- port (str): Port to connect to node, default 22 for ssh and 23
|
- port (str): Port to connect to node, default 22 for ssh and 23
|
||||||
for telnet.
|
for telnet.
|
||||||
|
|
||||||
- protocol (str): Select ssh, telnet, kubectl or docker. Default is ssh.
|
- protocol (str): Select ssh or telnet. Default is ssh.
|
||||||
|
|
||||||
- user (str): Username to of the node.
|
- user (str): Username to of the node.
|
||||||
|
|
||||||
@ -326,14 +326,6 @@ class node:
|
|||||||
connect = self._connect(timeout = timeout)
|
connect = self._connect(timeout = timeout)
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
||||||
if connect == True:
|
if connect == True:
|
||||||
# Attempt to set the terminal size
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(65535, 65535)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(10000, 10000)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if "prompt" in self.tags:
|
if "prompt" in self.tags:
|
||||||
prompt = self.tags["prompt"]
|
prompt = self.tags["prompt"]
|
||||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||||
@ -421,14 +413,6 @@ class node:
|
|||||||
'''
|
'''
|
||||||
connect = self._connect(timeout = timeout)
|
connect = self._connect(timeout = timeout)
|
||||||
if connect == True:
|
if connect == True:
|
||||||
# Attempt to set the terminal size
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(65535, 65535)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(10000, 10000)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if "prompt" in self.tags:
|
if "prompt" in self.tags:
|
||||||
prompt = self.tags["prompt"]
|
prompt = self.tags["prompt"]
|
||||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||||
@ -484,101 +468,47 @@ class node:
|
|||||||
return connect
|
return connect
|
||||||
|
|
||||||
@MethodHook
|
@MethodHook
|
||||||
def _generate_ssh_sftp_cmd(self):
|
def _connect(self, debug = False, timeout = 10, max_attempts = 3):
|
||||||
cmd = self.protocol
|
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
|
||||||
if self.idletime > 0:
|
|
||||||
cmd += " -o ServerAliveInterval=" + str(self.idletime)
|
|
||||||
if self.port:
|
|
||||||
if self.protocol == "ssh":
|
|
||||||
cmd += " -p " + self.port
|
|
||||||
elif self.protocol == "sftp":
|
|
||||||
cmd += " -P " + self.port
|
|
||||||
if self.options:
|
|
||||||
cmd += " " + self.options
|
|
||||||
if self.jumphost:
|
|
||||||
cmd += " " + self.jumphost
|
|
||||||
user_host = f"{self.user}@{self.host}" if self.user else self.host
|
|
||||||
cmd += f" {user_host}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _generate_telnet_cmd(self):
|
|
||||||
cmd = f"telnet {self.host}"
|
|
||||||
if self.port:
|
|
||||||
cmd += f" {self.port}"
|
|
||||||
if self.options:
|
|
||||||
cmd += f" {self.options}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _generate_kube_cmd(self):
|
|
||||||
cmd = f"kubectl exec {self.options} {self.host} -it --"
|
|
||||||
kube_command = self.tags.get("kube_command", "/bin/bash") if isinstance(self.tags, dict) else "/bin/bash"
|
|
||||||
cmd += f" {kube_command}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _generate_docker_cmd(self):
|
|
||||||
cmd = f"docker {self.options} exec -it {self.host}"
|
|
||||||
docker_command = self.tags.get("docker_command", "/bin/bash") if isinstance(self.tags, dict) else "/bin/bash"
|
|
||||||
cmd += f" {docker_command}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _get_cmd(self):
|
|
||||||
if self.protocol in ["ssh", "sftp"]:
|
if self.protocol in ["ssh", "sftp"]:
|
||||||
return self._generate_ssh_sftp_cmd()
|
cmd = self.protocol
|
||||||
|
if self.idletime > 0:
|
||||||
|
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
|
||||||
|
if self.port != '':
|
||||||
|
if self.protocol == "ssh":
|
||||||
|
cmd = cmd + " -p " + self.port
|
||||||
|
elif self.protocol == "sftp":
|
||||||
|
cmd = cmd + " -P " + self.port
|
||||||
|
if self.options != '':
|
||||||
|
cmd = cmd + " " + self.options
|
||||||
|
if self.logs != '':
|
||||||
|
self.logfile = self._logfile()
|
||||||
|
if self.jumphost != '':
|
||||||
|
cmd = cmd + " " + self.jumphost
|
||||||
|
if self.password[0] != '':
|
||||||
|
passwords = self._passtx(self.password)
|
||||||
|
else:
|
||||||
|
passwords = []
|
||||||
|
if self.user == '':
|
||||||
|
cmd = cmd + " {}".format(self.host)
|
||||||
|
else:
|
||||||
|
cmd = cmd + " {}".format("@".join([self.user,self.host]))
|
||||||
|
expects = ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: (ssh|sftp)', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:|[u|U]sername:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
|
||||||
elif self.protocol == "telnet":
|
elif self.protocol == "telnet":
|
||||||
return self._generate_telnet_cmd()
|
cmd = "telnet " + self.host
|
||||||
elif self.protocol == "kubectl":
|
if self.port != '':
|
||||||
return self._generate_kube_cmd()
|
cmd = cmd + " " + self.port
|
||||||
elif self.protocol == "docker":
|
if self.options != '':
|
||||||
return self._generate_docker_cmd()
|
cmd = cmd + " " + self.options
|
||||||
|
if self.logs != '':
|
||||||
|
self.logfile = self._logfile()
|
||||||
|
if self.password[0] != '':
|
||||||
|
passwords = self._passtx(self.password)
|
||||||
|
else:
|
||||||
|
passwords = []
|
||||||
|
expects = ['[u|U]sername:', 'refused', 'supported', 'invalid option', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid protocol: {self.protocol}")
|
raise ValueError("Invalid protocol: " + self.protocol)
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _connect(self, debug=False, timeout=10, max_attempts=3):
|
|
||||||
cmd = self._get_cmd()
|
|
||||||
passwords = self._passtx(self.password) if self.password[0] else []
|
|
||||||
if self.logs != '':
|
|
||||||
self.logfile = self._logfile()
|
|
||||||
default_prompt = r'>$|#$|\$$|>.$|#.$|\$.$'
|
|
||||||
prompt = self.tags.get("prompt", default_prompt) if isinstance(self.tags, dict) else default_prompt
|
|
||||||
password_prompt = '[p|P]assword:|[u|U]sername:' if self.protocol != 'telnet' else '[p|P]assword:'
|
|
||||||
|
|
||||||
expects = {
|
|
||||||
"ssh": ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: ssh', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
|
||||||
"sftp": ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: sftp', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
|
||||||
"telnet": ['[u|U]sername:', 'refused', 'supported', 'invalid|unrecognized option', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
|
||||||
"kubectl": ['[u|U]sername:', '[r|R]efused', '[E|e]rror', 'DEPRECATED', pexpect.TIMEOUT, password_prompt, prompt, pexpect.EOF, "expired|invalid"],
|
|
||||||
"docker": ['[u|U]sername:', 'Cannot', '[E|e]rror', 'failed', 'not a docker command', 'unknown', 'unable to resolve', pexpect.TIMEOUT, password_prompt, prompt, pexpect.EOF]
|
|
||||||
}
|
|
||||||
|
|
||||||
error_indices = {
|
|
||||||
"ssh": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
|
||||||
"sftp": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
|
||||||
"telnet": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
|
||||||
"kubectl": [1, 2, 3, 4, 8], # Define error indices for kube
|
|
||||||
"docker": [1, 2, 3, 4, 5, 6, 7] # Define error indices for docker
|
|
||||||
}
|
|
||||||
|
|
||||||
eof_indices = {
|
|
||||||
"ssh": [8, 9, 10, 11],
|
|
||||||
"sftp": [8, 9, 10, 11],
|
|
||||||
"telnet": [8, 9, 10, 11],
|
|
||||||
"kubectl": [5, 6, 7], # Define eof indices for kube
|
|
||||||
"docker": [8, 9, 10] # Define eof indices for docker
|
|
||||||
}
|
|
||||||
|
|
||||||
initial_indices = {
|
|
||||||
"ssh": [0],
|
|
||||||
"sftp": [0],
|
|
||||||
"telnet": [0],
|
|
||||||
"kubectl": [0], # Define special indices for kube
|
|
||||||
"docker": [0] # Define special indices for docker
|
|
||||||
}
|
|
||||||
|
|
||||||
attempts = 1
|
attempts = 1
|
||||||
while attempts <= max_attempts:
|
while attempts <= max_attempts:
|
||||||
child = pexpect.spawn(cmd)
|
child = pexpect.spawn(cmd)
|
||||||
@ -586,55 +516,54 @@ class node:
|
|||||||
print(cmd)
|
print(cmd)
|
||||||
self.mylog = io.BytesIO()
|
self.mylog = io.BytesIO()
|
||||||
child.logfile_read = self.mylog
|
child.logfile_read = self.mylog
|
||||||
|
if len(passwords) > 0:
|
||||||
|
loops = len(passwords)
|
||||||
|
else:
|
||||||
|
loops = 1
|
||||||
endloop = False
|
endloop = False
|
||||||
for i in range(len(passwords) if passwords else 1):
|
for i in range(0, loops):
|
||||||
while True:
|
while True:
|
||||||
results = child.expect(expects[self.protocol], timeout=timeout)
|
results = child.expect(expects, timeout=timeout)
|
||||||
results_value = expects[self.protocol][results]
|
if results == 0:
|
||||||
|
|
||||||
if results in initial_indices[self.protocol]:
|
|
||||||
if self.protocol in ["ssh", "sftp"]:
|
if self.protocol in ["ssh", "sftp"]:
|
||||||
child.sendline('yes')
|
child.sendline('yes')
|
||||||
elif self.protocol in ["telnet", "kubectl"]:
|
elif self.protocol == "telnet":
|
||||||
if self.user:
|
if self.user != '':
|
||||||
child.sendline(self.user)
|
child.sendline(self.user)
|
||||||
else:
|
else:
|
||||||
self.missingtext = True
|
self.missingtext = True
|
||||||
break
|
break
|
||||||
|
if results in [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16]:
|
||||||
elif results in error_indices[self.protocol]:
|
|
||||||
child.terminate()
|
child.terminate()
|
||||||
if results_value == pexpect.TIMEOUT and attempts != max_attempts:
|
if results == 12 and attempts != max_attempts:
|
||||||
attempts += 1
|
attempts += 1
|
||||||
endloop = True
|
endloop = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
after = "Connection timeout" if results_value == pexpect.TIMEOUT else child.after.decode()
|
if results == 12:
|
||||||
return f"Connection failed code: {results}\n{child.before.decode().lstrip()}{after}{child.readline().decode()}".rstrip()
|
after = "Connection timeout"
|
||||||
|
|
||||||
elif results in eof_indices[self.protocol]:
|
|
||||||
if results_value == password_prompt:
|
|
||||||
if passwords:
|
|
||||||
child.sendline(passwords[i])
|
|
||||||
else:
|
else:
|
||||||
self.missingtext = True
|
after = child.after.decode()
|
||||||
break
|
return ("Connection failed code:" + str(results) + "\n" + child.before.decode().lstrip() + after + child.readline().decode()).rstrip()
|
||||||
elif results_value == "suspend":
|
if results == 8:
|
||||||
child.sendline("\r")
|
if len(passwords) > 0:
|
||||||
sleep(2)
|
child.sendline(passwords[i])
|
||||||
else:
|
else:
|
||||||
endloop = True
|
self.missingtext = True
|
||||||
child.sendline()
|
break
|
||||||
break
|
if results in [9, 11]:
|
||||||
|
endloop = True
|
||||||
|
child.sendline()
|
||||||
|
break
|
||||||
|
if results == 10:
|
||||||
|
child.sendline("\r")
|
||||||
|
sleep(2)
|
||||||
if endloop:
|
if endloop:
|
||||||
break
|
break
|
||||||
if results_value == pexpect.TIMEOUT:
|
if results == 12:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
child.readline(0)
|
child.readline(0)
|
||||||
self.child = child
|
self.child = child
|
||||||
return True
|
return True
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import yaml
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class context_manager:
|
|
||||||
|
|
||||||
def __init__(self, connapp):
|
|
||||||
self.connapp = connapp
|
|
||||||
self.config = connapp.config
|
|
||||||
self.contexts = self.config.config["contexts"]
|
|
||||||
self.current_context = self.config.config["current_context"]
|
|
||||||
self.regex = [re.compile(regex) for regex in self.contexts[self.current_context]]
|
|
||||||
|
|
||||||
def add_context(self, context, regex):
|
|
||||||
if not context.isalnum():
|
|
||||||
print("Context name has to be alphanumeric.")
|
|
||||||
exit(1)
|
|
||||||
elif context in self.contexts:
|
|
||||||
print(f"Context {context} already exists.")
|
|
||||||
exit(2)
|
|
||||||
else:
|
|
||||||
self.contexts[context] = regex
|
|
||||||
self.connapp._change_settings("contexts", self.contexts)
|
|
||||||
|
|
||||||
def modify_context(self, context, regex):
|
|
||||||
if context == "all":
|
|
||||||
print("Can't modify default context: all")
|
|
||||||
exit(3)
|
|
||||||
elif context not in self.contexts:
|
|
||||||
print(f"Context {context} doesn't exist.")
|
|
||||||
exit(4)
|
|
||||||
else:
|
|
||||||
self.contexts[context] = regex
|
|
||||||
self.connapp._change_settings("contexts", self.contexts)
|
|
||||||
|
|
||||||
def delete_context(self, context):
|
|
||||||
if context == "all":
|
|
||||||
print("Can't delete default context: all")
|
|
||||||
exit(3)
|
|
||||||
elif context not in self.contexts:
|
|
||||||
print(f"Context {context} doesn't exist.")
|
|
||||||
exit(4)
|
|
||||||
if context == self.current_context:
|
|
||||||
print(f"Can't delete current context: {self.current_context}")
|
|
||||||
exit(5)
|
|
||||||
else:
|
|
||||||
self.contexts.pop(context)
|
|
||||||
self.connapp._change_settings("contexts", self.contexts)
|
|
||||||
|
|
||||||
def list_contexts(self):
|
|
||||||
for key in self.contexts.keys():
|
|
||||||
if key == self.current_context:
|
|
||||||
print(f"{key} * (active)")
|
|
||||||
else:
|
|
||||||
print(key)
|
|
||||||
|
|
||||||
def set_context(self, context):
|
|
||||||
if context not in self.contexts:
|
|
||||||
print(f"Context {context} doesn't exist.")
|
|
||||||
exit(4)
|
|
||||||
elif context == self.current_context:
|
|
||||||
print(f"Context {context} already set")
|
|
||||||
exit(0)
|
|
||||||
else:
|
|
||||||
self.connapp._change_settings("current_context", context)
|
|
||||||
|
|
||||||
def show_context(self, context):
|
|
||||||
if context not in self.contexts:
|
|
||||||
print(f"Context {context} doesn't exist.")
|
|
||||||
exit(4)
|
|
||||||
else:
|
|
||||||
yaml_output = yaml.dump(self.contexts[context], sort_keys=False, default_flow_style=False)
|
|
||||||
print(yaml_output)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def add_default_context(config):
|
|
||||||
config_modified = False
|
|
||||||
if "contexts" not in config.config:
|
|
||||||
config.config["contexts"] = {}
|
|
||||||
config.config["contexts"]["all"] = [".*"]
|
|
||||||
config_modified = True
|
|
||||||
if "current_context" not in config.config:
|
|
||||||
config.config["current_context"] = "all"
|
|
||||||
config_modified = True
|
|
||||||
if config_modified:
|
|
||||||
config._saveconfig(config.file)
|
|
||||||
|
|
||||||
def match_any_regex(self, node, regex_list):
|
|
||||||
return any(regex.match(node) for regex in regex_list)
|
|
||||||
|
|
||||||
def modify_node_list(self, *args, **kwargs):
|
|
||||||
filtered_nodes = [node for node in kwargs["result"] if self.match_any_regex(node, self.regex)]
|
|
||||||
return filtered_nodes
|
|
||||||
|
|
||||||
def modify_node_dict(self, *args, **kwargs):
|
|
||||||
filtered_nodes = {key: value for key, value in kwargs["result"].items() if self.match_any_regex(key, self.regex)}
|
|
||||||
return filtered_nodes
|
|
||||||
|
|
||||||
class Preload:
|
|
||||||
def __init__(self, connapp):
|
|
||||||
#define contexts if doesn't exist
|
|
||||||
connapp.config.modify(context_manager.add_default_context)
|
|
||||||
#filter nodes using context
|
|
||||||
cm = context_manager(connapp)
|
|
||||||
connapp.nodes_list = [node for node in connapp.nodes_list if cm.match_any_regex(node, cm.regex)]
|
|
||||||
connapp.folders = [node for node in connapp.folders if cm.match_any_regex(node, cm.regex)]
|
|
||||||
connapp.config._getallnodes.register_post_hook(cm.modify_node_list)
|
|
||||||
connapp.config._getallfolders.register_post_hook(cm.modify_node_list)
|
|
||||||
connapp.config._getallnodesfull.register_post_hook(cm.modify_node_dict)
|
|
||||||
|
|
||||||
class Parser:
|
|
||||||
def __init__(self):
|
|
||||||
self.parser = argparse.ArgumentParser(description="Manage contexts with regex matching", formatter_class=argparse.RawTextHelpFormatter)
|
|
||||||
self.description = "Manage contexts with regex matching"
|
|
||||||
|
|
||||||
# Define the context name as a positional argument
|
|
||||||
self.parser.add_argument("context_name", help="Name of the context", nargs='?')
|
|
||||||
|
|
||||||
group = self.parser.add_mutually_exclusive_group(required=True)
|
|
||||||
group.add_argument("-a", "--add", nargs='+', help='Add a new context with regex values. Usage: context -a name "regex1" "regex2"')
|
|
||||||
group.add_argument("-r", "--rm", "--del", action='store_true', help="Delete a context. Usage: context -d name")
|
|
||||||
group.add_argument("--ls", action='store_true', help="List all contexts. Usage: context --list")
|
|
||||||
group.add_argument("--set", action='store_true', help="Set the used context. Usage: context --set name")
|
|
||||||
group.add_argument("-s", "--show", action='store_true', help="Show the defined regex of a context. Usage: context --show name")
|
|
||||||
group.add_argument("-e", "--edit", "--mod", nargs='+', help='Modify an existing context. Usage: context --mod name "regex1" "regex2"')
|
|
||||||
|
|
||||||
class Entrypoint:
|
|
||||||
def __init__(self, args, parser, connapp):
|
|
||||||
if args.add and len(args.add) < 2:
|
|
||||||
parser.error("--add requires at least 2 arguments: name and at least one regex")
|
|
||||||
if args.edit and len(args.edit) < 2:
|
|
||||||
parser.error("--edit requires at least 2 arguments: name and at least one regex")
|
|
||||||
if args.ls and args.context_name is not None:
|
|
||||||
parser.error("--ls does not require a context name")
|
|
||||||
if args.rm and not args.context_name:
|
|
||||||
parser.error("--rm require a context name")
|
|
||||||
if args.set and not args.context_name:
|
|
||||||
parser.error("--set require a context name")
|
|
||||||
if args.show and not args.context_name:
|
|
||||||
parser.error("--show require a context name")
|
|
||||||
|
|
||||||
cm = context_manager(connapp)
|
|
||||||
|
|
||||||
if args.add:
|
|
||||||
cm.add_context(args.add[0], args.add[1:])
|
|
||||||
elif args.rm:
|
|
||||||
cm.delete_context(args.context_name)
|
|
||||||
elif args.ls:
|
|
||||||
cm.list_contexts()
|
|
||||||
elif args.edit:
|
|
||||||
cm.modify_context(args.edit[0], args.edit[1:])
|
|
||||||
elif args.set:
|
|
||||||
cm.set_context(args.context_name)
|
|
||||||
elif args.show:
|
|
||||||
cm.show_context(args.context_name)
|
|
||||||
|
|
||||||
def _connpy_completion(wordsnumber, words, info=None):
|
|
||||||
if wordsnumber == 3:
|
|
||||||
result = ["--help", "--add", "--del", "--rm", "--ls", "--set", "--show", "--edit", "--mod"]
|
|
||||||
elif wordsnumber == 4 and words[1] in ["--del", "-r", "--rm", "--set", "--edit", "--mod", "-e", "--show", "-s"]:
|
|
||||||
contexts = info["config"]["config"]["contexts"].keys()
|
|
||||||
current_context = info["config"]["config"]["current_context"]
|
|
||||||
default_context = "all"
|
|
||||||
|
|
||||||
if words[1] in ["--del", "-r", "--rm"]:
|
|
||||||
# Filter out default context and current context
|
|
||||||
result = [context for context in contexts if context not in [default_context, current_context]]
|
|
||||||
elif words[1] == "--set":
|
|
||||||
# Filter out current context
|
|
||||||
result = [context for context in contexts if context != current_context]
|
|
||||||
elif words[1] in ["--edit", "--mod", "-e"]:
|
|
||||||
# Filter out default context
|
|
||||||
result = [context for context in contexts if context != default_context]
|
|
||||||
elif words[1] in ["--show", "-s"]:
|
|
||||||
# No filter for show
|
|
||||||
result = list(contexts)
|
|
||||||
|
|
||||||
return result
|
|
@ -23,33 +23,30 @@
|
|||||||
</header>
|
</header>
|
||||||
<section id="section-intro">
|
<section id="section-intro">
|
||||||
<h2 id="connection-manager">Connection manager</h2>
|
<h2 id="connection-manager">Connection manager</h2>
|
||||||
<p>Connpy is a SSH, SFTP, Telnet, kubectl, and Docker pod connection manager and automation module for Linux, Mac, and Docker.</p>
|
<p>Connpy is a connection manager that allows you to store nodes to connect them fast and password free.</p>
|
||||||
<h3 id="features">Features</h3>
|
<h3 id="features">Features</h3>
|
||||||
<pre><code>- Manage connections using SSH, SFTP, Telnet, kubectl, and Docker exec.
|
<pre><code>- You can generate profiles and reference them from nodes using @profilename so you dont
|
||||||
- Set contexts to manage specific nodes from specific contexts (work/home/clients/etc).
|
need to edit multiple nodes when changing password or other information.
|
||||||
- You can generate profiles and reference them from nodes using @profilename so you don't
|
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. Then can
|
||||||
need to edit multiple nodes when changing passwords or other information.
|
be referenced using node@subfolder@folder or node@folder
|
||||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. They can
|
- If you have too many nodes. Get completion script using: conn config --completion.
|
||||||
be referenced using node@subfolder@folder or node@folder.
|
Or use fzf installing pyfzf and running conn config --fzf true
|
||||||
- If you have too many nodes, get a completion script using: conn config --completion.
|
- Create in bulk, copy, move, export and import nodes for easy management.
|
||||||
Or use fzf by installing pyfzf and running conn config --fzf true.
|
- Run automation scripts in network devices.
|
||||||
- Create in bulk, copy, move, export, and import nodes for easy management.
|
- use GPT AI to help you manage your devices.
|
||||||
- Run automation scripts on network devices.
|
|
||||||
- Use GPT AI to help you manage your devices.
|
|
||||||
- Add plugins with your own scripts.
|
- Add plugins with your own scripts.
|
||||||
- Much more!
|
- Much more!
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="usage">Usage</h3>
|
<h3 id="usage">Usage</h3>
|
||||||
<pre><code>usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
<pre><code>usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
||||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config,sync,context} ...
|
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
node|folder node[@subfolder][@folder]
|
node|folder node[@subfolder][@folder]
|
||||||
Connect to specific node or show all matching nodes
|
Connect to specific node or show all matching nodes
|
||||||
[@subfolder][@folder]
|
[@subfolder][@folder]
|
||||||
Show all available connections globally or in specified path
|
Show all available connections globaly or in specified path
|
||||||
|
Options:
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-v, --version Show version
|
-v, --version Show version
|
||||||
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
||||||
@ -73,7 +70,6 @@ Commands:
|
|||||||
plugin Manage plugins
|
plugin Manage plugins
|
||||||
config Manage app config
|
config Manage app config
|
||||||
sync Sync config with Google
|
sync Sync config with Google
|
||||||
context Manage contexts with regex matching
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="manage-profiles">Manage profiles</h3>
|
<h3 id="manage-profiles">Manage profiles</h3>
|
||||||
<pre><code>usage: conn profile [-h] (--add | --del | --mod | --show) profile
|
<pre><code>usage: conn profile [-h] (--add | --del | --mod | --show) profile
|
||||||
@ -90,26 +86,14 @@ options:
|
|||||||
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="examples">Examples</h3>
|
<h3 id="examples">Examples</h3>
|
||||||
<pre><code> #Add new profile
|
<pre><code> conn profile --add office-user
|
||||||
conn profile --add office-user
|
|
||||||
#Add new folder
|
|
||||||
conn --add @office
|
conn --add @office
|
||||||
#Add new subfolder
|
|
||||||
conn --add @datacenter@office
|
conn --add @datacenter@office
|
||||||
#Add node to subfolder
|
|
||||||
conn --add server@datacenter@office
|
conn --add server@datacenter@office
|
||||||
#Add node to folder
|
|
||||||
conn --add pc@office
|
conn --add pc@office
|
||||||
#Show node information
|
|
||||||
conn --show server@datacenter@office
|
conn --show server@datacenter@office
|
||||||
#Connect to nodes
|
|
||||||
conn pc@office
|
conn pc@office
|
||||||
conn server
|
conn server
|
||||||
#Create and set new context
|
|
||||||
conn context -a office .*@office
|
|
||||||
conn context --set office
|
|
||||||
#Run a command in a node
|
|
||||||
conn run server ls -la
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h2 id="plugin-requirements-for-connpy">Plugin Requirements for Connpy</h2>
|
<h2 id="plugin-requirements-for-connpy">Plugin Requirements for Connpy</h2>
|
||||||
<h3 id="general-structure">General Structure</h3>
|
<h3 id="general-structure">General Structure</h3>
|
||||||
@ -432,35 +416,32 @@ print(result)
|
|||||||
'''
|
'''
|
||||||
## Connection manager
|
## Connection manager
|
||||||
|
|
||||||
Connpy is a SSH, SFTP, Telnet, kubectl, and Docker pod connection manager and automation module for Linux, Mac, and Docker.
|
Connpy is a connection manager that allows you to store nodes to connect them fast and password free.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
- Manage connections using SSH, SFTP, Telnet, kubectl, and Docker exec.
|
- You can generate profiles and reference them from nodes using @profilename so you dont
|
||||||
- Set contexts to manage specific nodes from specific contexts (work/home/clients/etc).
|
need to edit multiple nodes when changing password or other information.
|
||||||
- You can generate profiles and reference them from nodes using @profilename so you don't
|
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. Then can
|
||||||
need to edit multiple nodes when changing passwords or other information.
|
be referenced using node@subfolder@folder or node@folder
|
||||||
- Nodes can be stored on @folder or @subfolder@folder to organize your devices. They can
|
- If you have too many nodes. Get completion script using: conn config --completion.
|
||||||
be referenced using node@subfolder@folder or node@folder.
|
Or use fzf installing pyfzf and running conn config --fzf true
|
||||||
- If you have too many nodes, get a completion script using: conn config --completion.
|
- Create in bulk, copy, move, export and import nodes for easy management.
|
||||||
Or use fzf by installing pyfzf and running conn config --fzf true.
|
- Run automation scripts in network devices.
|
||||||
- Create in bulk, copy, move, export, and import nodes for easy management.
|
- use GPT AI to help you manage your devices.
|
||||||
- Run automation scripts on network devices.
|
|
||||||
- Use GPT AI to help you manage your devices.
|
|
||||||
- Add plugins with your own scripts.
|
- Add plugins with your own scripts.
|
||||||
- Much more!
|
- Much more!
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
|
||||||
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config,sync,context} ...
|
conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
node|folder node[@subfolder][@folder]
|
node|folder node[@subfolder][@folder]
|
||||||
Connect to specific node or show all matching nodes
|
Connect to specific node or show all matching nodes
|
||||||
[@subfolder][@folder]
|
[@subfolder][@folder]
|
||||||
Show all available connections globally or in specified path
|
Show all available connections globaly or in specified path
|
||||||
|
Options:
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-v, --version Show version
|
-v, --version Show version
|
||||||
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
-a, --add Add new node[@subfolder][@folder] or [@subfolder]@folder
|
||||||
@ -484,7 +465,6 @@ Commands:
|
|||||||
plugin Manage plugins
|
plugin Manage plugins
|
||||||
config Manage app config
|
config Manage app config
|
||||||
sync Sync config with Google
|
sync Sync config with Google
|
||||||
context Manage contexts with regex matching
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manage profiles
|
### Manage profiles
|
||||||
@ -505,26 +485,14 @@ options:
|
|||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
```
|
```
|
||||||
#Add new profile
|
|
||||||
conn profile --add office-user
|
conn profile --add office-user
|
||||||
#Add new folder
|
|
||||||
conn --add @office
|
conn --add @office
|
||||||
#Add new subfolder
|
|
||||||
conn --add @datacenter@office
|
conn --add @datacenter@office
|
||||||
#Add node to subfolder
|
|
||||||
conn --add server@datacenter@office
|
conn --add server@datacenter@office
|
||||||
#Add node to folder
|
|
||||||
conn --add pc@office
|
conn --add pc@office
|
||||||
#Show node information
|
|
||||||
conn --show server@datacenter@office
|
conn --show server@datacenter@office
|
||||||
#Connect to nodes
|
|
||||||
conn pc@office
|
conn pc@office
|
||||||
conn server
|
conn server
|
||||||
#Create and set new context
|
|
||||||
conn context -a office .*@office
|
|
||||||
conn context --set office
|
|
||||||
#Run a command in a node
|
|
||||||
conn run server ls -la
|
|
||||||
```
|
```
|
||||||
## Plugin Requirements for Connpy
|
## Plugin Requirements for Connpy
|
||||||
### General Structure
|
### General Structure
|
||||||
@ -2529,7 +2497,7 @@ def getitems(self, uniques):
|
|||||||
- port (str): Port to connect to node, default 22 for ssh and 23
|
- port (str): Port to connect to node, default 22 for ssh and 23
|
||||||
for telnet.
|
for telnet.
|
||||||
|
|
||||||
- protocol (str): Select ssh, telnet, kubectl or docker. Default is ssh.
|
- protocol (str): Select ssh or telnet. Default is ssh.
|
||||||
|
|
||||||
- user (str): Username to of the node.
|
- user (str): Username to of the node.
|
||||||
|
|
||||||
@ -2587,7 +2555,7 @@ class node:
|
|||||||
- port (str): Port to connect to node, default 22 for ssh and 23
|
- port (str): Port to connect to node, default 22 for ssh and 23
|
||||||
for telnet.
|
for telnet.
|
||||||
|
|
||||||
- protocol (str): Select ssh, telnet, kubectl or docker. Default is ssh.
|
- protocol (str): Select ssh or telnet. Default is ssh.
|
||||||
|
|
||||||
- user (str): Username to of the node.
|
- user (str): Username to of the node.
|
||||||
|
|
||||||
@ -2856,14 +2824,6 @@ class node:
|
|||||||
connect = self._connect(timeout = timeout)
|
connect = self._connect(timeout = timeout)
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
||||||
if connect == True:
|
if connect == True:
|
||||||
# Attempt to set the terminal size
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(65535, 65535)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(10000, 10000)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if "prompt" in self.tags:
|
if "prompt" in self.tags:
|
||||||
prompt = self.tags["prompt"]
|
prompt = self.tags["prompt"]
|
||||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||||
@ -2951,14 +2911,6 @@ class node:
|
|||||||
'''
|
'''
|
||||||
connect = self._connect(timeout = timeout)
|
connect = self._connect(timeout = timeout)
|
||||||
if connect == True:
|
if connect == True:
|
||||||
# Attempt to set the terminal size
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(65535, 65535)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(10000, 10000)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if "prompt" in self.tags:
|
if "prompt" in self.tags:
|
||||||
prompt = self.tags["prompt"]
|
prompt = self.tags["prompt"]
|
||||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||||
@ -3014,101 +2966,47 @@ class node:
|
|||||||
return connect
|
return connect
|
||||||
|
|
||||||
@MethodHook
|
@MethodHook
|
||||||
def _generate_ssh_sftp_cmd(self):
|
def _connect(self, debug = False, timeout = 10, max_attempts = 3):
|
||||||
cmd = self.protocol
|
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
|
||||||
if self.idletime > 0:
|
|
||||||
cmd += " -o ServerAliveInterval=" + str(self.idletime)
|
|
||||||
if self.port:
|
|
||||||
if self.protocol == "ssh":
|
|
||||||
cmd += " -p " + self.port
|
|
||||||
elif self.protocol == "sftp":
|
|
||||||
cmd += " -P " + self.port
|
|
||||||
if self.options:
|
|
||||||
cmd += " " + self.options
|
|
||||||
if self.jumphost:
|
|
||||||
cmd += " " + self.jumphost
|
|
||||||
user_host = f"{self.user}@{self.host}" if self.user else self.host
|
|
||||||
cmd += f" {user_host}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _generate_telnet_cmd(self):
|
|
||||||
cmd = f"telnet {self.host}"
|
|
||||||
if self.port:
|
|
||||||
cmd += f" {self.port}"
|
|
||||||
if self.options:
|
|
||||||
cmd += f" {self.options}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _generate_kube_cmd(self):
|
|
||||||
cmd = f"kubectl exec {self.options} {self.host} -it --"
|
|
||||||
kube_command = self.tags.get("kube_command", "/bin/bash") if isinstance(self.tags, dict) else "/bin/bash"
|
|
||||||
cmd += f" {kube_command}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _generate_docker_cmd(self):
|
|
||||||
cmd = f"docker {self.options} exec -it {self.host}"
|
|
||||||
docker_command = self.tags.get("docker_command", "/bin/bash") if isinstance(self.tags, dict) else "/bin/bash"
|
|
||||||
cmd += f" {docker_command}"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _get_cmd(self):
|
|
||||||
if self.protocol in ["ssh", "sftp"]:
|
if self.protocol in ["ssh", "sftp"]:
|
||||||
return self._generate_ssh_sftp_cmd()
|
cmd = self.protocol
|
||||||
|
if self.idletime > 0:
|
||||||
|
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
|
||||||
|
if self.port != '':
|
||||||
|
if self.protocol == "ssh":
|
||||||
|
cmd = cmd + " -p " + self.port
|
||||||
|
elif self.protocol == "sftp":
|
||||||
|
cmd = cmd + " -P " + self.port
|
||||||
|
if self.options != '':
|
||||||
|
cmd = cmd + " " + self.options
|
||||||
|
if self.logs != '':
|
||||||
|
self.logfile = self._logfile()
|
||||||
|
if self.jumphost != '':
|
||||||
|
cmd = cmd + " " + self.jumphost
|
||||||
|
if self.password[0] != '':
|
||||||
|
passwords = self._passtx(self.password)
|
||||||
|
else:
|
||||||
|
passwords = []
|
||||||
|
if self.user == '':
|
||||||
|
cmd = cmd + " {}".format(self.host)
|
||||||
|
else:
|
||||||
|
cmd = cmd + " {}".format("@".join([self.user,self.host]))
|
||||||
|
expects = ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: (ssh|sftp)', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:|[u|U]sername:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
|
||||||
elif self.protocol == "telnet":
|
elif self.protocol == "telnet":
|
||||||
return self._generate_telnet_cmd()
|
cmd = "telnet " + self.host
|
||||||
elif self.protocol == "kubectl":
|
if self.port != '':
|
||||||
return self._generate_kube_cmd()
|
cmd = cmd + " " + self.port
|
||||||
elif self.protocol == "docker":
|
if self.options != '':
|
||||||
return self._generate_docker_cmd()
|
cmd = cmd + " " + self.options
|
||||||
|
if self.logs != '':
|
||||||
|
self.logfile = self._logfile()
|
||||||
|
if self.password[0] != '':
|
||||||
|
passwords = self._passtx(self.password)
|
||||||
|
else:
|
||||||
|
passwords = []
|
||||||
|
expects = ['[u|U]sername:', 'refused', 'supported', 'invalid option', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid protocol: {self.protocol}")
|
raise ValueError("Invalid protocol: " + self.protocol)
|
||||||
|
|
||||||
@MethodHook
|
|
||||||
def _connect(self, debug=False, timeout=10, max_attempts=3):
|
|
||||||
cmd = self._get_cmd()
|
|
||||||
passwords = self._passtx(self.password) if self.password[0] else []
|
|
||||||
if self.logs != '':
|
|
||||||
self.logfile = self._logfile()
|
|
||||||
default_prompt = r'>$|#$|\$$|>.$|#.$|\$.$'
|
|
||||||
prompt = self.tags.get("prompt", default_prompt) if isinstance(self.tags, dict) else default_prompt
|
|
||||||
password_prompt = '[p|P]assword:|[u|U]sername:' if self.protocol != 'telnet' else '[p|P]assword:'
|
|
||||||
|
|
||||||
expects = {
|
|
||||||
"ssh": ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: ssh', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
|
||||||
"sftp": ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: sftp', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
|
||||||
"telnet": ['[u|U]sername:', 'refused', 'supported', 'invalid|unrecognized option', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', password_prompt, prompt, 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"],
|
|
||||||
"kubectl": ['[u|U]sername:', '[r|R]efused', '[E|e]rror', 'DEPRECATED', pexpect.TIMEOUT, password_prompt, prompt, pexpect.EOF, "expired|invalid"],
|
|
||||||
"docker": ['[u|U]sername:', 'Cannot', '[E|e]rror', 'failed', 'not a docker command', 'unknown', 'unable to resolve', pexpect.TIMEOUT, password_prompt, prompt, pexpect.EOF]
|
|
||||||
}
|
|
||||||
|
|
||||||
error_indices = {
|
|
||||||
"ssh": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
|
||||||
"sftp": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
|
||||||
"telnet": [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16],
|
|
||||||
"kubectl": [1, 2, 3, 4, 8], # Define error indices for kube
|
|
||||||
"docker": [1, 2, 3, 4, 5, 6, 7] # Define error indices for docker
|
|
||||||
}
|
|
||||||
|
|
||||||
eof_indices = {
|
|
||||||
"ssh": [8, 9, 10, 11],
|
|
||||||
"sftp": [8, 9, 10, 11],
|
|
||||||
"telnet": [8, 9, 10, 11],
|
|
||||||
"kubectl": [5, 6, 7], # Define eof indices for kube
|
|
||||||
"docker": [8, 9, 10] # Define eof indices for docker
|
|
||||||
}
|
|
||||||
|
|
||||||
initial_indices = {
|
|
||||||
"ssh": [0],
|
|
||||||
"sftp": [0],
|
|
||||||
"telnet": [0],
|
|
||||||
"kubectl": [0], # Define special indices for kube
|
|
||||||
"docker": [0] # Define special indices for docker
|
|
||||||
}
|
|
||||||
|
|
||||||
attempts = 1
|
attempts = 1
|
||||||
while attempts <= max_attempts:
|
while attempts <= max_attempts:
|
||||||
child = pexpect.spawn(cmd)
|
child = pexpect.spawn(cmd)
|
||||||
@ -3116,55 +3014,54 @@ class node:
|
|||||||
print(cmd)
|
print(cmd)
|
||||||
self.mylog = io.BytesIO()
|
self.mylog = io.BytesIO()
|
||||||
child.logfile_read = self.mylog
|
child.logfile_read = self.mylog
|
||||||
|
if len(passwords) > 0:
|
||||||
|
loops = len(passwords)
|
||||||
|
else:
|
||||||
|
loops = 1
|
||||||
endloop = False
|
endloop = False
|
||||||
for i in range(len(passwords) if passwords else 1):
|
for i in range(0, loops):
|
||||||
while True:
|
while True:
|
||||||
results = child.expect(expects[self.protocol], timeout=timeout)
|
results = child.expect(expects, timeout=timeout)
|
||||||
results_value = expects[self.protocol][results]
|
if results == 0:
|
||||||
|
|
||||||
if results in initial_indices[self.protocol]:
|
|
||||||
if self.protocol in ["ssh", "sftp"]:
|
if self.protocol in ["ssh", "sftp"]:
|
||||||
child.sendline('yes')
|
child.sendline('yes')
|
||||||
elif self.protocol in ["telnet", "kubectl"]:
|
elif self.protocol == "telnet":
|
||||||
if self.user:
|
if self.user != '':
|
||||||
child.sendline(self.user)
|
child.sendline(self.user)
|
||||||
else:
|
else:
|
||||||
self.missingtext = True
|
self.missingtext = True
|
||||||
break
|
break
|
||||||
|
if results in [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16]:
|
||||||
elif results in error_indices[self.protocol]:
|
|
||||||
child.terminate()
|
child.terminate()
|
||||||
if results_value == pexpect.TIMEOUT and attempts != max_attempts:
|
if results == 12 and attempts != max_attempts:
|
||||||
attempts += 1
|
attempts += 1
|
||||||
endloop = True
|
endloop = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
after = "Connection timeout" if results_value == pexpect.TIMEOUT else child.after.decode()
|
if results == 12:
|
||||||
return f"Connection failed code: {results}\n{child.before.decode().lstrip()}{after}{child.readline().decode()}".rstrip()
|
after = "Connection timeout"
|
||||||
|
|
||||||
elif results in eof_indices[self.protocol]:
|
|
||||||
if results_value == password_prompt:
|
|
||||||
if passwords:
|
|
||||||
child.sendline(passwords[i])
|
|
||||||
else:
|
else:
|
||||||
self.missingtext = True
|
after = child.after.decode()
|
||||||
break
|
return ("Connection failed code:" + str(results) + "\n" + child.before.decode().lstrip() + after + child.readline().decode()).rstrip()
|
||||||
elif results_value == "suspend":
|
if results == 8:
|
||||||
child.sendline("\r")
|
if len(passwords) > 0:
|
||||||
sleep(2)
|
child.sendline(passwords[i])
|
||||||
else:
|
else:
|
||||||
endloop = True
|
self.missingtext = True
|
||||||
child.sendline()
|
break
|
||||||
break
|
if results in [9, 11]:
|
||||||
|
endloop = True
|
||||||
|
child.sendline()
|
||||||
|
break
|
||||||
|
if results == 10:
|
||||||
|
child.sendline("\r")
|
||||||
|
sleep(2)
|
||||||
if endloop:
|
if endloop:
|
||||||
break
|
break
|
||||||
if results_value == pexpect.TIMEOUT:
|
if results == 12:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
child.readline(0)
|
child.readline(0)
|
||||||
self.child = child
|
self.child = child
|
||||||
return True</code></pre>
|
return True</code></pre>
|
||||||
@ -3311,14 +3208,6 @@ def run(self, commands, vars = None,*, folder = '', prompt = r'>$
|
|||||||
connect = self._connect(timeout = timeout)
|
connect = self._connect(timeout = timeout)
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
||||||
if connect == True:
|
if connect == True:
|
||||||
# Attempt to set the terminal size
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(65535, 65535)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(10000, 10000)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if "prompt" in self.tags:
|
if "prompt" in self.tags:
|
||||||
prompt = self.tags["prompt"]
|
prompt = self.tags["prompt"]
|
||||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||||
@ -3447,14 +3336,6 @@ def test(self, commands, expected, vars = None,*, prompt = r'>$|#$|\$$|&g
|
|||||||
'''
|
'''
|
||||||
connect = self._connect(timeout = timeout)
|
connect = self._connect(timeout = timeout)
|
||||||
if connect == True:
|
if connect == True:
|
||||||
# Attempt to set the terminal size
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(65535, 65535)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self.child.setwinsize(10000, 10000)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if "prompt" in self.tags:
|
if "prompt" in self.tags:
|
||||||
prompt = self.tags["prompt"]
|
prompt = self.tags["prompt"]
|
||||||
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
Flask>=2.3.2
|
Flask>=2.3.2
|
||||||
Flask_Cors>=4.0.1
|
|
||||||
google_api_python_client>=2.125.0
|
google_api_python_client>=2.125.0
|
||||||
google_auth_oauthlib>=1.2.0
|
google_auth_oauthlib>=1.2.0
|
||||||
inquirer>=3.3.0
|
inquirer>=3.2.4
|
||||||
openai>=0.27.8
|
openai>=0.27.8
|
||||||
pexpect>=4.8.0
|
pexpect>=4.8.0
|
||||||
protobuf>=5.27.2
|
protobuf>=5.26.1
|
||||||
pycryptodome>=3.18.0
|
pycryptodome>=3.18.0
|
||||||
pyfzf>=0.3.1
|
pyfzf>=0.3.1
|
||||||
PyYAML>=6.0.1
|
PyYAML>=6.0.1
|
||||||
|
@ -4,7 +4,7 @@ version = attr: connpy._version.__version__
|
|||||||
description = Connpy is a SSH/Telnet connection manager and automation module
|
description = Connpy is a SSH/Telnet connection manager and automation module
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
keywords = networking, automation, docker, kubernetes, ssh, telnet, connection manager
|
keywords = networking, automation, ssh, telnet, connection manager
|
||||||
author = Federico Luzzi
|
author = Federico Luzzi
|
||||||
author_email = fluzzi@gmail.com
|
author_email = fluzzi@gmail.com
|
||||||
url = https://github.com/fluzzi/connpy
|
url = https://github.com/fluzzi/connpy
|
||||||
@ -29,7 +29,6 @@ install_requires =
|
|||||||
pexpect
|
pexpect
|
||||||
pycryptodome
|
pycryptodome
|
||||||
Flask
|
Flask
|
||||||
Flask_Cors
|
|
||||||
pyfzf
|
pyfzf
|
||||||
waitress
|
waitress
|
||||||
PyYAML
|
PyYAML
|
||||||
|
Loading…
Reference in New Issue
Block a user