Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions climatic/CoreCli.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(self,
connection,
username: Optional[str]='admin',
password: Optional[str]='admin',
identification: Optional[str]='',
timeout: Optional[int]=15,
quiet: Optional[bool]=False,
marker: Optional[str]="#",
Expand Down Expand Up @@ -103,6 +104,8 @@ def __init__(self,
self.wait_cmd_timeout = wait_cmd_timeout
if not hasattr(self, 'strip_cmds'):
self.strip_cmds = strip_cmds
if not hasattr(self, 'identification'):
self.identification = identification

self.logger = Logger.start(self.name)
self.connection = connection
Expand Down Expand Up @@ -357,10 +360,13 @@ def _sync(self, marker: str):

@param marker regex used to identify the start of a command line.
"""
# consume all markers since last 'expect'
while self.connection.terminal.expect([marker, pexpect.TIMEOUT], timeout=0.01) == 0:
continue

try:
while True:
self.connection.terminal.read_nonblocking(size=1024, timeout=0.1)
except pexpect.exceptions.TIMEOUT:
pass
except pexpect.exceptions.EOF:
pass

def _get_prompt_size(self) -> int:
""" Returns the prompt size: the number of visible chars from the beginning of the
Expand Down
58 changes: 57 additions & 1 deletion climatic/cli/Linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Optional

from ..CoreCli import CoreCli
from ..connections.Ser2Net import Ser2Net
from ..connections.Ssh import Ssh, PTY_WINSIZE_COLS
from ..connections.Ssh import PTY_WINSIZE_COLS as SSH_PTY_WINSIZE_COLS

Expand Down Expand Up @@ -42,20 +43,23 @@ def __init__(self,
username: str,
password: str,
port: Optional[int]=22,
id_file: Optional[str]=None,
**opts):
""" Initialize Linux Shell.
@param ip IP address of target. Ex: '234.168.10.12'
@param username username for opening SSH connection
@param password String with password corresponding to the username to login into
the connection that provides access to the CLI.
@param port Port used for SSH connection. Defaults to 22
@param id_file Path of file to be used as user identification (usually a private key)
@param opts Same options as CoreCli initializer.
"""
if not 'marker' in opts:
opts['marker'] = '#|>'

self.name = "Linux.SSH"
ssh = Ssh(ip, username, port=port)
self.has_id = id_file != None
ssh = Ssh(ip, username, port=port, identification=id_file)
Linux.__init__(self,
ssh,
username=username,
Expand Down Expand Up @@ -83,3 +87,55 @@ def logout(self):
""" Logout from CLI interface.
"""
self.connection.terminal.sendline('exit')


####################################################################################################
## Ser2NetLinux

class Ser2NetLinux(SshLinux):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it extendind the SshLinux instead of Linux? Is it only for the implementation of the logout function? If so, move it to the Linux class and extend the Ser2NetLinux from Linux, because the SshLinux may have invalid methods for Ser2NetLinux in the future.

Copy link
Copy Markdown
Author

@jrrech jrrech Oct 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I tried to reuse the login function as well, but it needed some tuning for Ser2Net in the end. It does make more sense extending Linux, though.

""" Connects to a remote Linux Shell using SSH.
Core implementation is done by Ssh and Linux.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace SSH with Ser2Net in the comments above

"""

def __init__(self,
ip: str,
port: int,
username: str,
password: str,
**opts):
""" Initialize Linux Shell.
@param ip IP address of ser2net server. Ex: '234.168.10.12'
@param port Port used for ser2net connection.
@param username username for opening ser2net connection
@param password String with password corresponding to the username to login into
the connection that provides access to the CLI.

@param opts Same options as CoreCli initializer.
"""
if not 'marker' in opts:
opts['marker'] = '#|>'

self.name = "Linux.Ser2Net"
ser2net = Ser2Net(ip, port)
Linux.__init__(self,
ser2net,
username=username,
password=password,
pty_winsize_cols=SSH_PTY_WINSIZE_COLS,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you should import and use the PTY_WINSIZE_COLS from Ser2Net instead

**opts)

def login(self):
""" Login to CLI interface.
"""
while True:
index = self.connection.terminal.expect(
['.ogin', '.assword', self.marker],
timeout=10)

if index == 0:
self.connection.terminal.sendline(self.username)
if index == 1:
self.connection.terminal.waitnoecho()
self.connection.terminal.sendline(self.password)
if index >= 2:
break
3 changes: 1 addition & 2 deletions climatic/connections/Ser2Net.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
PTY_WINSIZE_ROWS = 24
PTY_WINSIZE_COLS = 500


class Ser2Net(Connection):
""" Connects to a CLI using Ser2net.
The Ser2Net should be available and configured with an IP and port
Expand Down Expand Up @@ -36,7 +35,7 @@ def connect(self, logfile, logger=None):
self.terminal.setwinsize(PTY_WINSIZE_ROWS, PTY_WINSIZE_COLS)

def disconnect(self, logger=None):
""" For Ser2Net, the connection needs to be explicitly closed
""" For Ser2Net, the connection is closed during the logout
@param logger Optional logger for debug messages
"""
if logger != None:
Expand Down
15 changes: 11 additions & 4 deletions climatic/connections/Ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@ class Ssh(Connection):
The device should have the IP configured.
"""

def __init__(self, ip: str, user: str, port=SSH_PORT, ciphers: str = None):
def __init__(self, ip: str, user: str, port=SSH_PORT, ciphers: str = None, identification: str = None):
""" Initialize the SSH connection object.
@param ip IP address to connect to. Ex: '192.168.33.4'.
@param user The SSH connection user.
@param port The SSH connection port. Default is 22.
@param ciphers A comma sepparated list of ciphers. Ex: 'blowfish-cbc,3des-cbc'
@param ciphers A comma sepparated list of ciphers. Ex: 'blowfish-cbc,3des-cbc'
@param identification Path of file to be used for "-i" argument, usually a private key
"""
self.user = user
self.ip = ip
self.port = port
self.ciphers = ciphers
self.identification = identification

Connection.__init__(self)

Expand All @@ -40,8 +42,13 @@ def connect(self, logfile, logger=None):
if self.ciphers != None:
cipher_spec = '-c {}'.format(self.ciphers)

self.terminal = pexpect.spawn('ssh -p {2} {0}@{1} {3}'.format(
self.user, self.ip, self.port, cipher_spec), logfile=logfile, encoding='utf-8')
if self.identification != None:
self.terminal = pexpect.spawn('ssh -i {4} -p {2} {0}@{1} {3}'.format(
self.user, self.ip, self.port, cipher_spec, self.identification), logfile=logfile, encoding='utf-8')
else:
self.terminal = pexpect.spawn('ssh -p {2} {0}@{1} {3}'.format(
self.user, self.ip, self.port, cipher_spec), logfile=logfile, encoding='utf-8')

self.terminal.setwinsize(PTY_WINSIZE_ROWS, PTY_WINSIZE_COLS)

def disconnect(self, logger=None):
Expand Down